Layui + Flask 实现注册、登录功能(案例篇)
看了 layui 表单相关的知识,接下来就可以实现注册功能,功能逻辑如下:

项目创建
- 新建 flask 项目
- 下载 layui 文件,解压之后复制到指定文件
- 编写前端首页、注册页、登录页。注册页使用之前看过的注册页表单
- 编写后端路由,实现前后端基本的跳转
新建 flask 项目
新建项目的方式有很多种,只要有任意一种方式创建即刻。在这里我直接选择使用 pycharm 进行创建。
新建成功后目录如下
D:\flask-register>tree /fD:.│ app.py├─static└─templates
下载 layui 文件
访问 https://layui.dev/ ,点击直接下载之后就能得到 layui 的静态文件,将其复制到 static 目录下,完成之后的目录结构如下
D:\flask-register>tree /f卷 软件 的文件夹 PATH 列表卷序列号为 65F3-3762D:.│ app.py│├─static│ │ layui.js│ ││ ├─css│ │ layui.css│ ││ └─font│ iconfont.eot│ iconfont.svg│ iconfont.ttf│ iconfont.woff│ iconfont.woff2│└─templates
编写静态页面
- 复制 https://layui.dev/docs/2.8/layout/ 的内容进行修改。完成
index.html(首页)的制作。 - 复制 https://layui.dev/docs/2.8/form/#reg 修改为注册页
- 复制 https://layui.dev/docs/2.8/form/#login 修改为登录页
index.html
<html lang="en"><head><meta charset="UTF-8"><title>首页</title><link rel="stylesheet" href="/static/css/layui.css"></head><body><div class="layui-layout layui-layout-admin"><div class="layui-header"><div class="layui-logo layui-hide-xs layui-bg-black">正心全栈编程</div><ul class="layui-nav layui-layout-left"><li class="layui-nav-item"><a href="">最新活动</a></li><li class="layui-nav-item layui-this"><a href="">核心编程</a></li><li class="layui-nav-item"><a href="">爬虫开发</a></li><li class="layui-nav-item"><a href="">数据分析</a></li><li class="layui-nav-item"><a href="JavaScript:;">网站开发</a><dl class="layui-nav-child"><dd><a href="">前端基础</a></dd><dd><a href="">flask 框架</a></dd><dd><a href="">flask 项目</a></dd></dl></li></ul><ul class="layui-nav layui-layout-right"><li class="layui-nav-item layui-hide layui-show-sm-inline-block"><a href="javascript:;"><img src="//unpkg.com/outeres@0.0.10/img/layui/icon-v2.png" class="layui-nav-img">登录 / 正心</a><dl class="layui-nav-child"><dd><a href="javascript:;">个人中心</a></dd><dd><a href="javascript:;">设置</a></dd><dd><a href="javascript:;">退出</a></dd></dl></li></ul></div></div><script src="/static/layui.js"></script></body></html>
register.html
<html lang="en"><head><meta charset="UTF-8"><title>注册页</title><link rel="stylesheet" href="/static/css/layui.css"></head><body><style>.demo-reg-container {width: 320px;margin: 21px auto 0;}.demo-reg-other .layui-icon {position: relative;display: inline-block;margin: 0 2px;top: 2px;font-size: 26px;}</style><form class="layui-form" lay-filter="register-form"><div class="demo-reg-container"><div class="layui-form-item"><div class="layui-input-wrap"><div class="layui-input-prefix"><i class="layui-icon layui-icon-username"></i></div><input type="text" name="nickname" value="" lay-verify="required" placeholder="昵称" autocomplete="off"class="layui-input" lay-affix="clear"></div></div><div class="layui-form-item"><div class="layui-row"><div class="layui-col-xs7"><div class="layui-input-wrap"><div class="layui-input-prefix"><i class="layui-icon layui-icon-cellphone"></i></div><input type="text" name="mobile" value="" lay-verify="required|phone" placeholder="手机号"lay-reqtext="请填写手机号" autocomplete="off" class="layui-input" id="reg-cellphone"></div></div><div class="layui-col-xs5"><div style="margin-left: 11px;"><button type="button" class="layui-btn layui-btn-fluid layui-btn-primary"lay-on="reg-get-vercode">获取验证码</button></div></div></div></div><div class="layui-form-item"><div class="layui-input-wrap"><div class="layui-input-prefix"><i class="layui-icon layui-icon-vercode"></i></div><input type="text" name="vercode" value="" lay-verify="required" placeholder="验证码"lay-reqtext="请填写验证码" autocomplete="off" class="layui-input"></div></div><div class="layui-form-item"><div class="layui-input-wrap"><div class="layui-input-prefix"><i class="layui-icon layui-icon-password"></i></div><input type="password" name="password" value="" lay-verify="required" placeholder="密码"autocomplete="off" class="layui-input" id="reg-password" lay-affix="eye"></div></div><div class="layui-form-item"><div class="layui-input-wrap"><div class="layui-input-prefix"><i class="layui-icon layui-icon-password"></i></div><input type="password" name="confirmPassword" value="" lay-verify="required|confirmPassword"placeholder="确认密码" autocomplete="off" class="layui-input" lay-affix="eye"></div></div><div class="layui-form-item"><input type="checkbox" name="agreement" lay-verify="required" lay-skin="primary" title="同意"><a href="#terms" target="_blank" style="position: relative; top: 6px; left: -15px;"><ins>用户协议</ins></a></div><div class="layui-form-item"><button class="layui-btn layui-btn-fluid" lay-submit lay-filter="demo-reg">注册</button></div><div class="layui-form-item demo-reg-other"><label>社交账号注册</label><span style="padding: 0 21px 0 6px;"><a href="javascript:;"><i class="layui-icon layui-icon-login-qq" style="color: #3492ed;"></i></a><a href="javascript:;"><i class="layui-icon layui-icon-login-wechat" style="color: #4daf29;"></i></a><a href="javascript:;"><i class="layui-icon layui-icon-login-weibo" style="color: #cf1900;"></i></a></span><a href="/login">登录已有帐号</a></div></div></form><script src="/static/layui.js"></script><script>layui.use(function () {var $ = layui.$;var form = layui.form;var layer = layui.layer;});</script></body></html>
login.html
<html lang="en"><head><meta charset="UTF-8"><title>登录页</title><link rel="stylesheet" href="/static/css/layui.css"></head><body><style>.demo-login-container {width: 320px;margin: 21px auto 0;}.demo-login-other .layui-icon {position: relative;display: inline-block;margin: 0 2px;top: 2px;font-size: 26px;}</style><form class="layui-form"><div class="demo-login-container"><div class="layui-form-item"><div class="layui-input-wrap"><div class="layui-input-prefix"><i class="layui-icon layui-icon-cellphone"></i></div><input type="text" name="mobile" value="" lay-verify="required" placeholder="手机号"lay-reqtext="请填写手机号" autocomplete="off" class="layui-input" lay-affix="clear"></div></div><div class="layui-form-item"><div class="layui-input-wrap"><div class="layui-input-prefix"><i class="layui-icon layui-icon-password"></i></div><input type="password" name="password" value="" lay-verify="required" placeholder="密 码"lay-reqtext="请填写密码" autocomplete="off" class="layui-input" lay-affix="eye"></div></div><div class="layui-form-item"><div class="layui-row"><div class="layui-col-xs7"><div class="layui-input-wrap"><div class="layui-input-prefix"><i class="layui-icon layui-icon-vercode"></i></div><input type="text" name="captcha" value="" lay-verify="required" placeholder="验证码"lay-reqtext="请填写验证码" autocomplete="off" class="layui-input" lay-affix="clear"></div></div><div class="layui-col-xs5"><div style="margin-left: 10px;"><img src="https://www.oschina.net/action/user/captcha"id="captchaImage"></div></div></div></div><div class="layui-form-item"><input type="checkbox" name="remember" lay-skin="primary" title="记住密码"><a href="#forget" style="float: right; margin-top: 7px;">忘记密码?</a></div><div class="layui-form-item"><button class="layui-btn layui-btn-fluid" lay-submit lay-filter="demo-login">登录</button></div><div class="layui-form-item demo-login-other"><label>社交账号登录</label><span style="padding: 0 21px 0 6px;"><a href="javascript:;"><i class="layui-icon layui-icon-login-qq" style="color: #3492ed;"></i></a><a href="javascript:;"><i class="layui-icon layui-icon-login-wechat" style="color: #4daf29;"></i></a><a href="javascript:;"><i class="layui-icon layui-icon-login-weibo" style="color: #cf1900;"></i></a></span>或 <a href="/register">注册帐号</a></div></div></form><script src="/static/layui.js"></script><script>layui.use(function () {var form = layui.form;var layer = layui.layer;// 提交事件form.on('submit(demo-login)', function (data) {var field = data.field; // 获取表单字段值// 显示填写结果,仅作演示用layer.alert(JSON.stringify(field), {title: '当前填写的字段值'});// 此处可执行 Ajax 等操作// …return false; // 阻止默认 form 跳转});});</script></body></html>
后端适配
前端编写好了之后,需要适配后端路由才能实现访问。
from flask import Flask, render_templateapp = Flask(__name__)def index_view():return render_template('index.html')def register_view():return render_template('register.html')def login_view():return render_template('login.html')if __name__ == '__main__':app.run(debug=True)
后端路由编写好了之后,如果想要实现联调,就需要修改前端页面的逻辑。
- 没有登录之前不显示个人信息,只显示左上角的登录按钮,点击登录按钮调整到登录页面。
- 登录是没有账号,就先进行注册。注册页面如果有账号就跳转到登录页面。
- 注册账号成功之后跳转到登录页面
实现注册逻辑
注册逻辑观看最开始的时序图。
在实现逻辑之前,先完善一下前端跳转页面
1、没有登录之前不显示个人信息,只显示左上角的登录按钮,修改 index.html 的内容。
<ul class="layui-nav layui-layout-right"><li class="layui-nav-item layui-hide layui-show-sm-inline-block">{% if not is_login %}<a href="/login">登录</a>{% else %}<a href="javascript:;"><img src="//unpkg.com/outeres@0.0.10/img/layui/icon-v2.png" class="layui-nav-img">登录 / 正心</a><dl class="layui-nav-child"><dd><a href="javascript:;">个人中心</a></dd><dd><a href="javascript:;">设置</a></dd><dd><a href="javascript:;">退出</a></dd></dl>{% endif %}</li></ul>
2、登录是没有账号,就先进行注册。注册页面如果有账号就跳转到登录页面。
修改 login.html
<a href="/register">注册帐号</a>
修改 register.html
<a href="/login">登录已有帐号</a>
前端校验手机号
用户输入昵称、手机号码,点击获取验证码之前前端需要校验格式是否符合。
发送短信接口请看附录
1、输入手机号之后进行校验
<script>layui.use(function () {var $ = layui.$;var form = layui.form;var layer = layui.layer;var util = layui.util;// 普通事件util.on('lay-on', {// 获取验证码'reg-get-vercode': function (othis) {var isvalid = form.validate('#reg-cellphone'); // 主动触发验证,v2.7.0 新增// 验证通过if (isvalid) {{#layer.msg('手机号规则验证通过');#}// 此处可继续书写「发送验证码」等后续逻辑let data = form.val('register-form');// 发送请求fetch('/api/send_register_sms', {method: 'POST',headers: {'Content-Type': 'application/json'},body: JSON.stringify({'mobile': data.mobile})}).then(response => response.json()).then(data => {// 处理后端的响应数据if (!data.code) {layer.msg(data.message, {icon: 1})} else {layer.msg(data.message, {icon: 2})}})}}});});</script>
点击获取验证码之后,就会发送请求到后端,然后后端处理完之后返回结果给前端。
后端发送短信验证码
后端获取数据之后
- 解析前端传递过来的手机号,然后进行校验
- 调用第三方工具下发注册短信验证码(腾讯云、阿里云就有对于的服务)
- 将手机号、短信验证码记录到数据库。下次提交时再进行校验
import loggingimport reimport randomfrom flask import Flask, render_template, request, sessionapp = Flask(__name__)app.secret_key = 'notes.zhengxinonly.com'def send_register_sms():# 1. 解析前端传递过来的数据data = request.get_json()mobile = data['mobile']# 2. 校验手机号码pattern = r'^1[3-9]\d{9}$'ret = re.match(pattern, mobile)if not ret:return {'message': '电话号码不符合格式','code': -1}# 3. 发送短信验证码,并记录session['mobile'] = mobile# 3.1 生成随机验证码code = random.choices('123456789', k=6)session['code'] = ''.join(code)logging.warning(code)return {'message': '发送短信成功','code': 0}
因为是一个简单的案例,就没有调用腾讯云短信进行测试了,而是直接把正确结果返回。对于短信与验证码也没有存储到数据库了,而是放在 session 里面进行返回,下次请求注册时,再从里面进行提取。
实现注册逻辑
前端部分
在手机收到短信验证码之后,前端填入验证码,然后再输入两次密码,点击同意协议按钮,就能实现注册。在这同时需要做验证验证码与密码。
<script>layui.use(function () {var $ = layui.$;var form = layui.form;var layer = layui.layer;var util = layui.util;// 自定义验证规则form.verify({// 确认密码confirmPassword: function (value, item) {var passwordValue = $('#reg-password').val();if (value !== passwordValue) {return '两次密码输入不一致';}}});// 提交事件form.on('submit(demo-reg)', function (data) {var field = data.field; // 获取表单字段值// 是否勾选同意if (!field.agreement) {layer.msg('您必须勾选同意用户协议才能注册');return false;}// 此处可执行 Ajax 等操作fetch('/api/register', {method: 'POST',headers: {'Content-Type': 'application/json'},body: JSON.stringify(field)}).then(response => response.json()).then(data => {// 处理后端的响应数据if (!data.code) {layer.msg(data.message, {icon: 1})setTimeout(function () {location.href = '/login'}, 2000)} else {layer.msg(data.message, {icon: 2})}})return false; // 阻止默认 form 跳转});});</script>
后端逻辑
数据库
小案例,使用 sqlite 是最方便的。
db.py
import sqlite3class Database:def __init__(self):self.conn = sqlite3.connect('flask-layui.sqlite')self.cursor = self.conn.cursor()def create_table(self):sql = """create table user(id integer primary key,nickname varchar(255),mobile varchar(50),password varchar(50));"""self.cursor.execute(sql)self.conn.commit()def insert(self, nickname, mobile, password):sql = 'insert into user(nickname, mobile, password) values (?, ?, ?);'self.cursor.execute(sql, (nickname, mobile, password))self.conn.commit()def search(self, mobile):sql = 'select password from user where mobile=?;'self.cursor.execute(sql, (mobile,))return self.cursor.fetchone()db = Database()if __name__ == '__main__':db.create_table()# db.insert('正心全栈编程', '18675867241', '123456')# ret = db.search('18675867241')# print(ret)
实现登录逻辑
登录时序图

图片验证码
- 客户端请求服务器获得图片验证码
- 服务器返回图片,将生成的验证码保存到数据库
- 客户端携带验证码进行请求,服务器校验数据
- 登录成功之后跳转到首页,登录失败则重新登录
前端发送请求
生成 uuid 用于记录验证码
<script>layui.use(function () {var $ = layui.$;var captcha_uuid = '';// 点击按钮更新验证码$('#captchaImage').click(function () {// 浏览器要发起图片验证码请求/captcha_code?captcha_code_uuid=xxxxxcaptcha_uuid = generateUUID();document.getElementById('captchaImage').src = '/get_captcha?captcha_uuid=' + captcha_uuid;});captcha_uuid = generateUUID();document.getElementById('captchaImage').src = '/get_captcha?captcha_uuid=' + captcha_uuid;});</script>
图片验证码更新
<script>layui.use(function () {var form = layui.form;var layer = layui.layer;var $ = layui.$;var captcha_uuid = '';// 点击按钮更新验证码$('#captchaImage').click(function () {// 浏览器要发起图片验证码请求/captcha_code?captcha_code_uuid=xxxxxcaptcha_uuid = generateUUID();document.getElementById('captchaImage').src = '/get_captcha?captcha_uuid=' + captcha_uuid;});captcha_uuid = generateUUID();document.getElementById('captchaImage').src = '/get_captcha?captcha_uuid=' + captcha_uuid;});</script>
后端生成图片验证码
安装依赖
pip install captcha
然后编写后端的视图
get_captcha.py
# filename: /get_captcha.pyfrom io import BytesIOfrom random import choicesfrom captcha.image import ImageCaptchafrom flask import make_responsefrom PIL import Imagedef gen_captcha(content="0123456789"):"""生成验证码"""image = ImageCaptcha()# 获取字符串captcha_text = "".join(choices(content, k=4))# 生成图像captcha_image = Image.open(image.generate(captcha_text))return captcha_text, captcha_image# 生成验证码def get_captcha_code_and_content():code, image = gen_captcha()out = BytesIO()image.save(out, "png")out.seek(0)content = out.read() # 读取图片的二进制数据做成响应体return code, contentif __name__ == '__main__':code, content = get_captcha_code_and_content()print(code, content)
app.py
from get_captcha import get_captcha_code_and_contentdef get_captcha_view():# 1. 获取参数captcha_uuid = request.args.get("captcha_uuid")# 2. 生成验证码code, content = get_captcha_code_and_content()# 3. 记录数据到数据库(用session代替)session['code'] = coderesp = make_response(content) # 读取图片的二进制数据做成响应体resp.content_type = "image/png"# 4. 错误处理# 5. 响应返回return resp
前端请求进行登录
<script>layui.use(function () {var form = layui.form;var layer = layui.layer;var captcha_uuid = '';// 提交事件form.on('submit(demo-login)', function (data) {var field = data.field; // 获取表单字段值// 显示填写结果,仅作演示用field['captcha_uuid'] = captcha_uuid// 此处可执行 Ajax 等操作fetch('/api/login', {method: 'POST',headers: {'Content-Type': 'application/json'},body: JSON.stringify(field)}).then(response => response.json()).then(data => {if (!data.code) {layer.msg(data.message, {icon: 1})setTimeout(function () {location.href = '/'}, 2000)} else {layer.msg(data.message, {icon: 2})}})return false; // 阻止默认 form 跳转});});</script>
后端验证登录
编写登录接口
.post('/api/login')def login_api():data = request.get_json()ret = Database().search(data['mobile'])code = session['code']if code != data['captcha']:return {'message': '验证码错误','code': -1}if not ret:return {'message': '用户不存在','code': -1}pwd = ret[0]if pwd != data['password']:return {'message': '用户密码错误','code': -1}session['is_login'] = True # 记录用户登录return {'message': '用户登录成功','code': 0}
完善 index.html 渲染
@app.route('/')def index_view():is_login = session.get('is_login')return render_template('index.html', is_login=is_login)
附录:API
https://apifox.com/apidoc/shared-1705c3ba-b68b-4ae4-b635-eacc8f7949a9




