博客已经有段时间没更新了,有很多好的想法只是开了个简单的头就搁置了。也不想给自己找借口,就是最近有点松懈了。写博客是一个持之以恒的过程,没有学习哪来进步?总归坚持才是最难做的事情,能做到稳定输出的个人公众号和博客都是我敬佩的对象。希望我可以把这件事当作磨练自己的磨刀石,坚持下去,大家共勉!
邮件是一个系统中不可缺少的模块,因为邮箱相对可以认为是安全可靠的,它对于用户来说难以伪造。所以邮箱通常被用来做系统的用户校验,应用于一些高权限的操作的认证,例如注册账号,找回密码,重置密码...等等一系列的行为校验。当前主流的是使用短信验证码做这方面的校验,但是短信验证码是收费的,通过smtp发送邮件是不收费的。所以对于小项目还是用smtp发送邮件更划算。
结合手上一个实际的项目来简单介绍一下Flask-Mail这个插件,因为我再试图用Flask写一个简单的博客系统,把现在的文章都迁移过去。但是发现工程并不是那么简单,我现在卡在了前端上面,这是后话。对于博客系统的登录系统,需要使用邮件的场景有很多,注册账号,找回密码,评论通知...所以在项目中配置一个邮件发送模块是十分必要的。
Flask-Mail使用
Flask-Mail 扩展提供了一个简单的接口,可以在 Flask 应用中设置 SMTP 使得可以在视图函数中以及脚本中发送邮件信息。我的理解Flask-Mail是对发送邮件过程做了一个很好的封装,完成了一个符合Flask思想的邮件模块,大大简化了开发过程。
理论上支持SMTP的邮箱服务都可以使用Flask-Mail插件,目前所知道的有163邮箱,QQ邮箱,Gmail。如果你想让你的服务显得更加专业的话你甚至可以使用你的域名去申请腾讯的免费域名邮箱,这样发送的邮件不仅显得专业,并且邮箱账号方便管理吗,腾讯企业邮地址:https://exmail.qq.com/。免费的账号会有空间上的限制,但是对于小型开发者来说已经够用了。
Flask-Mail提供了简单的几个配置项,对应的都是相关的邮件配置,按照邮件服务商的帮助文件配置上去基本上就没什么问题。接下来就是大家所熟知的将插件插入Flask项目当中。发送邮件同样十分简单,在Flask-Mail中许要构建Message对象。Message就是邮件对象,包括了邮件的内容,收件人等等一系列信息,最后调用send方法就可以成功发送,下面给一段简单的演示。
from flask import Flask from flask_mail import Mail app = Flask(__name__) mail = Mail(app) # 初始化Flask-Mail插件 # 发送邮件方法 from app import mail def send_message(recipient, subject, body=None, template=None, **kwargs): message = Message(subject=subject, recipients=recipient if type(recipient) == list else [recipient], body=body, html=render_template(template, **kwargs) if template else template) mail.send(message)
调用简直可以说是傻瓜式,但是有经验的同学就知道,上面的代码有很大的缺陷。因为Flask的事件触发是通过路由访问触发的,每一次请求都需要有相应的返回。但是如果在路由中使用SMTP服务发送邮件,由于第三方服务是不可控的,如果发送邮件的过程占用了过多的时间,用户返回就会卡顿,十分影响用户体验。假如说邮件服务迟迟没有发送成功,同时我们的服务配置了请求超时断开,那么用户端会直接显示错误代码,但是事实上 我们的服务并没有出现问题,主要是邮箱服务导致了问题的发生,这个问题是许要我们处理的。
更改为异步发送邮件
上面发送邮件的方式可以看作是单线程操作,单线程操作就会遇到阻塞的问题。如果我们把路由处理和邮件发送放在不同的线程处理的话,就可以方式上述出现的问题了。下面代码演示了如何异步发送邮件,这是网上大多数资料给的解决方案。
from threading import Thread from flask import current_app, render_template from flask_mail import Message from app import mail def send_message(recipient, subject, body=None, template=None, **kwargs): message = Message(subject=current_app.config.get("MAIL_SUBJECT_PREFIX") + subject, recipients=recipient if type(recipient) == list else [recipient], body=body, html=render_template(template, **kwargs) if template else template) app = current_app._get_current_object() thread = Thread(target=send_async_mail, args=[app, message]) thread.start() return thread def send_async_mail(app, message): with app.app_context(): try: mail.send(message) except: pass
代码很简单,就是将邮件发送的方法调用放在子线程中启动。因为发送邮件方法要在Flask上下文环境中才能调用,所以我们许要手动的推入上下文环境,这样才能避免Flask的经典报错。
难以理解的代码就是在13-14行代码中,为什么传app对象不直接传current_app对象,而是要传current_app._get_current_object()?这里我凭借我有限的Flask知识解释一下,因为current_app对象是LocalProxy实现的,这个对象本身只能在Flask上下文环境中才能正确被调用。这里如果作为参数传入到别的线程中,current_app就不能指到Flask对象上去了,所以这里要把通过_get_current_object()方法吧app这个对象传入子线程。
没有上下文环境的话current_app对象也变得没有意义,这里的操作感觉有点像函数传递形参和实参的意思,这是我的个人理解。大家感兴趣可以具体百度一下,我说的不一定对哦!
当然Flask-Mail还是提供了很多更高级的使用方法,比如说发送批量邮件,发送邮件附件...感兴趣的可以研究一下Flask-Mail的文档。目测中英文文档没有太大出入,英文不好的可以直接看中文文档,翻译的很到位。
中文文档:http://www.pythondoc.com/flask-mail/
英文文档:https://pythonhosted.org/Flask-Mail/
邮件格式注意事项
邮件内容支持纯文本,还有Html。同时Flask-Mail提供了相应的支持,可以很方便的使用Jinja2渲染邮件页面。但是支持Html渲染并不代表所有的Html对象都能正确的被显示,事实上邮箱服务商为了避免一些安全问题的出现,屏蔽了很多html元素,所以同一封邮件可能在不同的邮箱客户端中显示也各有不同。所以好的邮件样式许要我们做类似于浏览器适配的操作来适配不同的邮箱客户端,为了能够有一个良好的页面效果,这里给出网上总结的一些建议。
- 布局使用Table
- 不要在Body标签外定义元素
- 尽量使用元素属性而不是定义样式
- 不要使用JavaScript脚本
- 指定元素的width、heigth、margin、padding...
参考资料:https://segmentfault.com/a/1190000008864116。给出的参考资料并不完善,想要详细了解规则的话可以自行搜索,搜索关键词:【HTML Email 格式】。
一首来自无欲无求的星爷演唱的《李香兰》送给大家
共有 0 条评论