原创

Python的Flask框架,订单项目为例


Flask相关资料

  • Flask 官网:https://flask.palletsprojects.com/
  • Flask 中文文档:https://flask.palletsprojects.com/zh-cn/stable/quickstart/
  • Flask 源码:https://github.com/pallets/flask/

目录架构

以demo项目名为例

order 
|    order
|    |    tatic
|    |    |    bootstrap.min.css
|    |    |    jquery-3.7.1.min.js
|    |    |    bootstrap.min.js
|    |    templates
|    |    |    layout.html
|    |    |    login.html
|    |    |    order_list.html
|    |    |    user_list.html
|    |    views
|    |    |    account.py
|    |    |    order.py
|    |    __init__.py
|    task
|    |    worker.py
|    utils
|    |    mycryputils.py
|    |    mydbutils.py
|    |    myredisutils.py
|    |    mytimeutils.py
|    |    myutils.py
|    app.py
|    db.sql
|    requirements.txt

文件详情

静态模板文件 layout.html

<!DOCTYPE html>
<html lang="zh-CN">
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
        <title>订单列表</title>
        <link rel="stylesheet" href="/static/bootstrap.min.css"/>
    </head>

    <body>
        <nav class="navbar navbar-default" style="border-radius: 0">
          <div class="container">
            <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="/users?page=1">单多多订单平台</a>
            </div>

            <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
              <ul class="nav navbar-nav">
                <li><a href="/users">用户管理</a></li>
                <li><a href="/order/list?page=1">订单管理</a></li>
                <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>
                    <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">
                      {{getLoginUserInfo().real_name}} <span class="caret"></span>
                  </a>
                  <ul class="dropdown-menu">
                    <li><a href="#" onclick="showInfo('开发中,敬请期待!')">个人资料</a></li>
                    <li><a href="#" onclick="showInfo('开发中,敬请期待!')">修改密码</a></li>
                    <li role="separator" class="divider"></li>
                    <li><a href="/logout">退出登陆</a></li>
                  </ul>
                </li>
              </ul>
            </div>
          </div>
        </nav>

        <!-- 消息提示,3秒后自动关闭 -->
        <div id="infoTopId" class="alert alert-success" style="display: none; position: fixed ; top: 80px; right: 30px; min-width: 300px;">
            <span id="infoTopMsg"></span>
        </div>

        {%block body%}{%endblock%}

        <script src="/static/jquery-3.7.1.min.js"></script>
        <script src="/static/bootstrap.min.js"></script>
        <script>
            function showInfo(msg) {
                let sec = 5;
                $('#infoTopMsg').html(msg + '<span  style="position:fixed; right: 60px"><u>' + sec + '</u> 秒后关闭</span>');
                $("#infoTopId").css('display', 'block');
                const timertop = setInterval(function () {
                    $('#infoTopMsg').html(msg + '<span  style="position:fixed; right: 60px"><u>' + (sec - 1) + '</u> 秒后关闭</span>');
                    if (sec <= 1) {
                        $("#infoTopId").css('display', 'none');
                        clearInterval(timertop);
                    }
                    sec--;
                }, 1000);
            }
        </script>
    </body>
</html>

登录静态文件 login.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>用户登录</title>
    <link rel="stylesheet" href="/static/bootstrap.min.css"/>
    <style>
        .form-box {
            width: 500px;
            border: 1px solid #ddd;
            padding: 15px 30px 15px 30px;
            margin: 200px auto 0;
        }
        .form-box h2 {
            text-align: center;
        }
        .form-box .form-group {
            margin-bottom: 15px;
        }
        .mybtn {
            color: white;
            justify-content: center;
            text-align: center;
        }
    </style>
</head>
<body>
    <div class="form-box">
        <h2>用户登录</h2>
        <form action="/login" method="post">
            <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 for="inputmobile">用户名:</label>
                <input type="text" id="inputmobile" required class="form-control" name="mobile" placeholder="请输入手机号" />
            </div>
            <div class="form-group">
                <label for="inputpassword">密  码:</label>
                <input type="password" id="inputpassword" required class="form-control" name="pwd" placeholder="请输入密码" />
            </div>
            <div class="form-group">
                <button class="mybtn  btn btn-info btn-block" type="submit">登 录</button>
            </div>
            <span style="color: red;">{{error}}</span>
        </form>
    </div>

    <script src="/static/jquery-3.7.1.min.js"></script>
    <script src="/static/bootstrap.min.js"></script>
    </body>
</html>

订单列表静态文件 order_list.html

{% extends 'layout.html' %}

{%block body%}
<div class="container" style="margin-top: 10px">
    <button type="button" class="btn btn-info" data-toggle="modal" data-target="#myModal" style="margin-bottom: 10px; text-align: right">
       <span class="glyphicon glyphicon-plus" aria-hidden="true"></span> 添加订单
    </button>
    <table class="table table-bordered table-hover">
        <thead>
            <tr>
                <th>订单编号</th>
                <th>地址</th>
                <th>数量</th>
                <th>状态</th>
                <th>用户</th>
                <th>创建时间</th>
                <th>修改时间</th>
                <th>操作</th>
            </tr>
        </thead>
        <tbody>
        {% for item in data_list %}
            <tr>
                <td>{{item.order_no}}</td>
                <td>{{item.url}}</td>
                <td>{{item.count}}</td>
                <td>

                    {% if item.active == 0 %}
                        <span class="label label-default">{{item.status_msg.text}}</span>
                        <span class="label label-default">已删除</span>
                    {% else %}
                        <span class="label label-{{item.status_msg.cls}}">{{item.status_msg.text}}</span>
                    {% endif %}
                </td>
                <td>{{item.real_name}}</td>
                <td>{{item.create_time}}</td>
                <td>{{item.update_time}}</td>
                <td>
                    {% if item.status == 0 and item.active == 1 %}
                    <a href="#" type="button" class="btn btn-info btn-sm {{item.status_msg.disabled}}" onclick='showUpdate("{{item}}")'>修改</a>
                    {% endif %}
                    {% if item.active == 1 %}
                    <a href="#" type="button" class="btn btn-danger btn-sm" onclick='showDelete("{{item.id}}")'>删除</a>
                    {% endif %}
                </td>
            </tr>
            <span style="display: none">{{item.status}}</span>
        {% endfor %}
        </tbody>
    </table>

