基于Flask+MySQL的学生成绩管理系统:数据库设计与实现 简介这份资源是面向高校数据库课程设计场景的MySQL学生成绩管理系统完整课程设计报告适合正在完成数据库课设、需要参考系统设计与实现思路的计算机相关专业学生。报告以Python Flask为后端框架、MySQL为数据库、HTMLCSSJS为前端技术采用前后端分离方式开发围绕管理员、教师、学生三类角色展开权限划分与功能设计。内容涵盖系统总体设计、需求分析、概要设计、系统实现及测试与总结五大章节具体包括功能结构设计、数据库概念结构、逻辑结构与物理设计、界面设计、主要功能程序代码以及测试用例与总结反思可帮助读者理清课设报告的写作框架与数据库设计流程。资源包共1个doc文档约3.95MB已有5074人学习下载适合作为数据库课程设计的参考范本与实现思路借鉴。1. 从一份“能跑就行”的课设说起MySQL 学生成绩管理系统要解决什么每到期末教务口最头疼的不是考试本身而是考完那一周里成堆的成绩表任课教师手里是 Excel辅导员手里是另一份 Excel专业负责人还要按专业、按课程再汇总一遍。人工核对费时费力改一个数字要牵连三张表出错几乎是必然。这份基于 Python Flask MySQL 的学生成绩管理系统瞄准的就是这个场景——把成绩的录入、修改、删除、查询、统计从手工表格搬到数据库里让管理员、教师、学生三类角色各看各的数据、各干各的活。它适合正在做数据库课设、Web 课程设计的学生直接照着搭结构也适合需要一份轻量教务原型的从业者参考。整套系统单机可跑前后端分离用 Postman 就能把接口逐个验通不依赖任何服务器部署这一点对课设交付特别友好。2. 需求与角色拆解三类用户、七张表撑起的最小闭环2.1 业务需求先落到角色权限上做课设最容易翻车的地方是一上来就画页面、写路由结果做到一半发现“这个字段该谁改”说不清。我一般会先把三类角色的动作列成一张权限矩阵再决定接口怎么切。这个系统的业务边界其实很清晰角色登录凭证可做的操作明确不能做管理员用户名 admin密码固定学生、教师、课程、专业、专业选课的增删改查不能改自己的密码教师教师编号初始密码同编号查看个人信息、管理所授课程、录入修改成绩、查看分数段统计、改密码不能碰别的教师的课学生学号初始密码同编号查看个人信息、查看已选课程与成绩、查看任课教师、改密码不能自己选课、不能改成绩这张表定下来后面的接口命名、页面跳转、SQL 的 where 条件基本就跟着走了。比如“教师只能看自己的课”落到查询上就是course表按teacher_id过滤而不是把所有课程查出来再在前端藏按钮——前端藏按钮是防不住直接调接口的这是血泪经验。2.2 数据需求决定表结构粒度需求分析阶段要产出的不是一段文字而是能直接翻译成建表语句的字段清单。这个系统涉及的核心数据源是学生、教师、管理员三类主体围绕它们派生出课程、专业、选课、成绩等表单。关键判断在于成绩到底挂在哪张表上。有的同学会把成绩直接塞进学生表一门课一个字段课程一多表就废了。正确做法是拆出student_course这张关联表用学号课程编号做联合主键成绩作为它的一个属性。同理专业和课程之间的“哪些专业要修哪些课”用major_course表达这样新增一个专业选课记录时可以通过触发器自动给该专业所有学生生成待录入的成绩行——这个设计在后面第 4 章会展开它是整套系统里最能体现“数据库课设”含金量的一个点。2.3 开发环境与选型理由原文给的栈是 Windows 10 PyCharm Community 2022.1.1 MySQL 8.0后端 Flask前端 HTMLCSSJS前后端分离Postman 做接口测试。这套组合对课设来说性价比很高Flask 轻路由和视图函数一一对应答辩时能一行行讲清楚请求怎么进来、SQL 怎么出去MySQL 8.0 支持触发器、外键、tinyint unsigned这些特性正好把数据库课上的知识点用上前后端分离则让“接口测试”成为一个可展示的独立环节而不是把逻辑全糊在模板里。需要提醒的是原文明确“未做服务器部署单机测试运行”所以方案里不要承诺并发、高可用这类指标验收口径就定在“本机启动、接口通、页面能点通全流程”。3. 数据库设计从 E-R 到七张表的物理落地3.1 概念结构三个实体、两条主线概念设计用自顶向下的方法先抓核心活动。这个系统的核心活动是“授课—选课—记成绩”围绕它展开的实体有学生、教师、管理员以及课程、专业、专业选课、学生选课含成绩。E-R 图里要表达的联系主要有两条主线一条是教师与课程的“讲授”联系一个教师教多门课一门课归一个教师另一条是学生与课程的“选修”联系多对多联系上带成绩属性。专业在这里扮演的是“批量选课”的抓手——专业与课程多对多学生归属某个专业于是专业选课一旦确定该专业学生的选课记录就能批量生成。把这三层关系画清楚逻辑结构设计就是机械翻译不会漏表。3.2 逻辑结构六个关系模式把 E-R 图转成关系模式主键外键标清楚原文给出的六个模式可以直接用学生学号姓名性别出生年份籍贯入学年份专业编号密码教师教师编号姓名性别出生年份密码课程课程编号课程名开课学年开课学期教师编号学分专业专业编号专业名学生选课学号课程编号成绩专业选课专业编号课程编号管理员用户名密码这里有个细节值得在报告里点一句student_course的成绩允许为空因为选课记录先于成绩产生教师还没录入时它就是 NULL。这个“可空”不是设计缺陷而是业务流程的真实反映答辩时被问到能答上来就是加分项。3.3 物理设计建表语句与外键策略物理设计落到具体字段类型。学号char(10)、教师编号char(5)、专业编号char(3)、课程编号char(5)都用定长字符而不是整型原因是编号可能带前导零用整型会把001变成1对不上教务的原始数据。密码字段统一varchar(128)给加密后的摘要留足空间。成绩用tinyint unsigned天然约束在 0~255再靠业务层卡 0~100。下面是核心建表代码注意外键的on delete cascade和联合主键的写法-- 创建数据库并进入 create database grade_manage_system; use grade_manage_system; -- 专业表先建因为学生表要引用它 create table major( major_id char(3) primary key, major_name varchar(20) not null unique ); -- 学生表major_id 外键专业删除时级联删除学生 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; -- 教师表 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 ); -- 课程表teacher_id 外键教师删除时级联删课 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; -- 专业选课表联合主键两个外键都级联 create table major_course( major_id char(3), course_id char(5) ); alter table major_course add 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; -- 学生选课成绩表联合主键成绩可空 create table student_course( student_id char(10), course_id char(5), grade tinyint unsigned ); alter table student_course add primary key (student_id, course_id); -- 管理员表 create table admin( admin_name char(15) primary key, password varchar(128) not null );这段代码里有几个参数值得解释。on delete cascade的选择是有取舍的删专业连带删学生、删教师连带删课在课设演示里能让“删除”操作看起来干净利落但真实教务系统里通常会用软删除或限制删除因为学生数据不能随便丢。报告里可以把这个差异写进“改进方向”体现你懂业务约束。联合主键(student_id, course_id)保证一个学生一门课只有一条成绩记录天然防重复录入比在应用层写查重更可靠。major_course的两个外键都级联是为了删课程时自动清掉专业选课关系避免悬空引用。3.4 触发器专业选课自动铺学生成绩行这是整套数据库设计里最“像样”的一处。管理员给某专业添加一门课时该专业下所有学生都应该出现一条待录入的成绩记录否则教师进课程管理看不到学生名单。手工插太蠢用触发器在major_course插入后自动执行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 ;new.major_id和new.course_id是触发器里引用刚插入行的语法after insert保证主表记录已经落库再铺明细。这里有个坑如果该专业学生已经通过别的途径选过这门课联合主键会报冲突插入直接失败。稳妥的做法是在select后面加where not exists子查询做去重或者用insert ignore。课设演示数据量小冲突概率低但报告里写上这句说明你想过边界情况。另外触发器在 MySQL 8.0 里创建需要delimiter切换很多同学在 Navicat 里直接跑会报语法错就是漏了这一步。4. 后端实现Flask 路由、登录态与成绩统计4.1 登录与角色分流登录页是三类角色共用的靠一个下拉框选身份提交后后端按身份去不同的表校验。核心逻辑是管理员查admin表比对固定密码教师查teacher表、学生查student表初始密码等于编号。校验通过后把角色和编号写进 session后续每个接口先读 session 判断权限再决定查哪些数据。下面是一个精简的登录视图重点看参数校验和 session 写入from flask import Flask, request, session, jsonify import pymysql app Flask(__name__) app.secret_key grade_manage_demo_key # 课设用固定值生产环境应随机生成 def get_conn(): return pymysql.connect(host127.0.0.1, userroot, passwordyour_password, databasegrade_manage_system, charsetutf8mb4) app.route(/api/login, methods[POST]) def login(): data request.get_json() role data.get(role) # admin / teacher / student username data.get(username) password data.get(password) conn get_conn() cur conn.cursor(pymysql.cursors.DictCursor) if role admin: cur.execute(select * from admin where admin_name%s and password%s, (username, password)) elif role teacher: cur.execute(select * from teacher where teacher_id%s and password%s, (username, password)) else: cur.execute(select * from student where student_id%s and password%s, (username, password)) user cur.fetchone() cur.close(); conn.close() if not user: return jsonify({code: 401, msg: 账号或密码错误}) session[role] role session[uid] username return jsonify({code: 200, msg: ok, role: role})pymysql.cursors.DictCursor让查询结果以字典返回前端拿字段名直接渲染省去下标对应。%s占位符是参数化查询防 SQL 注入千万别用字符串拼接——这是数据库课设答辩必问的点。session依赖secret_key课设里写死可以但报告里要注明生产环境应改为环境变量注入。登录失败统一返回 401 和同一句提示不区分“账号不存在”和“密码错误”避免被枚举账号。4.2 教师端成绩录入与分数段统计教师登录后进课程管理看到自己教的课点进去是该课程的学生名单和成绩输入框。保存成绩走一个批量更新接口统计则用一条case when的聚合 SQL 一次算出各分数段人数比拉回 Python 再循环计数高效得多app.route(/api/teacher/statistics/course_id) def course_statistics(course_id): if session.get(role) ! teacher: return jsonify({code: 403, msg: 无权限}) conn get_conn() cur conn.cursor(pymysql.cursors.DictCursor) # 一条 SQL 出五个分数段避免多次往返数据库 cur.execute( select sum(case when grade 90 then 1 else 0 end) as seg_90, sum(case when grade 80 and grade 90 then 1 else 0 end) as seg_80, sum(case when grade 70 and grade 80 then 1 else 0 end) as seg_70, sum(case when grade 60 and grade 70 then 1 else 0 end) as seg_60, sum(case when grade 60 then 1 else 0 end) as seg_fail, count(grade) as total from student_course where course_id %s and grade is not null , (course_id,)) stat cur.fetchone() cur.close(); conn.close() return jsonify({code: 200, data: stat})count(grade)只统计非空成绩和total一起返回前端就能显示“已录入 X 人”。sum(case when ...)的写法把五段统计压成一次查询参数只有一个course_id注意这里没有校验这门课是否属于当前教师——严谨的做法是先查course表确认teacher_id等于 session 里的 uid否则教师 A 能看教师 B 的统计。这个校验在课设里常被忽略但它是权限设计完整性的体现建议补上。4.3 管理员端增删改查的通用套路管理员的五类管理学生、教师、课程、专业、专业选课本质是同一套 CRUD区别只在表名和字段。与其写五套重复代码不如抽一个通用视图用白名单限制可操作的表和字段既减少代码量又避免 SQL 注入ALLOWED_TABLES { student: [student_id, student_name, sex, birth_year, province, enter_year, major_id, password], teacher: [teacher_id, teacher_name, sex, birth_year, password], course: [course_id, course_name, year, semester, teacher_id, credit], major: [major_id, major_name], major_course: [major_id, course_id], } app.route(/api/admin/table, methods[POST]) def admin_insert(table): if session.get(role) ! admin or table not in ALLOWED_TABLES: return jsonify({code: 403, msg: 无权限或表名非法}) data request.get_json() cols [c for c in ALLOWED_TABLES[table] if c in data] placeholders ,.join([%s] * len(cols)) sql finsert into {table}({,.join(cols)}) values({placeholders}) conn get_conn() cur conn.cursor() try: cur.execute(sql, [data[c] for c in cols]) conn.commit() return jsonify({code: 200, msg: 添加成功}) except Exception as e: conn.rollback() return jsonify({code: 500, msg: str(e)}) finally: cur.close(); conn.close()表名不能参数化所以用ALLOWED_TABLES白名单兜底这是动态拼表名时唯一安全的做法。字段同样按白名单过滤前端多传的字段直接丢弃。commit和rollback成对出现插入失败时事务回滚不会留下半条记录。新增学生时密码默认填学号可以在前端预填也可以在后端if not data.get(password)时补默认值两种都行报告里说明即可。5. 避坑与排查课设里最容易翻车的五个点5.1 中文乱码现象是页面显示问号原因是连接字符集没统一现象学生姓名、课程名在页面上显示成???或乱码。原因通常是三处字符集不一致——建库时没指定utf8mb4、PyMySQL 连接没带charset、HTML 页面没声明meta charsetutf-8。解决建库语句补default charsetutf8mb4连接串加charsetutf8mb4前端模板头部声明 UTF-8三处对齐后乱码消失。已经建好的库可以用alter database ... character set utf8mb4补救但表里已有数据可能仍需重导。5.2 外键报 1452现象是插入学生失败原因是专业还没建现象添加学生时接口返回Cannot add or update a child row错误码 1452。原因student.major_id是外键指向的major表里没有这个专业编号。解决管理员端添加学生的表单里专业编号应该做成下拉选择而不是手输数据源来自major表后端插入前先查一次专业是否存在不存在返回友好提示。演示顺序也要注意先建专业、再建学生否则一上来就报错答辩观感很差。5.3 触发器重复插入现象是加专业选课报主键冲突原因是学生已有该课记录现象给专业添加一门课时触发器执行报Duplicate entry。原因该专业某些学生此前已通过其他途径拥有这门课的student_course记录联合主键冲突导致整条触发器语句失败。解决把触发器里的insert ... select改成带where not exists的写法或改用insert ignore。更稳的做法是在应用层先查重再决定是否依赖触发器触发器只做兜底。5.4 登录态丢失现象是刷新页面就退出原因是 session 配置或跨域现象登录成功点一个页面就跳回登录页。原因Flask 的secret_key没设或每次重启变化导致 session 失效前后端分离时如果前端用fetch没带credentials: includecookie 不会随请求发送。解决secret_key固定课设可写死前端请求统一加credentials: include后端如需跨域则配置supports_credentialsTrue并指定具体来源不要用*。5.5 成绩越界现象是录入 120 分也能存进去原因是只靠字段类型约束现象教师录入成绩时输入 120 或 -5数据库照单全收。原因tinyint unsigned只挡负数不挡超过 100业务上限没卡。解决后端在更新成绩前做0 grade 100校验不合法直接返回 400数据库层可以加check (grade between 0 and 100)MySQL 8.0.16 起生效双保险。前端输入框加min/max只是体验优化不能当校验。6. 验收与答辩把“能跑”讲成“设计过”验收这套系统我一般按“数据链路走一遍”的方式而不是逐个页面点。具体做法是准备一份演示脚本从空库开始先由管理员建专业、建教师、建课程、做专业选课此时触发器铺出学生成绩行再建学生并确认其成绩行已存在然后切教师登录录入成绩、看统计最后切学生登录查成绩、改密码。这条链路走通说明七张表、外键、触发器、三类权限全部串起来了比单独演示“添加成功”有说服力得多。验收时重点看四个指标登录失败是否统一提示、越权接口是否返回 403、成绩统计各段人数之和是否等于已录入人数、删除专业后关联学生和选课记录是否按预期级联。可以用下面这张表做验收记录验收项操作预期结果实际角色隔离学生 session 调管理员接口返回 403触发器新增专业选课该专业学生出现待录入成绩行统计一致性教师查看分数段五段之和等于已录入人数级联删除删除某专业该专业学生及选课记录同步消失密码修改学生改密后旧密码登录旧密码失败、新密码成功答辩环节老师最爱问的三个问题基本可以预判为什么成绩单独建表而不是放学生表、触发器冲突怎么处理、密码为什么用varchar(128)。前两个在第 3、5 章已经有答案第三个答“为后续换成哈希摘要预留长度当前课设存明文但结构上不堵死升级路径”即可。演示时如果某个接口临时报错不要现场改代码直接切到 Postman 展示该接口的请求和响应说明“功能逻辑已验证页面层是渲染问题”把话题拉回你熟悉的接口层。从那以后我每次带课设答辩都强制自己先按这条数据链路空库走一遍走不通就不上台——这个习惯帮我挡掉过好几次“演示到一半发现初始数据没铺”的尴尬。希望这套结构和验收思路能帮你把这份 MySQL 学生成绩管理系统方案稳稳落地。本文还有配套的精品资源点击获取