Flask发送注册邮件
Flask发送注册邮件
1.导读
前面已经简单的学习了Flask上下文,以及Flask启动流程,现在结合Flask Mail,(还有Python相关线程进程的操作).结合这些,简单的实现一下
Flask
注册邮箱,并发送验证邮件.类似如下:graph LR;
A(登录页面)-->B(发送验证邮件)-->C(邮箱中验证)
2.搭建基本架构
❯ tree
.
├── app.py # 主app
├── bpModel # Python包文件,主要存放蓝图模板
│ ├── __init__.py
│ └── users.py # 主路由文件
├── config.py # 主配置文件
├── exts.py # 解决双向引入的问题
├── formModel.py # form模板
├── mailModel.py # 发送邮件模板
├── manager.py # 主要基本管理
├── migrations # 数据库版本迁移文件
│ ├── alembic.ini
│ ├── env.py
│ ├── README
│ ├── script.py.mako
│ └── versions
│ ├── c15ed05a9a50_.py
├── sqlModel.py # 数据库模板
├── static # 静态文件CSS/JS/Image目录
└── templates # 静态模板
├── activate.html # 激活
├── login.html # 登录
├── register.html # 注册
└── verification.html # 注册后返回的页面以下,从开头的思路出发,一步一步实现.
1.app.py
主
app
文件中,我们应该尽量减少路由,把核心的路由全部布置在bpModel
中from flask import Flask
import config
from bpModel import users
app = Flask(__name__)
app.config.from_object(config)
@app.route('/')
def index():
# 测试路由
return 'index page'
# 注册blueprint
app.register_blueprint(users.bp)
if __name__ == '__main__':
app.run()配置文件
config.py
DEBUG = True
TEMPLATES_AUTO_RELOAD = True蓝图文件
bpModel/users.py
from flask import Blueprint, render_template, redirect, url_for, flash, current_app
bp = Blueprint('users', __name__, url_prefix='/users')
# 设置路由
@bp.route('/register/', methods=['GET', 'POST'])
def register():
return render_template('register.html')
@bp.route('/login/', methods=['GET', 'POST'])
def login():
return render_template('login.html')
@bp.route('/verification/')
def verification():
return render_template('verification.html')在这一步主要用来实现能访问
127.0.0.1:5000/users/register/
,即实现基本的路由正常.
2.对接数据库
先写
register.html
,注意,这里我们使用的表单验证是Flask_form
,它是提供SECRET_KEY
和CSRF
保护的,如果已经遗忘的,可以温习https://wenyan.online/2020/10/18/flask-csrfd/
.
register.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>register</title>
</head>
<body>
<form action="" method="post">
{# 这里实现了一个隐藏的csrf_token,它的写法是固定的 #}
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div><label for="">Username:</label><input type="text" name="username"></div>
<div><label for="">Email:</label><input type="text" name="email"></div>
<div><label for="">Password:</label><input type="password" name="password"></div>
<div><label for="">Password(again):</label><input type="password" name="passwordR"></div>
<div><input type="checkbox" name="agree">I agree the policy.</div>
<div><input type="submit" value="Register" name="submit1"></div>
</form>
</body>
</html>
exts.py
from flask_sqlalchemy import SQLAlchemy
from flask_wtf.csrf import CSRFProtect
db = SQLAlchemy()
csrf = CSRFProtect()
app.py
# 添加
from exts import db, csrf
db.init_app(app)
csrf.init_app(app)表单验证比较简单
formModel.py
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField
from wtforms.validators import Length, Email, EqualTo
class RegisterForm(FlaskForm):
username = StringField(validators=[Length(6, 20, '长度不够')])
email = StringField(validators=[Email()])
password = PasswordField(validators=[Length(6, 20, '长度不够')])
passwordR = PasswordField(validators=[EqualTo('password')])
agree = BooleanField(validators=[]) # 验证为空,默认是False, 选择checkbox 变成True做好以上的基本准备后,就可以着手路由和数据库的对应.
首先,我们来设计一下数据库,它基本满足以下几个内容即可.
id
username
passowrd
verification
create_time
age
phone
序列号 用户名 密码 邮箱 是否激活 创建事件 年龄 电话 为了简便,只是截取前几项
sqlModel.py
from exts import db
class Users(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True, nullable=False, autoincrement=True)
username = db.Column(db.String(45), nullable=False)
email = db.Column(db.String(45), nullable=False)
password = db.Column(db.String(45), nullable=False)
verification = db.Column(db.Boolean, default=0) # flag,标识是否注册激活有了数据库的对应,基本可以创建数据库连接.
config.py
# 添加
# mysql
# 数据库支持
msg = "mysql+pymysql://root:[email protected]:3306/flask_mail_demo"
SQLALCHEMY_DATABASE_URI = msg
SQLALCHEMY_TRACK_MODIFICATIONS = False # 关闭追
manage.py
from flask_script import Manager
from exts import db
from flask_migrate import MigrateCommand, Migrate
from app import app
# 导入Manager并绑定app
manager = Manager(app)
# 导入flaks_migrate
# Migrate 绑定app,db
Migrate(app, db)
# MigrateCommand 可以使用Alembic的命令
#
manager.add_command('db', MigrateCommand) # db是别名
if __name__ == '__main__':
manager.run()然后使用版本控制工具
migrate
$ python manager.py db --help # 测试数据库连接
$ python manager.py db init # 初始化
$ python manager.py db migratge # 生成版本
$ python manager.py db upgrade # 升级到最新版本
3.生成路由
生成注册路由.
dbModel/users.py
# 设置路由
@bp.route('/register/', methods=['GET', 'POST'])
def register():
form = RegisterForm()
if form.validate_on_submit():
# 如果表单验证成功
# 添加到数据库中
username = form.username.data
email = form.email.data
password = form.password.data
user = Users(username=username, email=email, password=password)
db.session.add(user)
db.session.commit()
return redirect(url_for('users.verification', name=username))
else:
print(form.errors)
return render_template('register.html')以上,是基本的验证表单,并添加到数据库中的过程,但是我们希望的是,再添加到数据库的同时,一并发送邮件给注册账户的邮箱.
4.邮箱设置
修改
config.py
,设置基本的邮箱信息# 配置Mail
MAIL_SERVER = 'smtp.qq.com'
MAIL_PORT = 465
MAIL_USERNAME = '[email protected]'
MAIL_PASSWORD = "imkzuyoomc"
MAIL_USE_SSL = True
MAIL_USE_TLS = False
MAIL_DEFAULT_SENDER = '[email protected]'注意,这里使用了
MAIL_DEFAULT_SENDER
.在
exts.py
文件中引入Flask_mail
,并在app.py
中绑定app
# app.py
from flask import Flask, render_template
from bpModel import users
import config
from exts import db, mail, csrf
app = Flask(__name__)
app.config.from_object(config)
db.init_app(app)
mail.init_app(app)
csrf.init_app(app)
mailModel.py
from flask_mail import Message
from flask import render_template, current_app
from threading import Thread
from exts import mail
# 异步发送邮件
def async_send_mail(app, msg):
# 要求在Flask的一次访问中发送邮件,下面代码中新建的线程中并
# 不包含 上下文结构,手动推送
with app.app_context():
mail.send(msg)
def sendMail(to, subject, template, **kwargs):
try:
# 创建邮件
msg = Message(subject, recipients=[to])
# 回传浏览器
msg.html = render_template(template + '.html', **kwargs)
# 创建一个新线程,发送邮件
# 根据flask上下文,如果不再同一个 app 中,将无法发送邮件
app = current_app._get_current_object()
thread = Thread(target=async_send_mail, args=[app, msg])
thread.start()
return thread
except Exception as e:
print(e)这里需要注意,我们是在
bpModel/users.py
下的register
中发送邮件,根据flask
上下文,它是一个application context(current_app)
.如果不在同一个,将不能发送邮件,所以这里手动推送了current_app
.它的使用:app_context()
Binds the application only. For as long as the application is bound to the current context the flask.current_app points to that application. An application context is automatically created when a request context is pushed if necessary.
Example usage:
with app.app_context():
...再次修改
register
路由,发送邮件# 设置路由
@bp.route('/register/', methods=['GET', 'POST'])
def register():
form = RegisterForm()
if form.validate_on_submit():
# 如果表单验证成功
# 添加到数据库中
username = form.username.data
email = form.email.data
password = form.password.data
user = Users(username=username, email=email, password=password)
db.session.add(user)
db.session.commit()
# 生成激活校验的token
token = user.generate_active_token()
print(token)
# 发送激活邮件到注册邮箱
sendMail(user.email, '账户激活', 'activate', username=user.username, token=token)
flash('注册成功,请到你邮箱中点击激活!!!')
return redirect(url_for('users.verification', name=username))
else:
print(form.errors)
return render_template('register.html')注意生成的
token
,这个token
是在sqlModel.py
中定义好的.from exts import db
# 一种加密方式
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
from flask import current_app
class Users(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True, nullable=False, autoincrement=True)
username = db.Column(db.String(45), nullable=False)
email = db.Column(db.String(45), nullable=False)
password = db.Column(db.String(45), nullable=False)
verification = db.Column(db.Boolean, default=0) # flag,标识是否注册激活
# 生成账户激活的token
def generate_active_token(self, expires_in=3600):
s = Serializer(current_app._get_current_object().config['SECRET_KEY'], expires_in=expires_in)
print(s)
print(current_app._get_current_object().config['SECRET_KEY'])
return s.dumps({'id':self.id})
itsdangerous
主要用来签名和序列化,这个可以后面说,这里简单的理解就是用它来提供一个加密的序列化.因为,激活邮件的形式:
http://127.0.0.1:5000/users/activate/eyJhbGciOiJI/
,我希望它是一堆乱码,只有我自己能够加密解密序列化.所以这里使用了SECRET_KEY
.同样的,在
Flask
上下文中,我们使用current_app
获取当前使用的app
.
activate.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Mail</title>
</head>
<body>
<h1>Hello {{ username }}</h1>
<p>激活请点击右边链接,<a href="{{ url_for('users.activate', token=token, _external=True) }}">激活</a></p>
</body>
</html>
5.账户激活
在
users.py
中设计激活路由@bp.route('/activate/<token>/')
def activate(token):
if Users.check_activate_token(token):
flash('激活成功')
return redirect(url_for('users.login'))
else:
flash('激活失败')
return redirect(url_for('users.register'))注意
check_activate_token
,是已经设计好的解密token
sqlModel.py
from exts import db
# 一种加密方式
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
from flask import current_app
class Users(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True, nullable=False, autoincrement=True)
username = db.Column(db.String(45), nullable=False)
email = db.Column(db.String(45), nullable=False)
password = db.Column(db.String(45), nullable=False)
verification = db.Column(db.Boolean, default=0) # flag,标识是否注册激活
# 生成账户激活的token
def generate_active_token(self, expires_in=3600):
s = Serializer(current_app._get_current_object().config['SECRET_KEY'], expires_in=expires_in)
print(s)
print(current_app._get_current_object().config['SECRET_KEY'])
return s.dumps({'id':self.id})
# 账户激活
@staticmethod
def check_activate_token(token):
s = Serializer(current_app._get_current_object().config['SECRET_KEY'])
print(s)
print(current_app._get_current_object().config['SECRET_KEY'])
try:
data = s.loads(token)
except:
return False
# 得到用户
u = Users.query.get(data['id'])
if not u:
# 用户不存在
return False
if not u.verification:
# 用户没有激活
u.verification = 1
db.session.add(u)
db.session.commit()
return True它的逻辑也很简单,在我生成
token
的时候,已经包含了id
在内,所以,反向激活的时候,只要验证有没有id
在,如果有就把flag
变成1
,用来标记已经激活过了.需要注意的是一定要用同一个SECRET_KEY
.(一个dumps
,一个loads
).
6.注册并验证
运行后,注册,并查看注册邮箱中有无激活邮件,并尝试激活邮件.
点击运行后,能正常访问到
login
页面.
7.代码参考
- END -https://github.com/ningwenyan/demo_code/tree/master/flask_demo_code/T24