{% if pageinfo.page > 1 or pageinfo.islastpage == 0 %}
    <nav aria-label="Page navigation" style="text-align: right">
      <ul class="pagination">
          {% if pageinfo.page > 1 %}
            <li>
              <a href="/order/list?page={{pageinfo.page-1}}" aria-label="Previous">
                <span aria-hidden="true">&laquo;</span>
              </a>
            </li>
          {% endif %}
            <li><a href="#"><span id="curPageId">{{pageinfo.page}}</span></a></li>
          {% if pageinfo.islastpage == 0 %}
            <li>
              <a href="/order/list?page={{pageinfo.page+1}}" aria-label="Next">
                <span aria-hidden="true">&raquo;</span>
              </a>
            </li>
          {% endif %}
      </ul>
    </nav>
{% endif %}


</div>
    <!-- 消息提示,3秒后自动关闭 -->
    <div id="infoId" class="alert alert-success" style="display: none; position: fixed ; bottom: 30px; right: 30px">
      <a href="#" class="close" data-dismiss="alert" aria-label="close">&times;</a>
      <span id="infoMsg"></span>
    </div>


<!-- 新增 Modal -->
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
  <div class="modal-dialog" role="document">
    <div class="modal-content">
          <div class="modal-header">
            <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
            <h4 class="modal-title" id="myModalLabel">新增订单</h4>
          </div>
          <div class="modal-body form-horizontal">
              <div class="form-group">
                <label for="inputUrl" class="col-sm-2 control-label">网址</label>
                <div class="col-sm-10">
                  <input type="text" class="form-control" id="inputUrl" name="inputUrl" placeholder="请输入网址">
                    <span id="urlErrorMsg" style="color: red"></span>
                </div>
              </div>

              <div class="form-group">
                <label for="inputCount" class="col-sm-2 control-label">数量</label>
                <div class="col-sm-10">
                  <input type="number" class="form-control" id="inputCount" name="inputCount" placeholder="请输入数量">
                    <span id="countErrorMsg" style="color: red"></span>
                </div>
              </div>
          </div>
          <div class="modal-footer">
                <button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
                <button id="save-btn" class="btn btn-primary" disabled onclick="save()">提交</button>
          </div>
    </div>
  </div>
        <!-- 错误提示,3秒后自动关闭 -->
        <div id="errorId" class="alert alert-danger" style="display: none; position: fixed ; top: 30px; right: 30px">
          <a href="#" class="close" data-dismiss="alert" aria-label="close">&times;</a>
          <span id="errorMsg"></span>
        </div>
</div>

<!-- 修改 Modal -->
<div class="modal fade" id="updModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
  <div class="modal-dialog" role="document">
    <div class="modal-content">
          <div class="modal-header">
            <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
            <h4 class="modal-title" id="updModalLabel">修改订单</h4>
          </div>

          <div class="modal-body form-horizontal">
              <div class="form-group hidden">
                  <input type="text" id="updInputId" name="updInputId">
                  <input type="text" id="old_url" name="old_url">
                  <input type="text" id="old_count" name="old_count">
              </div>
              <div class="form-group">
                <label class="col-sm-2 control-label">订单编号</label>
                <div class="col-sm-10">
                  <input type="text" class="form-control" id="updInputOrderNo" disabled/>
                </div>
              </div>

              <div class="form-group">
                <label for="inputUrl" class="col-sm-2 control-label">网址</label>
                <div class="col-sm-10">
                  <input type="text" class="form-control" id="updInputUrl" name="updInputUrl" placeholder="请输入网址">
                    <span id="updUrlErrorMsg" style="color: red"></span>
                </div>
              </div>

              <div class="form-group">
                <label for="inputCount" class="col-sm-2 control-label">数量</label>
                <div class="col-sm-10">
                  <input type="number" class="form-control" id="updInputCount" name="updInputCount" placeholder="请输入数量">
                    <span id="updCountErrorMsg" style="color: red">资料没变动,请先修改</span>
                </div>
              </div>
          </div>
          <div class="modal-footer">
                <button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
                <button id="upd-btn" class="btn btn-primary" disabled onclick="update()">提交</button>
          </div>
    </div>
  </div>
        <!-- 错误提示,3秒后自动关闭 -->
        <div id="updErrorId" class="alert alert-danger" style="display: none; position: fixed ; top: 30px; right: 30px">
          <a href="#" class="close" data-dismiss="alert" aria-label="close">&times;</a>
          <span id="updErrorMsg"></span>
        </div>
</div>

<!-- 删除 Modal -->
<div class="modal fade" id="delModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
  <div class="modal-dialog" role="document">
    <div class="modal-content">
          <div class="modal-header">
            <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
            <h4 class="modal-title" id="delModalLabel">删除订单</h4>
          </div>

          <div class="modal-body form-horizontal">
              <input type="hidden" id="delInputId" name="delInputId" />
              <h2 style="color: red">确定删除吗?</h2>
          </div>
          <div class="modal-footer">
                <button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
                <button id="del-btn" class="btn btn-primary" onclick="deleteOrder()">提交</button>
          </div>
    </div>
  </div>
</div>

