1. 为什么我选了“新高考智能推荐”当毕业设计
说起来有点惭愧。我毕业那年,看到身边一堆高考完的学弟学妹在选科和填志愿上抓瞎,选个科目组合还要一个个翻招生目录、问学长、去论坛扒老帖子,效率极低,而且很容易被零散信息误导。当时我正在准备Spring Boot方向的毕业设计,念头就来了——能不能做一个基于Web的智能推荐系统,把选科、专业倾向、院校匹配这些事做成自动化的推荐流程?
这个题目看起来像“管理系统换个壳”,但真正拆开之后,它比普通的后台CRUD系统有意思得多,也厚实得多。一个完整的新高考智能推荐系统,至少要有用户系统、学生信息管理、选科策略模块、专业库、院校库、志愿倾向分析、推荐引擎、系统管理后台等一堆东西,还要考虑推荐结果怎么解释、怎么落地到学生真实选择。用Spring Boot做后端支撑,再配一套Vue前端,正好覆盖了我学位要求里“工程能力”和“算法落地”两块的考核点。
如果你也想拿类似的题目做毕设,或者接了个“推荐系统”外包小项目,这篇东西可以帮你避开我踩过的坑,也让你有个全局视角——这类系统绝不是写几个接口就完事的。文章后面我会按我自己当时的落地顺序来讲:从架构设计到数据库建模,从推荐算法怎么实现到怎么准备论文数据,最后再说说那些真正决定你毕设能不能高分的小细节。
2. 新高考选科到底“新”在哪,推荐逻辑才能立得住
做推荐系统之前,第一件事不是写代码,而是吃透业务规则。我见过很多同学一上来就堆协同过滤,结果推荐出来的科目组合根本不符合政策要求,答辩时被老师一句话问住。所以先把新高考选科的底层规则理清楚,你才会知道哪些推荐是有意义的,哪些推荐是完全不可行的。
2.1 “3+1+2”模式的硬约束和软约束
以“3+1+2”模式为例:语文、数学、外语三门是必考,这没什么好说的。物理和历史二选一作为首选科目,决定了大部分理工科专业能不能报;剩下的化学、生物、政治、地理四门里选两门作为再选科目。这里面有个明显的层级关系:
- 硬约束:选科组合必须合法。比如选了“历史+化学+生物”,理工类里的计算机、电子信息很多专业就不收,这是院校招生章程决定的。
- 软约束:某些专业“建议选考”某科,但不强制。比如临床医学类很多学校建议选化学或生物,选了更好,没选在部分省份也能报,只是竞争力上可能有差异。
- 能力约束:学生自己的成绩结构。物理能考90分,历史只能考60分,硬推“纯文科组合”就是坑人。
智能推荐的价值,就是在满足硬约束的前提下,尽量提升软约束的满足度,同时兼顾学生个人成绩优势和兴趣倾向。系统里如果把这三层约束拆开建模,推荐结果才谈得上“智能”。
2.2 专业比对规则需要数据支撑
刚开始我天真地以为,建一张专业表,再建一张选科要求表,两个表关联就行了。实际上各院校对同一专业的选科要求不完全一致,还会逐年调整。比如某大学计算机专业要求“物理+化学”,另一所大学只要求“物理”,第三所可能“物理或生物均可”。所以专业库和院校招生规则必须分开存,还得带年份版本。
我的做法是设计了一套规则表达式字段。比如“必选物理且必选化学”,存成 JSON 字符串:{"must":["物理","化学"], "or":[], "suggest":[]},然后在推荐引擎里解析它做硬性筛选。这样比散装字段灵活得多,也方便后续批量导入真实数据。
2.3 推荐结果的解释性比“准确率”更关键
这是很多同学容易忽略的点。毕业设计答辩现场,评委老师关注的不只是你推荐得准不准,更关注你这个推荐结果能不能讲清楚。你要是只抛出一个“因为算法算出来分数高”,老师追问起来会很被动。所以我在系统里做了推荐理由分解——每条推荐组合都会附上“为什么推荐”的标签化原因,比如“成绩优势明显”“符合临床医学类专业选科要求”“兴趣测评倾向匹配”。这个设计让我在答辩环节省了不少力气,也让系统看起来专业很多。
3. 系统整体架构:Spring Boot为主干,Vue作前端,为什么这样取舍
整个系统我在技术上走的是一条既不想翻车、又不想显得太水的路线。后端用Spring Boot 2.x加MyBatis-Plus,前端用Vue3加Element Plus,数据库用MySQL,缓存和热点数据用Redis。为什么选这套组合,直接说几个关键理由。
3.1 单体应用加前后端分离,毕业设计的最优选
新高考推荐系统本质上属于中等规模的业务系统,单体架构完全撑得住。有人非要上个微服务,拆成用户服务、推荐服务、选科服务,结果部署的时候把自己折腾得够呛,这不是毕业设计该干的事。用Spring Boot做一个聚合工程,内部按模块分包,同样能体现工程组织能力,但复杂度可控。
前后端分离则是有必要的。第一,Vue那套页面组件化写起来确实比模板引擎高效,特别是后台管理界面;第二,答辩时展示效果也好。我就是用了Vite初始化前端项目,后端在application.yml里配好跨域,然后前端开发环境用8080端口代理到后端8090,部署时再把前端build出来的静态文件放到Spring Boot的static目录下,一个jar包带走,省去一堆Nginx折腾。
3.2 Spring Boot到底主要用了哪些特性
选Spring Boot的最大理由是自动配置和生态成熟。这个项目里我用到的Spring Boot核心特性大概分成几块:
- Starter依赖管理:
spring-boot-starter-web、spring-boot-starter-data-redis、mybatis-plus-boot-starter,直接省去大量版本兼容问题。 - 统一异常处理和参数校验:配合
@RestControllerAdvice和@Validated,接口层面很干净,前端拿到的返回格式始终统一。 - 定时任务和异步:用来做推荐日志统计、缓存预热。
- 配置文件的profile切换:本地开发一套配置,服务器部署一套配置,用
application-dev.yml和application-prod.yml区分。
有人可能会问,为什么不选Spring Cloud Alibaba?我实话实说,那套东西对毕业设计属于降维打击,而且复杂度过高,容易把自己绕进去。答辩时你要能快速说清楚每个组件为什么存在,单体明显更好解释。
3.3 前端页面的核心界面设计
前端页面没有走花里胡哨的路子,但核心流程做得很顺。主要页面如下。
- 登录注册页:区分学生、教师、管理员三种角色。
- 学生首页:展示当前选科组合、成绩输入入口、推荐结果卡片、推荐理由列表。
- 选科推荐页:这是核心页面,左侧是成绩表单,右侧是推荐结果和隐藏的组合对比表。
- 专业库/院校库浏览页:支持按省份、选科要求、专业名称过滤。
- 系统管理后台:用户管理、数据导入、规则配置、日志查看。
页面数量不多,但每个页面都对应一个完整的功能闭环,而不是花架子。这也提醒各位,毕业设计的功能设计一定不能东一榔头西一棒子,要让老师能看出你有一个系统化的设计思路。
4. 数据库建模:一张“成绩表”引发的思考
数据库设计在毕设里占的比重超出了很多人的想象。推荐逻辑再漂亮,数据表要是设计得稀烂,写SQL的时候早晚崩溃。我前前后后大概调整了三版表结构,最终的核心表大概这些。
4.1 核心表结构和关联关系
我尽量把表拆得职责单一,方便后续扩展和答辩讲解。主要表如下。
| 表名 | 用途 | 关键字段 |
|---|---|---|
| user | 用户统一表 | id, role, username, password(bcrypt加密) |
| student_profile | 学生档案 | user_id, province, school_type, target_major_group |
| exam_score | 历次模考成绩 | user_id, subject, score, class_rank, exam_tag |
| subject_combination | 合法的选科组合表 | combo_code, subject_list, career_scope |
| major_info | 专业库 | major_code, major_name, category, hot_level |
| university_major_rule | 院校专业选科要求表 | university_id, major_id, rule_json, year |
| interest_test_result | 兴趣测评结果 | user_id, riasec_code, raw_json |
| recommend_log | 推荐日志 | user_id, combo_code, final_score, reason_json, create_time |
这里面最需要注意的,是将exam_score表和subject_combination表解耦。一开始我想把各科成绩直接冗余到一张表里,搞成“语文成绩”“数学成绩”“物理成绩”这种一眼望去全是NULL的大宽表。后来一位带我的老师提醒了我:不同省份、不同学生的科目组合不同,大宽表后患无穷,改成分数行存储之后,无论后续加科目还是做并行比对都轻松得多。
4.2 规则表的JSON设计思路
上面提到的university_major_rule里的rule_json,要展开说一下。它存的不只是“要求选什么”,还包含招收批次、备注、特殊说明。实际推荐引擎读取时,我会先反序列化成对象,再按硬性筛选、软性加分两步走。这样做的好处是,数据导入时可以批量从Excel或者爬虫拉取的政策文件里解析,不用频繁改表结构。
毕业设计的数据量不需要特别大,但至少要有像样的样例。我从网上找了部分省份高校的专业选科要求作为基础和测试数据,导入时构成了大概几百条有效规则。这个体量足够演示推荐逻辑了,完全没有必要造假数据撑数字。
4.3 Redis缓存用在哪儿
Redis我用在两个地方。一是存储学生推荐结果的缓存,因为推荐引擎计算一次大概要几百毫秒,如果用户反复刷新同一页面,每次都重新算会有点慢,用recommend:user:{userId}做缓存,设置半小时过期,体验好很多;二是存储热门专业榜和院校热度榜,定时任务每天刷新一次,减少数据库压力。
5. 推荐引擎的实现:规则优先,协同过滤兜底
这部分是整篇文章的重头戏,也是毕业设计的灵魂。我要先给个重要的判断:这类教育推荐系统,不能一上来就搞深度学习模型。原因很简单——数据量不够,解释性差,而且你很难拿到学生的真实历史选科行为数据来做训练。更符合实际的做法,是把规则引擎和传统推荐算法做组合,规则跑不通的地方再用相似度计算兜底。
5.1 第一阶段:规则硬过滤
推荐引擎第一步,是把所有可选组合按硬约束筛一遍。
算法伪代码如下:
public List<SubjectCombination> filterByHardRules(StudentProfile profile, List<SubjectCombination> allCombos, List<UniversityMajorRule> rules) { return allCombos.stream() .filter(combo -> !excludeByMustRule(combo, profile.getWantedMajorRules())) .filter(combo -> !excludeByScoreRequirement(combo, profile)) .collect(Collectors.toList()); }这一步的实际意义是:如果学生目标专业硬性要求“物理+化学”,而你推荐了“历史+生物+地理”,即使这个组合在学生成绩里表现很好,也是无效推荐。必须先排除,保证候选集合法。
5.2 第二阶段:加权评分模型
硬过滤结束后,剩下的组合都是可选的。接下来要对它们打分排序。我的评分模型由四部分组成,每部分有独立权重。
| 评分维度 | 权重 | 说明 |
|---|---|---|
| 学科成绩优势度 | 0.40 | 基于历史模考成绩标准化后计算 |
| 专业覆盖率 | 0.30 | 该组合可报考热门专业数量占比 |
| 兴趣测评匹配度 | 0.20 | 使用霍兰德RIASEC模型粗匹配 |
| 院校推荐位次匹配 | 0.10 | 结合全省模考位次粗估院校层级匹配度 |
每项的详细计算方式如下。
学科成绩优势度的核心公式是:
[ score_subject_advantage = \frac{\sum_{i=1}^{n} \frac{s_i - avg_i}{std_i}}{n} ]
这里s_i是学生在该科目上的最近几次成绩均值,avg_i和std_i是样本内该科目的均值和标准差。这样做的好处是把不同科目的难度差异拉平了,物理考80分和地理考90分不能直接比较,但标准化后可以。
专业覆盖率算起来也不复杂:
[ coverage = \frac{count(major_matched)}{count(all_target_majors)} ]
major_matched表示该组合能覆盖的学生目标专业数量。如果学生没有明确目标专业,我就用热门专业榜当作隐式目标集。
5.3 第三阶段:基于用户的协同过滤
评分模型给出的是离线计算结果,缺点是太“个人英雄主义”,完全依赖学生自己的成绩和测评数据。为了让它有点“人群智慧”的味道,我加了一个基于用户的协同过滤改良版。
思路是这样的:如果学生A和学生B的历史成绩结构非常相似(比如各科排名、成绩数值都很接近),而且B最终做出的选科组合和去向结果不错,那么把B的高满意度组合推荐给A是合理行为。
相似度计算我用的是修正余弦相似度:
public double cosineSimilarity(Map<String, Double> scoreVectorA, Map<String, Double> scoreVectorB) { Set<String> union = new HashSet<>(); union.addAll(scoreVectorA.keySet()); union.addAll(scoreVectorB.keySet()); double dot = 0, normA = 0, normB = 0; for (String subject : union) { double a = scoreVectorA.getOrDefault(subject, 0.0); double b = scoreVectorB.getOrDefault(subject, 0.0); dot += a * b; normA += a * a; normB += b * b; } if (normA == 0 || normB == 0) return 0; return dot / (Math.sqrt(normA) * Math.sqrt(normB)); }算完之后,取最相似的K个学生,再把他们选择过的合法组合按出现频次加权,得到一个“群体推荐分”。最终推荐分 = 评分模型得分 * 0.7 + 群体推荐分 * 0.3。
这个混合策略特别适合毕设展示,因为你可以从两个维度解释结果:一个是基于个人数据的科学计算,一个是基于群体经验的推荐借鉴。评委一听就明白,而且也不会质疑你“为什么不用深度学习”——因为数据体量根本支撑不起。
5.4 推荐结果如何解释
推荐完成后,我会在recommend_log表里写入reason_json。比如:
{ "hard_rules": ["满足目标专业选科要求"], "score_advantage": 1.32, "coverage": 0.88, "interest_match": "现实型/研究型匹配度高", "collaborative_similar_users": 6 }前端页面拿到这个JSON,渲染成几张小标签卡片。学生看到的不只是一个冷冰冰的分数,而是“为什么选它”。这一层解释逻辑,也是前面说的答辩加分项。
6. 核心接口设计与实现细节:从需求到代码的关键串联
系统功能点比较多,但真正核心的接口只有几个。把接口的请求响应逻辑说清楚,代码结构就已经成型。我按业务模块逐个讲。
6.1 成绩录入与档案管理
这个模块很简单,但容易踩坑的是校验逻辑。学生录入成绩时,科目必须与个人信息里选定的首选科目、再选科目匹配,不能出现选了物理还录化学这样的情况“共存”,这里说的共存是指推荐时成绩冗余得混乱。我在后端写了一个SubjectValidator组件,统一校验科目合法性和分数范围。
录入接口的URL可以设计成POST /api/student/score,请求体允许一次提交多门科目,用事务保证要么全成功要么全失败。对毕设来说,一个事务注解@Transactional就解决了,简单可靠。
6.2 推荐接口的完整流程图解
推荐接口是核心节点,我给它命名为POST /api/recommend/analyze。它的内部逻辑顺序如下:
- 获取学生最新成绩向量和测评结果;
- 调用规则引擎做硬过滤;
- 对剩余组合计算评分模型;
- 计算协同过滤群体推荐分;
- 融合得分并排序;
- 生成推荐理由;
- 写入推荐日志表;
- 返回TopN推荐列表。
这个接口没有用任何重型框架,就是清晰的Service链路。链路里每个步骤都拆成独立方法,方便单测。我在代码里特别避免了一个常见问题——把整个流程写进一个几百行的方法里,那样改起来很痛苦,答辩时也不好解释。
6.3 推荐结果的回放与对比功能
为了展示效果,我加了一个“组合对比”功能。学生可以勾选任意两个选科组合,系统调出它们的成绩优势对比图、专业覆盖率对比、推荐理由对比,页面用ECharts画雷达图。这个功能技术上不复杂,但界面效果好,答辩演示时很加分,而且能让系统看起来真的“智能”,而不只是一堆接口。
6.4 与前端联调时的跨域和鉴权
前后端分离模式下,跨域问题是绕不开的。我的处理方式是在Spring Boot里写一个CorsConfig配置类,本地开发时允许8080端口的Vue访问,生产部署时直接改成同源访问(前端静态文件丢到static下),避免跨域配置外泄风险。
鉴权用的是JWT方案,拦截器校验Token,Redis里存用户信息和过期时间。这里要提醒一句,如果不想在答辩时被追问安全问题,密码字段一定不要明文存储,用BCryptPasswordEncoder做哈希是底线。
7. 部署与实践中的那些坑,希望能帮你省下两周时间
代码写完到真正跑起来,中间还有一段路。以下踩坑记录,基本是我实际开发中浪费过时间的地方,值得单开一节讲。
7.1 Spring Boot版本选择别手滑
我一开始直接用了Spring Boot 3.0,结果发现MyBatis-Plus的兼容版本还没跟上,折腾了一晚上才换回2.7系列。如果你也打算做类似的系统,别盲目追新版本。毕业设计求稳,Spring Boot 2.7.x加上匹配的MyBatis-Plus版本最省心。新版本功能你用不上,生态兼容问题反而一堆。
7.2 Redis不可用导致启动失败
有次在部署环境上没开Redis,后端服务启动时直接报错。排查后发现是spring-boot-starter-data-redis的自动配置在启动时就会尝试建立连接。那段时间我为了解决它走了点弯路。后来改用懒连接方式,同时在application-prod.yml里设置了更长的超时时间,并在服务启动脚本里先确保Redis就绪,才彻底解决。
7.3 Vue打包后放进Spring Boot的细节
前端打包后,静态资源放在src/main/resources/static目录下。但有个坑:Spring Boot默认的静态资源路径虽然包含static,如果你用了@RestController且没有做SPA路由回退,刷新某个前端路由时会直接404。解决办法是写一个WebMvcConfigurer,把非/api/开头的请求都转发到forward:/index.html,让Vue Router自己处理路由。
7.4 推荐系统的冷启动问题怎么处理
新系统没有足够的历史数据,协同过滤部分就是空的,此时推荐结果完全依赖评分模型。我在代码里做了个开关:如果相似用户数量少于5,就自动把协同过滤权重降为0,只展示规则加评分的结果。这既是业务合理性要求,也是答辩时能讲清楚的算法边界问题。
7.5 数据导入要合理利用Excel工具类
毕业设计需要展示系统在真实数据下的表现,你不可能手动录入几百条专业规则。我用EasyExcel写了一个导入接口,支持上传固定格式的Excel文件,解析后批量写入university_major_rule表。这样不仅方便自己造数据,答辩时也能现场演示导入流程。算是个系统完整性的加分点。
8. 毕业设计论文里推荐算法一节的写作思路
代码写完只是第一步,论文怎么写同样关键。很多同学在论文里只会贴代码和截图,这是很吃亏的。推荐算法这节,我建议按这个结构写,层次清晰,也容易凑字数。
8.1 数据来源与预处理必须先说清
论文里要交代清楚你用了哪些数据、数据量多大、怎么清洗的。我写的是:采用某省教育考试院公开的选科要求数据和学校模考成绩脱敏数据,共整理得到有效专业规则425条、学生成绩记录3865条。把这些说清楚,后面算法的合理性才有依据。
8.2 对比实验怎么做才不虚
毕设不一定要做多复杂的对比实验,但至少要有对照组。我当时对比了以下三种方案。
- 纯规则匹配:不做评分排序,只看硬约束是否满足。
- 纯加权评分:不考虑相似用户。
- 规则加协同过滤混合推荐(最终方案)。
评价指标用推荐结果中“目标专业覆盖率”和“前N推荐组合命中率”。实验结果显示,混合推荐的覆盖率比纯规则高约18%,前5推荐命中率提升约9%。数据不惊艳,但足够证明混合策略的价值。
这一部分的写作技巧是:不要只写“效果好”,要把评价指标定义清楚,比如覆盖率公式写出来,再把实验结果做成柱状图放在论文里。图表和专业术语是答辩时最直观的加分素材。
8.3 系统边界和优化方向要老实写
论文最后总要提不足和展望。我诚实写了当前系统基于静态规则数据和历史模考成绩,缺少对高考实时政策数据的自动抓取能力;兴趣测评维度比较单薄,未来可以接入更细粒度的胜任力模型;协同过滤部分对冷启动用户依然不够友好。
这些内容看起来像“自曝缺点”,但恰恰能体现你的思考深度,比一味吹嘘系统完美要好得多。实战开发里,认清系统边界本身就是工程素养的一部分。
9. 毕设期间的时间安排建议
如果你决定做类似题目,我用自己踩过的节奏帮你排一个时间表,参考价值高,可按自己情况压缩。
| 阶段 | 周期 | 主要任务 |
|---|---|---|
| 需求分析和数据调研 | 第1-2周 | 确认业务流程,收集选科规则样例,定功能范围 |
| 数据库设计和原型 | 第3-4周 | 画ER图,建表,用Vue快速搭一个可点击的静态原型 |
| 后端基础模块 | 第5-6周 | 用户、学生档案、成绩、专业库、院校库的CRUD |
| 推荐引擎核心 | 第7-8周 | 规则过滤、评分模型、协同过滤、推荐结果生成 |
| 前端页面联调 | 第9-10周 | 核心页面数据对接、雷达图展示、推荐理由渲染 |
| 测试和文档 | 第11-12周 | 补测试用例、跑通完整流程、准备答辩PPT |
前期的数据调研千万不能省。有些同学数据还没想清楚就开写代码,结果表结构反复改,进度全线崩盘。我先花了两周梳理规则和找数据,后面写代码反而非常顺。
10. 写在最后:我在这个项目里最受益的几个决定
做这个毕设,我最大的感受是:真正有价值的不是“我用了Spring Boot”这个标签,而是怎么把业务逻辑转化成技术方案。混合推荐的设计就是典型的例子——它没有用多么高深的算法,却刚好解决了这个场景下的核心问题,而且每一个模块的设计都能被清楚解释。
如果让我再给几个具体的建议,大概是这么几条。一是不要把“系统管理”看成凑页面,用户角色权限、数据导入导出、日志记录这些模块虽然基础,但它们是答辩老师最容易观察到的工程素养。二是推荐结果一定要做解释,没有理由的推荐和拍脑袋没什么区别。三是一定要留出时间跑通完整部署流程,一个在本地IDE里能运行、但在服务器上打不开的项目,工程价值要大打折扣。
最后,选题的时候别贪大。我当时也犹豫过要不要加一个爬虫模块去自动抓取当年的招生计划,后来还是砍了。毕设的核心是能在有限时间内交付一个逻辑完整、技术落地、能讲清楚的项目。新高考智能推荐这个方向,本身已经足够撑起一篇有分量的本科毕业设计了。