简介这是一套基于 Python 技术栈的网站开发模板采用 Flask 作为后端框架前端整合 LayUI 与 Bootstrap 等 UI 组件数据层使用 MySQL 存储适合正在学习 Web 全栈开发、需要快速搭建后台管理或企业官网的初中级开发者参考使用。压缩包共 233 个文件约 1.29MB其中 58 个 py 文件承载路由与业务逻辑32 个 html 页面与 36 个 js、9 个 css 文件构成前端界面与交互另含 gif、png、jpg 等图片素材及字体、图标资源目录结构完整可直接运行或按需二次开发。目前已有 1584 人学习下载。模板覆盖登录、表格、弹层、日期选择等常见页面模块读者可借此理解 Flask 项目分层组织方式、前后端数据交互流程与静态资源引用规范并在此基础上替换业务逻辑快速完成课程设计、毕业设计或个人练手项目。1. 拿到一个「python flask LayUI mysql 网站模板.zip」之后先别急着解压很多人拿到这类压缩包的第一反应是双击解压、找app.py、pip install、python app.py然后浏览器一开发现 500回头骂模板垃圾。我见过太多这种翻车现场。这个标题其实描述的是一个非常具体的组合后端用 Flask 做路由和业务逻辑前端用 LayUI 做后台管理界面数据落在 MySQL 里整体打包成一个可以直接改的网站骨架。它解决的不是「从零写一个网站」的问题而是「我已经知道要做什么功能但不想再花三天搭登录页和表格页」的问题。适合谁适合需要快速交付中小型管理系统、后台工具、内部平台的开发者尤其是那些前端不想碰 Vue/React 全家桶、只想用 jQuery 风格写页面的人。这一章不写代码先把这套组合的边界和预期讲清楚后面五章再动手。2. 把模板跑起来之前先搞清楚 Flask LayUI MySQL 各自负责什么2.1 三层各管一段别让它们互相越界Flask 在这个组合里只做三件事接收 HTTP 请求、调用业务函数、返回 JSON 或渲染模板。它不负责生成 HTML 结构也不负责数据库表设计。LayUI 负责浏览器里看到的一切表格、表单、弹层、分页、日期选择器。它通过 AJAX 向 Flask 要数据拿到 JSON 后自己渲染。MySQL 只负责存数据表结构、索引、字符集这些事在 Flask 之外就要定好。常见的新手错误是让 Flask 直接拼 HTML 表格然后 LayUI 的 table 模块完全没用上。这样写出来的页面又丑又难维护。正确的分工是Flask 返回{code: 0, msg: , count: 100, data: [...]}这种格式LayUI 的table.render直接消费。另一个边界问题是数据库连接。Flask 里不要每次请求都pymysql.connect()也不要用全局单连接。常见做法是用DBUtils的PooledDB做连接池或者用Flask-SQLAlchemy的scoped_session。我一般会选后者因为模板里通常已经带了models.py改起来顺手。2.2 最小可运行环境的搭建步骤假设你已经解压到一个目录里面能看到app.py、requirements.txt、static/、templates/、sql/这几个东西。先别改代码按下面顺序走。第一步建虚拟环境并装依赖。不要用系统 Python 直接装版本冲突会让你怀疑人生。python -m venv venv # Windows 用 venv\Scripts\activate source venv/bin/activate pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple第二步建库并导入初始 SQL。模板里的sql/init.sql通常包含建表语句和一条管理员账号。mysql -u root -p -e CREATE DATABASE demo_db DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci; mysql -u root -p demo_db sql/init.sql第三步改配置文件。Flask 模板一般把数据库连接写在config.py或app.config里找到SQLALCHEMY_DATABASE_URI或pymysql.connect的参数改成你自己的。# config.py 片段 import os class Config: SECRET_KEY os.environ.get(SECRET_KEY) or dev-key-change-me MYSQL_HOST 127.0.0.1 MYSQL_PORT 3306 MYSQL_USER root MYSQL_PASSWORD your_password MYSQL_DB demo_db SQLALCHEMY_DATABASE_URI ( fmysqlpymysql://{MYSQL_USER}:{MYSQL_PASSWORD} f{MYSQL_HOST}:{MYSQL_PORT}/{MYSQL_DB}?charsetutf8mb4 ) SQLALCHEMY_TRACK_MODIFICATIONS False第四步启动。python app.py或flask run看控制台有没有报错。如果看到ModuleNotFoundError说明依赖没装全如果看到Access denied说明数据库账号密码不对如果看到Unknown database说明库没建。提示模板里的SECRET_KEY一定要改不要用默认值上线。这个 key 泄露意味着 session 可以被伪造。2.3 目录结构里哪些文件必须改哪些可以不动一个典型的 Flask LayUI 模板目录长这样project/ ├── app.py ├── config.py ├── models.py ├── requirements.txt ├── static/ │ ├── layui/ │ ├── css/ │ └── js/ ├── templates/ │ ├── login.html │ ├── index.html │ └── user/ │ └── list.html └── sql/ └── init.sql必须改的config.py里的数据库和密钥sql/init.sql里的默认账号密码templates/login.html里的标题和 logo。可以不动但建议看的models.py里的表定义app.py里的路由蓝图注册方式。不要动的static/layui/整个目录除非你要升级 LayUI 版本。我见过有人把static/layui/里的文件删了几个说「用不到」结果表格分页直接消失。LayUI 的模块是按需加载的但layui.js和layui.css必须完整。3. 用 Flask 写第一个业务接口从 LayUI 表格请求到 JSON 返回3.1 LayUI 表格的数据契约长什么样LayUI 的table模块默认会向url发一个 GET 请求带上page和limit两个参数。它期望的响应格式是固定的{ code: 0, msg: , count: 123, data: [ {id: 1, username: a, status: 1} ] }code为 0 表示成功非 0 会触发错误提示。count是总记录数用于分页计算。data是当前页的数据数组。这个契约不能改改了 LayUI 就不认。所以 Flask 这边的接口必须做两件事接收page和limit查数据库时用LIMIT offset, limit同时查一次总数。3.2 一个可复用的分页查询函数不要每个接口都写一遍分页逻辑。我一般会在utils.py里放一个通用函数# utils.py from models import db from sqlalchemy import text def paginate_query(base_sql, count_sql, params, page, limit): base_sql: 带 LIMIT 占位的数据查询语句 count_sql: 统计总数的语句 params: 参数字典 page: 当前页从 1 开始 limit: 每页条数 返回 (data_list, total_count) offset (page - 1) * limit # 先查总数 total db.session.execute(text(count_sql), params).scalar() # 再查当前页 page_params dict(params) page_params[offset] offset page_params[limit] limit rows db.session.execute(text(base_sql), page_params).fetchall() # 把 Row 对象转成 dict data [dict(row._mapping) for row in rows] return data, total逻辑说明count_sql和base_sql分开传是因为有些查询带JOIN时统计总数不能直接套。offset和limit用参数绑定不要用字符串拼接否则 SQL 注入风险直接拉满。row._mapping是 SQLAlchemy 1.4 的写法老版本用dict(row)。参数说明page和limit从request.args.get(page, 1, typeint)拿limit默认给 10 或 20。注意 LayUI 传过来的limit可能是字符串用typeint转一下。3.3 路由里怎么接住 LayUI 的请求# app.py 片段 from flask import request, jsonify from utils import paginate_query app.route(/api/user/list) def user_list(): page request.args.get(page, 1, typeint) limit request.args.get(limit, 10, typeint) keyword request.args.get(keyword, , typestr) where WHERE 11 params {} if keyword: where AND username LIKE :kw params[kw] f%{keyword}% base_sql f SELECT id, username, status, created_at FROM sys_user {where} ORDER BY id DESC LIMIT :offset, :limit count_sql fSELECT COUNT(*) FROM sys_user {where} data, total paginate_query(base_sql, count_sql, params, page, limit) return jsonify({ code: 0, msg: , count: total, data: data })逻辑说明where子句动态拼但值全部走参数绑定。ORDER BY id DESC保证新数据在前。返回的data里created_at如果是datetime对象jsonify会自动转成字符串但格式可能不是你想要的必要时在 SQL 里用DATE_FORMAT处理。参数说明keyword是可选搜索条件LayUI 的表格搜索栏会把它作为额外参数传过来。limit不要设太大超过 100 会让前端渲染变慢。注意如果模板用的是Flask-SQLAlchemy的 ORM 查询分页可以用query.paginate(page, per_page, error_outFalse)但返回结构要自己转成 LayUI 格式。两种方式都行看模板原本怎么写的。4. LayUI 前端表格和表单的对接细节以及 MySQL 表设计的三个硬约束4.1 table.render 的参数怎么和 Flask 接口对齐LayUI 的表格初始化代码通常写在templates/user/list.html里// static/js/user_list.js layui.use([table, form], function () { var table layui.table; var form layui.form; table.render({ elem: #userTable, url: /api/user/list, method: get, page: true, limit: 10, limits: [10, 20, 50], cols: [[ {field: id, title: ID, width: 80, sort: true}, {field: username, title: 用户名, minWidth: 120}, {field: status, title: 状态, width: 100, templet: function (d) { return d.status 1 ? 启用 : 禁用; }}, {field: created_at, title: 创建时间, width: 180}, {title: 操作, toolbar: #rowToolbar, width: 150} ]], parseData: function (res) { // 如果后端返回的字段名和 LayUI 默认不一致在这里转换 return { code: res.code, msg: res.msg, count: res.count, data: res.data }; } }); // 搜索按钮 form.on(submit(searchBtn), function (data) { table.reload(userTable, { where: data.field, page: {curr: 1} }); return false; }); });逻辑说明parseData是可选的如果后端已经返回code/msg/count/data可以省略。templet用来做字段的显示转换比如状态码转文字。table.reload的where会把搜索表单的字段作为额外参数拼到请求里Flask 那边用request.args.get接。参数说明limits控制每页条数下拉选项不要设太多。page: {curr: 1}表示搜索后回到第一页不加的话会停留在当前页导致看不到结果。4.2 MySQL 表设计的三个硬约束第一字符集必须utf8mb4排序规则utf8mb4_unicode_ci。不要用utf8那个是残废的存不了 emoji 和部分生僻字。建库建表都要显式指定。第二主键用BIGINT UNSIGNED AUTO_INCREMENT不要用INT。现在数据量涨得快INT上限 21 亿看着多但日志表、操作记录表很容易超。BIGINT一步到位。第三时间字段用DATETIME而不是TIMESTAMP。TIMESTAMP有 2038 年问题而且受时区影响。DATETIME存什么就是什么配合DEFAULT CURRENT_TIMESTAMP和ON UPDATE CURRENT_TIMESTAMP足够用。CREATE TABLE sys_user ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, username VARCHAR(64) NOT NULL COMMENT 登录名, password_hash VARCHAR(255) NOT NULL COMMENT 密码哈希, status TINYINT NOT NULL DEFAULT 1 COMMENT 1启用 0禁用, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci COMMENT系统用户表;逻辑说明password_hash存哈希不存明文用werkzeug.security.generate_password_hash生成。uk_username唯一索引防止重复注册。status用TINYINT而不是BOOL因为 MySQL 的BOOL本质就是TINYINT(1)不如直接写清楚。参数说明VARCHAR(64)对用户名够用VARCHAR(255)对哈希够用。不要用TEXT存这些短字段索引效率会差。4.3 登录态怎么在 Flask 和 LayUI 之间传递模板通常用 Flask 的session存登录状态。登录成功后session[user_id] user.id然后每个需要登录的接口用装饰器检查。from functools import wraps from flask import session, jsonify, redirect, url_for def login_required(f): wraps(f) def decorated(*args, **kwargs): if user_id not in session: # 如果是 AJAX 请求返回 JSON否则跳转登录页 if request.is_json or request.path.startswith(/api/): return jsonify({code: 401, msg: 未登录}), 401 return redirect(url_for(login)) return f(*args, **kwargs) return decorated逻辑说明LayUI 的 AJAX 请求如果收到 401可以在全局ajaxSetup里统一跳转登录页。不要在每个接口里单独处理。参数说明request.is_json判断请求体是不是 JSONrequest.path.startswith(/api/)是兜底判断。两个条件满足一个就走 JSON 返回。5. 避坑这套组合里最容易翻车的五个地方5.1 中文乱码从数据库到浏览器全链路排查现象页面上的中文显示成???或测试。原因字符集在某一层断了。解决按顺序查。第一MySQL 库和表的字符集是不是utf8mb4用SHOW CREATE TABLE sys_user;看。第二Flask 连接串里有没有?charsetutf8mb4。第三HTML 的meta charsetutf-8有没有。第四Python 文件本身是不是 UTF-8 编码。四个地方都对了乱码就没了。5.2 LayUI 表格不显示数据但接口返回正常现象浏览器 Network 里看到接口返回了 JSON但表格是空的。原因parseData没写对或者返回的code不是 0。解决先看返回的code值LayUI 只认 0。如果后端返回的是{code: 200}要么改后端要么在parseData里把 200 转成 0。另外检查data字段是不是数组如果返回的是{data: {list: [...]}}需要parseData里取res.data.list。5.3 分页总数不对翻到第二页数据重复现象第一页和第二页数据一样或者总数显示 0。原因count_sql写错了或者LIMIT的offset计算错了。解决offset (page - 1) * limit这个公式不能错。count_sql不要带LIMIT也不要用SELECT *包一层再COUNT直接SELECT COUNT(*) FROM 表 WHERE 条件。如果带JOIN注意COUNT(DISTINCT 主表.id)。5.4 修改了 static 里的文件浏览器不生效现象改了 CSS 或 JS刷新页面没变化。原因浏览器缓存。解决开发阶段按CtrlF5强制刷新或者在 Flask 里配置SEND_FILE_MAX_AGE_DEFAULT 0。上线后要给静态文件加版本号比如layui.css?v1.0.1否则用户缓存旧文件会导致页面错乱。5.5 数据库连接数暴涨MySQL 报 too many connections现象运行一段时间后接口全部 500MySQL 日志显示连接数满。原因每次请求都新建连接且没关闭或者连接池配置过大。解决用连接池SQLALCHEMY_ENGINE_OPTIONS里设pool_size5, max_overflow10, pool_recycle3600。pool_recycle很重要MySQL 默认 8 小时断开空闲连接不设这个会拿到失效连接。# config.py 补充 SQLALCHEMY_ENGINE_OPTIONS { pool_size: 5, max_overflow: 10, pool_recycle: 3600, pool_pre_ping: True }逻辑说明pool_pre_ping会在每次取连接时先 ping 一下失效的连接自动重建。pool_recycle3600表示连接存活超过 1 小时就回收避免被 MySQL 服务端断开。6. 把这套模板改造成可交付项目的三个进阶习惯6.1 用蓝图拆分路由别让 app.py 变成千行怪模板初始的app.py通常把所有路由都堆在一起。功能一多这个文件就没法看了。我一般会在项目中期做一次拆分按业务模块建views/目录每个模块一个蓝图文件。# views/user.py from flask import Blueprint, request, jsonify, session from utils import paginate_query, login_required from models import db user_bp Blueprint(user, __name__, url_prefix/api/user) user_bp.route(/list) login_required def list_users(): # 具体逻辑 pass user_bp.route(/add, methods[POST]) login_required def add_user(): # 具体逻辑 pass然后在app.py里注册from views.user import user_bp app.register_blueprint(user_bp)逻辑说明url_prefix统一加前缀蓝图内部路由写相对路径。这样app.py只负责创建应用、加载配置、注册蓝图保持在 50 行以内。参数说明蓝图名不要和模块名冲突user_bp比user安全。url_prefix结尾不要带斜杠否则路由会变成//list。6.2 给 LayUI 表格加一个统一的请求拦截器LayUI 底层用的是 jQuery 的$.ajax可以在页面加载时统一设置ajaxSetup处理 401 跳转和全局错误提示。// static/js/common.js layui.use([layer, jquery], function () { var $ layui.jquery; var layer layui.layer; $.ajaxSetup({ complete: function (xhr) { if (xhr.status 401) { layer.msg(登录已过期请重新登录, {icon: 2}); setTimeout(function () { top.location.href /login; }, 1500); } } }); });逻辑说明complete回调在请求结束后触发不管成功还是失败。判断xhr.status 401就跳转。用top.location.href而不是location.href防止页面被嵌套在 iframe 里跳不出去。参数说明layer.msg的icon: 2是错误图标。setTimeout给用户 1.5 秒看提示不要立即跳。6.3 上线前必做的三项检查第一关掉DEBUG。app.run(debugTrue)只能开发用上线必须debugFalse否则报错页面会暴露源码路径和配置信息。第二改掉所有默认密码。包括数据库密码、SECRET_KEY、初始管理员密码。模板里的默认值都是公开的不改等于没锁门。第三配一个生产级 WSGI 服务器。Flask 自带的开发服务器扛不住并发用gunicorn或uwsgi。gunicorn -w 4 -b 127.0.0.1:8000 app:app是最简配置-w 4表示 4 个 worker 进程一般设成 CPU 核数的 2 倍。# 生产启动示例 gunicorn -w 4 -b 127.0.0.1:8000 --access-logfile - --error-logfile - app:app逻辑说明--access-logfile -把访问日志打到标准输出方便被容器日志收集。app:app表示app.py文件里的app对象。参数说明-w不要设太大超过 CPU 核数 2 倍反而会因为上下文切换降低性能。如果用了gevent或eventlet可以用-k gevent切协程模式。我自己的习惯是每次拿到一个新模板先花 20 分钟把上面这些检查过一遍再开始写业务代码。这个习惯帮我省掉了至少三次上线当天的紧急回滚。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?