<script>
    function save() {
        var url = $('#inputUrl').val()
        var count = $('#inputCount').val()
        if( url === undefined || url === '' ){
            $('#urlErrorMsg').text('请输入网址');
            $('#inputUrl').focus();
            return;
        }
        if( !url.startsWith('http://') && !url.startsWith('https://')){
            $('#urlErrorMsg').text('请输入以`http://`或`https://`的网址');
            $('#inputUrl').focus();
            return;
        }
        if( count === undefined || count === '' ){
            $('#countErrorMsg').text('请输入数量');
            $('#inputCount').focus();
            return;
        }
        if( count < 1 ){
            $('#countErrorMsg').text('数量值必须大于0');
            $('#inputCount').focus();
            return;
        }
        if( count == '' || count < 1){
            $('#inputCount').focus();
            return;
        }
        $.ajax({
            type: "POST",
            url: "/order/create",
            data: JSON.stringify({
                'url': url,
                'count': count
            }),
            dataType: "json",
            headers: {
                'Content-Type': 'application/json; charset=utf-8',
                'Access-Control-Allow-Origin': '*'
            },
            success: function(data){
                if(data.code === 0){
                    window.location.reload();
                    showMsg(data.msg)
                }else{
                    showError(data.msg)
                }
            }
        });
    }
    document.getElementById('inputUrl').addEventListener('input', function(event) {
        if ($(this).val() === '') {
            $('#urlErrorMsg').text('请输入网址');
            $("#save-btn").prop("disabled", true);
        } else if (!$(this).val().startsWith('http://') && !$(this).val().startsWith('https://')){
            $('#urlErrorMsg').text('请输入以`http://`或`https://`的网址');
            $("#save-btn").prop("disabled", true);
        }else{
            $('#urlErrorMsg').text('');
            if ($('#inputCount').val() !== ''){
                $("#save-btn").prop("disabled", false);
            }

        }
    });
    document.getElementById('inputCount').addEventListener('input', function(event) {
        if ($(this).val() === '') {
            $('#countErrorMsg').text('请输入数量');
            $("#save-btn").prop("disabled", true);
        }else if ($(this).val() < 1){
            $('#countErrorMsg').text('数量值必须大于0');
            $("#save-btn").prop("disabled", true);
        }else{
            $('#countErrorMsg').text('');
            if ($('#inputUrl').val() !== ''){
                $("#save-btn").prop("disabled", false);
            }
        }
    });
    function showError(msg) {
        let sec = 5;
        $('#errorId').css('display', 'block');
        $('#errorMsg').html(msg + ';<br/><br/><u>' + sec + '</u> 秒后关闭');
        const timer = setInterval(function () {
            $('#errorMsg').html(msg + ';<br/><br/><u>' + (sec - 1) + '</u> 秒后关闭');
            if (sec <= 1) {
                $('#errorId').css('display', 'none');
                clearInterval(timer);
            }
            sec--;
        }, 1000);
    }
    function showMsg(msg) {
        let sec = 5;
        $('#infoId').css('display', 'block');
        $('#infoMsg').html(msg + ';<br/><br/><u>' + sec + '</u> 秒后关闭');
        const timer = setInterval(function () {
            $('#errorMsg').html(msg + ';<br/><br/><u>' + (sec - 1) + '</u> 秒后关闭');
            if (sec <= 1) {
                $('#errorId').css('display', 'none');
                clearInterval(timer);
            }
            sec--;
        }, 1000);
    }
    function showUpdate(obj) {
        obj = obj.replaceAll("'", '"').replaceAll("&quot;", '')
        const item = JSON.parse(obj);
        $("#updModal").modal('show');
        $("#updInputId").val(item.id);
        $("#updInputOrderNo").val(item.order_no)
        $("#old_url").val(item.url);
        $("#updInputUrl").val(item.url);
        $("#old_count").val(item.count);
        $("#updInputCount").val(item.count);
    }
    function update() {
        var id = $('#updInputId').val()
        var url = $('#updInputUrl').val()
        var count = $('#updInputCount').val()
        if( id === undefined || id === '' ){
            showUpdError('请输入ID');
            return;
        }
        if( url === undefined || url === '' ){
            $('#updUrlErrorMsg').text('请输入网址');
            $('#updInputUrl').focus();
            return;
        }
        if( !url.startsWith('http://') && !url.startsWith('https://')){
            // 提示错误信息
            $('#updUrlErrorMsg').text('请输入以`http://`或`https://`的网址');
            $('#updInputUrl').focus();
            return;
        }
        if( count === undefined || count === '' ){
            $('#updCountErrorMsg').text('请输入数量');
            $('#inputCount').focus();
            return;
        }
        if( count < 1 ){
            $('#updCountErrorMsg').text('数量值必须大于0');
            $('#updInputCount').focus();
            return;
        }
        if( count == '' || count < 1){
            $('#updInputUrl').focus();
            return;
        }
        $.ajax({
            type: "POST",
            url: "/order/update",
            data: JSON.stringify({
                'id': id,
                'url': url,
                'count': count
            }),
            dataType: "json",
            headers: {
                'Content-Type': 'application/json; charset=utf-8',
                'Access-Control-Allow-Origin': '*'
            },
            success: function(data){
                if(data.code === 0){
                    window.location.reload();
                    showMsg(data.msg)
                }else{
                    showUpdError(data.msg)
                }
            }
        });
    }
    document.getElementById('updInputUrl').addEventListener('input', function(event) {

        if ($(this).val() === '') {
            $('#updUrlErrorMsg').text('请输入网址');
            $("#upd-btn").prop("disabled", true);
        } else if (!$(this).val().startsWith('http://') && !$(this).val().startsWith('https://')){
            $('#updUrlErrorMsg').text('请输入以`http://`或`https://`的网址');
            $("#upd-btn").prop("disabled", true);
        } else {
            $('#updUrlErrorMsg').text('');
            if ($('#updInputCount').val() !== ''){
                $('#updCountErrorMsg').text('');
                $("#upd-btn").prop("disabled", false);
            }
            if ($('#updInputCount').val() === $('#old_count').val() && $('#old_url').val() === $('#updInputUrl').val() ){
				$('#updCountErrorMsg').text('资料没变动,请先修改');
				$("#upd-btn").prop("disabled", true);
			}
        }
    });
    document.getElementById('updInputCount').addEventListener('input', function(event) {
        if ($(this).val() === '') {
            $('#updCountErrorMsg').text('请输入数量');
            $("#upd-btn").prop("disabled", true);
        }else if ($(this).val() < 1){
            $('#updCountErrorMsg').text('数量值必须大于0');
            $("#upd-btn").prop("disabled", true);
        } else if ($('#updInputCount').val() === $('#old_count').val() && $('#old_url').val() === $('#updInputUrl').val() ){
            $('#updCountErrorMsg').text('资料没变动,请先修改');
            $("#upd-btn").prop("disabled", true);
        } else{
            $('#updInputCount').text('');
            if ($('#updInputUrl').val() !== ''){
                $('#updCountErrorMsg').text('');
                $("#upd-btn").prop("disabled", false);
            }
        }
    });
    function showUpdError(msg) {
        let sec = 5;
        //将errorId的display属性设置为block
        $('#updErrorId').css('display', 'block');
        $('#updErrorMsg').html(msg + ';<br/><br/><u>' + sec + '</u> 秒后关闭');
        const timer1 = setInterval(function () {
            $('#updErrorMsg').html(msg + ';<br/><br/><u>' + (sec - 1) + '</u> 秒后关闭');
            if (sec <= 1) {
                $('#updErrorId').css('display', 'none');
                clearInterval(timer1);
            }
            sec--;
        }, 1000);
    }
    function showDelete( id ){
        $("#delModal").modal('show');

        $("#delInputId").val(id);
    }
    function deleteOrder(){
        var id = $('#delInputId').val()
        if (id === '' || id === undefined)
            return
        $.ajax({
            type: "POST",
            url: "/order/delete",
            data: JSON.stringify({
                'id': id
            }),
            dataType: "json",
            headers: {
                'Content-Type': 'application/json; charset=utf-8',
                'Access-Control-Allow-Origin': '*'
            },
            success: function(data){
                window.location.reload();
            }
        });

    }
