Flask集成了发邮件功能,如果包在一个flask服务中,修改起来就比较麻烦。 本篇梳理一下Flask发送邮件的功能,并用docker打包成一个接口服务。Flask经典的异步发送邮件的方法我看参考中的那篇文章已经写的很好了(省了不少功夫 ^ ^)。 本篇主要从几方面进行改造和补充:
1 使用docker-compose方式。将其打包成一个docker-compose项目,通过命令行传入参数启动(这样以后换邮箱就比较方便)2 将这个服务改造为api方式的服务。3 使用gunicorn&gevent方式IO异步/并行运行4 将用户、密码和服务单独的校验令牌放在bashrc中。有一个qq邮箱,再安装了flask就可以了。
将视图函数改为api方式
@app.route('/send_mail/', methods=['POST', 'GET']) def send_mail(): res_dict ={} #1 获取参数 data = request.get_json() try: mail_title = data.get('subject') # mail_sender = data.get('sender') mail_recipients = data.get('recipients') mail_txt_body = data.get('txt_body') mail_html_body = data.get('html_body') except: res_dict['status'] = False res_dict['status_code'] = 400 res_dict['msg'] = '参数获取失败' return jsonify(res_dict) #2 调用邮件函数发送 try: msg = Message(subject=mail_title, recipients= mail_recipients) msg.body = mail_txt_body msg.html = mail_html_body thread = Thread(target=send_async_email, args=[app, msg]) thread.start() res_dict['status'] = True res_dict['status_code'] = 200 res_dict['msg'] = '邮件发送成功' return jsonify(res_dict) except: res_dict['status'] = False res_dict['status_code'] = 500 return jsonify(res_dict)结果成功
参数化之后的服务
# 定义参数函数 import argparse def mail_argparse(): # 短选项只能一个字母 parser = argparse.ArgumentParser(description='Mail Server Argument') parser.add_argument('-s', '--mailserver', default='smtp.qq.com') parser.add_argument('-p', '--mailport', default='465') parser.add_argument('-l', '--mailssl', default=True) parser.add_argument('-t', '--mailtls', default=False) parser.add_argument('-u', '--mailusername', default='xxx@qq.com') parser.add_argument('-w', '--mailpwd', default='vilrlwfyrthrbbgc') parser.add_argument('-d', '--defaultsender', default='xxx@qq.com') parser.add_argument('-o', '--mailtoken', default='sometoken') # 准备解析参数 args = parser.parse_args() res_dict = {} res_dict['MAIL_SERVER'] = args.mailserver res_dict['MAIL_PORT'] = args.mailport res_dict['MAIL_USE_SSL'] = args.mailssl res_dict['MAIL_USE_TLS'] = args.mailtls res_dict['MAIL_USERNAME'] = args.mailusername res_dict['MAIL_PASSWORD'] = args.mailpwd res_dict['MAIL_DEFAULT_SENDER'] = args.defaultsender res_dict['MAIL_TOKEN'] = args.mailtoken return res_dict if __name__ == '__main__': # > 使用命令行参数的方式启动 arg_dict = mail_argparse() app.config['MAIL_SERVER'] = arg_dict['MAIL_SERVER'] app.config['MAIL_PORT'] = arg_dict['MAIL_PORT'] app.config['MAIL_USE_SSL'] = arg_dict['MAIL_USE_SSL'] app.config['MAIL_USE_TLS'] = arg_dict['MAIL_USE_TLS'] app.config['MAIL_USERNAME'] = arg_dict['MAIL_USERNAME'] app.config['MAIL_PASSWORD'] = arg_dict['MAIL_PASSWORD'] app.config['MAIL_DEFAULT_SENDER'] = arg_dict['MAIL_DEFAULT_SENDER'] app.config['MAIL_TOKEN'] = arg_dict['MAIL_TOKEN'] print('>>> Running') print('>>> MAIL_USERNAME', app.config['MAIL_USERNAME']) print('>>> Token', app.config['MAIL_TOKEN']) # 打开调试模式,允许所有ip访问 app.run(debug=True, host='0.0.0.0')这样就改成了参数化启动的方式(因为每一个选项都设置了默认值,因此仍然可以直接启动)
现在我们将三个关键参数存到bash中,然后启动。
.bash_profile(或.bashrc)
export mailusr2="xxx@qq.com" export mailpwd2="xxx" export mailtoken2="test"这时可以根据.bash_profile中设置的用户启动。(当然,还有一种方法是使用python的os包获取bash中的变量)
python3 test2.py -u$mailusr2在发送邮件的视图函数(send_mail)里可以加上一个判断,如果token不一致,那么就不发送邮件
if mail_token != app.config['MAIL_TOKEN']: res_dict['status'] = False res_dict['status_code'] = 401 res_dict['msg'] = '授权令牌失败' return jsonify(res_dict)以上,邮件发送服务已经ok了,现在我们按照docker-compose项目的方式将其打包。
prj6 ├── .gitignore ├── docker-compose.yaml ├── flask_mail └── 说明.md其中docker-compose.yaml是核心编排文件,flask_mail是项目文件夹。对于项目文件夹,一般设置如下目录:
flask_mail ├── Dockerfile -> 镜像构建方式 ├── app -> 应用的程序文件 ├── config -> 整体的设置文件(app内还可以有自己的设置文件) ├── data -> 持久化数据的文件夹 ├── entry_flask_mail.py -> 整个app程序的入口 ├── env -> 存放环境依赖的包,以及启动sh ├── log -> 日志 └── packages -> 自定义的whl包这个应用特别简单,只要把上面的文件放到entry_flask_mail.py中就可以了(即只有一个入口文件)。有时候也可以把app看成一个简单的文件夹,里面可以有自定义的函数。在entry_xx.py函数中可以这样引用:
import app.funcs as funcs别忘了将邮件用户密码写入待启动服务器的bash
export mailusr2="xxx@qq.com" export mailpwd2="xxx" export mailtoken2="xxx"准备Dockerfile。
# 指定python版本 FROM python:3.6 # 指定镜像的工作目录 WORKDIR /opt/app # 将当前的文件夹整个拷贝到镜像 COPY . . # 安装需要的python包 RUN pip install -r env/requirements.txt -i https://mirrors.aliyun.com/pypi/simple/ # 暴露端口 EXPOSE 5000 # 镜像启动的脚本 ENTRYPOINT ["sh", "env/entrypoint.sh"]在env下增加requirements.txt, 应该只需要增加这两个包
Flask==1.0.2 Flask-Mail==0.9.1在env下增加entrypoint.sh, 用于启动
$source ~/.bashrc python3 entry_flask_mail.py -u$mailusr2 -w$mailpwd2 -d$mailusr2 -o$mailtoken2最后修改docker-compose文件
version: '2' services: # 服务名称 mail: # 构建文件 build: context: ./flask_mail/ dockerfile: Dockerfile restart: always # 挂载卷 volumes: - ./flask_mail/:/opt/app:rw # 端口映射 ports: - "9001:5000"修改了一下ubuntu的shell,也许没有必要(因为是容器中运行的脚本,当然无法读到宿主机的环境变量)
修改docker-compose文件,将环境变量(environment)转入
version: '2' services: # 服务名称 mail: # 构建文件 build: context: ./flask_mail/ dockerfile: Dockerfile restart: always # 挂载卷 volumes: - ./flask_mail/:/opt/app:rw # 端口映射 ports: - "9001:5000" environment: - mailusr2=${mailusr2} - mailpwd2=${mailpwd2} - mailtoken2=${mailtoken2}配置好以后在云主机上,切到对应目录,执行构建命令就可以了
docker-compose up --build最终发布成功,但是有几个地方需要注意:
重新初始化邮件函数。因为配置参数是启动时传入 的,邮件函数中的mail实例并没有对应的邮件用户名和密码。一般来说,如果报连接失败的化优先检查用户名、密码。或者是缺失,或者是真填错了。(我一开始想到服务器端口限制和容器端口透射了,想岔了) if __name__ == '__main__': # > 使用命令行参数的方式启动 arg_dict = mail_argparse() app.config['MAIL_SERVER'] = arg_dict['MAIL_SERVER'] app.config['MAIL_PORT'] = arg_dict['MAIL_PORT'] app.config['MAIL_USE_SSL'] = arg_dict['MAIL_USE_SSL'] app.config['MAIL_USE_TLS'] = arg_dict['MAIL_USE_TLS'] app.config['MAIL_USERNAME'] = arg_dict['MAIL_USERNAME'] app.config['MAIL_PASSWORD'] = arg_dict['MAIL_PASSWORD'] app.config['MAIL_DEFAULT_SENDER'] = arg_dict['MAIL_DEFAULT_SENDER'] app.config['MAIL_TOKEN'] = arg_dict['MAIL_TOKEN'] mail = Mail(app) # 异步发送邮件 def send_async_email(app, msg): with app.app_context(): mail.send(msg) print('>>> Running') print('>>> MAIL_USERNAME', app.config['MAIL_USERNAME']) print('>>> Token', app.config['MAIL_TOKEN']) print('app config', app.config) # 打开调试模式,允许所有ip访问 app.run(debug=True, host='0.0.0.0')1.使用Flask-Mail和qq邮箱SMTP服务发送邮件 2.python如何从bashrc中读取参数 3 Python 全栈系列30 -使用request包进行接口调试
