Loading

hellohelp

flask之路【第六篇】项目刷单网站实现

✅创建一个网站项目用来提交刷视频订单。

💎1 结构

功能结构图:

![屏幕截图 2025-06-13 235555](C:\Users\lx\Pictures\Screenshots\屏幕截图 2025-06-13 235555.png)

  • 数据库表结构:
屏幕截图 2025-06-14 180615

💎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.更新订单状态-改为已完成
  1. 初始化数据库未在队列中的订单
  • 从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)

  1. 获取redis队列中的订单
def run():
    db_queue_init()
    while True:
    	# 获取订单
        order_id = pop_queue()
        print(order_id)
        if not order_id:
            continue
  1. 更新订单状态-改为正在执行
  • 查询数据库中订单是否存在
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)
  1. 执行订单
 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,则立即关闭线程池,不再等待正在执行的任务完成。
        
  1. 切换订单状态为完成
update_order_status(order_id, 3)

📌 总结:程序工程文件为

|
├──%Project%:P_watchingvideo_ordering_web20250614     # 实现web功能
└──%Project%:P_watchingvideo_ordering_worker250617    # 实现worker功能执行部分
posted @ 2025-07-15 13:07  HordorZzz  阅读(20)  评论(0)    收藏  举报