高校心理咨询管理平台是计算机毕业设计里出现频率极高的一类选题,但真正能跑通、能讲清楚的并不多。大部分同学交上去的东西,本质是学生表和咨询师表的增删改查,测评是写死的几道题,预约是两个下拉框一选,论文里再配几张 E-R 图,答辩时被问一句“你这个危机预警是怎么触发的”就卡住了。这篇文章不聊虚的,直接从一套可运行的 SpringBoot 心理咨询平台该长什么样讲起,把预约冲突检测、量表计分、危机分级、角色权限隔离这几个真正能撑住答辩的技术点拆开讲,同时给出部署命令、接口示例和排错清单。
先说结论:这套系统的技术门槛不高,一台普通的开发笔记本就能跑起来,MySQL 加 SpringBoot 单体应用,不需要 GPU,不需要消息中间件,内存控制在合理范围内就很流畅。但它对“业务设计”的要求比一般的管理系统高,因为它处理的是敏感个人信息,权限、脱敏、操作留痕这三件事做不好,代码写得再花哨也不成立。
本文会按“能力速览 → 场景边界 → 架构选型 → 数据库设计 → 核心算法 → 部署启动 → 接口调用 → 排错 → 论文写法”的顺序展开。适合正在做这个题目的大三大四同学,也适合想拿一套学生管理系统练手 SpringBoot 的开发者。看完之后你应该能做到三件事:本地把服务跑起来、把测评和预约两条主链路走通、知道论文里哪些部分必须写实。
1. 核心能力速览
先把这套系统能干什么、需要什么条件,用一张表讲清楚。表里的参数分两类:一类是通用技术约束,可以直接作为设计依据;另一类是具体实现细节,需要按你实际选用的依赖版本调整。
| 能力项 | 说明 |
|---|---|
| 系统类型 | B/S 架构 Web 管理系统,前后端分离或服务端渲染均可 |
| 后端技术 | Java + SpringBoot,配合 MyBatis-Plus 或 JPA 做持久层 |
| 前端技术 | Vue + Element UI,或 Thymeleaf 服务端模板 |
| 数据库 | MySQL 5.7 或 8.x,字符集建议 utf8mb4 |
| 缓存/会话 | 可选 Redis,用于 Token 缓存和排班时段锁 |
| 角色模型 | 学生、咨询师、管理员三类主体,权限粒度到接口 |
| 核心模块 | 预约管理、心理测评、咨询记录、危机预警、文章科普 |
| 是否支持 API | 是,RESTful 风格,需登录态鉴权 |
| 是否支持批量任务 | 是,可通过定时任务批量生成排班、批量导出报表 |
| 硬件门槛 | 普通办公本可跑,无需独立显卡 |
| 启动方式 | mvn spring-boot:run或打包成 jar 后java -jar |
| 部署形态 | 单机单体优先,答辩演示不需要集群 |
| 适合场景 | 毕业设计、课程设计、Java Web 综合实训 |
| 不适合场景 | 真实生产环境的心理干预服务(需专业资质与合规评估) |
有一点必须提前讲明白:心理咨询业务涉及健康类敏感信息,这套系统作为教学项目,功能设计可以参考真实流程,但不能直接拿去做真实咨询业务的线上承载。如果要在真实场景使用,需要做等保评估、数据加密、知情同意、数据留存周期约定和危机转介机制,这些不是毕设范围内能覆盖的。
从材料看,这类题目的常见交付物是“源码 + 数据库脚本 + 开题报告 + 毕业论文 + 演示视频”。所以本文除了讲技术实现,也会在最后一章专门讲论文和开题报告怎么写实,因为这部分往往是失分最多的地方。
2. 适用场景与使用边界
2.1 这套系统解决什么问题
高校心理中心的真实痛点是排期和信息割裂。学生想预约要跑办公室,咨询师排班靠 Excel,测评结果散落在纸质问卷里,危机个案发现往往是滞后的。一套管理平台的价值就在于把这三条链路打通:学生在线预约、咨询师在线排班、管理员在线看统计。
具体到功能上,通常包含这些:
- 学生端:注册登录、浏览咨询师、在线预约、填写测评量表、查看自己的测评报告和历史预约、阅读心理科普文章。
- 咨询师端:维护个人简介和擅长方向、设置可预约时段、查看待处理预约、填写咨询记录、处理危机预警。
- 管理员端:账号管理、咨询师审核、排班总览、量表题库维护、统计报表、系统日志审计。
2.2 谁适合用它
第一类是做毕业设计的同学。这套系统覆盖的知识点足够完整:权限控制、事务处理、分页查询、文件上传、定时任务、接口设计,答辩时能问的点很多。
第二类是带课的老师。作为 SpringBoot 综合案例,它比图书管理系统更有业务纵深,时间区间冲突、量表计分、状态机流转这些内容能讲出东西。
2.3 不适合什么场景
不要把它当成真实心理服务平台的替代品。心理测评量表有版权,常用的 SDS、SAS、SCL-90 等在商用或公开场景下需要授权,毕设里自己编写的“简化版问卷”可以用于演示,但论文里必须说明是自行设计的模拟量表,不能声称具备临床诊断效力。
另外,系统里如果存放了真实的学生姓名、学号、咨询内容,就构成了个人信息处理行为。教学演示建议全部使用虚构数据,或者对姓名和学号做脱敏处理。咨询记录字段如果需要展示,可以做内容掩码,比如只显示首尾字符。
还有一点是危机干预的边界。系统可以做“风险提示”,可以做“建议转介”,但不能输出“该学生患有抑郁症”这类诊断结论。技术上要做的就是分级提示和人工复核入口,把判断权交给人。
3. 技术选型与整体架构
3.1 分层结构
SpringBoot 项目最忌讳把所有逻辑塞进 Controller。推荐的分层是经典的四层:
controller -> 接口层,负责参数校验、鉴权注解、统一返回 service -> 业务层,负责事务、状态机、算法计算 mapper/dao -> 持久层,MyBatis-Plus 或 JPA entity/dto -> 数据对象,实体与传输对象分离实体和 DTO 分离这件事在毕设里很容易被省略,但一旦接口需要返回“咨询师信息 + 剩余可约时段”这种组合数据,直接暴露实体类就会带来字段冗余和越权风险。建议至少把登录请求、预约请求、测评提交这三类做成独立 DTO。
3.2 依赖清单示例
下面是后端pom.xml的核心依赖结构,版本号用属性占位,实际按你本地的父工程或 SpringBoot 版本管理调整:
<properties> <java.version>17</java.version> <mybatis-plus.version>3.5.5</mybatis-plus.version> <jjwt.version>0.11.5</jjwt.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>${mybatis-plus.version}</version> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>${jjwt.version}</version> </dependency> </dependencies>如果选的是 SpringBoot 3.x,MyBatis-Plus 需要换成mybatis-plus-spring-boot3-starter,这是最容易踩的坑之一,用错 starter 启动时会报自动配置类找不到。JDK 版本也要对齐,SpringBoot 3 要求 JDK 17 及以上。
3.3 鉴权方案
毕设场景下,JWT 是性价比最高的选择:无状态、易演示、答辩时能画出流程图。做法是登录成功后签发 Token,前端存到本地,后续请求放在Authorization头里,后端用拦截器统一校验并解析出用户 ID 和角色。
角色隔离不要只在前端做菜单隐藏。后端接口必须用注解或拦截器做二次校验,否则学生改一下请求参数就能删别人的预约。这是答辩时经常被追问的点。
4. 数据库设计与核心表结构
4.1 核心表清单
一套完整的心理咨询平台,表数量通常在 10 到 15 张之间。核心的几张是:
| 表名 | 作用 | 关键字段 |
|---|---|---|
sys_user | 统一账号表 | 用户名、密码哈希、角色、状态 |
psy_student | 学生扩展信息 | 学号、院系、年级、紧急联系人 |
psy_counselor | 咨询师扩展信息 | 资质、擅长方向、评分、最大接待量 |
psy_schedule | 可预约排班 | 咨询师 ID、日期、时段、是否已被占 |
psy_appointment | 预约记录 | 学生、咨询师、起止时间、状态 |
psy_scale | 量表定义 | 名称、题目数、计分规则 |
psy_question | 题目 | 量表 ID、题干、选项、分值 |
psy_answer_record | 测评记录 | 学生、量表、粗分、标准分、等级 |
psy_record | 咨询记录 | 预约 ID、摘要、随访建议 |
psy_warning | 危机预警 | 学生、来源类型、风险等级、处理状态 |
把账号信息和角色扩展信息拆开,是为了避免sys_user变成一张几十个字段的大宽表。这个设计在论文里可以用一节讲“第三范式与适度反范式的权衡”,是加分项。
4.2 建表 SQL 示例
预约表是整套系统里最需要设计索引的表,因为冲突检测查询非常频繁:
CREATE TABLE `psy_appointment` ( `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', `student_id` BIGINT NOT NULL COMMENT '学生ID', `counselor_id` BIGINT NOT NULL COMMENT '咨询师ID', `start_time` DATETIME NOT NULL COMMENT '开始时间', `end_time` DATETIME NOT NULL COMMENT '结束时间', `status` TINYINT NOT NULL DEFAULT 0 COMMENT '0待确认 1已确认 2已完成 3已取消 4已拒绝', `topic` VARCHAR(200) DEFAULT NULL COMMENT '预约主题', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_counselor_time` (`counselor_id`, `start_time`, `end_time`), KEY `idx_student_status` (`student_id`, `status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='预约记录表';注意状态字段用数字而不是字符串,是为了减少存储并且方便走索引。但数字状态在代码里必须配一个枚举,否则半年后回来看代码,你也不知道 3 是取消还是拒绝。
测评记录表的粗分和标准分要分开存,标准分是算出来的结果,存下来可以避免每次翻页都重算,也方便后续做统计图表:
CREATE TABLE `psy_answer_record` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `student_id` BIGINT NOT NULL, `scale_id` BIGINT NOT NULL, `raw_score` INT NOT NULL COMMENT '粗分', `standard_score` INT NOT NULL COMMENT '标准分', `risk_level` TINYINT NOT NULL DEFAULT 0 COMMENT '0正常 1轻度 2中度 3重度', `answer_json` TEXT COMMENT '原始作答内容', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_student_scale` (`student_id`, `scale_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='测评作答记录';answer_json用 TEXT 存原始作答,是典型的“适度反范式”。正常应该拆一张答案明细表,但毕设场景下明细表会让查询变复杂,用 JSON 存整份作答、只把分数拆出来建索引,是更实用的折中。
5. 核心功能与关键算法实现
这一章是全文的重点。前面几张表只是壳子,真正让系统有技术含量的是下面这几个点。
5.1 预约时间冲突检测
最常见的错误写法是先查后插:
// 错误示范:存在并发漏洞 int count = mapper.countByCounselor(counselorId); if (count == 0) { mapper.insert(appointment); }单机演示可能看不出问题,但只要两个人同时点提交,就会出现同一时段被约两次。正确做法是“条件查询 + 数据库唯一约束兜底”,查询条件用区间重叠判断:
public boolean hasConflict(Long counselorId, LocalDateTime start, LocalDateTime end) { LambdaQueryWrapper<Appointment> qw = new LambdaQueryWrapper<>(); qw.eq(Appointment::getCounselorId, counselorId) .ne(Appointment::getStatus, AppointmentStatus.CANCELED.getCode()) .lt(Appointment::getStartTime, end) // 已有开始时间 < 请求结束时间 .gt(Appointment::getEndTime, start); // 已有结束时间 > 请求开始时间 return appointmentMapper.selectCount(qw) > 0; }区间重叠的判定逻辑值得在论文里单独写一段。两个时间段[A1, A2]和[B1, B2]重叠的充要条件是A1 < B2 且 A2 > B1,注意是严格小于和严格大于,因为前一场 10:00 结束、后一场 10:00 开始不算冲突,这样排班才能做到无缝衔接。
事务边界也要收紧,在@Transactional方法里先做冲突查询再插入,并且把隔离级别保持默认的可重复读即可。如果对并发要求更高,可以在 Redis 里对counselorId + 时段做短时锁,但毕设场景下没必要,讲清楚思路比堆技术更重要。
5.2 量表计分算法
以常见的自评量表为例,计分流程通常是:每题按 1 到 4 级评分,正向题和反向题分开累加得到粗分,再乘以系数得到标准分。下面是简化后的计分器:
public class ScaleScoreCalculator { /** 粗分转标准分,系数按量表说明配置,示例为 1.25 */ public static int toStandardScore(int rawScore, double factor) { return (int) Math.floor(rawScore * factor); } /** 按阈值划分等级,阈值来自量表定义表,不要硬编码在代码里 */ public static RiskLevel level(int standardScore, ScaleThreshold t) { if (standardScore < t.getMild()) { return RiskLevel.NORMAL; } else if (standardScore < t.getModerate()) { return RiskLevel.MILD; } else if (standardScore < t.getSevere()) { return RiskLevel.MODERATE; } return RiskLevel.SEVERE; } }两个工程细节要强调。第一,系数和阈值必须从数据库读,不能写死在 Java 里,因为不同量表规则完全不一样,写死之后每加一个量表就要改代码重新打包。第二,反向计分题必须标记出来,题目表加一个reverse_flag字段,计算时用满分 + 1 - 原始选项值做翻转,否则整套分数会失真。
作答完整性校验也要做。提交上来如果题目数量对不上,直接返回参数错误,不要算出一个缺题的分数,那比不返回还糟糕。
5.3 危机预警分级
预警不是靠一个关键词就报警。比较稳妥的设计是“多信号加权”:
| 信号来源 | 示例 | 权重建议 |
|---|---|---|
| 量表等级 | 标准分落入重度区间 | 高 |
| 主观描述 | 预约备注中出现极端表述 | 高 |
| 历史频次 | 近 30 天预约次数异常偏高 | 中 |
| 咨询师上报 | 咨询记录中手动标记风险 | 高 |
实现上可以做成规则引擎的简化版,每次产生新信号就写一条psy_warning记录,等级取所有未处理信号里的最高值,管理员端按等级倒序展示。
这里必须说清楚:系统只负责“提示”,不负责“判定”。预警列表旁边要有人工处理按钮,处理结果要留痕,包括处理人、处理时间、处理意见。这既是功能完整性,也是合规要求里“可追溯”的体现。
5.4 咨询师匹配打分
学生不一定知道自己该找谁。加一个简单的加权匹配,能让系统看起来聪明很多,而且实现成本不高:
public double matchScore(Counselor c, Student s) { // 擅长方向重合度,权重 0.5 double tagScore = calcTagOverlap(c.getTags(), s.getIssueTags()) * 0.5; // 当前负载越低分越高,权重 0.3 double loadRatio = c.getCurrentLoad() / (double) Math.max(1, c.getMaxLoad()); double loadScore = (1 - Math.min(loadRatio, 1.0)) * 0.3; // 历史评分,权重 0.2 double rateScore = (c.getAvgRating() / 5.0) * 0.2; return tagScore + loadScore + rateScore; }calcTagOverlap可以用简单集合交并比算:交集大小除以并集大小。这样一段不到二十行的方法,在论文里可以写成一节“基于加权的咨询师推荐策略”,配上权重表和公式,内容就有了。
要注意的是,权重不要拍脑袋写。论文里可以给一个说明:权重是通过对若干组模拟数据的排序结果做人工比对后确定的,属于经验取值,实际系统应支持配置化调整。
5.5 权限与数据脱敏
三层校验缺一不可:接口层的角色注解、服务层的数据归属校验、SQL 层的条件过滤。学生查询预约列表时,student_id必须从 Token 里取,而不是从请求参数里取,否则改个 ID 就能看到别人的咨询记录。
脱敏处理建议放在序列化层,用自定义注解标记需要脱敏的字段:
public class StudentVO { private Long id; @Sensitive(type = SensitiveType.NAME) private String realName; @Sensitive(type = SensitiveType.PHONE) private String phone; }姓名保留首字,手机号保留前三后四,这类规则写成一个工具类就够了。咨询记录的正文不建议脱敏,但必须做访问控制,只允许该咨询师本人和授权管理员查看,并且每次查看写一条审计日志。
6. 环境准备与本地部署启动
6.1 前置检查清单
在动手之前,把这几个东西确认一遍,能省掉后面一半的报错:
- JDK:17 或 8,取决于 SpringBoot 版本,
java -version能输出正确版本。 - Maven:3.6 以上,配置好仓库地址,否则依赖下载会卡住。
- MySQL:5.7 或 8.x,服务已启动,能本地连接。
- Node.js:如果前端是 Vue 项目,需要 16 以上;如果前端是 Thymeleaf,可以跳过。
- IDE:IntelliJ IDEA 或 Eclipse,记得装 Lombok 插件,否则实体类的 getter 会一片红。
- 端口:8080 和 3306 没被占用,用
netstat -ano | findstr 8080检查。
6.2 数据库初始化
建库和导表的顺序不要搞反:
CREATE DATABASE psy_platform DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE psy_platform; -- 然后执行项目里的 schema.sql 和 data.sqldata.sql里建议预置三类测试账号各一到两个,方便演示时快速切换角色。密码字段不要存明文,用 BCrypt 生成哈希再写进初始化脚本。
6.3 配置文件
server: port: 8080 servlet: context-path: /api spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/psy_platform?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false username: root password: change_me servlet: multipart: max-file-size: 10MB mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0 psy: jwt: secret: replace-with-a-random-string-at-least-32-chars expire-minutes: 120里面有两点值得注意。serverTimezone不配会导致时间字段差 8 小时,这是新手最常见的坑。JWT 的 secret 长度不够会直接抛异常,至少 32 个字符。
6.4 启动命令
开发期直接跑:
# 在项目根目录执行,跳过测试加快启动 mvn clean package -DskipTests java -jar target/psy-platform-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev或者用 Maven 插件启动:
mvn spring-boot:run -Dspring-boot.run.profiles=dev启动成功的标志是控制台出现Started Application in x.xxx seconds,并且能看到 Tomcat 监听在配置的端口上。这时候浏览器访问http://127.0.0.1:8080/api/doc.html(如果集成了 Swagger/Knife4j)或者直接访问前端地址,能看到登录页就算跑通了。
如果是前后端分离项目,前端还需要单独起一个服务:
cd frontend npm install npm run serve前端请求跨域时,要么在后端配 CORS,要么在vue.config.js里配代理。答辩环境建议用代理,避免额外配置。
7. 接口 API 调用与批量任务
7.1 统一返回结构
接口返回格式先统一,后面所有调用都省事:
{ "code": 200, "message": "success", "data": {} }业务异常统一用全局异常处理器捕获,返回对应的 code 和提示语,不要把堆栈直接吐给前端。
7.2 登录与鉴权
curl -X POST "http://127.0.0.1:8080/api/auth/login" \ -H "Content-Type: application/json" \ -d '{"username":"2021001","password":"123456"}'返回示例:
{ "code": 200, "message": "success", "data": { "token": "eyJhbGciOiJIUzI1NiJ9...", "role": "STUDENT", "userId": 1001 } }拿到 token 之后,后续请求统一带上:
curl -X GET "http://127.0.0.1:8080/api/counselor/list?page=1&size=10" \ -H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9..."7.3 创建预约接口
@RestController @RequestMapping("/api/appointment") @RequiredArgsConstructor public class AppointmentController { private final AppointmentService appointmentService; @PostMapping("/create") public Result<Long> create(@RequestBody @Valid AppointmentDTO dto, @RequestAttribute("userId") Long studentId) { // studentId 从 Token 解析,不信任前端传值 return Result.ok(appointmentService.create(studentId, dto)); } }对应 DTO 的校验注解:
public class AppointmentDTO { @NotNull(message = "咨询师不能为空") private Long counselorId; @NotNull(message = "开始时间不能为空") @Future(message = "预约时间必须晚于当前时间") private LocalDateTime startTime; @NotNull(message = "结束时间不能为空") private LocalDateTime endTime; @Size(max = 200, message = "主题不能超过200字") private String topic; }参数校验放在注解层是最省力的做法,@Valid触发后异常交给全局处理器统一转换,Controller 里就不用写一堆 if 判断。
7.4 批量任务设计
两类批量任务在这个系统里比较常见:一是批量生成咨询师排班,二是批量导出月度统计。
批量生成排班用 Spring 的定时任务就能做,建议加个开关,避免演示环境每天都跑:
@Component @ConditionalOnProperty(name = "psy.job.schedule-enabled", havingValue = "true") public class ScheduleGenerateJob { @Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点 public void generateNextWeek() { // 1. 查出所有在职咨询师 // 2. 按各自的可用星期规则,生成未来7天的时间片 // 3. 已存在的时段跳过,保持幂等 } }幂等这件事必须做。定时任务重复执行不应该是灾难,判断依据用“咨询师 + 日期 + 时段”做唯一索引,插入时用insert ignore或者先查后插加唯一约束兜底。
批量导出走异步更稳妥,接口只负责创建任务返回任务 ID,前端轮询进度。毕设里可以简化成同步分页导出,但要设置单次导出的最大行数,避免一次性拉全表把内存打满。
7.5 Python 批量调用示例
如果要做数据初始化或者压力测试,用脚本调接口比较方便:
import requests BASE = "http://127.0.0.1:8080/api" def login(username, password): r = requests.post(f"{BASE}/auth/login", json={"username": username, "password": password}, timeout=10) r.raise_for_status() return r.json()["data"]["token"] def create_appointment(token, counselor_id, start_time, end_time): headers = {"Authorization": f"Bearer {token}"} payload = { "counselorId": counselor_id, "startTime": start_time, "endTime": end_time, "topic": "自动化测试预约" } resp = requests.post(f"{BASE}/appointment/create", json=payload, headers=headers, timeout=10) return resp.status_code, resp.json() if __name__ == "__main__": tk = login("2021001", "123456") print(create_appointment(tk, 2001, "2025-06-01T10:00:00", "2025-06-01T11:00:00"))脚本里一定要设timeout,否则服务端卡住会把脚本也挂在那里。批量执行时加个间隔和失败重试,别一股脑循环几百次把连接池打爆。
8. 性能观察、资源占用与问题排查
8.1 该看哪些指标
这套系统没有重型计算,性能瓶颈基本集中在数据库。需要观察的是三块:
第一是接口响应时间。可以在拦截器里打日志,记录每个请求的耗时,超过 500ms 的标出来重点看。
第二是慢查询。MySQL 开启慢查询日志,阈值设成 1 秒,跑一遍预约列表和测评统计,看看有没有走全表扫描。预约表的idx_counselor_time索引就是为了这个查询建的,用EXPLAIN确认一下type不是ALL。
第三是连接池。默认的 HikariCP 连接数一般够用,但如果导出接口和分页接口同时跑,可能出现等待。可以在配置文件里显式设置:
spring: datasource: hikari: maximum-pool-size: 10 minimum-idle: 2 connection-timeout: 3000内存占用方面,一个普通的 SpringBoot 单体应用启动后堆内存几百兆属于正常范围,具体数值和依赖数量、是否加载缓存有关,需要在本机用 JVisualVM 或jstat实际观察,不要照搬别人的数字。
8.2 常见问题与排查表
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 启动报数据源初始化失败 | 数据库没启动或密码错误 | 看异常堆栈里的Communications link failure | 检查 MySQL 服务、账号密码、库名 |
| 时间字段差 8 小时 | JDBC URL 缺时区参数 | 对比数据库存值和接口返回值 | URL 加serverTimezone=Asia/Shanghai |
| 接口 401 未授权 | Token 过期或请求头格式错 | 看拦截器日志 | 确认Bearer前缀和空格 |
| 学生能查到别人的预约 | 查询条件用了前端传的 ID | 检查 SQL 的 where 条件 | 改为从 Token 解析用户 ID |
| 预约出现重复 | 先查后插存在并发间隙 | 压测复现 | 加唯一索引 + 事务,捕获重复键异常 |
| 测评分数明显偏低 | 反向计分题没翻转 | 抽查一道反向题手工算分 | 题目表加reverse_flag并生效 |
| 定时任务重复生成排班 | 任务不幂等 | 查看 schedule 表重复记录 | 加唯一索引,插入前判重 |
| 文件上传报 413 | 上传大小超限 | 看配置项 | 调整max-file-size和max-request-size |
| 前端请求跨域失败 | 后端未配 CORS | 浏览器控制台看 CORS 报错 | 配置跨域或使用开发代理 |
| 打包后启动找不到主类 | 缺少 spring-boot-maven-plugin | 看 jar 包结构 | 在 pom 里补插件配置并重新打包 |
排查顺序建议固定下来:先看控制台第一个异常,再看日志里最后一次 SQL,最后才怀疑业务代码。90% 的启动问题都在依赖版本和配置项上。
9. 开题报告与论文写作要点
技术做完只是完成了一半,这类题目在文档上有固定的踩分点。
开题报告部分,重点写清楚四件事:研究背景与意义、国内外研究现状、研究内容与技术路线、进度安排。研究现状不要泛泛而谈“信息技术发展迅速”,要落到具体功能点上,比如“现有系统在预约冲突处理和测评结果可视化方面存在不足”。
技术路线图要画出分层结构,Controller、Service、Mapper 三层各自负责什么,用什么框架,数据库怎么设计,这一节画图比写字有效。
论文部分的章节安排可以参考:
- 绪论(背景、意义、国内外现状、主要工作)
- 相关技术介绍(SpringBoot、MyBatis-Plus、MySQL、JWT,每个技术写它在本系统里承担什么角色,不要抄官方文档)
- 需求分析(用例图、功能性需求、非功能性需求)
- 系统设计(架构设计、功能模块设计、数据库设计、关键算法设计)
- 系统实现(按模块贴核心代码和界面截图)
- 系统测试(功能测试用例表、性能测试、异常测试)
- 总结与展望
第五章是重点,每个模块不要只贴一张截图,要配上“这个模块处理了什么问题、用了什么方法、关键代码在哪几行”。第六章的测试用例要有表格,包含用例编号、测试步骤、预期结果、实际结果、是否通过,这是评委最容易认可的部分。
算法那部分,把本文第 5 章的冲突检测和加权匹配写进去,给公式、给伪代码、给复杂度分析,论文的技术含量立刻就上来了。
10. 最佳实践与总结
最后说几条能少走弯路的建议。
第一,先把最小闭环跑通。数据库建好、登录能进、预约能创建、测评能出分,这四步走完再去做统计图表和文章模块。很多同学一上来就画大饼设计二十张表,结果连登录都跑不通。
第二,配置和代码分家。数据库密码、JWT 密钥、定时任务开关全部放进application-{profile}.yml,本地用dev,演示用prod,不要把生产配置提交到仓库。
第三,状态流转要有约束。预约状态不能随便改,用枚举加状态机校验,只允许“待确认 → 已确认 → 已完成”这类合法迁移,非法迁移直接抛业务异常。这条在答辩时问到的概率很高。
第四,日志分两类。业务日志记操作行为,方便审计和溯源;技术日志记异常堆栈,方便排查。不要把两者混在一个文件里。
第五,涉及真实数据一律脱敏。演示库全部用虚构姓名和学号,咨询记录里不要出现可识别个人身份的信息。如果要做导出功能,导出文件也要做同样的处理。
第六,接口访问范围要收紧。默认只监听本地或者内网,不要图省事在公网暴露一个没有鉴权的演示服务。管理端接口加二次校验,导出类接口加频率限制。
这套高校心理咨询管理平台的技术含量,不在于用了多少新框架,而在于能不能把“预约排期不冲突、测评计分不失真、风险提示有依据、敏感数据不越权”这四件事说圆。先把预约和测评两条主链路跑通,再补预警和统计,最后回到论文里把设计权衡讲清楚,这就是一套能拿得出手的毕设。第一次跑起来建议先小范围测数据,确认分数和冲突逻辑都对了,再批量灌数据做演示。