</script>
{%endblock%}

用户列表静态文件 user_list.html

{% extends 'layout.html' %}

{%block body%}
<div class="container" style="margin-top: 10px">
    {% if user_info.role == 2 %}
    <div style="margin-bottom: 10px">
        <a type="button"  class="btn btn-info" onclick="showAddUser()"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span> 添加用户</a>
    </div>
    {% endif %}
    <table class="table table-bordered table-hover">
        <thead>
            <tr>
                <th>编号</th>
                <th>姓名</th>
                <th>手机号</th>
                <th>角色</th>
                <th>创建时间</th>
                <th>修改时间</th>
                <th>操作</th>
            </tr>
        </thead>
        <tbody>
        {% for item in data_list %}
            <tr>
                <td>{{item.user_no}}</td>
                <td>{{item.real_name}}</td>
                <td>{{item.mobile}}</td>
                <td><span class="label label-{{item.role_msg.cls}}">{{item.role_msg.text}}</span></td>
                <td>{{item.create_time}}</td>
                <td>{{item.update_time}}</td>
                <td><a type="button" class="btn btn-info btn-sm" onclick='showUpdateUser("{{item}}")'>修改用户</a></td>
            </tr>
            <span style="display: none">{{item.status}}</span>
        {% endfor %}
        </tbody>
    </table>

{% if pageinfo.page > 1 or pageinfo.islastpage == 0 %}
    <nav aria-label="Page navigation" style="text-align: right">
      <ul class="pagination">
          {% if pageinfo.page > 1 %}
            <li>
              <a href="/users?page={{pageinfo.page-1}}" aria-label="Previous">
                <span aria-hidden="true">&laquo;</span>
              </a>
            </li>
          {% endif %}
            <li><a href="#"><span id="curPageId">{{pageinfo.page}}</span></a></li>
          {% if pageinfo.islastpage == 0 %}
            <li>
              <a href="/users?page={{pageinfo.page+1}}" aria-label="Next">
                <span aria-hidden="true">&raquo;</span>
              </a>
            </li>
          {% endif %}
      </ul>
    </nav>
{% endif %}

</div>

<!-- 模态框  -->
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
    <div class="modal-dialog" role="document">
        <div class="modal-content">
            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
                <h4 class="modal-title" id="modalTitleLabel">新增用户</h4>
            </div>
            <div id="modelBody" class="modal-body form-horizontal">
                。。。。
            </div>
            <!-- 错误提示,3秒后自动关闭 -->
            <div id="errorId" class="alert alert-danger" style="display:none; margin:0 15px 10px 15px;">
              <a href="#" class="close" data-dismiss="alert" aria-label="close">&times;</a>
              <span id="errorMsg"></span>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
                <button id="save-btn" class="btn btn-primary" onclick="save()">提交</button>
            </div>
        </div>

    </div>
</div>

