简介数据库课程设计平行志愿模拟录取系统完整项目面向计算机相关专业学生及需要完成数据库课设的开发者重点解决志愿填报业务中数据建模与批量录取规则实现问题。项目按照考生、院校、志愿、录取等核心实体设计数据表适配MySQL与SQL Server同时以Vue、TypeScript构建前端界面用Java实现后端接口形成从建库到展示的完整闭环。资源包包含969个文件压缩后仅3.87MB其中前端相关文件js、css、scss、vue、ts数量较多也包含32个Java源文件、SQL建库脚本、PDF说明文档以及Excel样例数据目录结构直观便于检索。目前已有104人浏览学习。通过学习这份项目可掌握数据库ER图转换关系模型、事务机制、存储过程应用与前后端联调方法并获取可直接运行的完整源码为课程答辩、功能扩展或二次开发提供扎实基础。1. 数据库课程设计里的平行志愿模拟录取系统先别急着解压 zip拿到“数据库课程设计-平行志愿模拟录取系统.zip”大多数人第一反应是解压、找报告模板、改个名字就交。但这个方向的核心其实是两件事一是把平行志愿的投档规则用关系模型表达清楚二是把“分数优先、遵循志愿、一轮投档”的逻辑写成能在数据库和代码之间跑通的东西。它适合正在做数据库课程设计、又不想只做增删改查的学生也适合想拿一个完整业务场景练手的人。我先说个反直觉结论这个题目里 70% 的分数在数据模型和投档规则上只有 30% 在代码本身。2. 数据模型先行平行志愿系统的表设计与初始化脚本先建库还是先写逻辑我的习惯是先把表结构定下来。平行志愿模拟录取系统里经常出现“考生表、院校表、志愿表”三张表就开干的方案跑起来才发现专业和院校没分开一个院校下多个专业没法存考生被录取之后没有地方记录最终落点调剂标识没字段规则只能写死在代码里。2.1 用四张核心表装下整个录取业务我会用考生表、院校计划表、志愿表、录取结果表四张表起步。考生表存学生基本信息考生号、姓名、总分和语文、数学、外语三科成绩。三科成绩专门用来做同分排序和总分不是加和关系这一点在设计表结构时就要想清楚。院校计划表存每所学校的招生计划和专业信息注意把专业放在同一张表里按行拆开这样“计算机科学与技术招 5 人”和“软件工程招 3 人”是两条记录而不是一个院校一条记录。志愿表存考生的填报顺序考生号、院校代码、专业代码、志愿序号1 表示第一志愿。平行志愿的特点是同一批次可以填多个志愿录取时先看第一志愿某校某专业如果这个专业名额满了就看第二志愿依次往后。录取结果表存最终落点考生号、录取院校、录取专业、状态码这张表在算法跑完后被写满。建表 SQL 如下字段注释直接写在 DDL 里方便答辩时讲清楚每张表的用途-- 考生表核心是总分同分排序用三科成绩 CREATE TABLE students ( stu_id CHAR(10) PRIMARY KEY COMMENT 考生号, stu_name VARCHAR(50) NOT NULL, total_score INT NOT NULL, chinese_score INT NOT NULL DEFAULT 0, math_score INT NOT NULL DEFAULT 0, english_score INT NOT NULL DEFAULT 0, INDEX idx_total (total_score DESC) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里INDEX idx_total (total_score DESC)是为后续“按总分排序”的查询准备的索引DESC 让数据库按从高到低的顺序维护索引。模拟录取时直接扫索引就能拿到“分数优先”的遍历顺序而不是靠临时排序。-- 院校计划表同一学校多个专业按 学校专业 拆行 CREATE TABLE colleges ( college_id CHAR(6) NOT NULL, major_code CHAR(8) NOT NULL, major_name VARCHAR(50) NOT NULL, plan_count INT NOT NULL COMMENT 招生计划人数, min_score INT DEFAULT NULL COMMENT 录取后回填最低分, PRIMARY KEY (college_id, major_code) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;复合主键(college_id, major_code)是这个表的关键它保证同一个学校的同一个专业只有一条计划记录。min_score 字段可以在算法跑完后回填用来快速查看录取最低分。-- 志愿表考生 院校 专业 志愿序号 CREATE TABLE volunteers ( stu_id CHAR(10) NOT NULL, college_id CHAR(6) NOT NULL, major_code CHAR(8) NOT NULL, preference_order TINYINT NOT NULL COMMENT 1第一志愿, 2第二志愿..., PRIMARY KEY (stu_id, preference_order), KEY idx_college (college_id, major_code) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;志愿表主键是(stu_id, preference_order)意思是一个考生在同一个志愿序号只能填一条数据库层面保证不会出现“第一志愿填了两所学校”的脏数据。这个约束在真实系统里非常重要。-- 录取结果表算法跑完后写入最终落点 CREATE TABLE admissions ( stu_id CHAR(10) PRIMARY KEY, college_id CHAR(6) NOT NULL, major_code CHAR(8) NOT NULL, status ENUM(ADMITTED,NOT_ADMITTED) NOT NULL DEFAULT NOT_ADMITTED, admitted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;status 用 ENUM 而不是 TINYINT因为“已录取/未录取”是固定状态用枚举可读性好也省得写一堆 status2 这种魔法数字。四张表合起来课程设计报告里的 E-R 图也能直接画考生与志愿是 1:N院校专业与志愿是 1:N考生与录取结果是 1:1。答辩时老师问“你的数据模型怎么设计的”直接对着这张图讲就行。2.2 初始化脚本事务、外键和增删改查的顺序建完表接下来是初始化数据。我见过很多同学把 INSERT 语句直接堆在一个文件里跑的时候中途报错前面插入的数据已经生效后面没插进去库里留下一堆半截数据。这就是典型的“初始化脚本没有事务边界”。正确的做法是把所有 INSERT 包在一个事务里START TRANSACTION; -- 清空旧数据注意先删子表再删父表 DELETE FROM admissions; DELETE FROM volunteers; DELETE FROM colleges; DELETE FROM students; -- 重新插入考生数据 INSERT INTO students (stu_id, stu_name, total_score, chinese_score, math_score, english_score) VALUES (1001, 张明, 618, 118, 135, 128), (1002, 李华, 612, 122, 130, 125); -- 插入院校计划 INSERT INTO colleges (college_id, major_code, major_name, plan_count) VALUES (A001, M001, 计算机科学与技术, 2), (A001, M002, 软件工程, 3); -- 插入志愿 INSERT INTO volunteers (stu_id, college_id, major_code, preference_order) VALUES (1001, A001, M001, 1); COMMIT;把 DELETE 和 INSERT 放进同一个事务好处是任何一个语句失败回滚后数据库还是原来的干净状态不会出现“删了没插进去”的中间态。课程设计里常被追问的“数据一致性”就是从这种细节体现的。注意 DELETE 顺序先删 admissions、volunteers 这些子表再删 colleges、students 父表。顺序反了有外键约束时直接报错这个高频踩坑点第 5 章会专门展开。实际交作业时我会把增删改查的示例单独放在一个 queries.sql 文件里按总分倒序查考生、按院校查录取名单、修改某个考生的分数、删除一条志愿记录。答辩时现场演示一句 SELECT比翻 PPT 有效得多。2.3 模拟数据生成分数分布和志愿填报的随机规则课程设计的数据量越大越能看出算法的正确性。最少做 100 个考生、5 所院校、每所院校 2 到 3 个专业足够跑通逻辑想拿高分建议做到 300 个考生以上因为平行志愿的“轮次”效果需要足够多的考生才明显。模拟数据怎么造我一般用 Python 一次性生成考生成绩和志愿表import random # 生成考生成绩总分呈正态分布控制在 400~700 区间 def gen_score(): score int(random.gauss(560, 60)) return max(400, min(700, score)) # 生成志愿顺序按“冲稳保”逻辑前几个志愿填热门院校后面填保底 students [] for i in range(300): stu_id f{1000 i:04d} total gen_score() chinese random.randint(90, 140) math random.randint(90, 140) english random.randint(90, 140) students.append((stu_id, f考生{i}, total, chinese, math, english))总分用高斯分布生成模拟真实考试“中间多、两端少”的分布。三科成绩独立随机这里只作为同分排序键如果希望三科之和等于总分可以先随机生成三科再相加两种做法都行课程设计不用太较真。志愿填报用“冲稳保”逻辑前几个志愿填录取线高的学校后面填分数线低的保底学校这样模拟出来的投档结果更接近真实场景。生成完数据后写入 CSV顺便演示“从 Excel 导入数据库”这条路import csv with open(students.csv, w, newline, encodingutf-8) as f: writer csv.writer(f) writer.writerow([stu_id, stu_name, total_score, chinese_score, math_score, english_score]) writer.writerows(students)然后通过 MySQL 的 LOAD DATA 命令批量导入几百条数据瞬间完成比一条条 INSERT 快得多。这一步做完数据环境就绪可以开始写核心算法了。3. 模拟录取核心把“分数优先、遵循志愿、一轮投档”写进代码3.1 先搞清楚投档规则再谈写代码平行志愿的规则三句话就能说清分数优先、遵循志愿、一轮投档。但很多课程设计只实现了前两句漏了“一轮投档”的语义。分数优先指所有考生按总分从高到低排序分高的人先选这决定了算法的遍历顺序必须按分数不能用插入顺序。遵循志愿指轮到某个考生时按他填的专业志愿顺序依次检查第一志愿专业没满就投进去满了就看第二志愿。一轮投档指每个考生在同一批次里只有一次投档机会一旦投进某个专业即使后面发现更好的志愿也不能改如果所有志愿的专业都满了只能落榜。这个规则直接引出一个实现问题如果两个考生同分谁先谁后真实高考规则是看单科成绩各省标准不同比较顺序一般是语文、数学、英语。我的排序键定为(total_score DESC, chinese_score DESC, math_score DESC, english_score DESC)后面所有代码都以这个顺序为准。这里采用的是专业平行志愿模式每个志愿精确到“某校某专业”投档直接看专业是否还有名额。如果你想做传统院校平行志愿把志愿表里的 major_code 去掉按院校总计划投代码逻辑改动很小。3.2 Python 实现按总分排序逐志愿检索按计划数投档我习惯的做法是“先查数据进内存再在内存里跑算法最后把结果写回 MySQL”。为什么不纯用 SQL 一次 UPDATE 解决因为课程设计的重点是展示你理解规则纯 SQL 做这种逐行判断非常绕debug 也困难。内存里跑一遍每一步能打印日志遇到结果不对马上知道是排序错了还是志愿顺序错了。import pymysql # 连接到本地 MySQL 数据库 conn pymysql.connect( host127.0.0.1, userroot, password你的密码, databasevolunteer_system, charsetutf8mb4 ) cursor conn.cursor() # 1. 读取所有考生按总分排序同分按单科成绩排序 cursor.execute( SELECT stu_id, total_score, chinese_score, math_score, english_score FROM students ORDER BY total_score DESC, chinese_score DESC, math_score DESC, english_score DESC ) students cursor.fetchall() # 2. 读取院校招生计划记录剩余名额 cursor.execute(SELECT college_id, major_code, plan_count FROM colleges) plans {} for college_id, major_code, plan_count in cursor.fetchall(): plans[(college_id, major_code)] plan_count # 剩余名额初始化为招生计划 # 3. 读取所有考生的志愿按考生号和志愿序号分组 cursor.execute( SELECT stu_id, college_id, major_code, preference_order FROM volunteers ORDER BY stu_id, preference_order ) volunteers {} for stu_id, college_id, major_code, order_no in cursor.fetchall(): volunteers.setdefault(stu_id, []).append((college_id, major_code)) # 4. 核心算法分数优先遵循志愿一轮投档 results {} admitted_count 0 for stu_id, total, chinese, math, english in students: admitted False if stu_id not in volunteers: # 异常数据兜底没填志愿的考生直接标记未录取 results[stu_id] (NOT_ADMITTED, None, None) continue for college_id, major_code in volunteers[stu_id]: if plans.get((college_id, major_code), 0) 0: # 该专业还有名额投档并占用名额 plans[(college_id, major_code)] - 1 results[stu_id] (ADMITTED, college_id, major_code) admitted_count 1 admitted True break if not admitted: results[stu_id] (NOT_ADMITTED, None, None) # 5. 写回录取结果表 for stu_id, (status, college_id, major_code) in results.items(): if status ADMITTED: cursor.execute( INSERT INTO admissions (stu_id, college_id, major_code, status) VALUES (%s, %s, %s, ADMITTED) ON DUPLICATE KEY UPDATE college_id VALUES(college_id), major_code VALUES(major_code), status ADMITTED , (stu_id, college_id, major_code)) else: cursor.execute( INSERT INTO admissions (stu_id, college_id, major_code, status) VALUES (%s, NULL, NULL, NOT_ADMITTED) ON DUPLICATE KEY UPDATE status NOT_ADMITTED , (stu_id,)) conn.commit() cursor.close() conn.close() print(f模拟录取完成共录取 {admitted_count} 人)逻辑说明分四块。第 1 步读取考生时用 ORDER BY 直接实现了“分数优先”后面循环的遍历顺序就是投档顺序。第 2 步把招生计划放到字典里key 是(college_id, major_code)对应表结构里“同一学校不同专业是不同行”的设计。第 3 步把志愿表按考生号分组成列表列表顺序就是志愿优先顺序。第 4 步是核心循环每个考生按志愿顺序依次检查剩余名额某个专业看到有名额就投同时名额减一并立刻 break 跳出志愿循环保证“一轮投档”——不会再回头去看后面的志愿。关于ON DUPLICATE KEY UPDATEadmissions 表主键是 stu_id同一考生的结果只允许存在一条。算法跑第二遍时不需要先清空 admissions重复执行会覆盖旧结果这让系统可以随时“重跑模拟”。课程设计答辩时老师可能为了验证效果改几个招生计划数让你重跑这个设计让重跑成本为零。3.3 三个必调参数招生计划、是否允许调剂、是否设专业级差代码里能调的参数有三个课程设计报告里把这些参数写明白比贴代码更有说服力。招生计划数就是投档的“容量”。真实系统里是每个学校提前公布的模拟系统里改 colleges 表的 plan_count 就能调节竞争激烈程度。把某所热门大学的计算机专业计划从 2 改成 1录取最低分立刻上浮。是否允许调剂这是平行志愿的高频考点。上面代码里没有调剂逻辑每个专业独立算名额投的是“专业志愿”。如果要做服从调剂版本需要在循环里加一步当某个考生在某所学校填的所有专业志愿都满时检查这所学校还有没有其他未满专业有就投到空缺专业。这一步多数课程设计都没做做了就是加分项。是否设专业级差有的录取规则里第二志愿专业要扣 3 分再参与排序。模拟系统里加级差等于读取考生分数时把 total_score 临时减去级差分再重新参与专业竞争。参数说明整理成表答辩时可以投影出来参数位置默认值说明招生计划colleges.plan_count按实际设定专业课招生人数直接影响录取最低分是否调剂算法代码配置FalseTrue 时实现“有空缺专业就投”专业级差配置项分数值0每降一个专业志愿扣 3 分示例4. 本地跑通最小系统导库、改配置、验证录取结果的完整步骤4.1 准备 MySQL 8.0 与 PyMySQL 环境这个题目最常见的落地环境是 Windows 本地装 MySQL 8.0因为课程设计验收一般就在自己笔记本上。装好后先确认服务在跑Windows 在服务管理器里查 MySQL80macOS 用 brew services list。Python 侧只需要一个 pymysql 包pip install pymysql建议把连接信息单独放到 config.py避免密码散落在各个脚本里。环境问题最常见的三种报错2003 连不上多半是 MySQL 服务没启动1045 密码错误检查 root 密码1049 数据库不存在建库语句没执行。按这个顺序排查能解决九成环境问题。关于“数据库连接池”这个概念课程设计的单机脚本不需要连接池因为一次算法运行只有几个查询连接建立和关闭的开销可以忽略。见过有同学为了显得专业硬上 SQLAlchemy 连接池结果事务边界变复杂回滚都没搞明白。我的建议是单连接跑完用完 close。如果答辩被问“连接池什么时候用”回答“高并发 Web 系统里复用连接、避免每请求创建连接”就够了不要在课程设计里硬上。4.2 用 source 导入 SQL 文件注意字符集zip 包解压后通常有 schema.sql 和 data.sql。用 MySQL 命令行导入mysql -uroot -p # 登录后执行 SET NAMES utf8mb4; source D:/volunteer_system/schema.sql; source D:/volunteer_system/data.sql;source 是 mysql 客户端的命令不是 SQL。路径里用正斜杠Windows 反斜杠容易转义出问题。先 SET NAMES 再导入是为了让客户端与服务器之间的字符集一致避免中文乱码。如果 SQL 文件本身是 GBK 保存的改成 SET NAMES gbk。如果 zip 包只有代码没有 SQL 文件就用第 2 章的表结构自己建库一样能跑通。注意先确认 SQL 文件的编码再决定 SET NAMES 的参数。这一步错了后面全是乱码。4.3 用三条核验 SQL 判断录取结果是否正确模拟录取跑完别急着看结果文件先用 SQL 做三件事判断算法到底跑对没有。第一条录取总人数不应该超过所有招生计划的总和。跑完算法后查 admissions 里 ADMITTED 的数量再查 colleges 的 plan_count 总和手动对比。如果录取人数超标说明代码里名额扣减逻辑有 bug。-- 核验 1录取人数与招生计划总量对比 SELECT COUNT(*) AS admitted_count FROM admissions WHERE status ADMITTED; SELECT SUM(plan_count) AS total_plan FROM colleges;第二条每个考生只能有一条录取记录。admissions 表主键是 stu_id按理不会重复但如果你改过表结构或者用 INSERT 而不是 ON DUPLICATE KEY UPDATE 重跑就可能出现重复行。这条 SQL 查出来为空才算正常-- 核验 2每个考生只能有一条录取记录 SELECT stu_id, COUNT(*) AS cnt FROM admissions GROUP BY stu_id HAVING cnt 1;第三条看各专业的录取最低分是否符合“高分先挑”的预期。热门专业的最低分应该比同校冷门专业高如果出现反常多半是 JOIN 产生了重复行或者排序键没生效-- 核验 3各专业录取最低分倒序看竞争热度 SELECT a.college_id, a.major_code, c.major_name, MIN(s.total_score) AS lowest_score FROM admissions a JOIN students s ON a.stu_id s.stu_id JOIN colleges c ON a.college_id c.college_id AND a.major_code c.major_code WHERE a.status ADMITTED GROUP BY a.college_id, a.major_code, c.major_name ORDER BY lowest_score DESC;如果验证结果不对第一件事是打印中间变量把排序后的考生列表前 20 名打出来看是不是真的按分数排的。这类问题九成出在排序键或是志愿分组上很少是数据库本身的问题。5. 平行志愿模拟录取的避坑指南5 个真实踩过的坑这一章的坑是我做这类课程设计时真实遇到过、以及帮人查过的血泪经验。每一条都是“现象 → 原因 → 解决”的结构你按顺序对一遍自己的代码能省下大半天 debug 时间。5.1 坑一同一考生被两所学校同时录取结果表出现逻辑矛盾现象跑完算法后查 admissions 表发现一个考生出现在 A 大学的录取名单里也出现在 B 大学的录取名单里。原因这是“一轮投档”规则没实现的典型表现。常见写法是外层循环遍历院校内层循环遍历考生先给 A 大学挑满人再给 B 大学挑人于是同一个高分考生被 A 大学录取后又因为分数高被 B 大学“录取”了一次。这个翻车现场在答辩里我见过不止一次。解决录取循环只能有一个入口——按分数排序后的考生列表。每一名考生只有一次机会投进一个专业后立即 break 跳出志愿循环后面的志愿不再参与。第 3 章代码里就是先 ORDER BY 再外层遍历考生、内层遍历志愿列表一旦投中立刻 break从结构上杜绝“一人多投”。5.2 坑二数据库并发锁导致的死锁模拟脚本卡死现象算法脚本跑着跑着不动了报 Lock wait timeout exceeded或者在事务里执行一条 UPDATE明明只更新一行却等到超时。原因多数是“为了防重跑先 DELETE 再 INSERT”造成的。DELETE 全表会锁住整张表如果此时另一个连接在更新同一张表就会互相等锁。课程设计虽然是单机但如果开着 MySQL 客户端手动查数据相当于两个连接同时操作同一张表死锁很容易出现。解决能不加锁就不加锁。先 DELETE 全表再 INSERT 的做法改成第 3 章的 ON DUPLICATE KEY UPDATE 方案用主键覆盖旧数据每次运行都是幂等重放不需要锁也不需要清表。如果确实需要清表把 DELETE 和 INSERT 放进同一个事务并尽快 COMMIT缩短锁持有时间。提示单机课程设计出现死锁先想想是不是自己开了多个 MySQL 窗口。关掉多余连接死锁大概率消失。5.3 坑三zip 里解压出来的 SQL 文件全是乱码导入直接报错现象解压后打开 schema.sql中文字段注释显示成“锟斤拷”之类的内容source 导入时要么报错要么数据里的中文变成问号。原因SQL 文件保存时用 UTF-8Windows 下 MySQL 控制台默认按 GBK 显示或者反过来文件是 GBK连接用了 UTF-8。这是编码层面的老问题不是代码 bug和用什么数据库软件无关。解决分两步。第一步确认文件编码用编辑器右下角看编码或者直接看文件开头有没有 UTF-8 BOM第二步source 之前先执行 SET NAMES utf8mb4 或者 SET NAMES gbk让连接字符集和文件一致。如果文件编码已经乱掉唯一的后悔药是把文件另存为 UTF-8 无 BOM 格式再导入。先改编码再导数据能少走很多弯路。5.4 坑四删除旧数据时外键约束不配合一执行就报错现象想清空数据库重新模拟执行 DELETE FROM students结果报 Cannot delete or update a parent row。原因表里建了外键考生表是父表志愿表或录取结果表是子表。删除父表记录时数据库会检查子表里有没有引用有引用就不让删。这是数据库在保护数据完整性。很多课程设计为了“数据严谨”给每张表都加了外键结果删除数据时忘了先删子表一头撞在外键墙上。解决删除顺序是“先子后父”。先 DELETE admissions、volunteers再 DELETE students、colleges。或者启动事务后临时关闭外键检查SET FOREIGN_KEY_CHECKS 0删完再 SET FOREIGN_KEY_CHECKS 1。第二种方式也能交差但答辩时建议说清楚平时保留外键保证完整性清库时临时关闭说明你懂外键的作用而不是图省事。5.5 坑五统计录取最低分热门专业最低分反而比冷门专业低现象用 MIN(total_score) 统计各专业录取最低分发现一个冷门专业的最低分比热门专业还高数据明显不符合“分数优先”的直觉。原因通常是聚合查询 JOIN 时出现了重复记录。录取结果表里每个考生只有一条记录理论上 JOIN 不会产生重复行但如果你核验时直接 JOIN 了志愿表一个考生填了多个志愿就会产生一对多MIN 聚合时就可能算错。解决统计只基于录取结果表 JOIN 考生表别带志愿表。如果非要带志愿表先 GROUP BY 志愿表去重。写聚合查询之前先单独跑一条不带聚合的 SELECT 看行数确认没有重复行再聚合。这个习惯能帮你避开绝大多数 SQL 统计坑。6. 答辩前最后一步用事务隔离级别和索引把系统讲出“数据库味”课程设计做到这里功能链路已经完整建表、初始化、模拟录取、结果核验全流程能跑通。但答辩和项目验收不一样老师不会只看结果对不对他会问“你的系统在并发下会不会出问题”“这条查询能不能走索引”。有两个技巧一定能用上事务隔离级别和索引利用。先讲事务隔离级别。模拟脚本一次运行里会读考生表、写录取结果表直到最后才 COMMIT。COMMIT 之前另一个连接查 admissions 看到的是旧数据这是 MySQL 默认的 REPEATABLE READ 隔离级别下的正常行为。答辩时主动说“我把写入放进一个事务里保证了录取结果的原子性要么全部写成功要么全部回滚不会出现半份录取名单”这句比“我的数据很准”有说服力得多。再讲索引利用。第 3 章的核心查询是按 total_score、chinese_score、math_score、english_score 排序。如果建表时只建了单列索引这条查询会走 filesort数据量大时性能明显下降。补一个复合索引ALTER TABLE students ADD INDEX idx_score_order ( total_score DESC, chinese_score DESC, math_score DESC, english_score DESC );跑一下 EXPLAIN 看是不是还出现 Using filesort这是数据库优化里最能直接演示的闭环发现问题、建立索引、验证生效。最后是我自己的习惯每次跑完模拟录取都把结果导出一份带时间戳的 CSV比如 admissions_20250101_1800.csv。答辩时老师问“你改了一次招生计划怎么证明结果变化”直接拿出两份 CSV 对比最低分就行不用现场重跑。这个习惯帮我少解释很多次。希望帮到你。本文还有配套的精品资源点击获取