flask之路【第六篇】项目刷单网站实现
目录
✅创建一个网站项目用来提交刷视频订单。
💎1 结构
功能结构图:

- 数据库表结构:
💎2. 功能开发
2.1 用户登录(登陆和订单部分)
💡基于蓝图创建项目结构 (├ 符号通过按住键盘alt和小键盘195实现;─符号通过按住键盘alt和小键盘196实现)
P_watchingvideo_ordering_web20250614/ #工程项目名称实现视频刷单订单的提交
├── app.py # 主应用
├── readme.md
├── Blueprints250614/ # 认证蓝图模块
│ ├── __init__.py #
│ ├── views/ # 路由定义
| | └──acount.py
| | └──order.py
│ ├── templates/ # 蓝图专属模板
│ │ └── auth/
│ │ └── login.html
│ └── static/
| └── bootstrap5/ #利用bootstrap5优化网页显示
| └── css/
| └── js/
├── utils/ # 工具箱
│ └── db.py #建立数据库连接池
└── templates/ # 全局模板
#标识包:当一个目录包含一个名为 __init__.py 的文件时,Python 就会将该目录视为一个包。即使 __init__.py 是空的,它也必须存在。
登录功能主要利用的知识点:基于蓝图构建项目结构、bootstrap5模板优化网页显示、pymysql连接池
-
基于蓝图构建项目结构
程序:_init_.py
from flask import Flask,redirect,session,request
def auth():
if request.path.startswith("/static"):
# 不让拦截器拦截静态文件
return
if request.path=='/login':
# 继续执行不拦截
return
if session.get('user_info'):
# 继续执行不拦截
return
else:
return redirect("/login")
def create_app():
app = Flask(__name__)
app.secret_key = "ss2d5fp8k9d33ll5896633" # 设置密钥以便用户session时存储cookie
from .views import account
from .views import order
# 注册蓝图
app.register_blueprint(account.ac_bp)
app.register_blueprint(order.od_bp)
app.before_request(auth)
return app
程序app.py
from Blueprints250614 import create_app # 从Blueprints250614.__init__.py中导入create_apps方法
# create_app()方法中已经生成了app实例和蓝图实例并将蓝图实例注册到了app实例当中去了
app = create_app()
if __name__ == '__main__':
app.run(debug=True)
程序:acount.py
from flask import Blueprint, render_template, request, redirect, session
import pymysql
# 蓝图对象,蓝图实例ac_bp
ac_bp = Blueprint('account', __name__)
@ac_bp.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'GET':
return render_template("login.html")
role = request.form['role']
mobile = request.form['mobile']
pwd = request.form['pwd']
print(role, mobile, pwd)
# 连接mysql,并执行sql语句查询用户密码是否正确
from utils import db
user_dict = db.fetch_one('select * from userinfo where mobile = %s and password = %s and role=%s',
(mobile, pwd, role))
print(user_dict)
if user_dict:
# 登录成功+跳转+存储用户session信息
session['user_info'] = user_dict
return redirect("/order/list")
return render_template("login.html", error="用户名或密码错误")
@ac_bp.route('/users', methods=['POST', "GET"])
def users():
return "用户列表"
程序 order.py
from flask import Blueprint, session, redirect
# 创建蓝图实例
od_bp = Blueprint('order', __name__)
@od_bp.route('/order/list')
def order_list():
# 读取cookie&解密用户信息
user_info = session.get("user_info")
print(user_info)
if not user_info: # 如果cookie中的session过期了或者没登陆,这个时候user_info是空的,要重定向到登录页面
return redirect("/login")
return "订单列表"
@od_bp.route('/order/create')
def order_create():
return "创建订单"
- bootstrap5模板优化网页显示
前端可以利用开源的bootstrap5,在未利用之前,login.html主体表单部分为
<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>
<script src="/static/bootstrap5/js/bootstrap.bundle.min.js" ></script>
</form>
扩展:应用bootstrap5的方法:
下载已编译版js和css文件,解压缩后将目录改名称为bootstrap5,放在你的网站目录,例如static目录下,之所以改名是为了书写的时候简单,避免输错。
在你的网页<head> </head>之间添加<link href="/static/bootstrap5/css/bootstrap.min.css" >
在你的网页 </body>之前,添加 <script src="/static/bootstrap5/js/bootstrap.bundle.min.js" ></script>
然后参看官网的各组件用法,拷贝修改即可
- pymysql连接池
程序db.py
from dbutils.pooled_db import PooledDB
import pymysql
from pymysql import cursors
POOL = PooledDB(
creator=pymysql, # 使用连接数据库的模块
mincached=2, # 初始化时,连接池中至少创建的空闲的连接,0表示不创建
maxcached=3, # 连接池中最多闲置的连接,0和NONE不限制
maxconnections=10, # 连接池最大连接数,0和NONE表示不限制连接数
blocking=True, # 连接池中如果没有可用的连接后,是否阻塞等待。TRUE,等待;False,不等的然后报错
setsession=[], # 开始会话前执行的命令列表,如【"set datestyle to ...","set time zone ..."】
ping=0, # 测试每个连接的连通情况,不用管
host='127.0.0.1', port=3306, user='root', passwd='My@20241103', db="practice20250614",charset="utf8mb4"
)
def fetch_one(sql, params):
# conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='My@20241103', db='test20250609')
conn = POOL.connection() #连接池的形式,进行数据库连接。
# cursor = conn.cursor() 如果是这个下边的结果返回的是一个列表
cursor = conn.cursor(cursor=cursors.DictCursor) #如果是这个返回的j将是字典
cursor.execute(sql, params)
result = cursor.fetchone()
cursor.close()
conn.close() #此时不是关闭连接了,而是将连接交还连接池中。
return result
def fetch_all(sql, params):
# conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='My@20241103', db='test20250609')
conn = POOL.connection() #连接池的形式,进行数据库连接。
# cursor = conn.cursor() 如果是这个下边的结果返回的是一个列表
cursor = conn.cursor(cursor=cursors.DictCursor) #如果是这个返回的j将是字典
cursor.execute(sql, params)
result = cursor.fetchall()
cursor.close()
conn.close() #此时不是关闭连接了,而是将连接交还连接池中。
return result
2.2订单列表
读取订单表,展示订单的信息
- 管理员,所有订单
- 客户,客户当前订单
联表查询
# data_list = db.fetch_all("select * from `order` where user_id = %s", (user_info["id"],))
data_list = db.fetch_all(
"select * from `order` left join userinfo on `order`.user_id = userinfo.id where `order`.user_id = %s",
(user_info["id"],))
程序order.py
from flask import Blueprint, session, render_template
from utils import db
# 创建蓝图实例
od_bp = Blueprint('order', __name__)
@od_bp.route('/order/list')
def order_list():
# 读取cookie&解密用户信息
user_info = session.get("user_info")
role = user_info["role"]
real_name = user_info["real_name"]
if role == 2:
# 角色管理员
# data_list = db.fetch_all("select * from `order` ", [])
# 联表查询left join
data_list = db.fetch_all("select * from `order` left join userinfo on `order`.user_id = userinfo.id", [])
else:
# 角色用户
# data_list = db.fetch_all("select * from `order` where user_id = %s", (user_info["id"],))
data_list = db.fetch_all(
"select * from `order` left join userinfo on `order`.user_id = userinfo.id where `order`.user_id = %s",
(user_info["id"],))
status_dict = {
1: {"text": "待执行", "cls": "primary"},
2: {"text": "正在执行", "cls": "info"},
3: {"text": "完成", "cls": "success"},
4: {"text": "失败", "cls": "danger"}
}
print(data_list)
return render_template("order_list.html", data_list=data_list, status_dict=status_dict, real_name=real_name)
@od_bp.route('/order/create')
def order_create():
return "创建订单"
order_list.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>用户订单</title>
<link href="/static/bootstrap5/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light" >
<div class="container">
<a class="navbar-brand" href="#">
<img src="/static/icon/balloon-heart.svg" alt="" width="30" height="24">
视频刷单平台</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="#">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">用户管理</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">订单管理</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Dropdown
</a>
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
<li><a class="dropdown-item" href="#">Action</a></li>
<li><a class="dropdown-item" href="#">Another action</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#">Something else here</a></li>
</ul>
</li>
<li class="nav-item">
<a class="nav-link disabled">帮助</a>
</li>
</ul>
<form class="d-flex">
<input class="form-control me-2" type="search" placeholder="检索" aria-label="Search">
<button class="btn btn-outline-success" type="submit">Search</button>
</form>
<ul class="navbar-nav ms-auto">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
{{ real_name }}
</a>
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
<li><a class="dropdown-item" href="#">Action</a></li>
<li><a class="dropdown-item" href="#">Another action</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="/login">切换账户</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<div class="container">
<table class="table table-striped" style="margin-top: 20px">
<thead>
<tr>
<th>ID</th>
<th>URL</th>
<th>数量</th>
<th>状态</th>
<th>用户名</th>
</tr>
</thead>
<tbody>
{% for item in data_list %}
<tr>
<td>{{ item.id }}</td>
<td>{{ item.url }}</td>
<td>{{ item.count }}</td>
<td><span class="badge bg-{{ status_dict[item.status].cls }}">{{ status_dict[item.status].text }}</span></td>
<td>{{ item.real_name }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<script src="/static/bootstrap5/js/bootstrap.bundle.min.js" ></script>
</body>
</html>
2.3创建订单
🎯创建订单
- 在数据库生成 【待执行】
- 放在redis队列 【订单id】
- 跳转列表页面
扩展: 全局函数(和网页交互变量)
app.template_global()
给所有模板传入函数,使用app.template_global()函数,给所有模板传递可以调用的函数get_real_name()
def get_real_name():
userinfo = session.get('user_info')
return userinfo["real_name"]
def create_app():
app = Flask(__name__)
app.secret_key = "ss2d5fp8k9d33ll5896633" # 设置密钥以便用户session时存储cookie
# 注册全局模板,传入每个模板可以调用的函数get_real_name
app.template_global()(get_real_name)
app.before_request(auth)
return app
扩展:html母版
<!-->layout.html母版里写入如下内容<-->
{% block body %}
{% endblock %}
<!-->继承母版的子版里写入如下内容<-->
{% extends "layout.html" %}
{% block body %}
content内容
{% end block %}
💎3 功能开发(work部分)
💡实现内容:
🎯worker执行订单
1.初始化数据库未在队列中的订单
读取pymysql中的待执行订单ID
去redis中获取待执行订单id
找到pymysql中有的,而redis队列中没有的数据 --> 重新放到redis中去
2.获取redis队列中的订单
3.更新订单状态-改为正在执行
查询数据库中订单是否存在
改数据库中订单的状态
4.执行订单
线程异步执行
5.更新订单状态-改为已完成
- 初始化数据库未在队列中的订单
-
从pymysql中取数据--读取pymysql中的待执行订单ID
-
from dbutils.pooled_db import PooledDB from pymysql import cursors import pymsql DB_POOL = PooledDB( creator=pymysql, # 使用连接数据库的模块 mincached=2, # 初始化时,连接池中至少创建的空闲的连接,0表示不创建 maxcached=3, # 连接池中最多闲置的连接,0和NONE不限制 maxconnections=10, # 连接池最大连接数,0和NONE表示不限制连接数 blocking=True, # 连接池中如果没有可用的连接后,是否阻塞等待。TRUE,等待;False,不等的然后报错 setsession=[], # 开始会话前执行的命令列表,如【"set datestyle to ...","set time zone ..."】 ping=0, # 测试每个连接的连通情况,不用管 host='127.0.0.1', port=3306, user='root', passwd='My@20241103', db="practice20250614", charset="utf8mb4" ) def fetch_all(sql, params): conn = DB_POOL.connection() # 连接池的形式,进行数据库连接。 cursor = conn.cursor(cursor=cursors.DictCursor) # 如果是这个返回的j将是字典 cursor.execute(sql, params) result = cursor.fetchall() cursor.close() conn.close() # 此时不是关闭连接了,而是将连接交还连接池中。 return result db_list = fetch_all("select id from `order` where status=1", {}) db_id_list = [item["id"] for item in db_list]
-
去redis中获取待执行订单id
conn = redis.Redis(connection_pool=Redis_POOL) total_count = conn.llen("watching_video_oder_queue") cache_list = conn.lrange("watching_video_oder_queue", 0, total_count) cache_int_list = {int(item.decode("utf-8")) for item in cache_list}
-
找到pymysql中有的,而redis队列中没有的数据 --> 重新放到redis中去
-
print(cache_int_list) print(db_id_list) need_push_list = db_id_list - cache_int_list # 4.重新放到队列, if need_push_list: conn.lpush("watching_video_oder_queue", *need_push_list)
扩展:生成器
⚠️如果一下读取(conn.lrange("watching_video_queue", 0, -1))会很占内存,考虑使用迭代生成器
或者一次只提取一部分数据
✅迭代生成器-逐一获取数据 def list_iter(name): ''' 自定义redis列表增量迭代 :param name:redis中的name,即迭代name对应的列表 :return yeild 返回列表元素 ''' conn = redis.Redis(connectiong_pool=Redis_Pool) total = conn.llen(name) for inde in range(total_count): yeild conn.lindex(name,index) # 使用 for item in list_iter("watching_video_queue"): print(item) ✅全部获取 conn = redis.Redis(connectiong_pool=Redis_Pool) queue_all_id = conn.lrange("watching_video_queue", 0, -1) ✅逐一获取 conn = redis.Redis(connectiong_pool=Redis_Pool) total_count = conn.llen("watching_video_queue") for i in range(0,total_count): ele = conn.lindex("watching_video_queue", i) ✅一次获取部分 💡☣️redis没有为list提供一次取一部分的方法 def fetch_part_queue(num): conn = redis.Redis(connectiong_pool=Redis_Pool) total_count = conn.llen("watching_video_queue") has_fetch_count = 0 while has_fetch_count < total_count: queue_id_list = conn.lrange("watching_video_queue", has_fetch_count,has_fetch_count+num) has_fetch_count+=len(queue_id_list) print(queue_id_list) fetch_part_queue(3)
- 获取redis队列中的订单
def run():
db_queue_init()
while True:
# 获取订单
order_id = pop_queue()
print(order_id)
if not order_id:
continue
- 更新订单状态-改为正在执行
- 查询数据库中订单是否存在
def get_order_obj(order_id):
res = fetch_one("select * from `order` where id=%s", [order_id, ])
return res
order_dict = get_order_obj(order_id)
if not order_dict:
continue
- 更新状态
def update_one(sql, params):
conn = DB_POOL.connection() # 连接池的形式,进行数据库连接。
cursor = conn.cursor(cursor=cursors.DictCursor) # 如果是这个返回的j将是字典
cursor.execute(sql, params)
conn.commit()
cursor.close()
conn.close() # 此时不是关闭连接了,而是将连接交还连接池中。
def update_order_status(order_id, status):
update_one("update `order` set status=%s where id=%s", [status, order_id])
update_order_status(order_id, 2)
- 执行订单
task(order_dict) #task自己定义
扩展:创建异步线程池,提高订单work执行效率
from concurrent.futures import ThreadPoolExecutor
# max_workers=20 代表同时执行20个
with ThreadPoolExecutor(max_workers=20) as executor:
for i in range(order_dict["count"]):
executor.submit(task, order_dict)
executor.shutdown(wait=True)
# wait=True 参数表示等待所有已提交的任务完成后再关闭线程池。如果设置为 False,则立即关闭线程池,不再等待正在执行的任务完成。
- 切换订单状态为完成
update_order_status(order_id, 3)
📌 总结:程序工程文件为
|
├──%Project%:P_watchingvideo_ordering_web20250614 # 实现web功能
└──%Project%:P_watchingvideo_ordering_worker250617 # 实现worker功能执行部分