<script>
    function showAddUser() {
        $("#myModal").modal('show');
        $("#modalTitleLabel").text('新增用户');
        $("#modelBody").html('' +
            '<div class="form-group">' +
                '<label class="col-sm-2 control-label">姓名</label>' +
                '<div class="col-sm-10">' +
                    '<input type="text" class="form-control" id="real_name" placeholder="请输入姓名">' +
                '</div>' +
            '</div>' +
            '<div class="form-group">' +
                '<label class="col-sm-2 control-label">密码</label>' +
                '<div class="col-sm-10">' +
                    '<input type="password" class="form-control" id="passwd" placeholder="请输入密码">' +
                '</div>' +
            '</div>'+
            '<div class="form-group">' +
                '<label class="col-sm-2 control-label">手机号</label>' +
                '<div class="col-sm-10">' +
                    '<input type="text" class="form-control" id="mobile" placeholder="请输入手机号">' +
                '</div>' +
            '</div>'+
            '<div class="checkbox">'+
                '<label class="col-sm-2 control-label"></label>' +
                '<label>'+
                  '<input type="checkbox" id="isManager"> 管理员'+
                '</label>'+
              '</div>'
        )
    }
    function showUpdateUser(obj) {
        obj = obj.replaceAll("'", '"').replaceAll("&quot;", '')
        const item = JSON.parse(obj);
        $("#myModal").modal('show');
        $("#modalTitleLabel").text('修改用户');
        $("#modelBody").html('' +
            '<div class="form-group">' +
                '<input type="hidden" class="form-control" id="id" value="' + item.id + '">' +
            '</div>' +
            '<div class="form-group">' +
                '<label class="col-sm-2 control-label">编号</label>' +
                '<div class="col-sm-10">' +
                    '<input type="text" disabled class="form-control" value="' + item.user_no + '">' +
                '</div>' +
            '</div>' +
            '<div class="form-group">' +
                '<label class="col-sm-2 control-label">姓名</label>' +
                '<div class="col-sm-10">' +
                    '<input type="text" class="form-control" id="real_name" placeholder="请输入姓名" required value="' + item.real_name + '">' +
                '</div>' +
            '</div>' +
            '<div class="form-group">' +
                '<label class="col-sm-2 control-label">密码</label>' +
                '<div class="col-sm-10">' +
                    '<input type="password" class="form-control" id="passwd" placeholder="若不修改,可放空">' +
                '</div>' +
            '</div>'+
            '<div class="form-group">' +
                '<label class="col-sm-2 control-label">手机号</label>' +
                '<div class="col-sm-10">' +
                    '<input type="text" class="form-control" id="mobile" placeholder="请输入手机号" required  value="' + item.mobile + '">' +
                '</div>' +
            '</div>'+
            '<div class="checkbox">'+
                '<label class="col-sm-2 control-label"></label>' +
                '<label>'+
                  '<input type="checkbox" id="isManager"> 管理员'+
                '</label>'+
              '</div>')

        $("#isManager").prop("checked", item.role === 2)
    }
    function save() {
        const id = $("#id").val();
        const real_name = $("#real_name").val();
        const mobile = $("#mobile").val();
        const passwd = $("#passwd").val();
        const isManager = $("#isManager").prop("checked");
        if (real_name === '') {
            showError('请输入姓名');
            return;
        }
        if (id === undefined && passwd === '') {
            showError('新增用户时,需要输入密码');
            return;
        }
        if (mobile === '') {
            showError('请输入手机号');
            return;
        }
        $.ajax({
            type: "POST",
            url: "/user/create",
            data: JSON.stringify({
                id: id,
                real_name: real_name,
                mobile: mobile,
                passwd: passwd,
                isManager: isManager
            }),
            headers: {
                'Content-Type': 'application/json; charset=utf-8',
                'Access-Control-Allow-Origin': '*'
            },
            success: function (data) {
                console.log(data);
                if (data.code === 0) {
                    window.location.reload();
                } else {
                    showError(data.msg);
                }
            },
            error: function (data) {
                showError('服务器错误');
            }
        });


    }
    function showError(msg) {
        let sec = 5;
        //将errorId的display属性设置为block
        $('#errorId').css('display', 'block');
        $('#errorMsg').html(msg + '; <span style="position:fixed; right: 50px"><u>' + sec + '</u> 秒后关闭</span>');
        const timer = setInterval(function () {
            $('#errorMsg').html(msg + '; <span style="position:fixed; right: 50px"><u>' + (sec - 1) + '</u> 秒后关闭</span>');
            if (sec <= 1) {
                $('#errorId').css('display', 'none');
                clearInterval(timer);
            }
            sec--;
        }, 1000);
    }
</script>
{%endblock%}

用户相关处理文件 account.py

from flask import  Blueprint, render_template, request, redirect, session, jsonify
from utils import mydbutils, mytimeutils, mycryputils, myutils
from dict import Global_Dict

#创建蓝图对象
ac = Blueprint('account', __name__)

@ac.route('/')
def index():
    return 'index'

@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 p_user where mobile = %s and role = %s and password = %s"
    user_dict = mydbutils.select_one(sql, [mobile, role, mycryputils.md5(pwd)])
    if not user_dict:
        return render_template('login.html', error='用户名或密码错误')
    session["user_info"] = {
        "id": user_dict['id'],
        "user_no": user_dict['user_no'],
        "real_name": user_dict['real_name'],
        "mobile": user_dict['mobile'],
        "role": user_dict['role']
    }
    return redirect('/users?page=1')

@ac.route('/users')
def users():
    user_info = session.get('user_info')
    role = user_info['role']
    page = int(request.args.get('page', 1))
    page_size = 10
    if role == 2:
        data_list = mydbutils.select_all("select id,user_no,mobile,real_name,role,c_time,m_time from p_user order by role desc, m_time  desc limit %s, %s",[(page-1)*page_size, page_size])
    else:
        data_list = mydbutils.select_all("select id,user_no,mobile,real_name,role,c_time,m_time from p_user  where id = %s",[user_info['id']])
    for data in data_list:
        data['role_msg'] = Global_Dict.role_info_dict.get(data['role'])
        if data['role_msg'] is None:
            data['role_msg'] = Global_Dict.role_info_dict.get(1)
        data['create_time'] = mytimeutils.longtime2string(data['c_time'])
        data['update_time'] = mytimeutils.longtime2string(data['m_time'])
    pageinfo = {
        'page': page,
        'islastpage': 1 if len(data_list) < page_size else 0,
    }
    return render_template('user_list.html', user_info = user_info, data_list = data_list, pageinfo=pageinfo)

