心理健康评测系统这名字听着挺专业,拆开看本质就是一套“问卷分发-在线作答-自动计分-结果报告”的管理系统。但在SpringBoot里做这套系统,难点恰恰不在框架本身,而在如何把心理量表的专业逻辑翻译成代码。我帮好几个学弟改过类似的毕业设计,也给自己学校心理中心做过内部工具,发现大多数翻车现场都出在同一点:把评测题当普通问卷做,完全忽略计分规则和常模对照。
顺便说一句,项目标题里的“心里健康”其实是错别字,正确写法是“心理健康”。我第一次建表时也写错过,后面改字段名改得头疼。这种细节虽然不影响功能,但在文档和答辩PPT里出现就有点尴尬,大家留意一下。
做一个这样的系统,你会接触到的技术包括SpringBoot自动装配、Spring Security鉴权、MyBatis-Plus、MySQL表设计、Vue或Thymeleaf前端、ECharts可视化,再算上部署用的Linux和Nginx,几乎把Web后端开发的全链路摸了一遍。所以这篇既是项目复盘,也是一份可以直接照着做的实操笔记。适合准备做毕业设计的学生、想从CRUD进阶到业务逻辑开发的SpringBoot新手,以及想把学生心理普查数字化的学校信息化老师。
1. 系统需求与整体设计:评测类项目该先想清楚的事
1.1 需求拆解:心理普查的痛点在哪
心理测评系统不是为了“有个在线问卷”而存在,学校场景下的真实痛点非常具体,我列几个最常见的。
批量普查效率低。一个年级上千人,纸笔量表从发放、填写、回收、录入到统计,少说要一周。测评系统要做的是把“发放-填写-回收”压缩到一节课内完成,把“录入-统计-出报告”压缩到秒级。这决定了系统必须支持管理员一键发布测评任务、学生扫码或登录即可作答、提交后实时出分。
数据极度敏感。心理健康和考试成绩不一样,答题内容、量表结论属于个人隐私数据。系统必须有严格的角色权限、不能随意导出、不能公开排名。很多第一次做这类项目的同学容易忽略这个问题,把心理测试结果当成普通成绩单来展示,这在真实场景里是会出事的。
需要持续跟踪。一次测评说明不了问题,要看趋势。系统得支持周期复测,有历史记录,能画出变化曲线,能发现“连续几次都在高位”的学生。从产品角度说,历史轨迹比单次分数更有价值。
预警门槛低。大多数学生的问题不是一下子严重起来的,而是有一个累积过程。系统需要在结果超出阈值时自动通知辅导员或心理教师,而不是等人工去翻报表。预警功能是整个系统“有用”和“花瓶”的分界线,后面我会专门讲实现方案。
把需求想清楚,系统的模块划分就自然出来了:用户模块(学生/教师/管理员)、量表题库模块、在线测评模块、结果分析模块、预警通知模块、统计报表模块。模块之间用一条数据流串起来就是:题库配置 -> 答题 -> 算分 -> 入结果库 -> 触发预警 -> 生成报表。架构上没有任何花哨的地方,单体应用足够。
1.2 技术选型:为什么不搞微服务
很多同学一上来就想着Spring Cloud、Nacos、微服务拆分,这个问题我必须泼冷水。
这类测评系统的真实用户量级是一所学校几千人,并发峰值也就是几百人同时答题。单体应用加一台服务器完全能扛住,微服务没有任何收益,反而把部署复杂度和故障排查成本拉满。如果答辩时老师问“为什么不用微服务”,你就回答:项目定位是中小规模内部系统,单体架构在可维护性、部署成本和资源利用率上更优,同时保留了按业务边界拆分的扩展空间。这个回答本身就很有水平。
我建议的选型如下:
- Spring Boot 2.7.x,JDK 8或11。Spring Boot 2.7是当前中文社区资料最全、踩坑成本最低的版本。如果非要用3.x,就要跟着用JDK 17以上,很多老教程和示例代码都不兼容,对新手不友好。
- 鉴权走Spring Security + JWT。课程设计用Shiro也行,但Spring Security是市场主流,面试也常问,学一遍不亏。
- ORM用MyBatis-Plus。单表CRUD不用写SQL,复杂统计用注解SQL,内置分页和逻辑删除,小项目开发效率提升明显。
- 前端二选一:管理后台用Vue 3 + Element Plus做前后端分离,测评页面用H5适配手机;如果想省事,直接用Thymeleaf渲染整个系统也完全够用。后面的讲解按“前端Vue + 后端SpringBoot”分离方案,因为这才是现在企业里SpringBoot项目的普遍用法。
这里顺带聊聊SpringBoot为什么适合这类项目,核心就是自动装配机制。比如引入spring-boot-starter-web后,Spring Boot通过@EnableAutoConfiguration自动完成Tomcat和SpringMVC的配置,开发者不用再写一堆XML。这就是SpringBoot对比传统SSM框架最大的价值,约定大于配置。理解了这个原理,后面配置出错时你才能判断是哪里没被自动装配覆盖到,而不是瞎猜。
1.3 角色与权限设计
角色设计直接决定数据库表结构和接口权限控制,建议做三类角色。
学生:注册/登录、修改个人资料、参加测评、查看自己的历史报告、查看系统的心理科普文章和求助渠道。
心理教师/辅导员:查看名下学生名单、查看测评结果统计、查看预警名单、导出脱敏报表。这里注意辅导员和心理教师权限要有区分,辅导员只能看汇总数据和自己班级学生的预警状态,心理教师才有权查看详细答题记录,这个区分在合规上很重要。
系统管理员:用户管理、量表管理、题目管理、角色权限配置、系统参数设置。
权限控制的细节在实际编码中很容易漏。比如学生访问他人报告的接口时,不仅需要“已登录”和“有查看权限”,还必须校验数据归属,即当前登录用户是否是这份报告的所有者。只校验角色不校验数据归属,是这类系统最常见的安全漏洞。
2. 数据库设计与量表计分:全系统最核心的逻辑
2.1 核心数据表设计
我把核心表列出来,标注用途和关键字段,可以直接照抄。
sys_user用户表:id、username、password(BCrypt加密存储)、real_name、role(0学生/1教师/2管理员)、class_id、grade、phone、status、create_time。
scale量表定义表:id、name、description、type(标识scl90/sds/sas等)、question_count、dimension_json(维度定义)、score_rule(计分规则说明)、status。
question题目表:id、scale_id、content、dimension(所属维度)、sort_no、reverse_flag(是否反向计分)、max_score(该题最高分)。
evaluation_record测评记录表:id、user_id、scale_id、status(未完成/已完成)、start_time、submit_time、total_score、result_level、result_content。
evaluation_answer答题明细表:id、record_id、question_id、option_value(用户选择的分值)。
warning_record预警记录表:id、user_id、record_id、level(黄/橙/红)、reason、handle_status(待处理/已处理)、handle_user、create_time。
关于测评答题明细,字段设计上要存“选项分值”而不是只存选项ID,因为量表计分规则后续可能会调整。存了原始分值,重算历史成绩也不至于丢数据。另外,所有表都建议添加逻辑删除字段deleted,用MyBatis-Plus的全局配置处理,不要物理删除任何测评相关数据,这对审计和追溯非常关键。
索引方面,evaluation_answer表的record_id字段一定要加索引,否则测评记录详情页会很慢。evaluation_record表的user_id和scale_id也要建联合索引,支撑“查询某个学生所有历史测评”的高频操作。
2.2 量表类型与计分规则
这是评测系统和其他CRUD管理系统最大的差异点,也是答辩时最能体现专业度的地方。
课程设计里最常用的两个量表给大家介绍一下。
SCL-90症状自评量表:90题,测9个因子(躯体化、强迫症状、人际关系敏感、抑郁、焦虑、敌对、恐怖、偏执、精神病性)。每题1-5分,总分超过160分或任意因子分超过2分则提示阳性。这个量表题量大,作为生产用途没问题,但演示时用户会填到崩溃。
SDS抑郁自评量表:20题,每题1-4分,其中10题为反向计分题。标准分等于原始分乘以1.25后取整数。53-62分为轻度,63-72分为中度,72分以上为重度。这个量表长度适中,适合做在线演示。
计分实现时最容易出错的点就是反向题。反向题选项顺序和得分方向相反,比如“我觉得生活很有意义”选“没有或很少”应得4分,而“我觉得自己是个失败者”选“没有或很少”应得1分。如果不处理反向题,算出来的分数会整体偏移,甚至把需要重点关注的学生漏掉。
我这里给一张反向计分的转换对照表,一目了然:
| 原始选项值 | 5级计分反向转换 | 4级计分反向转换 |
|---|---|---|
| 1 | 5 | 4 |
| 2 | 4 | 3 |
| 3 | 3 | 2 |
| 4 | 2 | 1 |
| 5 | 1 | - |
转换公式就是maxScore加1再减去原始值。注意maxScore是当前题目的最高分,不是整个量表的最高分,写代码时不要搞混。
一个完整计分流程大致是:根据scale_id查题目列表;遍历答题明细,按reverse_flag判断是否反向转换;按dimension分组累加得到各维度分;计算维度得分率即维度实际分除以维度满分,作为雷达图数据;总分对照常模区间,判定结果等级。
2.3 常模对照与结果分级
“常模”这个词第一次做项目的人可能陌生,说人话就是:把受测者的分数和一个大规模人群的平均水平作比较。比如SDS标准分53分,单独看只是一个数字,对照常模才知道它刚好踩在“轻度抑郁”的线上。
数据库里建议建一张norm常模表,字段包括:id、scale_id、dimension、gender、age_range、min_score、max_score、level、suggest_content。
level建议分四级:正常、轻度关注、中度关注、重度预警。结果等级直接影响预警逻辑,所以在提交测评时就要算好并存入evaluation_record的result_level字段,不要每次展示时现算,否则历史数据一变,之前的结果也跟着变,这在数据审计上是大忌。
还有一点很重要,结果页面的说明文字要写得稳妥,不制造焦虑。建议明确标注“本报告仅作参考,不构成临床诊断,如有需要请咨询学校心理中心或专业机构”。这既是合规要求,也是对学生负责。把这条写进系统里,答辩时老师会认为你考虑问题全面。
3. 核心功能从零实现:登录、测评、报告全流程
3.1 项目初始化与基础配置
创建项目的方式有两种:一种是从start.spring.io直接生成然后导入IDEA,另一种是IDEA内置的Spring Initializr。注意IDEA内置的地址有时连不上,遇到这种情况可以把Server URL改到国内镜像地址,比如阿里云的start.aliyun.com,速度会快很多。
依赖选择上建议这样配:
- Lombok,必须,省掉大量getter和setter;
- Spring Web;
- Spring Security;
- MyBatis-Plus注意,它不在Spring Initializr的依赖列表里,需要手动在pom中添加;
- MySQL Driver;
- Validation校验框架。
application.yml里有几个关键配置,我直接写出来:
server: port: 8081 spring: datasource: url: jdbc:mysql://localhost:3306/psycho_survey?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0JDBC连接串里的serverTimezone=Asia/Shanghai是无数新手的第一个坑,不加就会报时区异常。字符集必须显式指定utf8,不要依赖默认。数据库建议统一使用utf8mb4,因为MySQL的utf8其实是utf8mb3,存emoji或生僻字时会报错。
MyBatis-Plus的逻辑删除配置可以省去大量手写update语句。需要注意的是,逻辑删除字段在实体类里要加上@TableLogic注解,这样MyBatis-Plus执行删除操作时才会自动转成update。
3.2 登录鉴权:Spring Security + JWT
Security加JWT这套组合第一次接触会感觉绕。我用一句话概括核心思路:登录成功后,后端签发一个JWT字符串,里面包含用户ID和角色,前端保存起来,之后每次请求在Header里带上Authorization: Bearer xxx,后端解析JWT就知道是谁在访问。
JWT工具类的大致结构如下:
@Component public class JwtUtil { @Value("${jwt.secret}") private String secret; @Value("${jwt.expire}") private Long expire; public String createToken(Long userId, String role) { return Jwts.builder() .setSubject(String.valueOf(userId)) .claim("role", role) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + expire)) .signWith(SignatureAlgorithm.HS256, secret) .compact(); } public Claims parseToken(String token) { return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); } }这里有几个坑要提醒一下。secret不能写死在代码里,要放到配置文件,且长度不要少于32个字符。JWT是无状态的,服务端无法主动把某个token作废,做退出登录时要么前端直接丢弃token,要么引入黑名单机制,后者实现成本较高,课程设计直接走前端删除方案即可。token里的claim不要存敏感信息,心理健康数据绝不能塞进JWT。
Spring Security核心配置是定义哪些路径放行、哪些路径需要登录,然后添加JWT过滤器:
@Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers("/api/auth/**", "/actuator/health").permitAll() .antMatchers("/api/admin/**").hasRole("ADMIN") .antMatchers("/api/teacher/**").hasRole("TEACHER") .anyRequest().authenticated() .and() .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } }前后端分离时必须把Security默认的登录页和CSRF都关掉,否则前端调接口时会莫名跳转到登录页。JWT过滤器在SpringSecurity里执行顺序很重要,它必须在UsernamePasswordAuthenticationFilter之前,因为要先把解析出来的用户信息放入SecurityContext,后续的授权判断才能拿到角色。
3.3 在线测评流程实现
在线测评是整个系统的主流程,建议按下面几个步骤来实现。
学生点“开始测评”,前端向后端请求量表信息。后端返回量表基本信息加题目列表。注意心理咨询量表没有“正确答案”和“错误答案”之分,只有倾向性,所以接口不需要做答案隐藏逻辑。
逐题作答还是整卷作答,我的建议是:SDS这种20题的量表可以整卷展示,SCL-90这种90题的必须分页展示,否则用户会被超长表单吓跑。分页展示时前端要加进度条,能明显降低中途放弃率。
提交答案的接口设计如下:
POST /api/evaluation/submit Content-Type: application/json { "scaleId": 2, "recordId": 18, "answers": [ {"questionId": 201, "optionValue": 2}, {"questionId": 202, "optionValue": 4} ] }服务端收到答案后需要做几件事:校验recordId归属当前登录用户,防止越权提交;校验题目数量是否等于量表题目数,防止漏答;在同一个事务里批量插入evaluation_answer,计算得分,更新evaluation_record的total_score和result_level,必要时插入warning_record。
得分计算的核心逻辑我写一个简化版本:
Map<String, Double> dimScoreMap = new HashMap<>(); Map<String, Integer> dimFullMap = new HashMap<>(); for (AnswerDTO a : answers) { Question q = questionMapper.selectById(a.getQuestionId()); int val = a.getOptionValue(); if (q.getReverseFlag() == 1) { val = q.getMaxScore() + 1 - val; } dimScoreMap.merge(q.getDimension(), (double) val, Double::sum); dimFullMap.merge(q.getDimension(), q.getMaxScore(), Integer::sum); } double totalScore = dimScoreMap.values().stream().mapToDouble(Double::doubleValue).sum();这里有个实际踩过的坑:反向计分的转换公式必须是当前题目的maxScore + 1减去原始值,而不是整个量表的满分加1再减。因为同一量表内不同题可能满分不同,用后者会把分数算错。
防止重复提交最简单可靠的做法是在evaluation_record上对user_id和scale_id加联合索引,配合status字段保证一个用户对同一量表只能有一条“已完成”记录。用Redis分布式锁在这个场景下有点杀鸡用牛刀,但如果项目里已经集成了Redis,顺手做一下也行。
3.4 测评结果展示与可视化
结果展示建议分三层来做,层次清晰也方便答辩讲解。
第一层是得分总览。大号数字展示总分和等级,配一句简短的人类话解读。比如SDS标准分58分、等级“轻度”,页面文案可以写“轻度抑郁状态,建议多与同学交流、规律作息,如需帮助可预约学校心理中心”。
第二层是维度图。用ECharts雷达图展示各维度得分率,比如SCL-90的9个维度一屏展示,哪个维度偏高一眼就能看出来。雷达图的数据就是上面计算得到的维度得分率。
第三层是历史趋势。同一量表多次测评的分值变化折线图。这个功能看起来不起眼,但对心理跟踪非常有价值,能呈现一个学生从入学到毕业的状态曲线,辅导员可以据此判断干预措施是否有效。
我这里可以提一个拓展点。如果报告需要打包下载,可以引入Hutool的PdfUtil或itextpdf生成PDF,模板套上ECharts导出的图表图片路径即可。如果项目集成了MinIO对象存储,图表图片等文件可以传到MinIO桶里,服务器重启后也不会丢。
3.5 预警通知:让系统真正“有用”的功能
系统做完测评、算出分数,还只是“半个系统”。真正的价值在预警,分数达到预警线的学生,系统要第一时间通知到责任人。
我建议按下面的流程实现:
测评提交后,后端判断result_level是否达到“中度关注”及以上。达到则插入warning_record,记录预警级别和触发依据。按用户所属班级找到绑定辅导员,发站内信并往辅导员的待办列表加一条待处理任务。如果对通知时效要求更高,可以接入邮件服务,用Spring Boot的starter-mail发送提醒。
在测评高峰期,预警通知动作可能会拖慢提交接口的响应。更优雅的做法是引入消息队列,比如ActiveMQ,把通知动作异步化。这个方案对毕业设计来说属于加分项,如果时间紧张可以不做,但能在答辩时把思路讲出来,会显得你考虑问题很有层次。
发送的内容本身要谨慎,只写“该学生近期测评结果需要关注,请在工作时间联系学生了解情况”,绝不能在站内信或邮件里写具体诊断性描述,防止信息泄露造成二次伤害。预警等级建议分黄、橙、红三级,黄色为仅记录、橙色需要辅导员3个工作日内处理、红色需要心理教师24小时内介入,这个分级在真实业务中也比较常用。
4. 部署与问题排查:让项目真正跑起来的最后一步
4.1 打包与发布
SpringBoot项目的发布产物就是一个可执行jar包。在项目根目录执行:
mvn clean package -DskipTests打包完成后target目录下生成xxx.jar,在服务器上可以直接运行:
java -jar psycho-survey.jar但是这样启动有个问题,一旦退出SSH终端进程就没了。生产环境建议用systemd托管,写一个service文件:
[Unit] Description=Psycho Survey Application After=network.target [Service] User=www WorkingDirectory=/opt/psycho-survey ExecStart=/usr/bin/java -Xms256m -Xmx512m -jar psycho-survey.jar Restart=always RestartSec=10 SuccessExitStatus=143 [Install] WantedBy=multi-user.target然后执行几个命令让它开机自启并立即运行:
systemctl daemon-reload systemctl enable psycho-survey systemctl start psycho-survey访问层面建议再挂一层Nginx做反向代理。一方面Nginx处理静态资源和并发连接的能力比Tomcat强,另一方面以后要加HTTPS证书时只需要改Nginx配置,不需要动SpringBoot代码。
很多同学拿到的“部署文档”里通常就包含这些内容,但具体到自己机器上还是会遇到各种环境差异。核心就一句:先把本地跑通,再上服务器,不要一上来就在服务器上调试代码。
4.2 常见启动问题排查
我把这些年见到的最高频问题整理成一张速查表,按现象排查效率最高。
| 现象 | 原因 | 解决方案 |
|---|---|---|
| 启动报Communications link failure | 数据库没起来或地址端口不对 | 先在本机用Navicat或命令行测试MySQL能否连接 |
| 启动报Unknown database | 数据库没建 | 执行CREATE DATABASE psycho_survey DEFAULT CHARACTER SET utf8mb4 |
| 报Access denied for user | 账号密码错误或远程无权限 | 确认密码或执行grant授权,注意MySQL 8.0的认证插件差异 |
| 报时区异常 | JDBC连接串没加serverTimezone | URL追加serverTimezone=Asia/Shanghai |
| 前端页面调用接口报跨域 | 前后端端口不同,没配CORS | 后端加CorsFilter或注解@CrossOrigin |
| 上传文件名乱码 | 字符集没统一 | 前端表单enctype、后端解析、存储全部统一utf8 |
| 打包的jar无法运行 | Maven没先clean或JDK版本不一致 | 保持本地和服务器JDK版本一致,重新clean package |
这些坑没有一个是有技术难度的,但每一个都能卡住你半天。我自己的习惯是启动前先看一遍完整的堆栈日志,重点关注Caused by那行,不要被前面一大段包装层干扰。线上环境还可以把日志级别调成INFO,避免debug日志太多淹没关键错误。
4.3 数据与隐私安全注意
心理健康评测系统的数据比普通业务数据敏感得多,我把这个问题单独放一节,因为它真的非常重要。
以下几点是必须做到的:
第一,用户密码用BCrypt加密存储,不要用MD5。Spring Security自带BCryptPasswordEncoder,直接用就行。MD5在如今的计算能力面前已经形同虚设。
第二,测评答题和结果数据在入库前可以考虑做AES对称加密,密钥放在配置文件里。虽然这会增加一点编码复杂度,但对这个业务场景是完全值得的。
第三,所有查询接口都必须校验当前登录用户是否有权访问这批数据。后端不能信任前端的任何参数,尤其不能因为前端没显示某个按钮就认为后端不需要校验。
第四,日志里不要打印答题明细。调试时把完整request body输出到控制台看着很爽,但一旦上线,这些日志里全是敏感信息。把这条路堵死比什么都强。
第五,定期备份数据库。哪怕只是每天凌晨crontab跑一次mysqldump,真出了事故能救命。我见过不止一次学弟辛辛苦苦做了几个月,数据库没备份,服务器重置后全没了。
4.4 性能优化:几百人同时答题的场景
按照1000人同时在线的规模来预估,压力主要集中在两个地方。
一是登录和鉴权。JWT无状态设计天然适合水平扩展,不需要额外优化。
二是提交答案时的数据库写入放大。SCL-90一次提交就要批量插入90条answer记录,1000人同时提交就是9万条插入。MySQL单机配置一般的话,这一下会有点压力。
解决办法有两个方向。第一个是批量写入,用MyBatis-Plus的saveBatch替代一条条insert,能明显降低与数据库的交互次数。第二个更彻底,把答题明细异步写入消息队列,主流程只做校验、算分、返回结果,明细落库交给队列消费者慢慢处理。第二种方案多数毕设用不上,但如果能把这个设计讲明白,在答辩时绝对是个高分点。
另外提醒一下,前端在测评页面加个“提交中”的状态,按钮置灰,防止用户手抖连点两次。这既是体验优化,也减轻了后端重复请求的压力。
5. 个人复盘与经验建议
这种项目做完给我最大的感受是:CRUD谁都会写,但把一个领域的专业逻辑翻译成代码,才是真正拉开差距的地方。
比如反向计分,没有认真读量表说明的人,第一次算出的分数要么整体偏高要么整体偏低,看起来“结果正常”,但其实毫无参考价值。常模对照也一样,不懂常模就不会告诉用户“你在同龄人中的位置”,只会把原始分直接抛出去。这些东西教材上讲得少,但工作中非常常见,因为每一个业务系统背后,都有一段你不在那个行业就根本不了解的规则。
所以如果你正在做这套系统,或者准备用SpringBoot做类似的业务项目,我的建议是先把业务规则吃透再动手写代码。把量表逐题过一遍,标出哪些是反向题;把自己当成用户跑一遍测评流程,看看每一步的提示和数据是否符合预期。这些在健壮架构之外额外花的功夫,才是项目真正能打动人的地方。
最后分享一个小技巧,如果时间充裕,可以在题库里加一道“演示量表”或“迷你量表”。因为SCL-90或SDS题目太多,现场演示效果不好,一道8到10题的简化量表能让评委在3分钟内完整体验整个流程,比你对着PPT讲五分钟架构有效得多。我后来帮学弟改项目时都会建议加上这个,效果谁用谁知道。