简介面向需要完成数据库课程设计的高校学生这份MySQL学生成绩管理系统课设报告提供了一套完整的系统设计与实现方案。该系统基于Python Flask与MySQL构建覆盖管理员、教师、学生三类用户角色实现选课、成绩录入、修改、删除、查询及统计等核心功能可有效解决人工统计效率低、易出错的问题。资源为1个doc类型文档压缩包整体3.95MB内容依照课程设计报告标准结构展开依次包含系统总体设计、需求分析、概要设计、数据库概念结构与逻辑结构设计、系统界面与功能代码实现、测试与总结等章节。读者可从中学习FlaskMySQL项目如何做需求梳理、数据表设计、前后端分离接口测试并可参考其中的功能模块划分和实现代码直接作为同类课设的撰写蓝本。目前已有5073人学习适合需要快速搭建学生成绩管理系统并完成课设文档的开发者参考。1. 三个角色一套系统这就是你想要的数据库课设完整源码期末周最折腾人的不是考试是把几十个班的成绩汇总成表。这套基于 Python Flask MySQL 的学生成绩管理系统就是冲着这个场景来的管理员管理学生、教师、课程、专业教师录入和统计所授课程的成绩学生查成绩、选课信息、改密码。三种角色三种权限前后端分离用 Postman 就能把接口全部测通。它没做服务器部署单机就能跑特别适合课程设计、毕业设计做数据库课设参考或者想快速了解 Flask 怎么组织一个完整 Web 项目的开发者。系统在 Windows 10 PyCharm MySQL 8.0 环境下开发前端基于 HTML CSS JS 页面模板改造后端接口按模块拆开。下面我从数据库设计、后端接口、前端联调、单机运行一路讲下去把核心代码和踩坑点都拆开。2. 数据库设计拆开看6 张表加一个触发器整个系统的数据模型围绕三个核心实体展开学生、教师、管理员再加上课程、专业、选课关系。数据库设计做得很规矩从 E-R 图到关系模式再到物理建表每一步都能对上。2.1 实体关系与逻辑结构先看关系模式这套模式就是你建表的依据学生学号、姓名、性别、出生年份、籍贯、入学年份、专业编号、密码 教师教师编号、姓名、性别、出生年份、密码 课程课程编号、课程名、开课学年、开课学期、学分、教师编号 专业专业编号、专业名 学生选课学号、课程编号、成绩 专业选课专业编号、课程编号 管理员用户名、密码学生和教师通过“授课”联系一个教师教多门课一门课被多个专业选修学生通过 student_course 表与课程关联成绩就挂在关联记录上。管理员是独立的账户体系只负责维护基础数据。2.2 核心表结构说明数据库名grade_manage_system一共 7 张表其中 6 张是业务表。下面把最容易搞混的字段类型列出来表名关键字段类型与约束说明studentstudent_idchar(10) 主键学生编号即登录账号studentpasswordvarchar(128) not null存的是加密后的密码teacherteacher_idchar(5) 主键教师编号即登录账号coursecourse_idchar(5) 主键课程编号coursecredittinyint unsigned 0~10学分范围约束student_coursestudent_id course_id联合主键学生选课成绩表student_coursegradetinyint unsigned 0~100成绩可空教师后录入major_coursemajor_id course_id联合主键专业选课关系一个容易忽视的细节是char和varchar的选择编号类字段都用char定长因为学号、工号长度固定查询走索引效率更好姓名、籍贯这类长度不定的用varchar。课时设计里有一个major_course表单独存“专业能选哪些课”而不是直接让学生选课这样一来管理员只需配置专业与课程的映射选课关系就能批量生成。2.3 建表脚本与触发器自动派发选课记录建库建表脚本在项目文档里写得很完整直接按顺序执行即可。不过其中最有意思的是那个触发器它在向major_course插入一条专业选课关系后自动为该专业所有学生生成对应的选课记录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 ); 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 ); 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); 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; create table admin( admin_name char(15) primary key, password varchar(128) not 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 student_id, new.course_id, null from student where major_id new.major_id; end// delimiter ;这个触发器的逻辑很聪明当管理员添加一条“某专业可选某课程”的映射时MySQL 自动把这个专业下所有学生对应的student_course记录插进去grade默认是NULL等教师后续录入成绩。好处是学生不需要手动选课坏处是如果管理员误删major_course记录外键级联会把所有关联的成绩也一起删掉操作时要注意。执行这段脚本时还有两个细节。一是delimiter的问题如果你在 Navicat 或命令行里直接粘贴触发器语句不加delimiter会报语法错误——MySQL 默认以分号作为语句结束符而触发器内部有分号必须先把结束符临时改成//。二是外键的删除策略这里的on delete cascade表示删掉专业表记录该专业所有学生连带删除数据关联比较“重”生产环境建议改成on delete set null或加软删除标记课程设计用 cascade 反而省事。3. Flask 后端核心代码一个登录请求从进入到返回的完整链路这个项目的后端采用 Flask前后端分离接口用 Postman 测试。从源码结构可以看出它的路由是按用户角色拆的登录鉴权走 Flask session 机制。下面把最关键的几个接口拆开讲。3.1 项目目录结构与路由拆分典型的 Flask 单体项目布局入口文件加蓝图或直接集中管理路由。常见做法是把每个角色的接口拆成独立的 Python 模块grade_manage_system/ ├── app.py # Flask 应用入口 ├── models/ # 数据库连接与查询封装 │ ├── __init__.py # pymysql 连接池初始化 │ └── db.py # 查询通用方法 ├── routes/ │ ├── __init__.py │ ├── auth.py # 登录、登出、修改密码 │ ├── student.py # 学生端接口 │ ├── teacher.py # 教师端接口 │ └── admin.py # 管理员端接口 ├── static/ # css / js / 图片 └── templates/ # html 模板前端页面模板放在templates目录静态资源在static接口返回 JSON 格式数据前端用 fetch 或 ajax 调用。3.2 登录接口Session 鉴权怎么设计登录是三个角色共用的入口前端把角色、账号、密码以 JSON 格式 POST 到/api/login后端根据角色查不同表校验通过后把用户信息写进 session。from flask import Blueprint, request, session, jsonify from models.db import query_one import hashlib auth_bp Blueprint(auth, __name__) def encrypt_password(raw: str) - str: 对密码做 sha256 哈希返回十六进制字符串 sha hashlib.sha256() sha.update(raw.encode(utf-8)) return sha.hexdigest() auth_bp.route(/api/login, methods[POST]) def login(): data request.get_json() role data.get(role) # student | teacher | admin username data.get(username).strip() password data.get(password) table_map { student: student, teacher: teacher, admin: admin } id_field_map { student: student_id, teacher: teacher_id, admin: admin_name } table table_map[role] id_field id_field_map[role] # 统一查用户表密码字段存的是哈希值 sql fSELECT * FROM {table} WHERE {id_field} %s user query_one(sql, (username,)) if not user: return jsonify({code: 1, msg: 账号不存在}) if user[password] ! encrypt_password(password): return jsonify({code: 1, msg: 密码错误}) # 登录成功写入 session session[user_id] user[id_field] session[role] role session[user_name] user.get(student_name) or user.get(teacher_name) or username return jsonify({code: 0, msg: 登录成功, data: { role: role, user_id: user[id_field], user_name: session[user_name] }})这段代码有几点值得学习。密码不是明文存储而是用 sha256 哈希后存入varchar(128)字段这也是建表时密码字段长度设成 128 的原因——哈希后的十六进制串长度固定。登录时先按角色确定去哪个表查、用哪个字段做匹配再比对哈希值。比对时用了%s参数占位而不是拼接字符串避免 SQL 注入。session 里只放user_id、role、user_name三个字段后续接口判断权限时直接读 session。这里要注意 Flask 的 session 默认是客户端 cookie 存储的密钥要设置一个足够复杂的app.secret_key否则存在被解密的隐患。如果部署到公网建议改用服务端 session 方案。3.3 学生查成绩接口多表联查的完整链路学生登录后查看成绩前端请求/api/student/grade后端从 session 拿当前登录的学生学号连表查出课程名、学分、成绩等信息from flask import Blueprint, request, session, jsonify from models.db import query_all student_bp Blueprint(student, __name__) student_bp.route(/api/student/grade, methods[GET]) def get_grade(): # 鉴权确认当前登录的是学生角色 if session.get(role) ! student: return jsonify({code: 403, msg: 无权限}) student_id session.get(user_id) sql SELECT c.course_id, c.course_name, c.credit, c.year, c.semester, t.teacher_name, sc.grade FROM student_course sc JOIN course c ON sc.course_id c.course_id JOIN teacher t ON c.teacher_id t.teacher_id WHERE sc.student_id %s ORDER BY c.year DESC, c.semester result query_all(sql, (student_id,)) return jsonify({code: 0, data: result})接口通过session.get(role)做角色校验非学生身份直接返回 403。查询语句把学生选课表、课程表、教师表三表联查一次拿全课程信息、授课教师、成绩排序按学年和学期降序前端拿到 JSON 后直接渲染表格即可。学生端的“查看课程对应教师”和“修改密码”也是类似的写法修改密码需要先验证旧密码再比对两次新密码是否一致最后更新哈希值。改完不强制重新登录因为用户没变只更新了密码字段。3.4 教师录成绩接口事务与参数边界教师端最有代表性的是成绩录入接口。教师只能操作自己担任授课教师的课程这个约束要在 SQL 里卡住teacher_bp.route(/api/teacher/grade, methods[POST]) def update_grade(): if session.get(role) ! teacher: return jsonify({code: 403, msg: 无权限}) teacher_id session.get(user_id) data request.get_json() course_id data.get(course_id) student_id data.get(student_id) grade data.get(grade) # 校验成绩范围0 ~ 100 if grade is None or int(grade) 0 or int(grade) 100: return jsonify({code: 1, msg: 成绩必须在 0~100 之间}) # 先确认这门课确实是该教师教授的 check_sql SELECT course_id FROM course WHERE course_id %s AND teacher_id %s course query_one(check_sql, (course_id, teacher_id)) if not course: return jsonify({code: 1, msg: 只能操作自己教授的课程}) update_sql UPDATE student_course SET grade %s WHERE student_id %s AND course_id %s execute(update_sql, (grade, student_id, course_id)) return jsonify({code: 0, msg: 成绩更新成功})这里做了两层校验第一层是成绩值必须在 0 到 100 之间防止脏数据入库第二层是通过teacher_id关联课程表确认归属权避免教师 A 篡改教师 B 的课程成绩。参数说明course_id是课程表中 5 位字符编号student_id是学生学号grade是整数。实际项目里前端传过来的可能是字符串代码里的int(grade)转换如果失败建议再包一层 try-except 返回友好提示。管理员端接口是 CRUD 的标准套路五个实体对应五套增删改查路由逻辑集中在admin.py里。每个接口都带着session[role] admin的角色校验这是整个项目权限控制的骨架——业务层依赖 session路由层做拦截前后端分离下算是最简单实用的方案。4. 前端页面与联调模板工程四个模块工作区前端这块系统沿用了一套现成的 HTML 模板改出的页面核心是 HTML CSS JS和 Flask 后端通过 JSON 接口通信。这套页面我建议按模块理解一个登录页、三个角色工作区。4.1 页面文件体系文件路径对应模块主要功能templates/login.html登录页角色选择、账号密码提交、错误提示templates/student.html学生工作区个人信息、成绩列表、修改密码三个 Tabtemplates/teacher.html教师工作区个人信息、课程列表、选课学生详情、分数段统计templates/admin.html管理员工作区五个子模块的增删改查static/js/各页面的 fetch 请求汇总接口调用、渲染逻辑4.2 登录页接口对接方式前端通过 fetch 把表单数据转成 JSON 发给/api/login拿到返回后按角色跳转到对应工作区页面async function handleLogin() { const role document.getElementById(role).value; const username document.getElementById(username).value.trim(); const password document.getElementById(password).value; const response await fetch(/api/login, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ role, username, password }) }); const result await response.json(); if (result.code 0) { // 登录成功按角色跳转 const pageMap { student: /student.html, teacher: /teacher.html, admin: /admin.html }; window.location.href pageMap[result.data.role]; } else { // 显示错误提示 document.getElementById(errorMsg).textContent result.msg; } }参数说明role是登录身份可选值student、teacher、adminusername是学号或教师编号或 adminpassword是明文密码传输走 POST body JSON。这里前端没有对密码做任何处理是明文传给后端的单机运行无所谓真要部署上线就得加 HTTPS 和前端加密了。4.3 Postman 测试接口的方法文档里明确提到用 Postman 做接口测试这套系统的接口设计是标准的 REST 风格POST /api/login登录、GET /api/student/grade查成绩、POST /api/teacher/grade录成绩、POST /api/admin/student添加学生。测试时先要在 Postman 里设置请求头Content-Type: application/json然后填 JSON 体。有个细节Flask 的 session 依赖 cookiePostman 第一次登录后要在界面下方“Cookies”处确认 session 值已经写入。如果后续请求报 403 无权限多半是 Postman 没有正确携带 cookie而不是代码逻辑问题。在 Postman 的 Tests 标签里可以加一段自动断言pm.response.code 200方便批量回归。单机联调时Flask 默认跑在127.0.0.1:5000前端页面用相对路径请求接口所以不存在跨域问题。如果你把前端页面用 VS Code Live Server 打开再手动请求 Flask 接口就会遇到跨域报错因为浏览器端口变了——这正是为什么项目要求直接由 Flask 渲染页面前后端代码在同一端口下服务。5. 单机运行避坑记录数据库课设最容易翻车的 4 个写法这套系统我实际跑过也帮别人排查过。下面四个问题出现频率最高每一条都是真实的翻车现场。5.1 MySQL 8.0 客户端连接报错现象Flask 启动后访问接口报Access denied for user rootlocalhost或Authentication plugin caching_sha2_password cannot be loaded。原因MySQL 8.0 默认认证插件是caching_sha2_password而项目常见做法是用较早版本的mysql_native_password创建用户连接参数不一致。解决建库脚本之外执行一句 SQL 把用户认证方式改掉ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 你的密码; FLUSH PRIVILEGES;或者在 Flask 连接配置里显式指定autocommitTrue和charsetutf8mb4避免中文乱码隐患。这个报错的时间成本很高因为 Flask 启动时不会报错第一次请求数据库才暴露。5.2 触发器创建失败或反复触发现象执行建表脚本时报You have an error in your SQL syntax定位到触发器那段。原因没有改delimiterMySQL 在读到触发器内部的第一个分号时就把语句截断了。解决按我前面写的脚本先delimiter //再创建触发器结束后delimiter ;恢复。注意 Navicat 里如果选中语句直接执行它有自己处理流程命令行和脚本执行时必须手动控制。5.3 管理员误删专业学生成绩连坐消失现象管理员在页面删掉一条专业记录结果该专业所有学生的成绩记录全没了数据无法恢复。原因major_course的外键设置了on delete cascade删专业会级联删专业选课再级联删学生选课。解决简单方案是脚本里去掉major_course对major的外键级联改成on delete restrict让误删直接报错而不是静默清数据。课程设计阶段保留 cascade 拿到数据里查时注意顺序即可先删学生选课再删专业选课最后删专业。5.4 登录状态丢失接口一直 403现象Postman 登录成功但调用其他接口时看到403 无权限。原因Flask 的secret_key不稳定或每次启动随机生成导致 cookie 中的 session 签名失效也可能 Postman 没有保存 cookie。解决在app.py里固定一个随机字符串作为app.secret_key例如app.secret_key grade-system-demo-key。代码里不要每次启动os.urandom(24)否则开发者模式下关闭重开 session 全部过期排查时要先看浏览器 Application 一栏的 Cookie 值是否存在。6. 拿到源码后怎么改给教师端加一个“导出成绩 Excel”的思路先说一个最简单的入口级改造项目跑通后把教师端“查看统计”那个分数段数据导成 Excel 文件。这需求在实际课设答辩时很加分的。实现思路不复杂。后端加一个接口/api/teacher/grade/export参数是course_id内部查出课程所有选课学生和成绩用openpyxl写成一个xlsx文件返回from flask import send_file import openpyxl teacher_bp.route(/api/teacher/grade/export, methods[GET]) def export_grade(): course_id request.args.get(course_id) # 查出学生成绩数据省略查询逻辑 rows [ {student_id: 2021001001, student_name: 张三, grade: 88}, # ... ] wb openpyxl.Workbook() ws wb.active ws.title 成绩表 ws.append([学号, 姓名, 成绩]) for r in rows: ws.append([r[student_id], r[student_name], r[grade]]) file_path f./temp_{course_id}.xlsx wb.save(file_path) return send_file(file_path, as_attachmentTrue, download_namefcourse_{course_id}_grade.xlsx)前端教师页面加一个“导出”按钮fetch 这个接口时用window.location.href直接触发浏览器下载。改造时注意send_file返回后记得清理或覆盖临时文件或者用io.BytesIO直接内存中生成文件再返回避免服务器磁盘堆积垃圾文件。另一个常见的课程设计向改造点是给成绩加“班级排名”和“专业平均分”。做法是在get_grade查询里用一个窗口函数AVG(grade) OVER (PARTITION BY c.course_id) AS avg_gradeMySQL 8.0 原生支持窗口函数不需要额外装插件对学生端展示“你高于平均分多少”这个功能来说足够惊艳。这套系统整体的扩展点其实都藏在数据库设计里——因为三张基础表student、teacher、course都留了外键关系加一个“选课申请”或“加分项”字段只需要在对应表增加列前端加一个输入框。我自己的习惯是每次拿到这类课设项目第一步永远是先跑数据库脚本第二步用 Postman 把登录、查询、增删改的核心流程打一遍第三步才会去读页面代码。数据库表结构和接口返回格式都对上了前端渲染基本不会出大问题。这个流程我现在跑任何 Flask 项目都强制走一遍能省掉后面大半的排查时间。希望这一篇能帮你在课设答辩前少踩几个坑顺利跑起来。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?