@ac.route('/user/create', methods=['POST'])
def user_create():
    user_info = session.get('user_info')
    user_id = request.json.get('id')
    if not user_info or (user_info['role'] != 2 and int(user_info['id']) != int(user_id)):
        return jsonify({'code': 1, 'msg': '您没有权限'})
    mobile = request.json.get('mobile')
    real_name = request.json.get('real_name')
    pwd = request.json.get('passwd')
    isManager = request.json.get('isManager')
    role = 2 if isManager is True  else 1
    if not mobile or not real_name:
        return jsonify({'code': 1, 'msg': '参数错误'})
    if user_id is not None and int(user_id) > 0:
        upduserinfo = mydbutils.select_one("select * from p_user where id = %s", [user_id])
        if not upduserinfo:
            return jsonify({'code': 1, 'msg': '用户不存在'})
        if int(user_id) == 1 :
            role = 2
        if not pwd:
            params = [mobile, real_name, role, mytimeutils.getcurrentlongsecondtime(), upduserinfo['password'], user_id]
        else:
            params = [mobile, real_name, role, mytimeutils.getcurrentlongsecondtime(), mycryputils.md5(pwd), user_id]
        res = mydbutils.update("update p_user set mobile = %s, real_name = %s, role = %s, m_time = %s, password = %s where id = %s", params)
        if res != 1:
            return jsonify({'code': 1, 'msg': '修改失败'})
        return jsonify({'code': 0, 'msg': '修改成功'})
    else:
        params = [myutils.getuuid(), mobile, real_name, mycryputils.md5(pwd), role, mytimeutils.getcurrentlongsecondtime(), mytimeutils.getcurrentlongsecondtime()]
        res = mydbutils.insert("insert into p_user(user_no,mobile,real_name,password,role,c_time,m_time) values(%s,%s,%s,%s,%s,%s,%s)", params)
        if int(res) < 1:
            return jsonify({'code': 1, 'msg': '创建失败'})
        return jsonify({'code': 0, 'msg': '创建成功'})

@ac.route('/logout')
def logout():
    session.pop('user_info')
    return redirect('/login')

订单相关处理文件 order.py

import uuid
from flask import Blueprint, session, request, render_template, redirect, jsonify
from utils import mydbutils, myredisutils, mytimeutils, myutils
from dict import Global_Dict

#创建蓝图对象
od = Blueprint('order', __name__)

@od.route('/order/list')
def order_list():
    user_info = session.get('user_info')
    role = user_info['role']
    page = int(request.args.get('page', 1))
    page_size = 10
    if role == 2:
        data_list = mydbutils.select_all("select od.id,od.url,od.count,od.`status`,od.order_no,od.active,od.c_time,od.m_time, u.real_name from p_order od LEFT JOIN p_user u on od.user_no = u.user_no order by od.m_time desc limit %s, %s", [ (page-1)*page_size, page_size ])
    else :
        data_list = mydbutils.select_all("select od.id,od.url,od.count,od.`status`,od.order_no,od.active,od.c_time,od.m_time, u.real_name from p_order od LEFT JOIN p_user u on od.user_no = u.user_no  where od.user_no = %s and od.active = 1  order by od.m_time desc limit %s, %s", [user_info['user_no'], (page-1)*page_size, page_size])
    if data_list is None :
        pageinfo1 = {
            'page': page,
            'islastpage': 1,
        }
        return render_template('order_list.html', data_list={}, pageinfo=pageinfo1)
    if  len(data_list)  == 0  and page > 1:
        return redirect('/order/list?page=%s' % (page-1))
    for data in data_list:
        data['status_msg'] = Global_Dict.order_status_dict.get(data['status'])
        if data['status_msg'] is None:
            data['status_msg'] = Global_Dict.order_status_dict.get(998)
        data['create_time'] = mytimeutils.longtime2string(data['c_time'])
        data['update_time'] = mytimeutils.longtime2string(data['m_time'])
    pageinfo = {
        'page': page,
        'islastpage': 1 if len(data_list) < page_size else 0,
    }
    return render_template('order_list.html', data_list=data_list, pageinfo=pageinfo)

@od.route('/order/create', methods=['POST'])
def order_create():
    user_info = session.get('user_info')
    if not user_info or user_info['id'] <= 0:
        return jsonify({'code': 1, 'msg': '请先登录'})
    url = request.json.get('url')
    count = request.json.get('count')
    if not url or not count :
        return jsonify({'code': 1, 'msg': '参数错误'})
    if not url.startswith('http://') and not url.startswith('https://'):
        return jsonify({'code': 1, 'msg': '网址必须是http://或者https://开头的'})
    if not count.isdigit() or int(count) < 1:
        return jsonify({'code': 1, 'msg': '数量必须是数字且不小于100'})
    order_no = myutils.getuuid()
    params = [order_no, url, count, user_info['user_no'], mytimeutils.getcurrentlongsecondtime(),mytimeutils.getcurrentlongsecondtime()]
    orderId = mydbutils.insert("insert into p_order(order_no,url,count,user_no,`status`,c_time,m_time) values(%s,%s,%s,%s,0,%s,%s)", params)
    if orderId <= 0:
        return jsonify({'code': 1, 'msg': '创建失败'})
    myredisutils.push_queue(order_no)
    return jsonify({'code': 0, 'msg': '创建成功'})

@od.route('/order/update', methods=['POST'])
def order_update():
    order_id = request.json.get('id')
    url = request.json.get('url')
    count = request.json.get('count')
    user_info = session.get('user_info')
    orderinfo = mydbutils.select_one("select * from p_order where id = %s and active = 1", [order_id])
    if not orderinfo:
        return jsonify({'code': 1, 'msg': '订单不存在'})
    if orderinfo['status'] != 0:
        return jsonify({'code': 1, 'msg': '当前订单状态禁止修改'})
    if not user_info or (user_info['user_no'] != orderinfo['user_no'] and user_info['role'] != 2):
        return jsonify({'code': 1, 'msg': '您没权限修改该订单'})
    if not url or not count:
        return jsonify({'code': 1, 'msg': '参数错误'})
    if not url.startswith('http://') and not url.startswith('https://'):
        return jsonify({'code': 1, 'msg': '网址必须是http://或者https://开头的'})
    if not count.isdigit() or int(count) < 100:
        return jsonify({'code': 1, 'msg': '数量必须是数字且不小于100'})
    params = [url, count, mytimeutils.getcurrentlongsecondtime(), order_id]
    res = mydbutils.update("update p_order set url = %s, count = %s, m_time = %s where id = %s", params)
    if res != 1:
        return jsonify({'code': 1, 'msg': '修改失败'})
    return jsonify({'code': 0, 'msg': '修改成功'})

