Layui + Flask 实现注册、登录功能(案例篇)
看了 layui 表单相关的知识,接下来就可以实现注册功能,功能逻辑如下:
项目创建
- 新建 flask 项目
- 下载 layui 文件,解压之后复制到指定文件
- 编写前端首页、注册页、登录页。注册页使用之前看过的注册页表单
- 编写后端路由,实现前后端基本的跳转
新建 flask 项目
新建项目的方式有很多种,只要有任意一种方式创建即刻。在这里我直接选择使用 pycharm 进行创建。
新建成功后目录如下
D:\flask-register>tree /f
D:.
│ app.py
├─static
└─templates
下载 layui 文件
访问 https://layui.dev/ ,点击直接下载之后就能得到 layui 的静态文件,将其复制到 static
目录下,完成之后的目录结构如下
D:\flask-register>tree /f
卷 软件 的文件夹 PATH 列表
卷序列号为 65F3-3762
D:.
│ 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_template
app = 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 logging
import re
import random
from flask import Flask, render_template, request, session
app = 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 sqlite3
class 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=xxxxx
captcha_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=xxxxx
captcha_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.py
from io import BytesIO
from random import choices
from captcha.image import ImageCaptcha
from flask import make_response
from PIL import Image
def 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, content
if __name__ == '__main__':
code, content = get_captcha_code_and_content()
print(code, content)
app.py
from get_captcha import get_captcha_code_and_content
def get_captcha_view():
# 1. 获取参数
captcha_uuid = request.args.get("captcha_uuid")
# 2. 生成验证码
code, content = get_captcha_code_and_content()
# 3. 记录数据到数据库(用session代替)
session['code'] = code
resp = 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>
后端验证登录
编写登录接口
'/api/login') .post(
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