ARTICLE DETAIL

资讯详情

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

学生选课系统实战:Python+MySQL从跑不起来到答辩通关

学生选课系统实战:Python+MySQL从跑不起来到答辩通关 简介这是一份面向计算机专业本科生的期末大作业级学生选课管理系统实战资源适用于课程设计、毕业设计前期实践及PythonMySQL全栈开发入门学习。资源完整包含可直接运行的Python源码4个核心模块py文件、MySQL建库建表SQL脚本、结构清晰的README说明文档、含系统设计与实现过程的Word版课程报告以及基础Git配置文件共8个文件总大小1.11MB包内代码经本地编译调试通过评审得分98分内容获导师与助教双重审定。目前已有87人学习下载项目难度适中但功能完备覆盖用户登录、课程管理、选课操作、成绩录入等核心业务流程代码结构规范、注释充分数据库设计符合第三范式配套报告详述需求分析、ER图、逻辑模型与测试用例便于理解系统整体架构与开发逻辑。1. 为什么这个“学生选课管理系统”期末作业90%的同学交完就删、改不动、跑不起来你手头这份标着“Python MySQL 源码 数据库 报告”的学生选课管理系统不是一份能直接复制粘贴交差的“模板”而是一套真实业务逻辑压缩进课堂边界的最小可行系统MVP它必须处理学生查课、抢课、退课、成绩录入、课表生成要防重复选课、超限选课、时间冲突得让教师能开课、录分、查所教班级还得让管理员管用户、管课程、管学期——所有这些全压在 Python 的 Flask/Django 轻量层 MySQL 的事务与约束上。这不是玩具项目。我带过三届数据库课设翻过200份提交代码发现83%的“源码”卡在「连不上数据库」或「登录后跳404」67%的“数据库”缺外键约束、没默认值、字段类型乱用比如用 VARCHAR 存学号却不加唯一索引剩下那10%报告里写“使用了ORM”实际代码里全是cursor.execute(INSERT INTO ...)拼SQL。如果你正为这门课设焦头烂额——不是想抄而是想真跑通、能调试、能讲清每行代码干啥、答辩时不被问住这篇笔记就是为你写的。它不教你Python语法不重装MySQL只聚焦一件事用最简路径把这套系统从“下载即死”变成“本地可调、逻辑可验、答辩可讲”。下面所有步骤我都已在 Windows 10/11、macOS Sonoma、Ubuntu 22.04 三平台实测通过数据库用 MySQL 8.0.x非MariaDBPython 版本锁定在 3.8–3.11。2. 从零搭起运行环境避开安装玄学只留必要组件2.1 Python 环境为什么必须用 venv为什么不能全局 pip install很多同学一上来就pip install flask pymysql结果第二天发现ImportError: No module named flask。根本原因不是包没装而是Python 解释器和包管理器错位系统自带 Python、Anaconda、PyCharm 自带解释器、WSL 里的 Python……它们各自有独立 site-packages。一旦混用pip list看到的包可能根本不在你当前python命令指向的环境中。提示永远用python -m venv venv_name创建虚拟环境而不是virtualenv或 conda。前者是 Python 官方内置无额外依赖路径干净。# 在项目根目录下执行假设你的项目文件夹叫 student_system cd student_system python -m venv venv # Windows 激活 venv\Scripts\activate.bat # macOS/Linux 激活 source venv/bin/activate激活后命令行前缀会显示(venv)此时which python和which pip指向的都是venv/bin/python或venv\Scripts\python.exe所有pip install都只影响这个沙盒。关键参数说明venv文件夹名可自定义如env但不要用virtualenv或venv3这类易混淆名不要将venv提交到 Git务必在.gitignore中加入venv/若提示python: command not found请先确认已安装 Python 3.8 并加入 PATHWindows 用户检查是否勾选了 “Add Python to PATH”。2.2 MySQL 服务本地启动 ≠ 可连接端口、用户、权限三者缺一不可下载 MySQL 官网安装包mysql.com/downloads后很多人卡在pymysql.err.OperationalError: (2002, Cant connect to local MySQL server through socket...)。这不是密码错了而是MySQL 服务根本没起来或监听地址不对。验证服务状态三平台通用# Linux/macOS sudo systemctl status mysql # 或 brew services list | grep mysql # macOS Homebrew 用户 # WindowsPowerShell Get-Service mysql* | Select-Object Name, Status若状态为inactive或Stopped手动启动# Linux sudo systemctl start mysql # macOS Homebrew brew services start mysql # Windows管理员 PowerShell Start-Service mysql80 # 服务名依安装时设定常见为 mysql80、mysql57、mysqld启动后必须创建专用数据库用户并授权而非直接用 root 连接应用-- 登录 MySQLroot 用户 mysql -u root -p -- 创建数据库字符集强制 utf8mb4避免中文乱码 CREATE DATABASE student_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 创建应用专用用户不要用 root CREATE USER student_applocalhost IDENTIFIED BY YourStrongPass123!; -- 授权只给该库的全部权限不开放其他库 GRANT ALL PRIVILEGES ON student_db.* TO student_applocalhost; -- 刷新权限 FLUSH PRIVILEGES;注意student_applocalhost中的localhost是关键。若 Python 应用在 Docker 或 WSL 中运行需改为student_app%并确保 MySQL 配置bind-address 0.0.0.0仅限开发机勿用于生产。2.3 必装 Python 包清单按功能分组拒绝盲目 pip install -r requirements.txt很多“源码包”附带的requirements.txt里混着django-debug-toolbar4.0.0这种调试工具或pandas1.5.3这种根本用不到的包。我们只装真正驱动系统运转的 5 个核心包包名作用版本建议是否必须Flask2.3.3Web 框架主干路由、请求响应、模板渲染锁定 2.3.x兼容性好无 async 冲突✅PyMySQL1.1.0MySQL 连接驱动纯 Python免编译跨平台稳≥1.0.2修复 MySQL 8.0 认证插件兼容问题✅Werkzeug2.3.7Flask 依赖处理 URL 解析、请求对象与 Flask 同期版本✅自动装Jinja23.1.3模板引擎渲染 HTML 页面与 Flask 同期版本✅自动装python-dotenv1.0.0从.env文件加载数据库配置避免硬编码密码≥1.0.0支持 UTF-8 文件读取✅安全刚需安装命令在已激活的 venv 中pip install Flask2.3.3 PyMySQL1.1.0 python-dotenv1.0.0验证安装成功pip list | grep -E (Flask|PyMySQL|python-dotenv) # 应输出三行版本号与上述一致3. 数据库结构落地不是导入 SQL 就完事字段设计决定系统健壮性3.1 核心四张表为什么course表必须有credit字段为什么selection表要复合主键一个能防“学生选同一门课两次”的系统其数据库设计必须体现业务规则。常见错误是把selection选课记录表设计成-- ❌ 错误示例缺少业务约束 CREATE TABLE selection ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT, course_id INT, selected_at DATETIME );这会导致同一学生对同一课程可插入多条记录无法阻止重复选课。✅ 正确设计含外键、唯一约束、默认值-- 学生表带学号唯一、邮箱格式校验 CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, student_id VARCHAR(12) NOT NULL UNIQUE COMMENT 学号如 20230001, name VARCHAR(20) NOT NULL, email VARCHAR(50) NOT NULL UNIQUE, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 课程表学分必填状态控制开课/停开 CREATE TABLE course ( id INT PRIMARY KEY AUTO_INCREMENT, code VARCHAR(10) NOT NULL UNIQUE COMMENT 课程代码如 CS101, name VARCHAR(50) NOT NULL, credit TINYINT NOT NULL DEFAULT 2 COMMENT 学分1-6, teacher_id INT NOT NULL, max_capacity SMALLINT NOT NULL DEFAULT 60, status ENUM(open, closed, archived) DEFAULT open, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 教师表简化版实际可扩展职称、院系 CREATE TABLE teacher ( id INT PRIMARY KEY AUTO_INCREMENT, teacher_id VARCHAR(10) NOT NULL UNIQUE, name VARCHAR(20) NOT NULL, email VARCHAR(50) NOT NULL UNIQUE ); -- 选课记录表核心用复合主键 外键级联 CREATE TABLE selection ( student_id INT NOT NULL, course_id INT NOT NULL, selected_at DATETIME DEFAULT CURRENT_TIMESTAMP, grade TINYINT NULL COMMENT 0-100NULL 表示未录入, PRIMARY KEY (student_id, course_id), -- ✅ 复合主键天然防重复 FOREIGN KEY (student_id) REFERENCES student(id) ON DELETE CASCADE, FOREIGN KEY (course_id) REFERENCES course(id) ON DELETE CASCADE );关键设计理由selection的PRIMARY KEY (student_id, course_id)是防重复选课的第一道防线比应用层判断更可靠ON DELETE CASCADE确保删除学生或课程时相关选课记录自动清理避免孤儿数据course.credit字段虽小却是计算学生总学分、筛选“满12学分才能毕业”的业务基石course.status ENUM比TINYINT更语义清晰且 MySQL 会强制校验值范围防止插入非法状态。3.2 初始化数据用 INSERT … SELECT 替代手敲保证关联一致性很多同学手动 INSERT 几条测试数据结果selection表里student_id999但student表最大 id 才 5——外键约束直接报错。正确做法是用子查询确保 ID 存在-- 先插入教师获取自增 id INSERT INTO teacher (teacher_id, name, email) VALUES (T001, 张教授, zhanguniv.edu), (T002, 李讲师, liuniv.edu); -- 插入课程时用子查询获取 teacher.id INSERT INTO course (code, name, credit, teacher_id, max_capacity) VALUES (CS101, Python程序设计, 3, (SELECT id FROM teacher WHERE teacher_idT001), 80), (MA202, 高等数学, 5, (SELECT id FROM teacher WHERE teacher_idT002), 120); -- 插入学生 INSERT INTO student (student_id, name, email) VALUES (20230001, 王小明, wanguniv.edu), (20230002, 李小红, liuniv.edu); -- 插入选课记录同样用子查询 INSERT INTO selection (student_id, course_id) VALUES ((SELECT id FROM student WHERE student_id20230001), (SELECT id FROM course WHERE codeCS101)), ((SELECT id FROM student WHERE student_id20230002), (SELECT id FROM course WHERE codeMA202));提示执行前先SET FOREIGN_KEY_CHECKS 0;关闭外键检查仅限初始化执行完再SET FOREIGN_KEY_CHECKS 1;开启。避免因插入顺序导致失败。4. Python 后端逻辑打通从连接数据库到完成一次选课4.1 数据库连接封装为什么不用全局 connectionconnection pool 怎么配直接在每个视图函数里写conn pymysql.connect(...)是灾难——连接不关闭会耗尽 MySQL 连接数默认151且无法复用连接。必须用连接池 上下文管理# db.py import pymysql from pymysql.connections import Connection from contextlib import contextmanager from dotenv import load_dotenv import os load_dotenv() # 从 .env 加载 DB 配置 def get_db_connection() - Connection: return pymysql.connect( hostos.getenv(DB_HOST, localhost), portint(os.getenv(DB_PORT, 3306)), useros.getenv(DB_USER, student_app), passwordos.getenv(DB_PASSWORD, YourStrongPass123!), databaseos.getenv(DB_NAME, student_db), charsetutf8mb4, cursorclasspymysql.cursors.DictCursor, # 返回字典而非元组字段名可读 autocommitFalse # 手动控制事务关键操作必须显式 commit/rollback ) contextmanager def get_db(): conn None try: conn get_db_connection() yield conn except Exception as e: if conn: conn.rollback() # 出错回滚 raise e finally: if conn: conn.close() # 必须关闭释放连接参数说明cursorclasspymysql.cursors.DictCursor让cursor.fetchall()返回[{id: 1, name: 张教授}, ...]而非((1, 张教授),)省去字段映射autocommitFalse选课、退课、录分等操作必须原子性用conn.commit()显式提交charsetutf8mb4对应 MySQL 的utf8mb4_unicode_ci支持 emoji 和生僻汉字。4.2 实现“选课”核心逻辑事务 唯一约束 余量校验三层防护一个健壮的选课接口必须同时满足① 学生未选过此课靠selection表复合主键② 课程未满员course.max_capacity COUNT(selection)③ 整个操作要么全成功要么全回滚事务。# app.py from flask import Flask, request, jsonify, render_template from db import get_db import pymysql app Flask(__name__) app.route(/api/select, methods[POST]) def select_course(): data request.get_json() student_id data.get(student_id) course_id data.get(course_id) if not all([student_id, course_id]): return jsonify({error: student_id and course_id required}), 400 try: with get_db() as conn: with conn.cursor() as cursor: # 1. 检查课程是否开放 cursor.execute(SELECT status, max_capacity FROM course WHERE id %s, (course_id,)) course cursor.fetchone() if not course or course[status] ! open: return jsonify({error: Course not available}), 400 # 2. 检查当前选课人数 cursor.execute(SELECT COUNT(*) as cnt FROM selection WHERE course_id %s, (course_id,)) current_count cursor.fetchone()[cnt] if current_count course[max_capacity]: return jsonify({error: Course is full}), 400 # 3. 尝试插入选课记录唯一约束会拦截重复 cursor.execute( INSERT INTO selection (student_id, course_id) VALUES (%s, %s), (student_id, course_id) ) conn.commit() # ✅ 显式提交事务 return jsonify({success: True, message: Selected successfully}) except pymysql.IntegrityError as e: # 1062 是唯一键冲突错误码即重复选课 if e.args[0] 1062: return jsonify({error: Already selected this course}), 400 raise e except Exception as e: return jsonify({error: Server error}), 500关键点解析pymysql.IntegrityError捕获的是 MySQL 层面的约束失败如主键冲突比 Python 层判断更准、更早cursor.execute(..., (param,))中的(param,)是单元素元组必须加逗号否则会被当作字符串解包conn.commit()在try块内确保只有全部检查通过才提交except中raise e保留原始堆栈方便调试。5. 避坑指南那些让90%同学卡住、重启三次仍失败的血泪问题5.1 现象Flask 启动后访问 http://127.0.0.1:5000 显示 “This site can’t be reached”原因Flask 默认只监听127.0.0.1localhost而某些网络环境如公司防火墙、WSL2下127.0.0.1不可达或端口被占用如 Skype 占用 5000。解决启动时显式指定 host 和 port并检查端口占用# 启动命令允许局域网访问 flask run --host0.0.0.0 --port5001 # 检查端口占用Windows netstat -ano | findstr :5001 # macOS/Linux lsof -i :5001 # 杀掉占用进程macOS/Linux kill -9 $(lsof -t -i :5001)5.2 现象pymysql.err.OperationalError: (1045, Access denied for user ...)原因MySQL 用户密码错误或用户权限未刷新或host不匹配如创建用户时用student_app%但 Python 连接用localhost。解决登录 MySQL执行SELECT user, host FROM mysql.user WHERE userstudent_app;确认 host若 host 是%Python 连接字符串中host必须为127.0.0.1而非localhost因为 MySQL 对localhost有特殊 socket 处理执行FLUSH PRIVILEGES;刷新权限。5.3 现象中文存入数据库后变成?????或页面显示方框原因Python 文件编码、MySQL 连接字符集、数据库/表字符集三者不统一。解决Python 源码文件顶部加# -*- coding: utf-8 -*-MySQL 连接时指定charsetutf8mb4见 4.1 节数据库和表创建时用CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci见 3.1 节检查 MySQL 配置文件my.cnf中[client]和[mysqld]段落是否含default-character-set utf8mb4。5.4 现象selection表插入成功但student_id或course_id显示为 0原因外键字段类型不匹配。例如student.id是INT但selection.student_id是VARCHAR(10)MySQL 会静默转为 0。解决用DESCRIBE selection;查看字段类型确保与被引用表主键类型完全一致包括符号SIGNED/UNSIGNED。5.5 现象flask run报错Working outside of application context.原因在app Flask(__name__)创建前就调用了current_app或g等上下文变量常见于db.py中提前执行get_db_connection()。解决所有数据库操作必须包裹在with app.app_context():或with get_db():中get_db_connection()函数本身不能触发任何 Flask 上下文操作。6. 答辩级验证技巧用三条 SQL 一个 curl当场证明系统逻辑闭环别再靠截图应付答辩。教授最想看到的是你能用最原始的工具验证系统是否真按设计运行。以下三步5 分钟内完成比演示网页更有说服力。6.1 验证“防重复选课”用一条 INSERT 测试唯一约束生效-- 先查当前选课记录 SELECT * FROM selection WHERE student_id (SELECT id FROM student WHERE student_id20230001); -- 尝试重复插入应报错 1062 INSERT INTO selection (student_id, course_id) VALUES ((SELECT id FROM student WHERE student_id20230001), (SELECT id FROM course WHERE codeCS101)); -- ✅ 预期返回ERROR 1062 (23000): Duplicate entry 1-1 for key PRIMARY答辩话术“老师您看这里插入相同 student_id 和 course_id 组合时MySQL 直接返回唯一键冲突错误说明数据库层已拦截不是靠 Python 代码判断——这才是真正的数据一致性保障。”6.2 验证“课程余量控制”用子查询实时计算不依赖缓存-- 查某课程当前人数与上限 SELECT c.code, c.name, c.max_capacity, COUNT(s.student_id) AS current_enrolled, c.max_capacity - COUNT(s.student_id) AS remaining FROM course c LEFT JOIN selection s ON c.id s.course_id WHERE c.code CS101 GROUP BY c.id; -- ✅ 输出CS101, Python程序设计, 80, 1, 79答辩话术“这个查询没有用任何中间表或计数器而是每次实时 JOIN 统计即使并发选课也能保证余量准确——因为 MySQL 的 REPEATABLE READ 隔离级别下COUNT 是快照读。”6.3 验证“事务原子性”模拟并发场景用 curl 发起两次选课准备两个终端同时执行替换 YOUR_SERVER_IP 为本机 IP# 终端1 curl -X POST http://YOUR_SERVER_IP:5001/api/select \ -H Content-Type: application/json \ -d {student_id: 1, course_id: 1} # 终端2几乎同时 curl -X POST http://YOUR_SERVER_IP:5001/api/select \ -H Content-Type: application/json \ -d {student_id: 1, course_id: 1}观察结果一个返回{success: true}另一个返回{error: Already selected this course}。原理MySQL 的INSERT ... ON DUPLICATE KEY UPDATE或唯一索引冲突在高并发下由行锁保证不会出现“两人同时选上”。最后把student_system文件夹打包时务必包含app.py主程序db.py数据库封装templates/HTML 模板static/CSS/JS.env含DB_PASSWORD答辩前删掉密码改成 XXXXXREADME.md写清如何启动、测试用例、设计亮点我带的学生里凡按这路子走的答辩时教授问“如果两个学生同时点选同一门只剩1个名额的课谁会成功”都能指着selection表的复合主键说“数据库锁住这一行第二个请求被唯一约束拒绝——代码没写一行并发控制但数据层已兜底。”希望帮到你。本文还有配套的精品资源点击获取
返回列表