@od.route('/order/delete', methods=['POST'])
def order_delete():
    id = request.json.get('id')
    user_info = session.get('user_info')
    orderinfo = mydbutils.select_one("select * from p_order where id = %s and active = 1", [id])
    if not orderinfo:
        return jsonify({'code': 1, 'msg': '订单不存在'})
    if not user_info or (user_info['user_no'] != orderinfo['user_no'] and user_info['role'] != 2):
        return jsonify({'code': 1, 'msg': '您没权限删除该订单'})
    res = mydbutils.update("update p_order set active = 0, m_time = %s where id = %s", [mytimeutils.getcurrentlongsecondtime(), id])
    if res != 1:
        return jsonify({'code': 1, 'msg': '删除失败'})
    return jsonify({'code': 0, 'msg': '删除成功'})

初始化文件 init.py

from flask import Flask, request, session, redirect

def auth():
    if request.path == '/login' or request.path.startswith('/static'):
        return
    if session.get('user_info'):
        return
    return redirect('/login')

def getLoginUserInfo():
    return session.get('user_info')

def create_app():
    # 使用Flask创建app
    app = Flask(__name__)

    # 设置密钥  session用到
    app.secret_key = '123456'

    # 注册蓝图
    from .views import account
    from .views import order
    app.register_blueprint(account.ac)
    app.register_blueprint(order.od)

    #设置拦截器
    app.before_request(auth)

    # 设置全局模板函数
    app.template_global()(getLoginUserInfo)

    # 设置session过期时间 无操作15分钟过期
    app.permanent_session_lifetime = 900

    # 设置session自动保存
    @app.before_request
    def before_request():
        session.modified = True
    return app

任务处理文件 worker.py

import time
from utils import myredisutils, mydbutils, mytimeutils
from concurrent.futures import ThreadPoolExecutor

def main():
    while True:
        orderId = myredisutils.pop_queue()
        if orderId is None:
            print(f"等待任务订单...")
            continue
        order_info = mydbutils.select_one("select * from p_order where order_no = %s and active = 1", [orderId])
        if order_info is None:
            print(f"{orderId} 订单不存在")
            continue
        if order_info['status'] != 0:
            print(f"{orderId} 订单状态异常")
            continue
        try:
            print(f"{orderId} 开始处理订单")

            ## 更新订单状态
            mydbutils.update("update p_order set `status` = 1, m_time = %s where order_no = %s", [mytimeutils.getcurrentlongsecondtime(),orderId])
            print(f"{orderId} 正在运行的订单状态更新完成")

            # 模拟处理订单
            time.sleep(5)
            thread_pool = ThreadPoolExecutor(max_workers=10)
            for i in range(order_info['count']):
                thread_pool.submit(task_excute, order_info)
            thread_pool.shutdown(wait= True)

            print(f"{orderId} 订单处理完成")
            ## 更新订单状态
            mydbutils.update("update p_order set `status` = 2, m_time = %s where order_no = %s",[mytimeutils.getcurrentlongsecondtime(), orderId])
            print(f"{orderId} 处理完成订单状态更新完成")
        except Exception as e:
            print(f"{orderId} 订单处理异常:{e}")

            ## 订单处理异常
            mydbutils.update("update p_order set `status` = 3, m_time = %s where order_no = %s",[mytimeutils.getcurrentlongsecondtime(), orderId])
            print(f"{orderId} 处理异常订单状态更新完成")

def initsync():
    # 同步数据库里有,队列里没有的数据
    ## 1.查询数据库中所有存在且是待执行的订单
    db_list = mydbutils.select_all("select order_no from p_order where active = 1 and status = 0",  [])
    db_no_list = { item['order_no'] for item in db_list}

    ## 2.查询队列中所有存在且是待执行的订单
    cache_no_list  = myredisutils.select_all()
    need_push = db_no_list - cache_no_list
    myredisutils.push_queues(need_push)

def task_excute(info):
    pass

if __name__ == '__main__':
    initsync()
    main()

    # 这行代码看起来是要将data列表中的每个元素先解码为UTF-8字符串,然后再转换为整数
    # 但是data变量在上文中被初始化为空列表[],所以这行代码实际上不会执行任何操作
    # 如果data是从Redis或其他地方获取的字节数据,则需要先正确赋值给data变量
    # 此处保留原逻辑,但实际运行时data需要包含有效的编码数据
    # data = []
    # datas = [int(item.decode('utf-8')) for item in data]  # 将字节对象转换为整数

加密工具文件 mycryputils.py

import hashlib

def md5(text):
    obj = hashlib.md5(text.encode("utf-8"))
    return obj.hexdigest()

数据库工具文件 mydbutils.py

import pymysql
from dbutils.pooled_db import PooledDB

POOL = PooledDB(
    creator=pymysql, #使用链接数据库的模块
    maxconnections=5, # 连接池允许的最大连接数,0和None表示不限制连接数
    mincached=1,  # 初始化时,链接池中至少创建的空闲的链接,0表示不创建
    maxcached=2,  # 链接池中最多闲置的链接,0和None不限制
    maxshared=3, # 链接池中最小共享的链接数量,0和None表示全部共享
    blocking=True, # 连接池中如果没有可用连接后,是否阻塞等待。 True:等待 False:不等待然后报错
    ping=0,     # ping MySQL服务端,检查是否服务可用。 0 不检查, 1 默认,请求时检查, 2 创建cursor时检查, 4 操作的时候检查, 7 一直检查
    setsession=[], # 开始会话前执行的命令列表 如:["set datestyle to.….","set time zone"]
    host="10.100.2.250",
    port=3306,
    user="root",
    password="root",
    database="test",
    charset="utf8"
)

