ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python+MySQL宿舍管理系统:从数据库设计到答辩避坑的毕设实战

Python+MySQL宿舍管理系统:从数据库设计到答辩避坑的毕设实战 简介基于Python与MySQL的学校宿舍管理网站系统是一份面向高校计算机相关专业毕业设计或课程设计的完整项目包适用于需要系统学习Web开发全流程的初中级学习者。项目覆盖用户管理、宿舍分配、床位管理、信息查询和报告统计等核心模块代码按模型、视图、模板、路由、配置分层组织并附有数据库建表脚本及环境配置说明便于从零搭建和二次开发。资源包大小约8.09MB压缩包内包含源代码与文档结构清晰能够帮助读者快速定位到对应功能模块。目前已有85人参与学习浏览内容对理解Python Web框架如Django/Flask与MySQL在真实场景中的配合尤其有帮助。通过实际部署与调试读者能够掌握从数据库设计、业务逻辑实现到动态页面渲染的完整链路同时学会如何处理学生信息录入、床位状态更新、入住率统计等典型业务逻辑为课程设计或毕业设计提供可复用的参考方案。1. 学校宿舍管理网站系统为什么是 Python MySQL 毕设的常青树它到底在解决什么每年毕业设计开题季都能在实验室看到同一个画面学弟打开一个标着“优质毕业设计”的压缩包里面是一套宿舍管理系统。这套系统的生命力不在功能多花哨而在它恰好覆盖了毕业设计要考核的全部要素——Python 后端、MySQL 数据库、Web 页面、增删改查、权限区分、统计报表。宿舍管理的业务边界足够清晰学生要入住、要换寝、要报修、要交住宿费管理员要分配房间、查看空床位、导出入住率。这些需求既不冷门也不超纲一个普通学生用三个月时间从零写出来完全可行。如果你正在找课程设计或毕业设计项目这套基于 Python MySQL 的系统是一个投入产出比很高的选择下面从建表到跑通再到答辩演示的完整路径一次讲清楚。2. 先把地基打牢宿舍管理系统的表结构设计与 MySQL 连接参数2.1 业务边界怎么定宿舍管理不是进销存很多第一次做毕设的人拿到“宿舍管理系统”就开始设计表结果把表设计得像超市收银台——什么字段都往里塞什么表都想建。这是最大的开局错误。宿舍管理的核心业务只有四个动词入住、换寝、退宿、报修。围绕这四个动词数据模型只要覆盖三件事谁住在哪学生与宿舍的关联、谁报修了什么报修工单、谁交了多少钱费用记录。我一般会先画一张极简的业务草图但这里只说结论学生表和宿舍表是主表入住关系通过 student 表里的 dorm_id 外键表达也可以用独立的入住记录表但毕设用外键字段更直观报修和费用是一对多的事务表。超过六张表的模型对毕设来说都是过度设计后期光是维护数据关系就会消耗大量时间。2.2 五张核心表的建表 SQL从学生到宿舍的完整链路实际落地时我推荐的表结构是五张核心表加一张用户表users登录用户管理员、宿管员、学生共用students学生档案dormitories宿舍信息repairs报修工单payments住宿费用下面是完整的建表 SQL在 MySQL Workbench 或者 Navicat 里直接执行即可CREATE DATABASE IF NOT EXISTS dormitory DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE dormitory; -- 用户表三种角色共用一张表用 role 字段区分 CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(30) NOT NULL UNIQUE COMMENT 登录名, password VARCHAR(100) NOT NULL COMMENT 密码毕设可直接存MD5, role ENUM(admin, manager, student) NOT NULL DEFAULT student COMMENT 角色, student_id INT DEFAULT NULL COMMENT 学生用户关联的学生ID, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 宿舍表capacity 是床位总数current_count 是当前已住人数 CREATE TABLE dormitories ( id INT AUTO_INCREMENT PRIMARY KEY, building VARCHAR(20) NOT NULL COMMENT 楼栋名如 3栋, room_no VARCHAR(20) NOT NULL COMMENT 房间号如 301, capacity INT NOT NULL DEFAULT 4 COMMENT 床位数量, current_count INT NOT NULL DEFAULT 0 COMMENT 当前入住人数, gender ENUM(男, 女) NOT NULL COMMENT 宿舍性别, UNIQUE KEY uk_building_room (building, room_no) ); -- 学生表dorm_id 指向宿舍是入住关系的载体 CREATE TABLE students ( id INT AUTO_INCREMENT PRIMARY KEY, student_no VARCHAR(20) NOT NULL UNIQUE COMMENT 学号, name VARCHAR(50) NOT NULL COMMENT 姓名, gender ENUM(男, 女) NOT NULL COMMENT 性别, major VARCHAR(100) COMMENT 专业, phone VARCHAR(20), dorm_id INT DEFAULT NULL COMMENT 当前宿舍ID, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_student_dorm FOREIGN KEY (dorm_id) REFERENCES dormitories(id) ON DELETE SET NULL ); -- 报修表一个学生可能有多条报修记录 CREATE TABLE repairs ( id INT AUTO_INCREMENT PRIMARY KEY, student_id INT NOT NULL, dorm_id INT NOT NULL, content TEXT NOT NULL COMMENT 报修内容, status ENUM(待处理, 维修中, 已完成) DEFAULT 待处理, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (student_id) REFERENCES students(id) ON DELETE CASCADE, FOREIGN KEY (dorm_id) REFERENCES dormitories(id) ON DELETE CASCADE ); -- 费用表记录住宿费缴纳情况 CREATE TABLE payments ( id INT AUTO_INCREMENT PRIMARY KEY, student_id INT NOT NULL, amount DECIMAL(10,2) NOT NULL COMMENT 金额, pay_time DATETIME DEFAULT CURRENT_TIMESTAMP, semester VARCHAR(20) COMMENT 学期如 2024-2025-1, FOREIGN KEY (student_id) REFERENCES students(id) ON DELETE CASCADE );这段 SQL 里有三个容易忽略的地方。第一所有表和字段都加了 COMMENTNavicat 里看表结构一目了然答辩时评委如果打开数据库看设计这个细节非常加分。第二students.dorm_id 的外键用了 ON DELETE SET NULL意思是宿舍被删除时学生的 dorm_id 自动置空不会出现指向不存在宿舍的孤儿数据而 repairs 和 payments 用了 ON DELETE CASCADE因为报修和缴费记录随学生删除一起清掉是合理的业务逻辑。第三dormitories 表的 uk_building_room 唯一索引保证了不会出现“3栋 301”被插入两条记录的情况。2.3 pymysql 连接 MySQL 参数一个都不能错表建好之后接下来是让 Python 和 MySQL 建立连接。最常用的是 pymysql 这个纯 Python 库安装命令很简单pip install pymysql cryptography连接参数里面有几个坑会在后面的避坑章节详细说先给一份可以直接用的配置# db.py import pymysql DB_CONFIG { host: localhost, port: 3306, user: root, password: 你的密码, database: dormitory, charset: utf8mb4, cursorclass: pymysql.cursors.DictCursor, autocommit: False, } def get_conn(): return pymysql.connect(**DB_CONFIG)逐个参数说明host 不要随便写成 127.0.0.1在 Linux 服务器上 localhost 会走 unix socket 文件而 127.0.0.1 走 TCP如果 MySQL 没开 TCP 监听用 127.0.0.1 就会报 Cant connect 错误。port 默认 3306除非你在 my.cnf 里改过。charset 必须写 utf8mb4不要用 utf8因为 MySQL 8.0 里 utf8 已经是 utf8mb3 的别名存 Emoji 或生僻字会失败。cursorclass 用 DictCursor 返回字典而不是元组写代码时用 row[name] 而不是 row[1]可读性好很多。autocommit 这个参数是毕设翻车的高发区。MySQL 默认 autocommit1但 pymysql 建立连接时会把它关掉也就是说你执行 INSERT/UPDATE 后必须显式 conn.commit() 数据才会真正落盘。如果不 commit当前连接里查询能看到数据因为事务未结束换个连接就看不到了。2.4 连接池该不该上一个被答辩老师问烂但很多人答不上来的设计点毕设系统的并发量可能只有几十人同时在线用连接池看起来是杀鸡用牛刀。但答辩老师几乎一定会问一个问题“如果有一百个学生同时提交入住申请你的数据库受得了吗”如果回答“每次请求都新建连接”那等于承认数据库会被连接开销拖垮。反过来如果你加了连接池哪怕只是用 Python 的 DBUtils 库做最基础的连接复用这个问题就能答得漂亮。用 DBUtils 的 PooledDB 改造上面的 get_conn 函数from dbutils.pooled_db import PooledDB import pymysql pool PooledDB( creatorpymysql, maxconnections20, # 池子最大连接数 mincached2, # 空闲时保持的最小连接数 maxcached5, # 空闲时保持的最大连接数 blockingTrue, # 无连接时阻塞等待 setsession[SET AUTOCOMMIT 0], hostlocalhost, port3306, userroot, password你的密码, databasedormitory, charsetutf8mb4, cursorclasspymysql.cursors.DictCursor ) def get_conn(): return pool.connection()几个参数按学生项目的规模这样设就够了maxconnections20 意味着最多同时 20 条数据库连接超过的请求会阻塞等待mincached2 保证程序启动后至少有 2 条连接准备就绪第一次查询不用经历 TCP 握手。blockingTrue 比直接抛错友好在并发高峰时表现是“等一下”而不是“拒绝服务”。PooledDB 会监控连接的健康状态MySQL 的 wait_timeout 超时断开后池子能自动丢弃坏连接并新建。连接池里的连接一旦长时间空闲被 MySQL 服务端断开旧代码拿到这条“死连接”执行查询就会抛错。设置 mincached 别太高2-3 就够同时可以在每次取连接后先 ping 一下def get_conn(): conn pool.connection() conn.ping(reconnectTrue) return connreconnectTrue 让 pymysql 在连接断开时自动重新握手这个小细节能让你的系统在实验室挂机一晚上后第二天还能正常跑避免“昨天还能用今天打开就报错”的尴尬。3. 用 Flask 把后端跑通登录鉴权、宿舍分配与报修工单的实现3.1 为什么选 Flask 而不是 Django毕设的时间成本账毕设系统选 Web 框架时最常见的纠结是 Flask 和 Django 二选一。Django 自带 admin 后台、ORM、认证系统功能全但学习曲线陡而且它的 admin 后台会自动生成一套管理界面很多学生做完后自己都说不清数据是怎么走的。Flask 则只保留最核心的路由和模板渲染认证、admin 全部自己写。对毕设来说自己写的东西才讲得清楚答辩老师问“你这个权限怎么实现的”你从 Session 说起三句话就能讲明白如果用 Django 的 auth 模块讲不清楚时容易被觉得是“抄框架”。我的建议很直接如果项目周期在三个月以内选 Flask如果导师明确要求用 Django 才用 Django。下面所有代码都基于 Flask。3.2 登录接口与 Session 管理从 HTML 表单到数据库的完整链路先搭一个最小可运行的项目结构dormitory_system/ ├── app.py # Flask 主入口 ├── db.py # 数据库连接池 ├── templates/ # Jinja2 模板 │ ├── login.html │ ├── index.html │ └── students.html └── static/ # CSS/JS 文件app.py 最核心的两段逻辑是登录认证和 Session 校验。登录接口接收 POST 表单用参数化查询去 users 表匹配用户from flask import Flask, render_template, request, redirect, session, url_for from db import get_conn app Flask(__name__) app.secret_key dormitory-secret-key-2024 # 生产环境要用随机值 app.route(/login, methods[GET, POST]) def login(): error None if request.method POST: username request.form.get(username, ).strip() password request.form.get(password, ).strip() conn get_conn() cur conn.cursor() cur.execute( SELECT * FROM users WHERE username%s AND passwordMD5(%s), (username, password) ) user cur.fetchone() cur.close() conn.close() if user: session[uid] user[id] session[role] user[role] return redirect(url_for(index)) error 用户名或密码错误 return render_template(login.html, errorerror) app.route(/index) def index(): if uid not in session: return redirect(url_for(login)) return render_template(index.html, rolesession[role])这里有几个关键的参数细节。第一SQL 里用了 %s 占位符而不是字符串拼接pymysql 会自动做转义能挡住 SQL 注入——答辩时如果老师问“你怎么防 SQL 注入”这句就能答。第二密码存的是 MD5 摘要毕设不需要上 bcrypt 这种重量级方案注意这里在 SQL 里直接调用 MD5() 函数写入用户的时候也要用同样的方式保证两端一致。第三Session 的 secret_key 必须设置否则 Flask 的 session 无法签名刷新页面登录态就丢了。3.3 宿舍分配的核心逻辑按性别、楼栋、空床位自动分配宿舍分配是系统的核心业务。常见做法是管理员选择一栋楼的某间宿舍系统检查该宿舍是否未满、性别是否匹配然后执行分配。这里最容易翻车的是“并发分配同一间宿舍”——两个学生同时提交都查到还有 1 个空位都执行了入住。后面第 4.2 节会讲事务处理先给单用户版本的分配逻辑app.route(/assign, methods[POST]) def assign(): if session.get(role) not in (admin, manager): return 无权限操作, 403 student_id request.form.get(student_id) dorm_id request.form.get(dorm_id) conn get_conn() cur conn.cursor() try: # 查宿舍当前人数和容量 cur.execute( SELECT capacity, current_count, gender FROM dormitories WHERE id%s, (dorm_id,) ) dorm cur.fetchone() if not dorm: return 宿舍不存在, 404 if dorm[current_count] dorm[capacity]: return 该宿舍已满请选择其他宿舍, 400 # 查学生性别防止男女生混住 cur.execute(SELECT gender FROM students WHERE id%s, (student_id,)) student cur.fetchone() if not student: return 学生不存在, 404 if student[gender] ! dorm[gender]: return 性别不匹配不能分配此宿舍, 400 # 更新宿舍人数和学生宿舍ID cur.execute( UPDATE dormitories SET current_countcurrent_count1 WHERE id%s, (dorm_id,) ) cur.execute( UPDATE students SET dorm_id%s WHERE id%s, (dorm_id, student_id) ) conn.commit() return redirect(url_for(students)) except Exception as e: conn.rollback() return f分配失败: {str(e)}, 500 finally: cur.close() conn.close()这个版本在事务边界上已经做对了先查宿舍状态确认有床位、性别匹配再执行更新最后 commit。所有数据库操作放在同一个连接里用 try/except/finally 保证出错时滚回。但有个问题要特别说明这段代码在并发场景下不等于安全。两个请求同时查到 current_count3 capacity4都会认为有床位然后都执行 UPDATE最终 current_count 变成 5超过容量。解决这个问题要靠数据库层面的行锁第 4.2 节专门讲。3.4 Jinja2 模板渲染Python 变量怎么变成页面上的表格后端接口返回的数据最终要靠 Jinja2 模板渲染成 HTML。以学生列表页为例视图函数把学生列表传给模板app.route(/students) def students(): if uid not in session: return redirect(url_for(login)) conn get_conn() cur conn.cursor() cur.execute( SELECT s.id, s.student_no, s.name, s.gender, s.major, s.phone, d.building, d.room_no FROM students s LEFT JOIN dormitories d ON s.dorm_id d.id ORDER BY s.student_no ) data cur.fetchall() cur.close() conn.close() return render_template(students.html, studentsdata)注意这里用 LEFT JOIN 而不是 INNER JOIN因为存在还没分配宿舍的学生——如果学生没有 dorm_idINNER JOIN 会让这个学生直接从列表里消失。这是很小的细节但能避免“新生列表里少了一个人”这种难排查的问题。对应的 students.html 片段table classtable table-bordered thead tr th学号/thth姓名/thth性别/thth专业/thth电话/thth宿舍/th /tr /thead tbody {% for stu in students %} tr td{{ stu.student_no }}/td td{{ stu.name }}/td td{{ stu.gender }}/td td{{ stu.major }}/td td{{ stu.phone }}/td td {% if stu.building %} {{ stu.building }}栋 {{ stu.room_no }}室 {% else %} span classtext-muted未分配/span {% endif %} /td /tr {% endfor %} /tbody /tableJinja2 模板里 {%%} 是控制结构{{}} 是变量输出。这里的 if stu.building 判断就是处理未分配宿舍学生的展示逻辑。不要在 Python 里拼 HTML 字符串那既难维护又会被评委追问“为什么不分离”。Flask 内置的 render_template 会自动去 templates 目录找同名文件目录名不要拼错否则会报 TemplateNotFound。4. 从“能跑”到“能答辩”权限控制、事务与入住率报表4.1 三种角色权限控制admin、manager、student 各看各的页面宿舍管理系统至少要有三种角色系统管理员admin能增删改查所有数据宿管员manager能分配宿舍和处理报修但不能删宿舍、不能改费用学生student只能看自己的宿舍信息和提交报修申请。最简实现是装饰器。写一个 role_required 装饰器在每个路由函数上声明允许的角色from functools import wraps def role_required(*roles): def decorator(f): wraps(f) def wrapper(*args, **kwargs): if role not in session: return redirect(url_for(login)) if session[role] not in roles: return 无权限访问, 403 return f(*args, **kwargs) return wrapper return decorator app.route(/dormitories) role_required(admin, manager) def dormitories(): # 宿舍列表管理管理员和宿管员都能看 ... app.route(/dormitories/int:dorm_id/delete) role_required(admin) def delete_dorm(dorm_id): # 删除宿舍只有 admin 能干 ...装饰器的执行顺序要记牢app.route 在外层role_required 在里层这样 Flask 先注册路由然后请求进来时先走权限检查。如果把顺序写反路由注册会拿到被包装后的函数容易出现“权限检查不生效”的诡异现象。这种 bug 属于典型的黑匣子问题表面上报错信息看不出来调试时只有加日志看请求走了哪个分支才明白。权限这块还有一个容易忽略的点前端按钮要让无权限的用户看不到。比如学生登录后不应该看到“删除宿舍”这个按钮。实际操作是在模板里判断角色{% if session.role admin %} a href/dormitories/{{ dorm.id }}/delete classbtn btn-danger删除/a {% endif %}服务端校验和前端隐藏要同时做前端只是体验后端才是安全边界。4.2 用事务和行锁解决“一间宿舍被分给两个人”第 3.3 节留了一个并发问题两个请求同时分配同一间宿舍的最后一个床位。数据库层面的标准解法是 SELECT ... FOR UPDATE 行锁app.route(/assign_safe, methods[POST]) def assign_safe(): student_id request.form.get(student_id) dorm_id request.form.get(dorm_id) conn get_conn() cur conn.cursor() try: # 锁住宿舍行其他事务要读取这行会被阻塞 cur.execute( SELECT capacity, current_count, gender FROM dormitories WHERE id%s FOR UPDATE, (dorm_id,) ) dorm cur.fetchone() if not dorm: return 宿舍不存在, 404 if dorm[current_count] dorm[capacity]: conn.rollback() return 该宿舍已满, 400 cur.execute( UPDATE dormitories SET current_countcurrent_count1 WHERE id%s, (dorm_id,) ) cur.execute( UPDATE students SET dorm_id%s WHERE id%s, (dorm_id, student_id) ) conn.commit() return 分配成功 except Exception as e: conn.rollback() return f分配失败: {str(e)}, 500 finally: cur.close() conn.close()FOR UPDATE 会让 MySQL 对被选中的行加排他锁事务提交前其他事务无法修改或再次加锁这行。如果两个请求同时进来第二个请求的 SELECT ... FOR UPDATE 会阻塞等第一个 commit 后它读到的是 current_count4 的新数据自然就判断宿舍已满。这样“超卖”问题就解决了。事务隔离级别默认是 REPEATABLE READ在 InnoDB 下这条 FOR UPDATE 会被当作当前读读到的是最新已提交的数据不需要改隔离级别就能生效。这里要补充一个容易忽略的细节FOR UPDATE 生效的前提是表引擎是 InnoDB。如果建表时用了 MyISAM行锁不生效整个查询期间会对表加共享锁并发一高就全表阻塞。检查方式很简单执行 SHOW TABLE STATUS LIKE dormitories; 看 Engine 字段是否为 InnoDB。MySQL 8.0 默认就是 InnoDB但如果你是从老项目里拷来的表一定要确认。4.3 入住率统计报表给答辩评委一个能讲三分钟的数据亮点毕设答辩最怕的是“功能都有了但讲不出亮点”。入住率统计报表是一个低成本高回报的亮点一个页面、一条 SQL就能把宿舍资源的利用率数字化地呈现出来。按楼栋统计入住率的 SQL 如下SELECT d.building, SUM(d.capacity) AS total_beds, SUM(d.current_count) AS occupied_beds, ROUND(SUM(d.current_count) / SUM(d.capacity) * 100, 2) AS occupancy_rate FROM dormitories d GROUP BY d.building ORDER BY occupancy_rate DESC;在 Flask 里包一层路由把结果渲染成柱状图或纯表格都行。如果是纯表格模板里直接遍历如果要做柱状图引入 ECharts——它是纯前端 JS 库不需要服务器额外支持。把 SQL 查询出的数据转成列表传给模板app.route(/report) role_required(admin, manager) def report(): conn get_conn() cur conn.cursor() cur.execute( SELECT d.building, SUM(d.capacity) AS total_beds, SUM(d.current_count) AS occupied_beds, ROUND(SUM(d.current_count) / SUM(d.capacity) * 100, 2) AS occupancy_rate FROM dormitories d GROUP BY d.building ORDER BY occupancy_rate DESC ) report_data cur.fetchall() cur.close() conn.close() return render_template(report.html, reportreport_data)报表的价值在于能发现管理问题。比如某栋楼入住率 95%另一栋只有 60%可以提出“是否要做跨楼栋调剂”的业务建议。答辩时你把这个观察讲出来就不是简单的增删改查展示了而是体现你理解了数据背后的业务逻辑。如果再给报表加一个时间维度的筛选条件例如统计“本学期各楼栋报修数量”就能看出哪栋楼设施老化严重。SQL 加 WHERE create_time BETWEEN %s AND %s前端放一个日期选择器代码量不大但数据库查询、参数传递、模板回显这些点全都能覆盖到是典型的低成本加分项。5. 避坑指南Python MySQL 宿舍管理系统最容易踩的 5 个坑5.1 Cant connect to local MySQL server through socket服务没起还是 host 配错现象pymysql 连接时报错 “Cant connect to local MySQL server through socket /tmp/mysql.sock (2)”。原因这个报错在 Linux 和 macOS 上最常见。host 用了 localhost 时pymysql 尝试通过 unix socket 文件连接但 MySQL 服务没有启动或者 socket 文件路径不是默认位置。这个报错跟防火墙、端口都没关系。Windows 上的变体是 “Cant connect to MySQL server on localhost (10061)”那是 TCP 连接被拒多数是 MySQL 服务类型设成了 Manual开机没自动拉起。解决先分两层排查。第一层确认 MySQL 服务是否在运行Linux 下执行 systemctl status mysqldmacOS 下用 brew services listWindows 下用 net start 查看 MySQL 服务。第二层确认连接参数如果 MySQL 在远程服务器上把 host 改成实际 IP强制走 TCP。如果想继续用 localhost 走 socket可以指定 unix_socket 参数pymysql.connect( hostlocalhost, unix_socket/var/run/mysqld/mysqld.sock, userroot, password密码, databasedormitory )对于部署在云服务器上的毕设系统我的建议是统一用内网 IP 作为 host避免折腾 socket 路径。5.2 中文乱码宿舍号变成“3æ ̂”的成因与修法现象页面显示宿舍楼栋名正常但从 MySQL 查询出来变成一堆乱码比如“3栋”显示成“3æ ̂”或者插入数据库后 SELECT 出来是问号。原因三个层面的字符集没有统一。第一层是数据库字段字符集CREATE DATABASE 时没写 CHARACTER SET utf8mb4或者从别处拷来的库是 latin1。第二层是连接字符集pymysql 连接时 charset 参数没写或写成了 utf8。第三层是前端页面编码HTML 没声明 。解决从建库开始统一所有环节全用 utf8mb4。检查现有库的字符集执行 SHOW CREATE DATABASE dormitory; 如果发现不是 utf8mb4用下面的语句修复ALTER DATABASE dormitory CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ALTER TABLE students CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;Python 连接参数里 charsetutf8mb4HTML 模板开头写 。三个环节全部对齐后乱码问题就会消失这个坑属于典型的环境配置问题跟业务代码无关。5.3 插入了数据但前端查不到pymysql 的 autocommit 陷阱现象在 Python 里执行 INSERT 后马上 SELECT能查到新数据但关掉程序重新启动或者用 Navicat 打开数据库看到的还是旧数据。原因pymysql 连接默认 autocommitFalseINSERT/UPDATE 之后没有执行 conn.commit()事务一直处于未提交状态。当前连接内查询能看到自己事务里的数据其他连接看不到造成了一种“数据已写入”的错觉。Navicat 打开看到的还是旧数据就是因为它开的是另一个独立连接。解决在每次写操作后显式调用 conn.commit()。更省事的做法是在建立连接时设置 autocommitTruepymysql.connect( hostlocalhost, userroot, password密码, databasedormitory, charsetutf8mb4, autocommitTrue )但对有事务需求的操作比如宿舍分配autocommitTrue 会让每条语句自动提交FOR UPDATE 行锁在语句结束时就释放了无法保证分配原子性。所以我的习惯是连接层保持 autocommitFalse在需要落盘的地方显式 commit把事务控制权握在自己手里。5.4 删除宿舍时报外键约束错误FOREIGN KEY 的连锁反应现象执行 DELETE FROM dormitories WHERE id1 报错 “Cannot delete or update a parent row: a foreign key constraint fails”。原因students 表的外键 dorm_id 默认行为是 RESTRICT只要还有学生引用这间宿舍宿舍就不能被删除。毕设里常见的场景是管理员退宿了一个学生但没把学生的 dorm_id 置空或者干脆没走退宿流程直接去删宿舍。解决方案一是业务上先清空引用在删除宿舍前把所有 dorm_id 指向它的学生置为 NULLUPDATE students SET dorm_id NULL WHERE dorm_id 1; DELETE FROM dormitories WHERE id 1;方案二是改外键约束行为在 students 表创建时用 ON DELETE SET NULL前面建表 SQL 里已经用了这个写法。这样删除宿舍时 MySQL 自动把学生的 dorm_id 置空。如果表已经建好可以用 ALTER TABLE 修改约束ALTER TABLE students DROP FOREIGN KEY fk_student_dorm; ALTER TABLE students ADD CONSTRAINT fk_student_dorm FOREIGN KEY (dorm_id) REFERENCES dormitories(id) ON DELETE SET NULL;修改外键前先确认约束名在 Navicat 的设计表界面里能看到约束名称名字对不上就执行不了。5.5 部署到云服务器后浏览器访问不了安全组和 bind-address 的双重门现象本地用 Flask run 能访问部署到云服务器后用公网 IP 访问 http://公网IP:5000 超时或者 MySQL 用 Navicat 连不上。原因两层原因叠加。第一Flask 默认只监听 127.0.0.1需要指定 host0.0.0.0 才能对外访问第二云服务器安全组默认不开放 5000/3306 端口需要在云控制台加一条入站规则。MySQL 的 bind-address 如果绑定了 127.0.0.1即使安全组开放了 3306外部也连不上。解决Flask 启动时写死监听地址app.run(host0.0.0.0, port5000, debugFalse)MySQL 的配置文件 /etc/my.cnf 里把 bind-address 改成 0.0.0.0或注释掉重启 mysqld。安全组在云控制台加两条规则TCP 5000 端口放行TCP 3306 端口放行只放行你本机 IP 会更安全。最后不要忘了MySQL 8.0 默认的 root 用户只允许 localhost 登录要创建一个允许远程访问的用户CREATE USER dorm_app% IDENTIFIED BY 强密码; GRANT ALL PRIVILEGES ON dormitory.* TO dorm_app%; FLUSH PRIVILEGES;MySQL 8.0 默认的 caching_sha2_password 认证插件对旧版 Navicat 和某些客户端不兼容如果报 authentication plugin 错误改用 mysql_native_password 可以解决ALTER USER dorm_app% IDENTIFIED WITH mysql_native_password BY 强密码;这个坑是部署环节最折磨人的很多人本地跑通了一切顺利一上云就卡在这一步。6. 答辩前夜的三件事初始化数据、备份数据库和设计演示路线6.1 用存储过程生成测试数据别手动敲 200 条学生记录演示的时候最怕现场数据太少宿舍列表空空荡荡分配宿舍时下拉框里没几个学生。提前用一条存储过程批量生成测试数据DROP PROCEDURE IF EXISTS generate_test_data; DELIMITER $$ CREATE PROCEDURE generate_test_data(IN stu_count INT) BEGIN DECLARE i INT DEFAULT 1; -- 先造 20 间宿舍5 栋楼每栋 4 间 WHILE i 20 DO INSERT INTO dormitories (building, room_no, capacity, gender) VALUES (CONCAT(CEIL(i/4), 栋), CONCAT(30, i), 4, IF(i % 2 1, 男, 女)); SET i i 1; END WHILE; -- 再造学生 SET i 1; WHILE i stu_count DO INSERT INTO students (student_no, name, gender, major, phone, dorm_id) VALUES ( CONCAT(2024, LPAD(i, 3, 0)), CONCAT(测试, i, 号), IF(i % 2 1, 男, 女), ELT(MOD(i, 5) 1, 计算机科学, 软件工程, 网络工程, 数据科学, 人工智能), CONCAT(138, LPAD(MOD(i, 100000000), 8, 0)), MOD(i, 20) 1 ); SET i i 1; END WHILE; END$$ DELIMITER ; CALL generate_test_data(200);执行完 CALL generate_test_data(200); 数据库里就有 200 个学生、20 间宿舍演示时数据量足够支撑任何页面。存储过程里 CONCAT、LPAD、ELT 几个函数都是 MySQL 字符串处理的常用操作评委问起来也很好讲。6.2 演示路线图先登录、再分配、最后报表答辩演示只有几分钟路线设计直接影响评委观感。我会按这个顺序走先打开登录页用管理员账号登录展示不同角色登录后看到的菜单不同这一步已经把权限功能演示完了。然后进入学生列表页筛选一个“未分配”的学生演示分配宿舍分配后马上刷新宿舍列表床位数量发生变化这一步把事务和关联更新展示清楚。最后打开入住率报表指着一栋楼的柱状图说入住率高、另一栋有调剂空间收尾落在数据价值上。中间如果评委问“数据库长什么样”再打开 Navicat 展示几张表的关联关系不要一上来就展示数据库结构那样会把演示节奏拖慢。6.3 用 mysqldump 一键备份防止答辩现场翻车演示前最担心的就是数据库被搞坏。答辩前一晚跑一次完整备份把整库导出成一个 SQL 文件mysqldump -u root -p --default-character-setutf8mb4 dormitory dormitory_backup.sql恢复的时候用mysql -u root -p dormitory dormitory_backup.sql备份文件一定要加 --default-character-setutf8mb4 参数否则备份出的 SQL 文件里中文注释和字符串会变成乱码恢复进去整库中文化就毁了。这是我踩过的坑当时备份没加这个参数恢复后所有楼栋名乱码花了半小时才用第 5.2 节的 ALTER TABLE 转回来。这几件事做完剩下的就看临场表达了。第一次做这种系统的人最容易在“数据库连不上”“页面 500”这类环境问题上翻车而上面这几条恰好把这类问题提前堵住。希望帮到你。本文还有配套的精品资源点击获取
返回列表