实现效果
登录页面
用户个人主页
新建订单
一、表结构设计
user_order
userinfo
建表
SQL
-- ------------------------------
-- 1. 创建用户表 userinfo
-- ------------------------------
CREATE TABLE userinfo (
-- 主键:自增整数(推荐用 SERIAL 实现自增,确保非空且唯一)
id SERIAL PRIMARY KEY,
-- 手机号:varchar(11),非空(11位手机号,如中国大陆手机号)
mobile VARCHAR(11) NOT NULL,
-- 密码:varchar(64),非空(通常存储 MD5/SHA256 哈希后的密码,长度足够)
password VARCHAR(64) NOT NULL,
-- 真实姓名:varchar(16),非空(最长16个字符,覆盖大多数姓名场景)
real_name VARCHAR(16) NOT NULL,
-- 角色:int,非空(建议用枚举含义,如 1=普通用户,2=管理员,3=操作员等)
role INT NOT NULL,
-- 可选优化:添加手机号唯一约束(避免重复注册)
CONSTRAINT uk_userinfo_mobile UNIQUE (mobile)
);
-- ------------------------------
-- 2. 创建订单表 user_order(避免 order 关键字)
-- ------------------------------
CREATE TABLE user_order (
-- 主键:自增整数,确保订单ID唯一
id SERIAL PRIMARY KEY,
-- 订单关联的URL:varchar(255),非空(存储目标URL,长度满足大多数场景)
url VARCHAR(255) NOT NULL,
-- 数量:int,非空(如订单商品数量、任务次数等,需根据业务定义正负范围)
count INT NOT NULL CHECK (count > 0), -- 可选约束:确保数量为正数
-- 关联用户ID:int,非空(关联 userinfo 表的 id,确保订单归属有效用户)
user_id INT NOT NULL,
-- 订单状态:int,非空(建议枚举含义,如 0=待处理,1=已完成,2=已取消等)
status INT NOT NULL,
-- 外键约束:确保 user_id 必须在 userinfo 表中存在(避免无效用户订单)
CONSTRAINT fk_order_userinfo FOREIGN KEY (user_id)
REFERENCES userinfo (id)
-- 可选:用户删除时的订单处理策略(根据业务选择)
ON DELETE RESTRICT, -- RESTRICT:禁止删除有订单的用户;CASCADE:删除用户时同步删除订单
-- 可选优化:添加订单查询索引(按用户ID+状态,提升查询效率)
CONSTRAINT idx_order_user_status UNIQUE (user_id, status)
);
插入示例数据
为了简单,这里密码直接使用明文。
SQL
-- 向用户表 userinfo 插入5条示例数据(明文密码)
INSERT INTO userinfo (mobile, password, real_name, role) VALUES
('13800138000', 'zhangsan123', '张三', 1), -- 普通用户
('13900139000', 'admin456', '李四', 2), -- 管理员角色
('13700137000', 'wangwu789', '王五', 1),
('13600136000', 'operator000', '赵六', 3), -- 操作员角色
('13500135000', 'sunqi2023', '孙七', 1);
-- 向订单表 user_order 插入5条示例数据(关联上面的用户ID)
INSERT INTO user_order (url, count, user_id, status) VALUES
('https://example.com/product/1001', 2, 1, 0), -- 张三的待处理订单
('https://example.com/service/2002', 1, 2, 1), -- 李四的已完成订单
('https://example.com/goods/3003', 5, 3, 0), -- 王五的待处理订单
('https://example.com/item/4004', 3, 1, 2), -- 张三的已取消订单
('https://example.com/order/5005', 10, 5, 1); -- 孙七的已完成订单
userinfo表
user_order表
二、功能开发
1. 基于flask蓝图创建项目
创建项目
__init__.py
Python
from flask import Flask
def create_app():
app = Flask(__name__)
return app
app.py
Python
from FlaskDemo import create_app
app = create_app()
if __name__ == '__main__':
app.run()
创建蓝图
根据功能创建2个蓝图
account.py
Python
from flask import Blueprint
# 蓝图对象
ac = Blueprint("account", __name__)
@ac.route('/login')
def login():
return "登录"
@ac.route('/users')
def users():
return "用户列表"
order.py
Python
from flask import Blueprint
# 蓝图对象
od = Blueprint("order", __name__)
@od.route('/order/list')
def order_list():
return "订单列表"
@od.route('/order/create')
def order_create():
return "创建订单"
注册蓝图
__init__.py
Python
from flask import Flask
from .views import account, order
def create_app():
app = Flask(__name__)
# 注册 相当于加入视图函数
app.register_blueprint(account.ac)
app.register_blueprint(order.od)
return app
2. 用户登录
用户输入身份、手机号、密码后点击提交,会从数据库进行校验。
正确则跳转到订单页面。
错误则提示错误信息。
templates/login.html
新建一个模板,作为登录页面。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<h1>用户登录</h1>
<form method="post" action="/login">
<select name="role">
<option value="1">客户</option>
<option value="2">管理员</option>
</select>
<input type="text" name="mobile" placeholder="手机号"/>
<input type="password" name="pwd" placeholder="密码"/>
<input type="submit" value="提 交"/>
<span style="color:red;"> {{ error }} </span>
</form>
</body>
</html>
views/account.py
Python
from flask import Blueprint, render_template, request, redirect
import psycopg2
from dbutils.pooled_db import PooledDB
# 蓝图对象
ac = Blueprint("account", __name__)
POOL = PooledDB(
# 连接池配置
creator=psycopg2, # 用来连接数据库的模块
maxconnections=10, # 连接池最大允许的连接数 0和None表示不限制连接数
mincached=2, # 初始化时,连接池中最少创建的空闲连接,0表示不创建
maxcached=3, # 连接池中最多闲置的的连接,0和None不限制
blocking=True, # 连接池中如果没有可用连接,是否阻塞等待
setsession=[], # 开始会话前执行的命令列表 如:["set datestyle to ...", "..."]
ping=0,
# 数据库配置
host="127.0.0.1",
port="5432",
user="postgres",
password="123456",
database="postgres"
)
@ac.route('/login', methods=["GET", "POST"])
def login():
if request.method == "GET":
return render_template("login.html")
role = request.form.get("role")
mobile = request.form.get("mobile")
pwd = request.form.get("pwd")
print(role, mobile, pwd)
# 去数据库校验
conn = POOL.connection() # 通过连接池获取连接
cursor = conn.cursor()
cursor.execute("select * from userinfo where role=%s and mobile=%s and password=%s", [role, mobile, pwd])
user_data = cursor.fetchone()
cursor.close()
conn.close() # 将此连接交还连接池
# 登录成功,跳转到订单列表
if user_data:
return redirect('/order/list')
# 登录失败,显示错误信息
return render_template("login.html", error="用户名或密码错误")
@ac.route('/users')
def users():
return "用户列表"
登录
正确
错误
优化界面
下载
下载jQuery-3.6.0:https://code.jquery.com/jquery-3.6.0.min.js
下载Bootstrap-3.4.1:https://getbootstrap.com/docs/3.4/getting-started/#download
login.html
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
<link rel="stylesheet" href="/static/bootstrap-3.4.1/css/bootstrap.css">
<style>
.login-box {
margin-top: 30px;
margin-left: auto;
margin-right: auto;
width: 450px;
border: 1px solid #ddd;
padding: 50px;
}
.login-box h2 {
text-align: center;
}
</style>
</head>
<body>
<div class="login-box">
<h2>用户登录</h2>
<form method="post" action="/login">
<div class="form-group">
<select name="role" class="form-control">
<option value="1">客户</option>
<option value="2">管理员</option>
</select>
</div>
<div class="form-group">
<label>手机号</label>
<input type="text" class="form-control" placeholder="手机号" name="mobile"/>
</div>
<div class="form-group">
<label>密码</label>
<input type="password" class="form-control" placeholder="密码" name="pwd"/>
</div>
<input type="submit" class="btn btn-primary" value="登 录">
<span style="color:red;"> {{ error }} </span>
</form>
</div>
</body>
</html>
优化数据库操作
将数据库相关操作隔离出来
db.py
Python
import psycopg2
from dbutils.pooled_db import PooledDB
from psycopg2 import extras # 导入额外的游标类型
POOL = PooledDB(
# 连接池配置
creator=psycopg2, # 用来连接数据库的模块
maxconnections=10, # 连接池最大允许的连接数 0和None表示不限制连接数
mincached=2, # 初始化时,连接池中最少创建的空闲连接,0表示不创建
maxcached=3, # 连接池中最多闲置的的连接,0和None不限制
blocking=True, # 连接池中如果没有可用连接,是否阻塞等待
setsession=[], # 开始会话前执行的命令列表 如:["set datestyle to ...", "..."]
ping=0,
# 数据库配置
host="127.0.0.1",
port="5432",
user="postgres",
password="123456",
database="postgres"
)
def fetch_one(sql, params):
# 去数据库校验
conn = POOL.connection() # 通过连接池获取连接
cursor = conn.cursor(cursor_factory=extras.DictCursor) # 使游标返回字典类型
cursor.execute(sql, params)
result = cursor.fetchone()
cursor.close()
conn.close() # 将此连接交还连接池
return result
account.py
Python
from flask import Blueprint, render_template, request, redirect
from utils import db
# 蓝图对象
ac = Blueprint("account", __name__)
@ac.route('/login', methods=["GET", "POST"])
def login():
if request.method == "GET":
return render_template("login.html")
role = request.form.get("role")
mobile = request.form.get("mobile")
pwd = request.form.get("pwd")
# 执行数据库校验
sql = "select * from userinfo where role=%s and mobile=%s and password=%s"
params = [role, mobile, pwd]
user_data = db.fetch_one(sql, params)
# 登录成功,跳转到订单列表
if user_data:
return redirect('/order/list')
# 登录失败,显示错误信息
return render_template("login.html", error="用户名或密码错误")
@ac.route('/users')
def users():
return "用户列表"
请求拦截
使用session存储用户信息。
判断:
- 如果访问登录页面,所有人都可以访问。
- 其他业务页面,登录成功的人才能访问。
已登录,继续访问
未登录,返回登录页面
__init__.py
增加密钥和拦截器
Python
from flask import Flask, request, session, redirect
from .views import account, order
def auth():
# 访问静态文件,继续向后执行,不拦截 不加这个会导致页面失去效果
if request.path.startswith("/static"):
return
# 访问登录页面,继续向后执行,不拦截
if request.path == '/login':
return
# 用户已经登录过了,继续向后执行,不拦截
user_info = session.get("user_info")
if user_info:
return
return redirect('/login')
def create_app():
app = Flask(__name__)
app.secret_key = 'wsz4bdjny4dy1jyz3qhdxd0rs'
# 注册 相当于加入视图函数
app.register_blueprint(account.ac)
app.register_blueprint(order.od)
# 拦截器
app.before_request(auth)
return app
account.py
增加session
Python
from flask import Blueprint, render_template, request, redirect, session
from utils import db
# 蓝图对象
ac = Blueprint("account", __name__)
@ac.route('/login', methods=["GET", "POST"])
def login():
if request.method == "GET":
return render_template("login.html")
role = request.form.get("role")
mobile = request.form.get("mobile")
pwd = request.form.get("pwd")
# 执行数据库校验
sql = "select * from userinfo where role=%s and mobile=%s and password=%s"
params = [role, mobile, pwd]
user_data = db.fetch_one(sql, params)
# 登录成功,跳转到订单列表
if user_data:
session["user_info"] = {
'role': user_data['role'],
'real_name': user_data['real_name'],
'id': user_data['id']
}
return redirect('/order/list')
# 登录失败,显示错误信息
return render_template("login.html", error="用户名或密码错误")
@ac.route('/users')
def users():
return "用户列表"
order.py
Python
from flask import Blueprint, session, redirect
# 蓝图对象
od = Blueprint("order", __name__)
@od.route('/order/list')
def order_list():
return "订单列表"
@od.route('/order/create')
def order_create():
return "创建订单"
@od.route('/order/delete')
def order_delete():
return "删除订单"
3. 订单列表
读取订单表,展示订单的信息。
- 管理员:所有订单
- 客户:当前客户自己的订单
展示当前用户订单
templates/order_list.html
用于展示所有订单
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<table border="1">
<thead>
<tr>
<td>订单ID</td>
<td>URL</td>
<td>数量</td>
<td>状态</td>
<td>用户ID</td>
</tr>
</thead>
<tbody>
{% for item in data_list %}
<tr>
<td>{{item.id}}</td>
<td>{{item.url}}</td>
<td>{{item['count']}}</td> <!-- count与PostgreSQL方法命名冲突 -->
<td>{{item.status}}</td>
<td>{{item.user_id}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</body>
</html>
db.py
增加fetch_all,方便取所有数据
Python
import psycopg2
from dbutils.pooled_db import PooledDB
from psycopg2 import extras # 导入额外的游标类型
POOL = PooledDB(
# 连接池配置
creator=psycopg2, # 用来连接数据库的模块
maxconnections=10, # 连接池最大允许的连接数 0和None表示不限制连接数
mincached=2, # 初始化时,连接池中最少创建的空闲连接,0表示不创建
maxcached=3, # 连接池中最多闲置的的连接,0和None不限制
blocking=True, # 连接池中如果没有可用连接,是否阻塞等待
setsession=[], # 开始会话前执行的命令列表 如:["set datestyle to ...", "..."]
ping=0,
# 数据库配置
host="127.0.0.1",
port="5432",
user="postgres",
password="123456",
database="postgres"
)
def fetch_one(sql, params):
# 去数据库校验
conn = POOL.connection() # 通过连接池获取连接
cursor = conn.cursor(cursor_factory=extras.DictCursor) # 使游标返回字典类型
cursor.execute(sql, params)
result = cursor.fetchone()
cursor.close()
conn.close() # 将此连接交还连接池
return result
def fetch_all(sql, params):
# 去数据库校验
conn = POOL.connection() # 通过连接池获取连接
cursor = conn.cursor(cursor_factory=extras.DictCursor) # 使游标返回字典类型
cursor.execute(sql, params)
result = cursor.fetchall()
cursor.close()
conn.close() # 将此连接交还连接池
return result
order.py
增加获取当前用户订单的逻辑
Python
from flask import Blueprint, session, render_template
from utils import db
# 蓝图对象
od = Blueprint("order", __name__)
@od.route('/order/list')
def order_list():
user_info = session.get("user_info")
role = user_info['role']
if role == 2:
data_list = db.fetch_all("select * from user_order", [])
else:
id = user_info['id']
data_list = db.fetch_all("select * from user_order where user_id=%s", [id, ])
return render_template("order_list.html", data_list=data_list)
@od.route('/order/create')
def order_create():
return "创建订单"
@od.route('/order/delete')
def order_delete():
return "删除订单"
优化:显示用户名 + 状态
order.py 修改order_list(),增加联表查询
Python
@od.route('/order/list')
def order_list():
user_info = session.get("user_info")
role = user_info['role'] # 2-管理员
if role == 2:
sql = "select * from user_order left join userinfo on user_order.user_id = userinfo.id;"
data_list = db.fetch_all(sql, [])
else:
id = user_info['id']
sql = "select * from user_order left join userinfo on user_order.user_id = userinfo.id where user_id=%s;"
data_list = db.fetch_all(sql, [id, ])
status_dict = {
0: "正在下单",
1: "待执行",
2: "正在执行",
3: "完成",
4: "失败"
}
return render_template("order_list.html", data_list=data_list, status_dict=status_dict)
order_list.html 修改表体,将状态显示为文字,增加用户名列
html
<tbody>
{% for item in data_list %}
<tr>
<td>{{item.id}}</td>
<td>{{item.url}}</td>
<td>{{item['count']}}</td> <!-- count与PostgreSQL方法命名冲突 -->
<td>{{status_dict[item.status]}}</td>
<td>{{item.user_id}}</td>
<td>{{item.real_name}}</td>
</tr>
{% endfor %}
</tbody>
前端美化
增加导航栏,美化状态显示。
order.py
Python
from flask import Blueprint, session, render_template
from utils import db
# 蓝图对象
od = Blueprint("order", __name__)
@od.route('/order/list')
def order_list():
user_info = session.get("user_info")
role = user_info['role'] # 2-管理员
if role == 2:
sql = "select * from user_order left join userinfo on user_order.user_id = userinfo.id;"
data_list = db.fetch_all(sql, [])
else:
id = user_info['id']
sql = "select * from user_order left join userinfo on user_order.user_id = userinfo.id where user_id=%s;"
data_list = db.fetch_all(sql, [id, ])
status_dict = {
0: {"text": "正在下单", "cls": "default"},
1: {"text": "待执行", "cls": "primary"},
2: {"text": "正在执行", "cls": "warning"},
3: {"text": "完成", "cls": "success"},
4: {"text": "失败", "cls": "danger"}
}
return render_template("order_list.html", data_list=data_list, status_dict=status_dict, real_name=user_info['real_name'])
@od.route('/order/create')
def order_create():
return "创建订单"
@od.route('/order/delete')
def order_delete():
return "删除订单"
order_list.html
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="/static/bootstrap-3.4.1/css/bootstrap.css">
</head>
<body>
<nav class="navbar navbar-default" style="border-radius:0">
<div class="container-fluid">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">订单平台</a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li class="active"><a href="#">Link <span class="sr-only">(current)</span></a></li>
<li><a href="#">Link</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"> {{real_name}} <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="#">Action</a></li>
<li><a href="#">Another action</a></li>
<li><a href="#">Something else here</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">Separated link</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">One more separated link</a></li>
</ul>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="#">Action</a></li>
<li><a href="#">Another action</a></li>
<li><a href="#">Something else here</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">Separated link</a></li>
</ul>
</li>
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
<div class="container">
<table class="table table-bordered">
<thead>
<tr>
<td>订单ID</td>
<td>URL</td>
<td>数量</td>
<td>状态</td>
<td>用户ID</td>
<td>用户名</td>
</tr>
</thead>
<tbody>
{% for item in data_list %}
<tr>
<td>{{item.id}}</td>
<td>{{item.url}}</td>
<td>{{item['count']}}</td> <!-- count与PostgreSQL方法命名冲突 -->
<td><span class="label label-{{status_dict[item.status].cls}}">{{status_dict[item.status].text}}</span></td>
<td>{{item.user_id}}</td>
<td>{{item.real_name}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</body>
</html>
4. 创建订单
前端模板提取
几个业务页面的主题是一致的,可以提取为一个模板,方便之后维护。
提取出的模板如下:
{% block body %} {% endblock %} 相当于占位符。
layout.html
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<link rel="stylesheet" href="/static/bootstrap-3.4.1/css/bootstrap.css">
</head>
<body>
<nav class="navbar navbar-default" style="border-radius:0">
<div class="container-fluid">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">订单平台</a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li class="active"><a href="#">Link <span class="sr-only">(current)</span></a></li>
<li><a href="#">Link</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"> {{real_name}} <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="#">Action</a></li>
<li><a href="#">Another action</a></li>
<li><a href="#">Something else here</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">Separated link</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">One more separated link</a></li>
</ul>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="#">Action</a></li>
<li><a href="#">Another action</a></li>
<li><a href="#">Something else here</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">Separated link</a></li>
</ul>
</li>
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
<div class="container">
{% block body %}
{% endblock %}
</div>
<script src="/static/jquery-3.6.0.min.js"></script>
<script src="/static/bootstrap-3.4.1/js/bootstrap.js"></script>
</body>
</html>
这样有这部分代码的页面就可以更简洁。
order_create.html
html
{% extends 'layout.html' %}
{% block body %}
<div class="panel panel-default">
<div class="panel-heading">
新建订单
</div>
<div class="panel-body">
<div class="form-group">
<input type="text" class="form-control" placeholder="url">
<input type="number" class="form-control" placeholder="数量">
</div>
<button type="submit" class="btn btn-success"> 提交订单 </button>
</div>
</div>
{% endblock %}
order_list.html
html
{% extends 'layout.html' %}
{% block body %}
<div style="margin-bottom:10px">
<a class="btn btn-success" href="/order/create">
<span class="glyphicon glyphicon-plus-sign"></span> 添加
</a>
</div>
<table class="table table-bordered">
<thead>
<tr>
<td>订单ID</td>
<td>URL</td>
<td>数量</td>
<td>状态</td>
<td>用户ID</td>
<td>用户名</td>
</tr>
</thead>
<tbody>
{% for item in data_list %}
<tr>
<td>{{item.id}}</td>
<td>{{item.url}}</td>
<td>{{item['count']}}</td> <!-- count与PostgreSQL方法命名冲突 -->
<td><span class="label label-{{status_dict[item.status].cls}}">{{status_dict[item.status].text}}</span></td>
<td>{{item.user_id}}</td>
<td>{{item.real_name}}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}
order.py
Python
from flask import Blueprint, session, render_template
from utils import db
# 蓝图对象
od = Blueprint("order", __name__)
@od.route('/order/list')
def order_list():
user_info = session.get("user_info")
role = user_info['role'] # 2-管理员
if role == 2:
sql = "select * from user_order left join userinfo on user_order.user_id = userinfo.id;"
data_list = db.fetch_all(sql, [])
else:
id = user_info['id']
sql = "select * from user_order left join userinfo on user_order.user_id = userinfo.id where user_id=%s;"
data_list = db.fetch_all(sql, [id, ])
status_dict = {
0: {"text": "正在下单", "cls": "default"},
1: {"text": "待执行", "cls": "primary"},
2: {"text": "正在执行", "cls": "warning"},
3: {"text": "完成", "cls": "success"},
4: {"text": "失败", "cls": "danger"}
}
return render_template("order_list.html", data_list=data_list, status_dict=status_dict, real_name=user_info['real_name'])
@od.route('/order/create')
def order_create():
user_info = session.get("user_info")
return render_template("order_create.html", real_name=user_info['real_name'])
@od.route('/order/delete')
def order_delete():
return "删除订单"
页面效果
优化
每次切换任务界面,都要获取session中的用户名来替换导航栏中的用户名,非常麻烦。
可以使用模板函数,让模板自动获取用户名。
__init__.py
核心代码
Python
from flask import session
def get_real_name():
user_info = session.get("user_info")
real_name = user_info['real_name']
return real_name
def create_app():
app = Flask(__name__)
...
app.template_global()(get_real_name)
...
return app
layout.html核心代码
在需要显示用户名的地方嵌入如下代码即可。
html
{{ get_real_name() }}
这样就不必每个页面都获取一次用户名了。
5. 提交订单
点击提交
可以看到新的订单出现了
我这里遇到一个报错。当时示例数据库是用AI生成的,加了外键导致错误,现在删了省的麻烦。
SQL
ALTER TABLE user_order
DROP CONSTRAINT idx_order_user_status;
db.py 新增插入
Python
def insert(sql, params):
conn = POOL.connection()
cursor = conn.cursor(cursor_factory=extras.DictCursor)
cursor.execute(sql, params)
conn.commit()
result = cursor.fetchone()
lastrowid = result[0] if result else None
cursor.close()
conn.close()
return lastrowid
utils/cache.py Redis连接与入队
Python
import redis
REDIS_POOL = redis.ConnectionPool(
host='127.0.0.1',
password='',
port=6379,
encoding='utf-8',
max_connections=100
)
TASK_QUEUE = "task"
RESULT_QUEUE = "task_result"
def push_queue(value):
conn = redis.Redis(connection_pool=REDIS_POOL)
conn.lpush(TASK_QUEUE, value)
order.py
Python
@od.route('/order/create', methods=["GET", "POST"])
def order_create():
if request.method == "GET":
return render_template("order_create.html")
url = request.form.get('url')
count = request.form.get('count')
# 写入数据库
user_info = session.get("user_info")
sql = "insert into user_order(url, count, user_id, status) values(%s, %s, %s, 0) RETURNING id"
params = [url, count, user_info['id']]
order_id = db.insert(sql, params)
print(order_id)
# 写入redis队列
cache.push_queue(order_id)
return redirect('/order/list')
6. Worker执行订单
新建一个Worker项目,utils从老项目直接复制过来
db.py
Python
import psycopg2
from dbutils.pooled_db import PooledDB
from psycopg2 import extras # 导入额外的游标类型
POOL = PooledDB(
# 连接池配置
creator=psycopg2, # 用来连接数据库的模块
maxconnections=10, # 连接池最大允许的连接数 0和None表示不限制连接数
mincached=2, # 初始化时,连接池中最少创建的空闲连接,0表示不创建
maxcached=3, # 连接池中最多闲置的的连接,0和None不限制
blocking=True, # 连接池中如果没有可用连接,是否阻塞等待
setsession=[], # 开始会话前执行的命令列表 如:["set datestyle to ...", "..."]
ping=0,
# 数据库配置
host="127.0.0.1",
port="5432",
user="postgres",
password="123456",
database="postgres"
)
def fetch_one(sql, params):
# 去数据库校验
conn = POOL.connection() # 通过连接池获取连接
cursor = conn.cursor(cursor_factory=extras.DictCursor) # 使游标返回字典类型
cursor.execute(sql, params)
result = cursor.fetchone()
cursor.close()
conn.close() # 将此连接交还连接池
return result
def fetch_all(sql, params):
# 去数据库校验
conn = POOL.connection() # 通过连接池获取连接
cursor = conn.cursor(cursor_factory=extras.DictCursor) # 使游标返回字典类型
cursor.execute(sql, params)
result = cursor.fetchall()
cursor.close()
conn.close() # 将此连接交还连接池
return result
def insert(sql, params):
conn = POOL.connection()
cursor = conn.cursor(cursor_factory=extras.DictCursor)
cursor.execute(sql, params)
conn.commit()
result = cursor.fetchone()
lastrowid = result[0] if result else None
cursor.close()
conn.close()
return lastrowid
def update(sql, params):
conn = POOL.connection()
cursor = conn.cursor(cursor_factory=extras.DictCursor)
cursor.execute(sql, params)
conn.commit()
cursor.close()
conn.close()
cache.py
Python
import redis
from utils import db
REDIS_POOL = redis.ConnectionPool(
host='127.0.0.1',
password='',
port=6379,
encoding='utf-8',
max_connections=100
)
TASK_QUEUE = "task"
RESULT_QUEUE = "task_result"
def push_queue(value):
conn = redis.Redis(connection_pool=REDIS_POOL)
conn.lpush(TASK_QUEUE, value)
def pop_queue():
conn = redis.Redis(connection_pool=REDIS_POOL)
data = conn.brpop(TASK_QUEUE, timeout=10)
if not data:
return
return data[1].decode('utf-8')
def fetch_total_queue():
conn = redis.Redis(connection_pool=REDIS_POOL)
total_count = conn.llen(TASK_QUEUE)
conn.lrange(name=TASK_QUEUE, start=0, end=total_count)
def db_queue_init():
"""
1. 去数据库获取待执行的订单ID
2. 去redis中获取待执行的订单ID
3. 找到数据库中有 且 redis队列没有的 订单ID → 放入redis队列
:return:
"""
# 1. 去数据库获取待执行的订单ID
sql = "select id from user_order where status=1"
params = []
db_list = db.fetch_all(sql, params)
# db_id_list = [item['id'] for item in db_list]
db_id_list = { item['id'] for item in db_list } # 集合
# 2. 去redis中获取待执行的订单ID
conn = redis.Redis(connection_pool=REDIS_POOL)
total_count = conn.llen(TASK_QUEUE)
cache_list = conn.lrange(TASK_QUEUE, start=0, end=total_count)
# cache_int_list = [int(item.decode('utf-8')) for item in cache_list]
cache_int_list = { int(item.decode('utf-8')) for item in cache_list}
# 3. 找到数据库中有 且 redis队列没有的 订单ID → 放入redis队列
need_push = db_id_list - cache_int_list
conn.lpush(TASK_QUEUE, *need_push)
Worker.py
Python
from concurrent.futures import ThreadPoolExecutor
import time
from utils import db, cache
def update_order(order_id, status):
sql = "update user_order set status=%s where id=%s"
params = [status, order_id]
db.update(sql, params)
def get_order_object(order_id):
sql = "select * from user_order where id=%s"
params = [order_id]
res = db.fetch_one(sql, params)
return res
def task(info_dict):
time.sleep(3)
def run():
# 1. 初始化数据库未在队列的订单
cache.db_queue_init()
while True:
# 2. 去队列中获取订单
order_id = cache.pop_queue()
if not order_id:
continue
# 3. 查看订单是否存在
order_dict = get_order_object(order_id)
if not order_dict:
continue
# 4. 更新订单状态 待执行 → 执行中
update_order(order_id, 2)
# 5. 执行订单
print("执行订单任务:", order_id)
thread_pool = ThreadPoolExecutor(10)
for i in range(order_dict['count']):
thread_pool.submit(task, order_dict)
thread_pool.shutdown()
# 6. 执行完成
update_order(order_id, 3)
if __name__ == '__main__':
run()
参考资料
1\] [【最快速度搞定Flask-框架教程】用5小时讲完的python-flask项目实战全套教程](https://www.bilibili.com/video/BV11PoTYkEE1?spm_id_from=333.788.videopod.episodes&vd_source=d14dcbb430127497f9d223f1e6337035&p=15)