def select_one(sql, params):
    """
        # 1.新增(需commit)
        cursor.execute("insert into tbl(name,password) values('武沛齐','123123')")
        conn.commit()

        # 2.删除(需commit)
        cursor.execute("delete from tbl where id=l")
        conn.commit()

        # 3.修改(需commit)
        cursor.execute("update tb1 set name='xx' where id=1")
        conn.commit()

        # 4.查询
        cursor.execute("select *from tb where id>10")
        data =cursor.fetchone()  #cursor.fetchall()
        print(data)

        # 关闭连接
        cursor.close()
        conn.close()
    """
    try:
        conn = POOL.connection()
        curses = conn.cursor(cursor=pymysql.cursors.DictCursor)
        curses.execute(sql, params)
        result = curses.fetchone()
        curses.close()
        conn.close() # 将连接还给连接池
        return result
    except Exception as e:
        print(e)
        return None

def select_all(sql, params):
    try :
        conn = POOL.connection()
        curses = conn.cursor(cursor=pymysql.cursors.DictCursor)
        curses.execute(sql, params)
        result = curses.fetchall()
        curses.close()
        conn.close()
        return result
    except Exception as e:
        print(e)
        return None

def insert(sql, params):
    try :
        conn = POOL.connection()
        curses = conn.cursor(cursor=pymysql.cursors.DictCursor)
        curses.execute(sql, params)
        conn.commit()
        curses.close()
        conn.close()
        return curses.lastrowid
    except Exception as e:
        print(e)

def update(sql, params):
    try :
        conn = POOL.connection()
        curses = conn.cursor(cursor=pymysql.cursors.DictCursor)
        res = curses.execute(sql, params)
        conn.commit()
        curses.close()
        conn.close()
        return res
    except Exception as e:
        print(e)

def delete(sql, params):
    try :
        conn = POOL.connection()
        curses = conn.cursor(cursor=pymysql.cursors.DictCursor)
        res = curses.execute(sql, params)
        conn.commit()
        curses.close()
        conn.close()
        return res
    except Exception as e:
        print(e)

redis工具文件 myredisutils.py

import redis

REDIS_CONN_PARAMS = {
    "host" : "10.100.2.230",
    "password" : "123456",
    "port" : 6379,
    "db" : 1,
    "encoding" : "utf-8",
    "max_connections" : 100
}

REDIS_POOL = redis.ConnectionPool(**REDIS_CONN_PARAMS)
TASK_QUEUE = "order_queue"

def push_queue(value):
    conn = redis.Redis(connection_pool=REDIS_POOL)
    if value:
        conn.lpush(TASK_QUEUE, value)

def push_queues(value):
    conn = redis.Redis(connection_pool=REDIS_POOL)
    if value:
        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 select_all():
    conn = redis.Redis(connection_pool=REDIS_POOL)
    total_count = conn.llen(TASK_QUEUE)
    cache_list = conn.lrange(TASK_QUEUE, 0, total_count)
    cache_no_list = { item.decode('utf-8') for item in cache_list }
    return cache_no_list

时间工具文件 mytimeutils.py

import time

def longtime2string(longtime):
    return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(longtime))

def getcurrentlongsecondtime():
    return time.time()

通用工具文件 myutils.py

import uuid

def getuuid():
    return str(uuid.uuid4()).replace('-', '')

启动文件 app.py

from order import create_app

app = create_app()

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=80, debug=True)

数据库建表文件 db.sql

CREATE TABLE `p_user` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `user_no` varchar(32) NOT NULL COMMENT '用户编号',
  `mobile` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '手机号,用于登录',
  `password` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `real_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '用户真实姓名',
  `role` tinyint(1) DEFAULT '1' COMMENT '角色  1普通用户   2管理员',
  `c_time` bigint(20) DEFAULT NULL COMMENT '创建时间',
  `m_time` bigint(20) DEFAULT NULL COMMENT '修改时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

INSERT INTO `p_user` (`user_no`, `mobile`, `password`, `real_name`, `role`, `c_time`, `m_time`) VALUES ( 'aa16f307ee7641bc9d391c8613c42ff1', '130', '202cb962ac59075b964b07152d234b70', '管理员', 2, 1768492800, 1768869917);


CREATE TABLE `p_order` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `user_no` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户id',
  `url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '链接',
  `count` int(11) DEFAULT NULL COMMENT '数量',
  `order_no` varchar(32) NOT NULL COMMENT '订单号',
  `status` tinyint(1) DEFAULT NULL COMMENT '0待执行  1正在执行  2完成  3失败',
  `active` tinyint(1) DEFAULT '1' COMMENT '是否存在 0已删除  1存在',
  `c_time` bigint(20) DEFAULT '0' COMMENT '创建时间',
  `m_time` bigint(20) DEFAULT '0' COMMENT '修改时间',
  PRIMARY KEY (`id`),
  KEY `idx_user_no` (`user_no`)
) ENGINE=InnoDB AUTO_INCREMENT=22 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

依赖版本文件 requirements.txt

asgiref==3.11.0
blinker==1.9.0
Booktype==1.5
click==8.3.1
colorama==0.4.6
DBUtils==3.1.2
Django==6.0.1
Flask==3.1.2
itsdangerous==2.2.0
Jinja2==3.1.6
MarkupSafe==3.0.3
PyMySQL==1.1.2
redis==7.1.0
setuptools==80.9.0
simplejson==3.20.2
sqlparse==0.5.5
tzdata==2025.3
Werkzeug==3.1.5
Python
  • 作者:一介闲人(联系作者)
  • 发表时间: 2026-02-02 11:47
  • 版权声明:原创-转载需保持署名
  • 公众号转载:请在文末添加本文链接
  • 评论