Flask+MySQL学生成绩管理系统:数据库课设实战与二次开发指南

发布时间:2026/11/4 3:00:04
Flask+MySQL学生成绩管理系统:数据库课设实战与二次开发指南 简介这份资源是面向高校数据库课程设计场景的MySQL学生成绩管理系统完整课程设计报告适合计算机相关专业学生、任课教师及教务管理人员参考用于解决成绩录入、统计与查询效率低、人工统计易出错的问题。压缩包内共1个doc文档约3.95MB内容为华东交通大学课程设计报告按系统总体设计、需求分析、概要设计、系统实现、测试与总结五章展开涵盖项目背景与意义、管理员/教师/学生三类角色的权限划分、数据库概念结构与逻辑结构设计、界面设计与主要功能程序代码以及功能测试与总结反思。系统采用PythonFlaskMySQL实现前端基于HTML、CSS、JavaScript并借鉴模板改良前后端分离开发借助Postman完成接口测试可在单机环境运行。已有5074人学习下载读者可据此了解课设报告的写作框架、需求分析与数据库设计思路并参考成绩增删改查、选课与权限管理等模块的实现方式。1. 从一份数据库课设说起这个 Flask MySQL 成绩管理系统能跑出什么期末周最让人头大的不是考试本身而是教务那边催着要的成绩统计表。一个专业几百号人、七八门课用 Excel 手工汇总改一个分数就得重算一遍错一行还查不出来。这份 MySQL 学生成绩管理系统就是冲着这个场景做的Python Flask 做后端MySQL 8.0 存数据前端用 HTML CSS JS 拼页面前后端分离单机就能跑起来。它把管理员、教师、学生三类角色的权限拆得清清楚楚选课、录成绩、分数段统计、改密码这些教务高频操作都有对应页面。对正在做数据库课设、想找一个结构完整又能二次开发的教育管理类项目的同学来说这套代码的参考价值在于它的表设计和触发器写法而不是界面有多花哨。2. 技术栈与目录结构Flask 后端怎么和 MySQL 对上话2.1 技术选型拆解为什么是 Flask 而不是 Django这个项目选 Flask 是有现实考虑的。课设场景下Django 自带的 ORM、Admin、认证体系虽然省事但会把数据库层包得太厚反而不利于展示「表结构怎么设计、SQL 怎么写」这个课设核心。Flask 轻路由和视图函数一一对应学生看代码时能直接看到每个接口背后执行了哪条 SQL这对数据库课程设计来说是加分项。后端大致是这么组织的app.py作为入口创建 Flask 应用并注册蓝图config.py里放数据库连接参数models/或直接在视图里用pymysql/mysql-connector-python执行 SQLroutes/下按角色拆成admin.py、teacher.py、student.py、auth.py几个蓝图。前端是独立的静态页面放在static/和templates/下通过fetch或axios调后端 JSON 接口。这种前后端分离的写法在课设里不算常见但好处是接口能用 Postman 单独测排查问题时能快速定位是前端传参错了还是 SQL 写错了。数据库连接这块常见做法是用连接池而不是每次请求都新建连接。下面是一个典型的配置和获取连接的写法# config.py DB_CONFIG { host: 127.0.0.1, port: 3306, user: root, password: your_password, database: grade_manage_system, charset: utf8mb4, cursorclass: pymysql.cursors.DictCursor # 返回字典方便转 JSON } # db.py import pymysql from dbutils.pooled_db import PooledDB from config import DB_CONFIG pool PooledDB( creatorpymysql, maxconnections10, # 最大连接数课设单机够用 mincached2, # 启动时预建 2 个连接 blockingTrue, # 连接耗尽时阻塞等待而不是报错 **DB_CONFIG ) def get_conn(): return pool.connection()DictCursor这个参数值得单独说一句默认游标返回的是元组前端拿到[[2021001, 张三, 88]]还得自己映射字段名换成字典游标后直接是[{student_id: 2021001, student_name: 张三, grade: 88}]jsonify一下就能返回省掉一层转换。maxconnections设 10 是单机课设的保守值真要多用户并发再往上调但 MySQL 默认max_connections是 151别设得比它还大。2.2 目录结构七个表撑起三类角色的全部操作项目的目录大致长这样我按实际开发习惯补全了文件职责说明grade_manage_system/ ├── app.py # Flask 入口注册蓝图、启动服务 ├── config.py # 数据库连接配置 ├── db.py # 连接池封装 ├── routes/ │ ├── auth.py # 登录、登出、改密码 │ ├── admin.py # 学生/教师/课程/专业/选课的增删改查 │ ├── teacher.py # 课程管理、成绩录入、分数段统计 │ └── student.py # 个人信息、成绩查询 ├── static/ │ ├── css/ # 页面样式 │ └── js/ # fetch 调用接口、表单校验 ├── templates/ │ ├── login.html │ ├── admin/ # 管理员各功能页 │ ├── teacher/ │ └── student/ └── sql/ └── init.sql # 建库建表 触发器 初始数据数据库一共七张表student、teacher、course、major、admin、student_course、major_course。这个表数量在课设里算克制但关系设计得比较完整——student.major_id外键指向majorcourse.teacher_id外键指向teacherstudent_course是学生和课程的多对多中间表并带grade字段major_course是专业和课程的多对多中间表。三类角色的权限差异本质上就是「能操作哪几张表」管理员全表增删改查教师只能改student_course.grade和查自己teacher_id下的课程学生只能读自己的student行和student_course行。提示major_course和student_course都是联合主键建表时别忘了primary key (major_id, course_id)和primary key (student_id, course_id)否则同一学生同一课程能插进多条成绩记录统计时直接翻倍。3. 数据库设计落地建表、外键与那个自动选课的触发器3.1 七张表的建表顺序与级联删除建表有严格的顺序依赖被引用的表必须先建。major和teacher要先于student和coursecourse又要先于major_course和student_course。项目里的建表脚本按这个顺序走但外键是后来用alter table补的这种写法在课设里很常见——先建表结构再统一加约束方便调试时单独开关。-- 1. 专业表被 student 和 major_course 引用最先建 create table major( major_id char(3) primary key, major_name varchar(20) not null unique ); -- 2. 学生表 create table student( student_id char(10) primary key, student_name varchar(20) not null, sex char(2), birth_year char(4), province varchar(20), enter_year char(4) not null, major_id char(3) not null, password varchar(128) not null ); alter table student add foreign key (major_id) references major(major_id) on delete cascade; -- 3. 教师表 create table teacher( teacher_id char(5) primary key, teacher_name varchar(20) not null, sex char(2), birth_year char(4), password varchar(128) not null ); -- 4. 课程表 create table course( course_id char(5) primary key, course_name varchar(20) not null, year char(4) not null, semester char(2) not null, teacher_id char(5) not null, credit tinyint unsigned not null ); alter table course add foreign key (teacher_id) references teacher(teacher_id) on delete cascade; -- 5. 专业选课表联合主键 create table major_course( major_id char(3), course_id char(5), primary key (major_id, course_id) ); alter table major_course add foreign key (major_id) references major(major_id) on delete cascade; alter table major_course add foreign key (course_id) references course(course_id) on delete cascade; -- 6. 学生选课成绩表联合主键 create table student_course( student_id char(10), course_id char(5), grade tinyint unsigned, primary key (student_id, course_id) ); alter table student_course add foreign key (student_id) references student(student_id) on delete cascade; alter table student_course add foreign key (course_id) references course(course_id) on delete cascade; -- 7. 管理员表 create table admin( admin_name char(15) primary key, password varchar(128) not null );on delete cascade是这套表设计里最值得注意的一处。删掉一个专业该专业下所有学生和major_course记录会跟着删删掉一门课所有选课成绩也会跟着删。这在课设演示时很方便但真实教务系统里通常不敢这么干——成绩是审计数据删课程不该连带删成绩。二次开发时如果要做「软删除」把cascade换成restrict然后在应用层加is_deleted标记字段是更稳妥的做法。grade字段用tinyint unsigned存 0~100比int省空间但要注意unsigned的范围是 0~255插入 101 不会报错而是直接存进去。应用层必须做校验不能指望数据库拦。3.2 触发器实现自动选课一行 insert 带出全班记录项目里最有意思的一段 SQL 是major_course上的after insert触发器。管理员给某个专业添加一门课时触发器自动把这个专业下所有学生都插入student_course成绩先置null。这样教师登录后就能直接看到待录成绩的学生名单不用再手动一个个加。delimiter // create trigger trigger_insert_1 after insert on major_course for each row begin insert into student_course(student_id, course_id, grade) select s.student_id, new.course_id, null from student s where s.major_id new.major_id; end// delimiter ;逐段拆一下after insert表示在major_course插入成功后触发for each row是行级触发器new.course_id和new.major_id引用刚插入的那行数据。insert ... select把student表里major_id匹配的所有学生一次性写进student_coursegrade给null表示「已选课未出分」。这里有个坑如果某个学生之前已经通过其他途径选过这门课student_course的联合主键会冲突整个触发器执行失败major_course的插入也会回滚。稳妥的写法是加insert ignore或者on duplicate key update grade grade让重复记录静默跳过。另外delimiter //不能省否则 MySQL 客户端会把触发器体内的第一个分号当成语句结束。注意触发器逻辑藏在数据库里应用层代码完全看不到。新人接手时如果不知道有这个触发器排查「为什么加了一门课学生成绩表突然多出几百行」会非常痛苦。建议在init.sql里写清楚注释或者在 README 里单独列一节说明。4. 后端接口实现登录鉴权、成绩录入与分数段统计4.1 登录与角色分流一个接口怎么区分三类用户登录接口的设计有个取舍是三个角色各一个登录接口还是一个接口带role参数。项目用的是后者前端登录页有个下拉框选身份提交时带上role后端根据角色去不同的表查密码。# routes/auth.py from flask import Blueprint, request, jsonify, session import hashlib from db import get_conn auth_bp Blueprint(auth, __name__) TABLE_MAP { admin: (admin, admin_name), teacher: (teacher, teacher_id), student: (student, student_id), } auth_bp.route(/api/login, methods[POST]) def login(): data request.get_json() role data.get(role) username data.get(username, ).strip() password data.get(password, ) if role not in TABLE_MAP: return jsonify({code: 400, msg: 非法角色}), 400 table, id_col TABLE_MAP[role] conn get_conn() try: with conn.cursor() as cur: sql fselect password from {table} where {id_col} %s cur.execute(sql, (username,)) row cur.fetchone() finally: conn.close() if not row: return jsonify({code: 401, msg: 账号不存在}), 401 # 密码用 sha256 存储比对时同样哈希 if hashlib.sha256(password.encode()).hexdigest() ! row[password]: return jsonify({code: 401, msg: 密码错误}), 401 session[role] role session[uid] username return jsonify({code: 0, msg: ok, role: role})TABLE_MAP把角色映射到表名和主键列名避免写三份几乎一样的查询。f-string拼表名这里看着像 SQL 注入但table和id_col都来自白名单字典用户输入只进%s占位符是安全的。密码比对用sha256课设里够用真实项目该上bcrypt加盐。session存角色和用户 ID后续接口用装饰器校验权限比如教师接口先检查session.get(role) teacher。初始密码规则是学生和教师的密码默认等于自己的编号管理员固定admin/asdfg13579。这个设计在演示时方便但上线前必须强制改密否则任何人知道学号就能登录。4.2 教师录成绩与分数段统计一条 update 加一条 group by教师的核心操作是给student_course里的grade赋值以及按分数段统计人数。录成绩接口要校验这门课确实属于当前登录教师防止教师改别人课的成绩。# routes/teacher.py teacher_bp.route(/api/grade, methods[POST]) def update_grade(): if session.get(role) ! teacher: return jsonify({code: 403, msg: 无权限}), 403 data request.get_json() course_id data[course_id] student_id data[student_id] grade int(data[grade]) if not 0 grade 100: return jsonify({code: 400, msg: 成绩需在 0~100}), 400 conn get_conn() try: with conn.cursor() as cur: # 先确认这门课归当前教师 cur.execute( select 1 from course where course_id%s and teacher_id%s, (course_id, session[uid]) ) if not cur.fetchone(): return jsonify({code: 403, msg: 非本人课程}), 403 cur.execute( update student_course set grade%s where student_id%s and course_id%s, (grade, student_id, course_id) ) conn.commit() finally: conn.close() return jsonify({code: 0, msg: 已保存})分数段统计用一条case when聚合就能出结果不用在 Python 里循环select sum(case when grade 90 then 1 else 0 end) as excellent, sum(case when grade 80 and grade 90 then 1 else 0 end) as good, sum(case when grade 70 and grade 80 then 1 else 0 end) as medium, sum(case when grade 60 and grade 70 then 1 else 0 end) as pass, sum(case when grade 60 then 1 else 0 end) as fail, count(*) as total from student_course where course_id %s and grade is not null;grade is not null这个条件不能漏否则未录成绩的学生会被算进fail段统计结果直接失真。sum(case when ...)的写法比多次count(*) where少扫几遍表课设数据量下差别不大但习惯养成了对以后有好处。4.3 管理员增删改查外键约束下的删除顺序管理员的删除操作最容易翻车。因为外键带cascade删major会连带删student和major_course删course会连带删student_course。但student和course之间没有直接外键删学生不会自动清student_course——等等student_course.student_id是有外键指向student的所以删学生会级联删成绩。真正要注意的是删除顺序和事务一次删多个关联实体时包在事务里失败能回滚。admin_bp.route(/api/student/sid, methods[DELETE]) def delete_student(sid): if session.get(role) ! admin: return jsonify({code: 403}), 403 conn get_conn() try: with conn.cursor() as cur: cur.execute(delete from student where student_id%s, (sid,)) affected cur.rowcount conn.commit() except Exception as e: conn.rollback() return jsonify({code: 500, msg: str(e)}), 500 finally: conn.close() return jsonify({code: 0, deleted: affected})rowcount返回实际删除行数前端据此提示「删除成功」还是「记录不存在」。rollback在异常分支里必须调否则连接归还池子时可能带着未提交事务下一个请求拿到这条连接会看到脏数据。5. 避坑与排查跑这个项目最容易卡住的五个地方5.1 现象启动报Access denied for user rootlocalhost原因基本是config.py里的密码和本机 MySQL 实际密码不一致或者 MySQL 8.0 的认证插件是caching_sha2_password而驱动版本太老不认。解决分两步先用命令行mysql -u root -p确认能登进去能登说明密码对再检查pymysql版本低于 0.10 的升级一下或者把用户认证方式改成mysql_native_password。改完记得重启 Flask配置是启动时读的。5.2 现象触发器建好了但加专业选课没生成学生成绩记录先查触发器在不在show triggers from grade_manage_system;。如果不在多半是建触发器时delimiter没生效客户端把触发器体截断了。如果触发器在但没触发检查major_course插入的major_id在student表里有没有对应学生——专业下没学生insert ... select自然插 0 行。还有一种情况是联合主键冲突导致整个触发器静默失败看 MySQL 错误日志能找到Duplicate entry。5.3 现象前端调接口一直 401Postman 里却正常这是跨域和 session 的经典组合问题。前后端分离时前端页面和 Flask 不在同一个源浏览器默认不带 cookiesession就丢了。解决要么在 Flask 里配CORS(supports_credentialsTrue)且前端fetch加credentials: include要么课设阶段干脆把前端页面用 Flask 的render_template托管同源就没这问题。项目原文说「未做服务器部署单机测试」那用后者更省事。5.4 现象成绩统计里「不及格」人数比实际多九成是 SQL 里漏了grade is not null。未录成绩的记录grade是nullnull 60在 SQL 里结果是null不是true但sum(case when grade 60 then 1 else 0 end)里null走else分支算 0——等等这里要仔细case when null 60条件为null不满足when走else 0所以不会多算。真正多算的场景是用count(*)减各段之和或者用where grade 60单独查时把null排除了导致总数对不上。排查方法先select count(*) from student_course where course_id? and grade is null看有多少未录再和各段之和加总核对。5.5 现象删除专业后学生还在但登录报错student.major_id外键带cascade删专业应该连带删学生。如果学生还在说明建表时外键没加上——项目里外键是alter table补的如果执行顺序错了或者中途报错被忽略约束就不存在。用show create table student;看输出里有没有CONSTRAINT ... FOREIGN KEY没有就手动补上。补之前先清理孤儿数据否则加约束会失败。6. 二次开发改造从课设 Demo 到能用的成绩工具这套代码跑通之后最值得动手改的是三处按投入产出比排序。第一处是密码存储。现在用sha256无盐哈希学号当初始密码等于裸奔。改成bcrypt只要动auth.py两行登录比对用bcrypt.checkpw改密用bcrypt.hashpw再在student和teacher表加个pwd_changed标记首次登录强制跳改密页。这个改动小但演示时能讲出「安全设计」的加分点。第二处是触发器改成应用层事务。触发器逻辑藏在数据库里调试和迁移都麻烦。把major_course插入后的批量选课挪到admin.py里用同一个事务包住「插专业选课 批量插学生选课」代码可读性和可控性都更好。改造时注意保留insert ignore语义避免重复选课报错。第三处是加一个「成绩导出」接口。教务最终要的是 Excel用openpyxl把student_course联查student、course的结果写成.xlsx前端一个下载按钮就完事。这个功能不在原项目里但它是这类系统真实落地时被问得最多的需求加上去之后项目完整度明显不一样。改造点涉及文件工作量演示价值bcrypt 密码auth.py、建表脚本半天高触发器转应用层admin.py、init.sql一天中成绩导出 Excel新增 export.py半天高验证改造是否成功我一般走一遍固定链路管理员建专业 → 加专业选课看学生成绩表是否自动生成→ 教师登录录一个 95 分和一个 55 分 → 看统计里优秀和不及格各 1 人 → 学生登录查成绩 → 导出 Excel 核对行数。这条链路覆盖了三类角色和触发器、聚合、导出三个关键逻辑任何一环断了都能立刻定位。血泪经验是每次改完 SQL 相关代码先把init.sql在干净库上重跑一遍再测别在旧数据上验证。我有次在已有数据的库上测级联删除结果把演示数据删了一半临时补数据补到半夜。从那以后我每次动表结构或触发器都强制在全新数据库上从init.sql走一遍完整链路确认无误再碰开发库。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询