ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Flask + MySQL 高校成绩管理系统实战:从环境搭建到存储过程排名

Flask + MySQL 高校成绩管理系统实战:从环境搭建到存储过程排名 简介这是一套面向高校计算机相关专业学生的成绩管理系统完整项目源码基于Python语言、Flask框架与MySQL数据库开发适合用作期末课程设计、毕业设计参考或Web开发入门练手。项目在PyCharm中可直接打开运行涵盖学生信息维护、课程设置、成绩录入与查询统计等典型教务场景能帮助读者理解前后端交互与数据库设计的完整链路。压缩包共1480个文件以720个py源码和601个pyc编译文件为主体另含dll动态库、exe可执行文件、wheel安装包及html、css、js等前端资源整体约16.35MB目录结构清晰便于按模块定位学习。目前已有104人学习下载。读者可从中获取可运行的Flask项目骨架、MySQL建表与操作逻辑、前端页面样式以及依赖环境配置参考适合对照源码梳理MVC分层思路并在此基础上二次开发。1. 从一张成绩单说起Flask MySQL 的高校成绩管理系统到底解决什么问题期末周刚过教务老师发来一张 Excel里面是三个专业、两百多号人的成绩要求按班级排名、算加权平均分、导出补考名单。手动做一遍要一下午改一个分数还得从头核对。这类重复劳动正是「Python 高校成绩管理系统」要吃掉的东西用 Flask 做后端接口和页面路由用 MySQL 存学生、课程、成绩三张核心表在 PyCharm 里把项目跑起来最后打包成一个能在局域网访问的 Web 应用。它适合两类人——正在找课程设计或毕设题目的学生以及想用一个完整项目把 Python 语法、Flask 路由、MySQL 增删改查串起来的入门者。整套东西不复杂但坑集中在环境配置和数据库连接上下面按「先跑通、再讲透、最后避坑」的顺序拆开。2. 环境先落地PyCharm 建项目、MySQL 建库、Flask 装依赖2.1 为什么选 Flask 而不是 Django高校成绩管理系统的业务边界很清楚学生登录看成绩、教师录入和修改成绩、管理员维护课程和学生名单。没有复杂的权限树也不需要自带后台。Django 自带 ORM、Admin、认证体系开箱即用但目录结构重改一个字段要动 migrationsFlask 是微框架路由、模板、数据库连接都要自己接反而让初学者看清「一个请求从浏览器到数据库再回来」的完整链路。常见做法是 Flask Flask-SQLAlchemy PyMySQL 三件套SQLAlchemy 负责把 Python 对象映射成表PyMySQL 负责和 MySQL 通信。选 Flask 的另一个理由是调试轻改完代码保存即生效不用等重载。2.2 PyCharm 里建项目与虚拟环境打开 PyCharm新建项目时选 Pure PythonLocation 填score_system解释器选 New environment using Virtualenv基础解释器指向本机 Python 3.10 或 3.11。虚拟环境的意义是把 Flask、SQLAlchemy 这些包装在项目目录下的venv里不污染全局。建好后在底部 Terminal 里确认# 确认当前解释器是项目内的 venv which python # Windows 用 where python python --version # 应输出 3.10.x 或 3.11.x如果which python指向系统路径而不是score_system/venv/bin/python说明 PyCharm 没把终端切到虚拟环境去 Settings → Project → Python Interpreter 手动选中 venv 里的解释器。这一步不做后面pip install会装到全局换台机器就复现不了。2.3 安装依赖并锁定版本在项目根目录建requirements.txt内容如下Flask3.0.3 Flask-SQLAlchemy3.1.1 PyMySQL1.1.1 cryptography42.0.8然后执行pip install -r requirements.txtcryptography不是可选项。MySQL 8.0 默认用caching_sha2_password认证插件PyMySQL 连接时必须依赖它做握手缺了会报RuntimeError: cryptography is required for sha256_password。版本号写死是为了避免半年后重装时 Flask 4.x 改了 API 导致代码跑不起来。2.4 MySQL 建库与建表MySQL 8.0 安装时记住 root 密码装完用命令行或 Workbench 连上执行CREATE DATABASE score_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE score_db; CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, sno VARCHAR(20) UNIQUE NOT NULL, name VARCHAR(50) NOT NULL, class_name VARCHAR(50) ); CREATE TABLE course ( id INT PRIMARY KEY AUTO_INCREMENT, cname VARCHAR(50) NOT NULL, credit DECIMAL(3,1) DEFAULT 0 ); CREATE TABLE score ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL, course_id INT NOT NULL, grade DECIMAL(5,2) DEFAULT 0, FOREIGN KEY (student_id) REFERENCES student(id), FOREIGN KEY (course_id) REFERENCES course(id) );字符集必须用utf8mb4否则学生姓名里的生僻字会变成问号。grade用DECIMAL(5,2)而不是FLOAT因为浮点数存 89.1 可能变成 89.09999算平均分时误差累积。外键约束保证不会出现「成绩表里有学生 id 但学生表里没有这个人」的脏数据。3. 把 Flask 和 MySQL 接起来连接串、模型与第一个查询接口3.1 连接串怎么写才不出错在项目根目录建app.py先写最小可运行版本from flask import Flask, jsonify from flask_sqlalchemy import SQLAlchemy app Flask(__name__) # 格式mysqlpymysql://用户名:密码主机:端口/库名?charsetutf8mb4 app.config[SQLALCHEMY_DATABASE_URI] ( mysqlpymysql://root:你的密码127.0.0.1:3306/score_db?charsetutf8mb4 ) app.config[SQLALCHEMY_TRACK_MODIFICATIONS] False db SQLAlchemy(app) app.route(/ping) def ping(): db.session.execute(db.text(SELECT 1)) return jsonify({status: db ok}) if __name__ __main__: app.run(debugTrue, host0.0.0.0, port5000)连接串里三个地方最容易翻车主机写localhost在某些系统上会走 socket 而不是 TCP报error 2002 (HY000)统一写127.0.0.1更稳密码里如果有或#必须做 URL 编码否则解析错位charsetutf8mb4不能省否则中文写入报Incorrect string value。host0.0.0.0是为了让同局域网的同学能访问只在本机调试写127.0.0.1也行。3.2 用模型类替代手写 SQL接着定义三张表对应的模型class Student(db.Model): __tablename__ student id db.Column(db.Integer, primary_keyTrue) sno db.Column(db.String(20), uniqueTrue, nullableFalse) name db.Column(db.String(50), nullableFalse) class_name db.Column(db.String(50)) def to_dict(self): return {id: self.id, sno: self.sno, name: self.name, class_name: self.class_name}to_dict方法的作用是把 ORM 对象转成字典jsonify才能序列化。不写这个方法直接返回对象会报Object of type Student is not JSON serializable。__tablename__显式指定表名避免 SQLAlchemy 默认把类名转小写后和已有表对不上。3.3 查询接口与参数校验加一个按班级查学生的接口from flask import request app.route(/students) def list_students(): cls request.args.get(class_name, ).strip() query Student.query if cls: query query.filter(Student.class_name cls) students query.order_by(Student.sno).all() return jsonify([s.to_dict() for s in students])request.args.get拿的是 URL 问号后面的参数比如/students?class_name计算机2101。.strip()去掉前后空格防止前端传了带空格的班级名导致查不到。order_by(Student.sno)按学号排序比按 id 排更符合教务习惯。这里没有做分页两百人以内没问题上千人就要加limit和offset否则一次查询把整表拉进内存。3.4 成绩录入与 MySQL update 语法录入成绩用 POST更新用 PUT对应 SQL 的INSERT和UPDATEapp.route(/score, methods[POST]) def add_score(): data request.get_json() # 先查是否已有该生该课成绩有则更新无则插入 exist Score.query.filter_by( student_iddata[student_id], course_iddata[course_id] ).first() if exist: exist.grade data[grade] else: db.session.add(Score( student_iddata[student_id], course_iddata[course_id], gradedata[grade] )) db.session.commit() return jsonify({ok: True})这段逻辑对应 MySQL 的INSERT ... ON DUPLICATE KEY UPDATE但用 ORM 写更直观。db.session.commit()必须调用否则数据只在会话里进程一退就丢。如果并发量大两个请求同时判断「不存在」然后都插入会撞唯一索引稳妥做法是给(student_id, course_id)加联合唯一约束捕获IntegrityError后转为更新。4. 页面与部署模板渲染、静态资源路径和局域网访问4.1 Jinja2 模板渲染成绩列表Flask 默认从templates目录找模板建templates/scores.html!DOCTYPE html html headmeta charsetutf-8title成绩列表/title/head body table border1 trth学号/thth姓名/thth课程/thth成绩/th/tr {% for row in rows %} tr td{{ row.sno }}/td td{{ row.name }}/td td{{ row.cname }}/td td{{ row.grade }}/td /tr {% endfor %} /table /body /html路由里用render_template传入数据app.route(/view) def view_scores(): rows db.session.query( Student.sno, Student.name, Course.cname, Score.grade ).join(Score, Score.student_id Student.id) \ .join(Course, Course.course_id Course.id).all() return render_template(scores.html, rowsrows)join的顺序不能反先连成绩表再连课程表条件写错会报Unknown column。查询返回的是 Row 对象模板里用属性名访问即可。4.2 静态资源路径错误的排查部署到服务器后最常见的报错是 CSS、JS 加载 404。Flask 默认静态目录是项目根下的static模板里必须用url_for生成路径link relstylesheet href{{ url_for(static, filenamecss/style.css) }}直接写/static/css/style.css在本地能跑但项目挂到子路径比如/score/下就全断。url_for会根据应用配置自动补前缀。如果用了 Nginx 反代还要确认location /static/指向了正确的物理目录否则请求打到 Flask 之前就被 Nginx 拦下返回 404。4.3 局域网访问与端口app.run(host0.0.0.0, port5000)之后同网段的人用http://你的IP:5000访问。Windows 防火墙会弹窗询问是否允许选允许专用网络。如果连不上先在服务器本机curl 127.0.0.1:5000/ping确认服务活着再从另一台机器ping服务器 IP最后查防火墙入站规则。生产环境不要用app.run用waitress-serve --host0.0.0.0 --port5000 app:appWindows 上 waitress 比 gunicorn 兼容性好。5. 避坑与排查五个让项目跑不起来的高频问题5.1 报错Access denied for user rootlocalhost现象启动 Flask 时抛pymysql.err.OperationalError: (1045, Access denied for user rootlocalhost)。 原因连接串里的密码和 MySQL 实际 root 密码不一致或者 MySQL 8.0 装完后 root 只允许auth_socket登录。 解决用mysql -u root -p能登进去说明密码对登不进去就重置能登进去但 Flask 连不上执行ALTER USER rootlocalhost IDENTIFIED WITH caching_sha2_password BY 新密码;再FLUSH PRIVILEGES;。5.2 中文写入变成问号或报Incorrect string value现象插入学生姓名「张伟」后查出来是??或者直接报编码错误。 原因建库时没指定utf8mb4或者连接串漏了charsetutf8mb4。 解决ALTER DATABASE score_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;同时确认连接串带 charset 参数。已经建的表要ALTER TABLE student CONVERT TO CHARACTER SET utf8mb4;。5.3RuntimeError: cryptography is required现象PyMySQL 连接 MySQL 8.0 时抛这个错。 原因MySQL 8.0 默认认证插件是caching_sha2_passwordPyMySQL 需要cryptography包做 RSA 加密握手。 解决pip install cryptography写进requirements.txt。不想装就把用户认证插件改成mysql_native_password但不推荐新版本 MySQL 会逐步废弃它。5.4 改了代码页面没变化现象修改模板或路由后刷新浏览器还是旧内容。 原因debugTrue没开或者浏览器缓存了静态文件。 解决开发时确保app.run(debugTrue)模板改动会自动重载静态文件加版本号style.css?v2强制刷新。生产环境关掉 debug用 waitress 重启进程。5.5 外键约束导致删学生失败现象删除学生时报Cannot delete or update a parent row: a foreign key constraint fails。 原因该学生在score表里还有成绩记录外键挡住了删除。 解决先删成绩再删学生或者建表时给外键加ON DELETE CASCADE。教务场景建议先软删除加is_deleted字段标记保留历史数据可追溯。6. 进阶用存储过程算班级排名与加权平均分成绩管理里最绕的不是增删改查是统计。班级排名、加权平均分、补考名单这三样用 Python 循环算也能出结果但数据量一大就慢。把计算下沉到 MySQL 存储过程一次调用返回整张排名表是更省事的做法。先建一个算加权平均分的存储过程DELIMITER // CREATE PROCEDURE class_avg(IN cls VARCHAR(50)) BEGIN SELECT s.sno, s.name, ROUND(SUM(sc.grade * c.credit) / SUM(c.credit), 2) AS weighted_avg FROM student s JOIN score sc ON sc.student_id s.id JOIN course c ON c.id sc.course_id WHERE s.class_name cls GROUP BY s.id ORDER BY weighted_avg DESC; END // DELIMITER ;DELIMITER //是为了让 MySQL 把整个BEGIN...END当成一条语句不然遇到中间的分号就截断了。SUM(sc.grade * c.credit) / SUM(c.credit)就是加权平均学分高的课权重大。ROUND(..., 2)保留两位小数避免前端显示一长串。Flask 里调用app.route(/rank) def rank(): cls request.args.get(class_name, ) result db.session.execute( db.text(CALL class_avg(:cls)), {cls: cls} ) rows [dict(r._mapping) for r in result] return jsonify(rows)db.text里用:cls占位第二个参数传字典SQLAlchemy 会自动转义不要用字符串拼接否则班级名里带引号就是注入漏洞。r._mapping把 Row 转成字典直接dict(r)在新版本 SQLAlchemy 里会报错。验证存储过程是否正确拿一个已知数据的手算结果对一遍比如某班三门课学分分别是 3、2、4成绩 80、90、70加权平均应是(80*390*270*4)/(324) 75.56。跑一遍接口看输出是不是这个数对不上就查JOIN条件是不是漏了课程 id 对应关系。我自己的习惯是每次改完存储过程先在 Workbench 里CALL class_avg(计算机2101);单独跑一遍确认 SQL 本身没问题再去调 Flask 接口。这样出问题时能立刻判断是 SQL 写错还是 Python 传参错省掉来回猜的时间。希望帮到你。本文还有配套的精品资源点击获取
返回列表