Python 全栈系列29 -Flask 发送邮件

    科技2022-08-17  110

    说明

    Flask集成了发邮件功能,如果包在一个flask服务中,修改起来就比较麻烦。 本篇梳理一下Flask发送邮件的功能,并用docker打包成一个接口服务。Flask经典的异步发送邮件的方法我看参考中的那篇文章已经写的很好了(省了不少功夫 ^ ^)。 本篇主要从几方面进行改造和补充:

    1 使用docker-compose方式。将其打包成一个docker-compose项目,通过命令行传入参数启动(这样以后换邮箱就比较方便)2 将这个服务改造为api方式的服务。3 使用gunicorn&gevent方式IO异步/并行运行4 将用户、密码和服务单独的校验令牌放在bashrc中。

    1 Hellowork

    from flask import Flask, request, jsonify from flask_mail import Mail, Message from threading import Thread # 从bash引入用户密码 import os app = Flask(__name__) app.config['MAIL_SERVER'] = 'smtp.qq.com' app.config['MAIL_PORT'] = 465 app.config['MAIL_USE_SSL'] = True app.config['MAIL_USE_TLS'] = False app.config['MAIL_USERNAME'] = 'xxx@qq.com' app.config['MAIL_PASSWORD'] = 'xxxxxx' app.config['MAIL_DEFAULT_SENDER'] = 'Test Admin<xxx@qq.com>' # 填邮箱,默认发送者 mail = Mail(app) # 异步发送邮件 def send_async_email(app, msg): with app.app_context(): mail.send(msg) # 使用get方法发送测试邮件 @app.route('/test_sendmail/') def test_sendmail(): msg = Message(subject='Hello World', sender= app.config['MAIL_DEFAULT_SENDER'], # 需要使用默认发送者则不用填 recipients=['@qq.com']) # 邮件内容会以文本和html两种格式呈现,而你能看到哪种格式取决于你的邮件客户端。 msg.body = 'sended by flask-email' msg.html = '<b>测试Flask发送邮件<b>' thread = Thread(target=send_async_email, args=[app, msg]) thread.start() return '<h1>邮件发送成功</h1>' # > 使用POST进行接口调用(使用藏在bash里的token进行验证) if __name__ == '__main__': # > 使用命令行参数的方式启动 # 打开调试模式,允许所有ip访问 app.run(debug=True, host='0.0.0.0')

    有一个qq邮箱,再安装了flask就可以了。

    2 sendmail

    将视图函数改为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)

    3 使用request进行调试

    import requests as req import json url = 'http://localhost:5000/send_mail/' data = {'subject':'接口测试', 'recipients':['xxx@qq.com'], 'txt_body':'来自接口的测试 txt显示', 'html_body': '<b>来自接口的测试 html显示</b>'} resp = req.post(url, json=data) resp_dict = json.loads(resp.text) print(resp_dict)

    结果成功

    4 参数化启动服务(用户,密码,令牌)

    参数化之后的服务

    # 定义参数函数 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')

    这样就改成了参数化启动的方式(因为每一个选项都设置了默认值,因此仍然可以直接启动)

    5 通过读取bash中的配置生效

    参数bash变量名内容用户emailusr1bash里的秘密密码emailpwd1bash里的秘密令牌emailtoken1bash里的秘密

    现在我们将三个关键参数存到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)

    6 整理为docker-compose项目

    以上,邮件发送服务已经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"

    7 发布测试

    scp -r /Users/yukai/code_notgo/prj6 root@111.111.111.111:/opt

    修改了一下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包进行接口调试

    Processed: 0.009, SQL: 9