
简介这份资源是面向高校计算机相关专业毕业生的完整毕设方案主题为基于微信小程序的大学生心理健康测评管理系统适合正在准备毕业设计、需要同时交付代码与论文的本科或专科学生参考。压缩包共1508个文件约41.99MB以vue前端组件、js脚本、java后端源码为主辅以wxml与wxss小程序页面、json配置、png与svg图标素材、sql建表脚本及md说明文档覆盖前后端与数据库全链路。系统功能涵盖咨询师管理、学生用户管理、心理健康档案、咨询师预约、心理测评与题库维护、通知公告、论坛、字典值及轮播图管理等模块结构完整、业务闭环清晰。目前已有79人学习下载读者可据此快速理解测评类管理系统的表结构设计与接口划分对照论文梳理需求分析、模块实现与测试思路并在此基础上完成二次开发与答辩准备。1. 从一份“能跑起来”的心理测评系统说起这个毕设到底在做什么很多同学做毕业设计选题阶段最怕两件事一是题目太虚写不出代码二是题目太泛答辩时被老师追问“你的核心功能到底是什么”。基于微信小程序的大学生心理健康测评管理系统这个题目恰好卡在一个很舒服的位置——它既有明确的业务场景大学生心理测评又有清晰的技术边界微信小程序 Java 后端还自带一套可量化的数据流量表 → 答题 → 计分 → 报告 → 预警。我见过太多类似选题最后做成“登录 增删改查”的壳子问题不在技术难度而在没想清楚测评这两个字的重量。心理测评不是普通表单提交它涉及量表维度、计分规则、常模比对、结果解释甚至还要考虑隐私和伦理边界。一个能拿得出手的毕设至少要跑通“学生答完一套 SCL-90 或 PHQ-9系统自动算出因子分并给出分级建议”这条链路。这篇文章面向正在做这个题目的同学也面向想用微信小程序 Java 快速搭一套测评类系统的开发者。我会按“选型 → 建表 → 接口 → 小程序端 → 计分算法 → 避坑”的顺序把每个环节的参数、代码和踩过的坑讲清楚。你不需要有现成的项目源码跟着走就能搭出可演示、可答辩、可扩展的版本。2. 技术选型与数据库设计为什么用 Spring Boot MyBatis-Plus 而不是裸 JDBC2.1 后端框架选型Spring Boot 2.7 MyBatis-Plus 的性价比毕业设计的时间通常只有 8 到 12 周其中还要写论文、做答辩 PPT。后端选型的第一原则是减少重复代码而不是追求“技术先进”。Spring Boot 2.7 是目前教程最多、踩坑最容易搜到答案的版本搭配 MyBatis-Plus 可以省掉大量单表 CRUD 的 XML 配置。我一般会这样搭项目结构# 项目初始化Spring Initializr 或 IDEA 直接建 # 依赖Spring Web、MyBatis-Plus、MySQL Driver、Lombok、Hutool # 目录结构 src/main/java/com/example/psy/ ├── controller/ # 接口层 ├── service/ # 业务层 ├── mapper/ # MyBatis-Plus Mapper ├── entity/ # 数据库实体 ├── dto/ # 请求/响应对象 ├── config/ # 跨域、拦截器、Knife4j └── utils/ # 计分工具、JWT 工具MyBatis-Plus 的核心价值在于你写好实体类它就能帮你生成基础 SQL。比如TableName(scale_question)加TableId(type IdType.AUTO)单表查询几乎不用写 SQL。对于毕设里大量的“查量表、查题目、查记录”操作这能省下至少两三天。提示不要用 MyBatis-Plus 的代码生成器直接生成全部代码生成的 Service 里一堆空方法反而干扰阅读。手写 Mapper 继承BaseMapperT就够了。2.2 数据库表设计五张核心表撑起整个测评流程心理测评系统的数据模型比普通管理系统多一层“量表结构”。我建议至少设计五张表下面这张表是我在实际项目中反复调整后的版本表名作用关键字段user学生/管理员账号id, openid, nickname, role, phonescale量表主表id, name, description, question_count, dimension_jsonscale_question题目表id, scale_id, content, options_json, dimension, reverse_scoreassessment_record测评记录id, user_id, scale_id, answer_json, score_json, level, create_timewarning_log预警记录id, user_id, record_id, level, handled, create_time其中dimension_json存量表的维度定义比如 SCL-90 的躯体化、强迫症状、人际关系敏感等。options_json存选项和分值reverse_score标记反向计分题。answer_json存用户原始作答score_json存各维度得分和总分。-- 量表题目表建表语句MySQL 8.0 CREATE TABLE scale_question ( id bigint NOT NULL AUTO_INCREMENT, scale_id bigint NOT NULL COMMENT 所属量表, content varchar(500) NOT NULL COMMENT 题目内容, options_json json NOT NULL COMMENT 选项及分值, dimension varchar(50) DEFAULT NULL COMMENT 所属维度, reverse_score tinyint DEFAULT 0 COMMENT 是否反向计分 0否1是, sort_order int DEFAULT 0, PRIMARY KEY (id), KEY idx_scale (scale_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;options_json用 JSON 类型而不是拆成选项表是因为选项结构固定且不需要单独查询。一个题目四个选项存成[{label:没有,value:1},{label:很轻,value:2}]比建一张选项表再关联要轻量得多。reverse_score字段是心理测评特有的像 SDS 抑郁自评量表里有反向题计分时必须做max min - score转换这个后面会细讲。2.3 微信小程序登录与手机号获取的接口设计小程序端绕不开登录。2023 年之后wx.getUserInfo不再返回真实昵称头像主流做法是wx.login拿 code 换 openid再让用户手动填昵称。如果毕设要求“获取手机号”需要用button open-typegetPhoneNumber但个人开发者账号没有这个权限这是很多同学翻车的地方。// 小程序端登录逻辑 wx.login({ success: (res) { if (res.code) { wx.request({ url: http://localhost:8080/api/auth/login, method: POST, data: { code: res.code }, success: (resp) { // 后端返回 token 和用户信息 wx.setStorageSync(token, resp.data.data.token) } }) } } })后端用 code 调微信接口换 openid如果用户不存在就自动注册。这里有个坑微信接口的 appid 和 secret 必须放在后端不能写在小程序代码里否则反编译就能拿到。毕设答辩时老师如果问“你的密钥怎么管理的”答“放在后端配置文件并用环境变量覆盖”是加分项。注意个人开发者无法使用获取手机号接口如果论文里写了这个功能要么申请企业主体要么改成“手动输入手机号 验证码”的方案别在答辩时被问住。3. 测评核心逻辑从答题到计分再到预警的完整链路3.1 量表配置如何把 SCL-90 的 90 道题塞进数据库心理量表不是随便编的SCL-90、SDS、SAS、PHQ-9 这些都有标准题目和计分规则。以 SCL-90 为例90 道题、10 个因子每道题 1 到 5 级评分。手动录入 90 条数据不现实我一般写一个初始化脚本把量表数据以 JSON 格式导入。# 量表初始化脚本Python 一次性执行 import json import pymysql scale_data { name: SCL-90症状自评量表, question_count: 90, dimensions: [躯体化, 强迫症状, 人际关系敏感, 抑郁, 焦虑, 敌对, 恐怖, 偏执, 精神病性, 其他], questions: [ {content: 头痛, dimension: 躯体化, reverse: 0}, {content: 神经过敏心中不踏实, dimension: 焦虑, reverse: 0}, # ... 其余 88 题 ] } conn pymysql.connect(hostlocalhost, userroot, password123456, dbpsy) cursor conn.cursor() cursor.execute( INSERT INTO scale (name, question_count, dimension_json) VALUES (%s, %s, %s), (scale_data[name], scale_data[question_count], json.dumps(scale_data[dimensions])) ) scale_id cursor.lastrowid for idx, q in enumerate(scale_data[questions]): options json.dumps([ {label: 没有, value: 1}, {label: 很轻, value: 2}, {label: 中等, value: 3}, {label: 偏重, value: 4}, {label: 严重, value: 5} ], ensure_asciiFalse) cursor.execute( INSERT INTO scale_question (scale_id, content, options_json, dimension, reverse_score, sort_order) VALUES (%s, %s, %s, %s, %s, %s), (scale_id, q[content], options, q[dimension], q[reverse], idx 1) ) conn.commit()这段脚本的关键在于dimension字段和reverse_score字段。SCL-90 没有反向计分题但 SDS 有 10 道反向题。如果你做的量表包含反向题计分时必须先转换。sort_order保证题目顺序和标准量表一致否则被试的作答效度会受影响。3.2 计分算法因子分、总分和分级标准的代码实现计分是心理测评系统的灵魂。以 SCL-90 为例需要计算总分、总均分、阳性项目数、阴性项目数、10 个因子分。因子分 该因子所有题目得分之和 / 该因子题目数。分级标准通常是总分超过 160 分或阳性项目数超过 43 项建议进一步检查。// 计分服务核心方法 public ScoreResult calculate(Long scaleId, MapLong, Integer answers) { ListScaleQuestion questions questionMapper.selectList( new LambdaQueryWrapperScaleQuestion().eq(ScaleQuestion::getScaleId, scaleId) ); MapString, ListInteger dimensionScores new HashMap(); int totalScore 0; int positiveCount 0; for (ScaleQuestion q : questions) { Integer rawScore answers.get(q.getId()); if (rawScore null) continue; // 反向计分处理 int score q.getReverseScore() 1 ? (6 - rawScore) : rawScore; totalScore score; if (score 1) positiveCount; dimensionScores.computeIfAbsent(q.getDimension(), k - new ArrayList()).add(score); } MapString, Double factorScores new HashMap(); for (Map.EntryString, ListInteger entry : dimensionScores.entrySet()) { double avg entry.getValue().stream().mapToInt(Integer::intValue).average().orElse(0); factorScores.put(entry.getKey(), Math.round(avg * 100.0) / 100.0); } String level totalScore 160 || positiveCount 43 ? 建议进一步检查 : 正常范围; return new ScoreResult(totalScore, positiveCount, factorScores, level); }6 - rawScore是反向计分的通用公式前提是 1 到 5 级评分。如果是 0 到 4 级评分公式要改成4 - rawScore。这个细节很多网上的代码都写错了导致反向题得分完全相反。positiveCount统计的是得分大于 1 的题目数对应 SCL-90 的阳性项目数。分级阈值 160 和 43 是标准常模不要随意改论文里要注明出处。3.3 预警机制什么情况下触发预警以及怎么通知预警是“管理系统”区别于“测评工具”的关键。不是所有高分都要预警我一般设三级总分超过 200 或某一因子分超过 3 分触发一级预警超过 160 触发二级超过 120 触发三级。一级预警需要辅导员介入二级建议预约咨询三级仅记录。// 预警判断逻辑 public void checkWarning(Long userId, ScoreResult result, Long recordId) { int level 0; if (result.getTotalScore() 200 || result.getFactorScores().values().stream().anyMatch(s - s 3.0)) { level 1; } else if (result.getTotalScore() 160) { level 2; } else if (result.getTotalScore() 120) { level 3; } if (level 0) { WarningLog log new WarningLog(); log.setUserId(userId); log.setRecordId(recordId); log.setLevel(level); log.setHandled(0); warningMapper.insert(log); } }预警记录不要只存不处理。管理端要能看到预警列表并且有“标记已处理”的按钮。答辩时老师很可能问“预警之后呢”你要能说出“辅导员登录管理端查看预警详情线下约谈后在系统里标记处理结果”。这样整个闭环才完整。4. 微信小程序端实现页面结构、请求封装与测评交互4.1 页面结构规划五个核心页面就够了小程序端不需要太多页面我一般规划五个首页量表列表、测评页答题、报告页结果展示、记录页历史测评、个人中心。首页用scroll-view或普通列表展示可用量表每个量表卡片显示名称、题目数、预计用时。// pages/index/index.js 首页加载量表列表 Page({ data: { scales: [] }, onLoad() { wx.request({ url: http://localhost:8080/api/scale/list, header: { Authorization: wx.getStorageSync(token) }, success: (res) { this.setData({ scales: res.data.data }) } }) }, goAssessment(e) { const scaleId e.currentTarget.dataset.id wx.navigateTo({ url: /pages/assessment/assessment?scaleId${scaleId} }) } })测评页是交互最复杂的。SCL-90 有 90 道题不可能一页全展示。常见做法是分页每页 10 题底部有上一页/下一页最后一页提交。也可以用swiper一题一屏但 90 次滑动体验很差。我建议分页 进度条用户能看到“第 30/90 题”。4.2 请求封装与 token 管理避免每个页面都写一遍 wx.request小程序原生wx.request不支持拦截器每个页面都写一遍 header 和错误处理很痛苦。我一般封装一个request.js// utils/request.js const BASE_URL http://localhost:8080 function request(options) { return new Promise((resolve, reject) { wx.request({ url: BASE_URL options.url, method: options.method || GET, data: options.data || {}, header: { Content-Type: application/json, Authorization: wx.getStorageSync(token) || }, success: (res) { if (res.statusCode 200 res.data.code 200) { resolve(res.data) } else if (res.statusCode 401) { wx.redirectTo({ url: /pages/login/login }) reject(res.data) } else { wx.showToast({ title: res.data.msg || 请求失败, icon: none }) reject(res.data) } }, fail: reject }) }) } module.exports { request }这样页面里只需要const { request } require(../../utils/request)然后request({ url: /api/scale/list })。token 过期统一跳登录页不用每个页面判断。注意BASE_URL在开发时是 localhost真机调试要换成局域网 IP上线要换成备案域名。毕设答辩演示用开发者工具即可但论文里要写清楚部署方案。4.3 答题交互与提交如何保证答案不丢失答题过程中用户可能切后台、接电话如果答案只存在内存里切回来就没了。我一般用wx.setStorageSync实时保存答案// pages/assessment/assessment.js Page({ data: { questions: [], answers: {}, currentPage: 1, pageSize: 10 }, onLoad(options) { this.scaleId options.scaleId this.loadQuestions() }, loadQuestions() { request({ url: /api/question/list?scaleId${this.scaleId} }).then(res { this.setData({ questions: res.data }) // 恢复未提交的答案 const saved wx.getStorageSync(answers_${this.scaleId}) if (saved) this.setData({ answers: saved }) }) }, selectOption(e) { const { qid, value } e.currentTarget.dataset const answers this.data.answers answers[qid] value this.setData({ answers }) wx.setStorageSync(answers_${this.scaleId}, answers) }, submit() { const total this.data.questions.length const answered Object.keys(this.data.answers).length if (answered total) { wx.showToast({ title: 还有${total - answered}题未答, icon: none }) return } request({ url: /api/assessment/submit, method: POST, data: { scaleId: this.scaleId, answers: this.data.answers } }).then(res { wx.removeStorageSync(answers_${this.scaleId}) wx.redirectTo({ url: /pages/report/report?recordId${res.data.recordId} }) }) } })answers用对象存key 是题目 idvalue 是选项分值。提交前校验是否全部作答未答完给出提示。提交成功后清除本地缓存跳转报告页。这里有个细节wx.redirectTo而不是wx.navigateTo因为提交后不应该再回到答题页。5. 避坑与常见问题那些答辩前一夜才发现的 bug5.1 跨域问题开发者工具不报错但真机请求失败现象微信开发者工具里请求正常真机预览时所有接口失败。原因开发者工具默认关闭了跨域检查真机走的是真实网络请求后端没配 CORS。解决在后端加全局跨域配置Spring Boot 里用WebMvcConfigurerConfiguration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE) .allowCredentials(true) .maxAge(3600); } }注意allowedOriginPatterns而不是allowedOrigins后者在allowCredentials(true)时会报错。这是 Spring Boot 2.4 之后的变更很多老教程还在用allowedOrigins(*)直接复制会启动失败。5.2 反向计分写反SDS 量表得分越算越高现象用户明明选的是“没有”得分却很高。原因反向题公式写成了rawScore而不是max min - rawScore。解决确认评分范围。1 到 5 级用6 - rawScore0 到 4 级用4 - rawScore。建议在scale_question表里加一个score_range字段或者直接在代码里写死量表的评分范围不要靠猜。5.3 小程序顶部导航栏高度写死不同机型错位现象自定义导航栏在 iPhone 上正常在安卓上被状态栏遮挡。原因状态栏高度写成了固定值 44px。解决用wx.getSystemInfoSync().statusBarHeight动态获取导航栏总高度 状态栏高度 44。这个坑在热搜词里也有人问“微信小程序顶部导航栏高度”说明是高频问题。const systemInfo wx.getSystemInfoSync() const statusBarHeight systemInfo.statusBarHeight const navBarHeight statusBarHeight 445.4 测评记录重复提交用户连点提交按钮现象同一次测评产生两条记录计分结果还不一样。原因提交按钮没有防抖用户快速点击多次。解决提交时加loading状态请求完成前禁用按钮。后端也可以用userId scaleId 时间窗口做幂等但毕设阶段前端防抖就够了。submit() { if (this.data.submitting) return this.setData({ submitting: true }) request({ ... }).finally(() { this.setData({ submitting: false }) }) }5.5 数据库 JSON 字段查询MyBatis-Plus 默认不支持现象想查dimension_json里包含“抑郁”的量表用like查不到。原因MySQL 的 JSON 字段需要用JSON_CONTAINS或JSON_EXTRACTMyBatis-Plus 的like生成的是普通字符串匹配。解决要么用Select写原生 SQL要么把维度定义单独存一张表。毕设阶段建议后者查询更简单论文里也好画 ER 图。6. 进阶技巧让毕设从“能跑”变成“能拿优”的三个细节6.1 用 Knife4j 生成接口文档答辩时直接投屏Knife4j 是 Swagger 的增强版界面比原生 Swagger 好看得多。加依赖、加配置类、加注解启动后访问doc.html就能看到所有接口。答辩时老师问“你有哪些接口”直接投屏展示比翻代码有说服力。dependency groupIdcom.github.xiaoymin/groupId artifactIdknife4j-openapi2-spring-boot-starter/artifactId version4.3.0/version /dependency配置类里设置EnableKnife4j和Bean的Docket指定扫描包路径。接口上用ApiOperation写清楚用途比如“提交测评答案并返回计分结果”。这个细节花不了半小时但能让论文的“系统实现”章节有图可贴。6.2 测评报告的可视化用 ECharts 画因子分雷达图报告页只显示分数太单薄。用 ECharts 的雷达图展示 10 个因子分一眼就能看出哪项偏高。小程序里用ec-canvas组件后端返回因子分数据前端渲染。// report.js 雷达图配置 const option { radar: { indicator: Object.keys(factorScores).map(name ({ name, max: 5 })) }, series: [{ type: radar, data: [{ value: Object.values(factorScores), name: 因子分 }] }] }雷达图的max设成 5因为 SCL-90 是 1 到 5 级评分。如果做的是 0 到 4 级的量表改成 4。这个图放在论文里比表格直观答辩时老师也容易理解。6.3 论文里的“系统测试”章节用真实数据跑一遍很多同学的测试章节写得很虚“点击按钮功能正常”。我建议用真实量表数据跑一遍完整流程注册 → 登录 → 选择 SCL-90 → 作答 90 题 → 提交 → 查看报告 → 管理端查看预警。每一步截图记录响应时间。如果总分超过 160检查预警是否触发。这样测试章节有数据、有截图、有结论比空泛的描述强得多。我自己的习惯是答辩前三天用三个不同账号各跑一遍完整流程把截图按顺序命名存好。答辩时如果老师问“你这个预警准不准”直接翻到测试章节的截图比现场演示更稳。希望帮到你。本文还有配套的精品资源点击获取