SpringBoot+Vue学生心理咨询评估系统:量表测评与权限控制毕设全解析
发布时间:2026/10/7 6:00:44 作者:尧图编辑部 阅读量:1,286

简介基于Springboot与Vue的学生心理咨询评估系统毕业设计源码包面向高校计算机相关专业毕业生及需要搭建心理健康服务平台的开发者。系统采用前后端分离架构覆盖用户注册登录、个人资料管理、SAS/SDS/EPQ心理测评、评估结果分析、咨询预约与记录管理等核心业务能支撑从基础学习、课程设计到二次开发的全过程。资源总计338个文件约19.99MB以70个Java后端源码、33个Vue前端组件、SQL数据库脚本、批处理启动脚本为主体同时包含开题报告、演示视频、PPT等辅助材料并附有旧版备份文件便于对照排错。丰富的SVG图标、JS配置及XML配置共同支撑起完整的前后端交互页面。目前已有107人学习下载。借助包内提供的部署脚本、开题文档与演示视频可快速跑通项目并理解代码组织方式对于需要完成类似课题的读者这套包含完整业务逻辑和数据库设计的资源能够帮助节省搭建时间同时掌握SpringbootVue整合的关键技术要点。1. 学生心理咨询评估系统一份能直接改的 SpringbootVue 毕业设计源码每年到了毕设季后台问得最多的就是“有没有前端后端都齐全、能跑起来、答辩能讲清技术点”的项目。学生心理咨询评估系统恰好卡在这个需求上它不像电商、图书管理那样烂大街业务上又能把量表测评、预警干预、角色权限这些点全部串起来属于典型的“看着不难、做起来全是细节”的选题。这份基于 SpringbootVue 的毕业源码前端是 Vue 全家桶后端是 Springboot 分层架构数据库用 MySQL特点是量表评分不是写死的常量而是通过表结构配置出来的提交测评后能自动生成评估报告咨询师端还有预约排班和干预记录。适合正在做毕设、想找个完整案例复现后二次开发的同学也适合刚转 Java 后端、想看看真实项目怎么组织代码的从业者。下面我按“先理解设计、再复现关键链路、最后避坑”的顺序拆给你。2. 能落地的前提架构、数据表设计与权限模型先对齐在写任何业务代码之前先把这项目的骨架搞清楚。这套系统的技术栈是 Springboot MyBatis-Plus MySQL Vue典型的前后端分离结构。后端不是传统的 JSP 页面而是纯 REST API前端是独立的 Vue 工程通过 Axios 调用接口。你拿到源码后第一件事不是急着跑而是看目录结构、看数据库脚本、看登录鉴权方式这三样对齐了后面改起来才顺手。2.1 前后端分离的两个工程目录结构与跨域约定后端工程一般是标准的 Maven 结构src/main/java下面按controller、service、mapper、entity、config分包。前端工程则是src/views下按角色学生端、咨询师端、管理端分页面目录src/api下放接口请求封装。两个工程是独立的开发时分别起在 8080 和 3000或 8081通过代理或者后端配置跨域来互通。# 后端启动默认端口 8080 mvn spring-boot:run # 前端启动默认端口 8081Vue CLI 项目 cd frontend npm install npm run serve后端接口统一以/api开头前端开发环境的代理配置在vue.config.js里。我一般会先确认后端有没有全局跨域配置类没有的话自己加一个不然前端页面能打开、接口全部被浏览器拦掉这是前后端分离项目最常见的开局翻车点。Configuration public class CorsConfig { Bean public CorsFilter corsFilter() { CorsConfiguration config new CorsConfiguration(); config.addAllowedOriginPattern(*); config.addAllowedMethod(*); config.addAllowedHeader(*); config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/**, config); return new CorsFilter(source); } }这个配置类的逻辑是允许所有来源、所有请求方法、所有请求头访问后端接口并允许携带 Cookie 或 Authorization 头。参数上你只需要关注addAllowedOriginPattern——生产环境就别用*了改成你的前端域名setAllowCredentials(true)和*不能同时用在addAllowedOrigin上这是 CORS 规范的硬性要求会直接导致浏览器拒绝响应。开发阶段图省事用上面的写法没问题部署上线前记得收窄。2.2 数据库设计量表不是写死的常量这个项目最值得看的数据表不是用户表而是量表相关的三张表量表表、题目表、选项表。我见过很多学生自己做测评系统把 SCL-90 的 90 道题直接塞进 Java 代码里每个选项对应的分值用 switch-case 判断——这样做的后果是换一个量表就要改代码重新编译答辩时老师一问“怎么扩展新量表”就答不上来。这份源码用的是配置化设计。表名核心字段作用scaleid, name, type, description, score_rule存储量表元信息如“SDS 抑郁自评量表”评分规则类型scale_questionid, scale_id, question_content, dimension, sort_order存储题目dimension 记录题目属于哪个因子维度scale_optionid, question_id, option_label, option_text, score_value存储每个选项的文本和对应分值assessment_recordid, user_id, scale_id, total_score, standard_score, level, report_url, status存储测评记录、评分结果和报告状态用这种方式设计后新增一个量表只需要往三张表里插数据后端一行代码不用改。题目和选项是两张独立表而不是用 JSON 字段存是因为测评报告要按维度聚合分析比如“躯体化”“强迫症状”“人际关系”各算各的分关系表查起来比解析 JSON 方便得多。评估记录表里的status字段我提醒一句它不只是“已完成/未完成”还承担了报告生成状态的标记比如0表示已提交未生成报告1表示生成中2表示完成这个状态机后面讲定时任务的时候会再提到。2.3 角色权限与登录状态JWT 和路由守卫各自负责什么系统里至少有三类角色学生、咨询师、管理员。学生能测量表、看自己的报告、预约咨询咨询师能查看分配给自己的学生、填写干预记录管理员管用户、管量表、管数据统计。这份源码的权限控制是前后端双层的——后端用 JWT 拦截器校验接口权限前端用 Vue Router 的全局前置守卫控制页面跳转。后端拦截器常见的写法是实现HandlerInterceptor在preHandle里校验请求头里的Authorization字段从 JWT 里解出用户 ID 和角色放行或抛出未授权异常。Component public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (OPTIONS.equals(request.getMethod())) { return true; } String token request.getHeader(Authorization); if (StringUtils.isBlank(token)) { throw new BusinessException(401, 未登录或登录已过期); } Claims claims JwtUtil.parseToken(token.replace(Bearer , )); request.setAttribute(userId, claims.get(userId)); request.setAttribute(role, claims.get(role)); return true; } }这段逻辑里最容易踩坑的是 OPTIONS 预检请求浏览器在跨域场景下POST 请求会先发一个不带 Authorization 头的 OPTIONS 请求来探测服务端是否允许跨域如果你直接校验 token预检请求会直接被拦掉前端报“CORS error”后端日志里什么都看不到。所以第一行必须放行 OPTIONS。parseToken方法内部就是Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody()密钥放在application.yml里统一管理别硬编码在代码中。前端路由守卫生效要记得在main.js里router.beforeEach注册否则你写了守卫代码但不生效用户直接改 URL 就能跳进管理页。3. 后端核心实现量表评分引擎与测评报告生成链路理解完表结构和权限模型就可以进到系统最核心的业务代码了。学生心理咨询评估系统的价值不在登录注册而在“测评—评分—报告”这条链路上。评分规则怎么配、标准分怎么算、报告怎么生成、数据怎么汇总到咨询师端这几个问题每个都是答辩时老师喜欢追问的细节也是你二次开发时必然会动刀子的地方。3.1 评分引擎从题目选项到总分和维度的聚合逻辑量表的计分方式分成两类正向计分和反向计分。比如抑郁量表里“我感到高兴不起来”这种题选“没有”得 1 分选“总是”得 4 分而“我对未来仍然抱有希望”这种题必须反过来选“没有”得 4 分。这份源码把“是否反向”作为题目表的一个字段is_reversed而不是让选项表里存死分值这样同一套选项模板可以复用。public ScaleScoreResult calculateScore(Long recordId) { ListAnswer answers answerMapper.selectByRecordId(recordId); MapString, Integer dimensionScores new HashMap(); int rawScore 0; for (Answer answer : answers) { ScaleQuestion question questionMapper.selectById(answer.getQuestionId()); ScaleOption option optionMapper.selectById(answer.getOptionId()); int score option.getScoreValue(); if (question.getIsReversed() 1) { score 5 - score; // 假设选项分值是 1~4反向题直接反转 } rawScore score; dimensionScores.merge(question.getDimension(), score, Integer::sum); } ScaleResult result new ScaleResult(); result.setRawScore(rawScore); result.setStandardScore(convertToStandard(rawScore, scale)); result.setDimensionScores(dimensionScores); return result; }这段代码的参数说明is_reversed是题目表里的 TINYINT 字段1表示反向计分scoreValue存在选项表里正向题和反向题共用同一套选项只是在计算时做了 5 减的转换。注意这里有个隐含约定选项分值必须是 1 到 4如果某个量表是 0 到 4 计分这个5 - score就得改成4 - score不然反向题目的分数全算错。dimensionScores用MapString, Integer聚合每个因子的得分因为不同量表的维度数量和名称完全不同——SCL-90 有 9 个维度SDS 只有 1 个维度写死成类字段是行不通的用 Map 才能做到通用。3.2 粗分转标准分SDS/SAS 的换算公式与预警判定很多量表不是直接拿原始分看的SDS抑郁自评量表和 SAS焦虑自评量表的粗分要乘以 1.25 后取整数部分才得到标准分。标准分再对照中国常模SDS 标准分 53~62 为轻度抑郁63~72 为中度72 以上为重度SAS 标准分 50~59 轻度60~69 中度69 以上重度。这个换算逻辑在源码里是独立成方法而不是写在 Controller 里的说明作者的分层意识是到位的。public int convertToStandard(int rawScore, Scale scale) { double factor scale.getScoreFactor(); // 存表里SDS/SAS 是 1.25其它量表是 1.0 double standard rawScore * factor; int result (int) Math.floor(standard); if (result scale.getMinStandard()) { result scale.getMinStandard(); } return result; }floor是向下取整这是和“四舍五入”最大的区别很多手工核对分数对不上的案例就是错在这里。scoreFactor我建议也存进量表表而不是写死在代码里因为不同量表的换算系数不同未来的扩展不是按“量表类型”判断而是按“表的配置”判断。换算完标准分之后就是预警判定标准分超过阈值时系统会自动生成一条预警记录同时给咨询师端发一个待办任务这是测评系统“评估”能力和“干预”能力的分界线也是你写进论文里的一个亮点功能。3.3 咨询预约与排班冲突事务和锁的正确使用场景测评之后自然会连到咨询预约学生选择咨询师、选择时间、提交预约。这里最典型的并发问题是两个学生同时选了同一个咨询师的同一个时间段如果不做控制两张订单都能提交成功。源码里用的是数据库唯一索引加事务解决而不是在 Java 代码里加 synchronize 锁——因为单机锁在集群部署下不起作用而唯一索引是数据库层面的硬约束。ALTER TABLE appointment ADD UNIQUE KEY uk_consultant_slot (consultant_id, slot_start_time);这条 SQL 的意思是同一个咨询师consultant_id在同一个开始时间slot_start_time只能有一条预约记录。Java 侧要做的是捕获这个唯一索引冲突异常转成“该时间段已被预约”的友好提示而不是让用户看到 500 页面。事务要加在“写入预约记录 更新咨询师排班表”这个组合操作上用Transactional注解注意事务只对 RuntimeException 回滚如果你抛的是自定义的 checked exception需要显式标注rollbackFor否则会出现预约记录回滚了、排班状态却更新了的灵异情况。4. 前端从 Login 到 ReportVue 页面设计与接口联调后端接口通了前端才能真正跑起来。这套系统前端的核心页面分布大概是学生端有登录注册、量表列表、测评答题页、报告详情页、预约咨询页咨询师端有待办列表、学生档案页、干预记录填写页管理员端是用户管理、量表管理、数据统计。我按“改起来最费劲、答辩最容易被问”的三个点来拆路由规划、动态表单渲染、Axios 封装。4.1 路由规划按角色拆文件权限守卫只做前端拦截Vue Router 的配置不建议把几十个路由全部堆在index.js里这套源码的做法是按模块拆modules文件夹每个角色一个路由文件。学生端的路由挂在/student路径下咨询师端挂在/consultant下管理员端挂在/admin下。守卫逻辑里判断角色和路由 meta 里的角色是否匹配不匹配就重定向到登录页。const router new VueRouter({ mode: history, routes: [ { path: /login, component: Login }, { path: /student, component: StudentLayout, meta: { roles: [student] }, children: [ { path: scale-list, component: ScaleList }, { path: answer/:scaleId, component: AnswerPage }, { path: report/:recordId, component: ReportPage } ] } ] }) router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (!token to.path ! /login) return next(/login) const role localStorage.getItem(role) if (to.meta.roles !to.meta.roles.includes(role)) return next(/login) next() })这里最关键的参数是meta.roles它是路由级的角色标记放在子路由上而不是父路由上因为你希望同一个布局下的某些页面只允许特定角色访问。这段代码的问题是history模式——打包部署后如果服务器没有配 fallback 规则刷新页面会直接 404这个坑我放到避坑章节里细说。还有个细节是localStorage.getItem(role)这种取值方式太脆弱用户改了 localStorage 就能越权但毕设项目里做到这个程度基本够答辩了生产级要用动态路由表从后端拉。4.2 答题页组件化动态渲染题目与答题进度控制答题页是前端代码里最体现 Vue 功底的部分。量表题目数量不定从 20 题到 90 题都有选项数量也不定有 4 选、5 选。页面得根据后端返回的量表配置动态渲染不能用死模板写死 20 个 radio。源码里的做法是页面加载时先请求量表详情接口拿到题目列表和选项列表后循环渲染。template div v-for(question, index) in questions :keyquestion.id h4{{ index 1 }}. {{ question.content }}/h4 el-radio-group v-modelanswers[question.id] el-radio v-foropt in question.options :keyopt.id :labelopt.id {{ opt.text }}/el-radio /el-radio-group /div /template script export default { data() { return { questions: [], answers: {}, currentStep: 0, stepSize: 10 } }, methods: { async fetchScale(scaleId) { const res await this.$api.getScaleDetail(scaleId) this.questions res.data.map(q ({ ...q, options: q.options.sort((a, b) a.sortOrder - b.sortOrder) })) }, canSubmit() { return this.questions.every(q this.answers[q.id] ! undefined) } } } /scriptanswers是一个以题目 ID 为 key、选项 ID 为 value 的对象提交时直接把这个对象发给后端后端逐题回表查分值。用el-radio-group的v-model绑定对象属性是 Vue 2 的一个小陷阱——如果你直接给this.answers[q.id]赋值必须保证这个 key 已经在data里初始化过或者用this.$set()否则不会触发视图更新。stepSize是分页答题的步长10 题一页防止 90 道题一次性渲染导致页面卡顿这个参数取决于你实际使用的量表题量题量少的话可以不分页。还可以拓展一个功能currentStep没到最后一页时前端拦截“上一题”和“下一题”的校验。4.3 Axios 封装拦截器处理 token 和错误提示这套系统的所有请求都走一个统一的 Axios 实例而不是每个页面各自引入 axios 再配 baseURL。统一封装的好处有三个自动带 token、统一处理 401 跳登录、统一弹错误提示。代码量不大但是收益最大。// src/api/request.js import axios from axios import { Message } from element-ui import router from /router const service axios.create({ baseURL: process.env.VUE_APP_BASE_API, timeout: 15000 }) service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] Bearer token } return config }) service.interceptors.response.use( response { const res response.data if (res.code ! 200) { Message.error(res.message || 请求失败) return Promise.reject(new Error(res.message)) } return res }, error { if (error.response error.response.status 401) { localStorage.removeItem(token) router.push(/login) } Message.error(error.message || 网络异常) return Promise.reject(error) } ) export default serviceVUE_APP_BASE_API是环境变量在.env.development和.env.production里配置不同的值这样开发环境走代理、生产环境走真实地址切换环境不用改代码。注意这个拦截器对响应做了两层判断HTTP 状态码是否为 2xx由 Axios 的validateStatus决定是否走 error以及业务状态码res.code是否为 200。很多同学只判断第二层导致 HTTP 500 时前端拿到的是 Axios 包装过的一长串英文文本而不是后端返回的业务提示。Message.error的调用位置也很关键——放在拦截器里统一弹页面代码里就不用每个方法都 try-catch 弹提示了。5. 避坑与排查学生测评系统最常见的六个运行问题跑通一条主链路容易把坑踩平了才是这个系统能稳定演示的前提。我按自己实际复现这类项目时踩过的坑结合源码里最容易出问题的地方整理了六条高频异常按“现象 → 原因 → 解决”给你梳理清楚你在答辩前自查一遍能省很多折腾。5.1 问题一提交量表答案后接口报 500,日志出现 ClassCastException现象答题页所有题目都选完了点提交前端提示“服务器内部错误”后端日志里有一长串异常核心信息是java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String。原因后端接收答案的 DTO 里把questionId或optionId定义成了 String 类型而前端从v-model拿到的值是数字。JSON 反序列化时 Jackson 做了类型转换但类型对不上就报转换异常。这类问题在联调阶段最隐蔽因为前端自测时数据一切正常只有真实接口请求才会暴露类型不匹配。解决打开后端的答案 DTO 类把questionId和optionId的字段类型改成Long或 Integer前端answers对象的值在提交前也用Number()强制转一次。更保险的做法是在 DTO 字段上加上JsonFormat注解或者统一前后端都用字符串传 ID哪个方向定下来就不要改。5.2 问题二前端能打开页面但所有接口都报 CORS error现象npm run serve启动前端页面渲染正常登录接口的请求发出去了Network 面板显示请求 OK但浏览器控制台报Access-Control-Allow-Origin相关错误Vue 里 catch 到的是 “Network Error”。原因后端是 8080 端口前端是 8081 端口浏览器跨域拦截。启动后端时如果 IDEA 里跑的是旧代码后端还没加载CorsConfig这个配置类或者加载了但被 Spring Security 的过滤器链抢先拦截了跨域头就不会出现在响应里。解决先确认后端启动日志里有没有扫到CorsConfig再用 Postman 直接请求后端接口看响应头里有没有Access-Control-Allow-Origin字段。如果确认被 Spring Security 拦了在 Security 配置里放行 OPTIONS 请求并显式加上跨域配置光靠独立的 CorsFilter 在 Security 的过滤器链中默认是无效的。开发环境更省事的方案是前端配代理vue.config.js里设置devServer.proxy让前端请求走同源路径彻底绕过跨域。5.3 问题三学生重复点击提交按钮测评记录插入了两条现象答题页点提交后因为后端响应慢学生又点了两次数据库里出现两条内容完全一样的测评记录但学生只做了一次题。原因前端提交按钮在请求发出后没有加 loading 或 disabled 状态后端也没有做幂等校验。这是测评系统最不该踩的坑——重复的记录会直接污染评分统计。解决前端按钮加loading属性请求期间禁用按钮后端在插入记录前先查一下该学生、该量表、最近五分钟内是否已经有完成状态的记录。更规范的做法是在assessment_record表加一个业务唯一键比如(user_id, scale_id, create_time)的联合唯一索引但时间字段精度不够时会误伤所以实用方案就是 Redis 或数据库层面的幂等表。5.4 问题四标准分总是比手工算的少一分现象拿 SDS 量表实测学生答完题后端算出的标准分和用原始分乘以 1.25 手算的结果不一样系统结果总是少 1 分或 2 分。原因Math.floor(rawScore * 1.25)的结果是向下取整手算时大多数人会习惯性四舍五入到整数。SDS 原始分 50乘以 1.25 是 62.5向下取整是 62四舍五入是 63正好差 1 分。这不是 Bug是评分标准里“取整数部分”这个描述被实现了两次。解决先确认你用的量表常模标准是什么——SDS 的判定标准是按“标准分的整数部分”还是“四舍五入”。记住一旦定准了用floor全系统的分数计算就要保持一致包括后台管理端数据统计里的分数表不能一个地方 floor、另一个地方四舍五入。建议写单元测试直接覆盖边界值比如原始分 40、52、62 各算一次防止后续改动把规则带偏。前端报告页展示的分数线和后端判定要一致这里最容易出现“报告显示正常范围但后端预警已触发”的矛盾。5.5 问题五前端打包放进后端刷新页面 404现象npm run build之后把dist文件夹放进 Springboot 的src/main/resources/static下启动后端访问首页正常但点击路由跳转后再刷新页面变成白页或 404。原因前端用了 Vue Router 的history模式路由路径是真实的 URL如/student/scale-listSpringboot 默认只处理静态资源请求这个路径在后端找不到对应的静态文件就返回 404 了。解决两种方案任选。方案一前端路由改成hash模式URL 变成/student/scale-list#/report/1刷新不会丢失但 URL 不够美观方案二后端加一个 Controller 做路径转发把非/api开头的所有请求都转发到index.html。毕设答辩更建议方案二因为管理端嵌入的图表和数据接口都在/api下转发规则写得干净就不影响接口访问。注意转发时不要拦截静态资源路径否则 js/css 文件会被错误地返回 HTML 内容浏览器控制台会报Unexpected token 之类的解析错误。5.6 问题六定时任务误删了未完成的报告生成任务现象系统里配了一个定时任务每天凌晨清理“过期”的测评记录结果第二天咨询师发现多个学生的报告状态变成了“已取消”但实际数据并没有被删除——因为测评记录表和报告生成任务表有外键删不掉但状态被改了。原因定时任务的清理逻辑判断的是record.status 0已提交未生成报告本意是清理超过 24 小时没生成报告的僵尸记录。但开发时手动测试过报告生成接口把记录状态改成了 0 后没跑完成就退出了第二天定时任务全给标记成异常误伤了正常数据。解决定时任务的筛选条件要加时间边界只处理create_time超过 24 小时且状态仍为 0 的记录同时在报告生成链路里加一个“生成中”的中间状态并设置超时阈值不支持把一个状态同时用作“排队中”和“失败”。从那以后我每次给这类系统加定时任务都会强制把状态机和补偿逻辑完整走一遍不再依赖“看起来不会出问题”。6. 一个可以马上升级的点报告生成进度用 SSE 推给前端如果你想让这个系统在答辩或实际演示时多一个“能讲”的亮点我建议给报告生成页面加上 SSE 实时进度。测评报告不是瞬间生成的——要聚合多个维度的分数、查常模对照表、拼 HTML 模板耗时基本在 1 到 3 秒之间。在用户毫无反馈的空白页等待是很差的体验而使用 SSEServer-Sent Events服务端单向推送进度实现成本远低于 WebSocket是这类轮询式进度展示的最优解。后端在提交测评后返回recordId前端拿到后立刻建立一个 SSE 连接服务端按步骤推送百分比。// 前端建立 SSE 连接 const eventSource new EventSource(/api/report/progress/${recordId}) eventSource.onmessage (event) { const data JSON.parse(event.data) this.progress data.progress // 0~100 this.currentStep data.message // 如 正在计算维度得分 if (data.progress 100) { eventSource.close() this.goToReport(recordId) } }// 后端 SSE 推送 GetMapping(value /report/progress/{recordId}, produces MediaType.TEXT_EVENT_STREAM_VALUE) public SseEmitter getProgress(PathVariable Long recordId) { SseEmitter emitter new SseEmitter(0L); ReportProgressTask task reportProgressService.getTask(recordId); task.setEmitter(emitter); // 模拟推三段进度 emitter.send(SseEmitter.event().data(new ReportProgressVO(20, 题目答案解析中))); emitter.send(SseEmitter.event().data(new ReportProgressVO(60, 维度分数聚合中))); emitter.send(SseEmitter.event().data(new ReportProgressVO(100, 报告生成完成))); emitter.complete(); return emitter; }这段代码的逻辑说明SseEmitter是 Springboot 针对 SSE 的原生支持0L表示超时时间不限制——这里要小心生产环境不能真设 0否则连接不释放会占满线程建议改成 60000L60 秒。EventSource是浏览器原生对象不需要引入额外依赖比 WebSocket 在毕设场景里友好很多因为它天然支持断线重连服务端挂了客户端会自动重新建立连接。注意 SSE 只支持 GET 请求所以前端需要把recordId拼在 URL 上不能放在 request body 里——这个限制很容易忘用 Postman 测试时会疑惑为什么参数读不到。如果你在 Spring Boot 2.x 上用还需要确认项目中是否引入了spring-boot-starter-webSSE 依赖就在这个基础包里不需要额外加依赖。验证方式很简单提交一份量表测评后打开浏览器 Network 面板找到progress请求看响应类型是不是text/event-stream再看是否有三段data消息按顺序返回。前端收到 100 后跳转报告页这个时序要和后端推送的完成时机对齐——建议在报告落库之后再emitter.complete()否则会出现进度条走完了、报告页却打开一片空白的情况。从那以后我每次做这类带耗时任务的页面都会先确认“任务完成”的事件到底是从哪个端发出的而不是接口一返回就急着刷新页面。这套基于 SpringbootVue 的学生心理咨询评估系统从量表配置、评分计算、报告生成到预约干预链路完整且每层都有值得展开讲的技术点是典型的“一个项目覆盖多个知识点”的毕业设计选题。你拿到源码后我建议按这个顺序复现先导数据库再起后端然后起前端登录后跑通一个量表的完整测评流程确认报告能正常生成接着重点改两个地方练手——新增一个量表以及给报告页加 SSE 进度提示这两个改动能让你对这套系统的理解远超普通照抄的同学。希望这篇拆解帮到你祝答辩顺利。本文还有配套的精品资源点击获取