萌新在写网站的发送邮件验证,为了防止用户滥发,所以加了权限。前端简单地disable按钮一刷新就没了,纯粹视觉提示作用,所以在后端models里为user加了一个resend_right,当为True时才能重新发送,False不行。
所以在models里,user模型有一个column是这样的(SQLAlchemy):
resend_right = db.Column(db.Boolean, default=True)
当然前端是等待60秒后可以重新发送,所以后端也计时60秒后重新赋值True给resend_right。我就想这种等待性IO/数据库读取录入等操作当然是多线程处理。
所以我写了resend_right权限重置的方法:
def async_reset(app, user):
with app.app_context():
time.sleep(55)
user.resend_right = True
def resend_right_reset(user):
app = current_app._get_current_object()
thr = Thread(target=async_reset, args=[app, user])
thr.start()
return thr
然后在views的路由函数里面调用它:
# Resend confirmation email route, need to be protected
@auth.route('/resend_email/')
@login_required
def resend_confirmation():
mail_host ='http://mail.' + re.split('@', current_user.email)[1]
if not current_user.resend_right:
flash("请不要尝试刷新页面来短时间内重复发送验证邮件,你可以在一分钟后再试")
return render_template('auth/confirm.html',user=current_user, mail_host=mail_host)
token = current_user.generate_confirmation_token()
.........
结果无效,所以我测试了一下,发现路由函数无问题,resend_right_reset无问题。假如我把user.rend_right=True写进resend_right_reset是能够正常运作的,但一旦用多线程来处理就始终无法重置。然后我分析,多线程这里用了current_app._get_current_object()获取全局对象,然后app.app_context()拿到了上下文导入到多线程里,应该就没问题了。但为什么不行?
求教,非常感谢!
Tell me an idea that has nothing to do with this topic.
For the verification code, you can use redis to save it and set a survival time. As soon as the time is up, the corresponding value will disappear in redis. Just go to redis to get the submitted value, because redis is a cache database. , the speed will be faster than sql, and then it can prevent others from modifying parameters and submitting them. I personally think the method of adding timestamps is inappropriate, because I can modify the submission parameters directly.
I have two guesses:
Some global objects, including
flask
的实现机制里,包括current_app
, are all proxy objects. The proxy object is the top element of the stack corresponding to the local stack. This object is a thread local variable (Threading Local), which means that for each Requests that references to objects with the same name are different.I didn’t see commit code in async_reset, so I guess the
current_user
in the routing code behind should be obtained from the database again, not the object that was originally updated by you (the reference is different), so try commit?In fact, this solution is not particularly good. It is best to write the sending time when sending an email, and then determine the interval from the current time to the last sending time in the routing. This avoids the overhead of database writing and allocating threads.
After being reminded by @TKfeng, redis cache + TTL is the appropriate solution. It has good performance and is suitable for fine-grained service splitting in the later stage. His idea is right.