简介:面向计算机相关专业毕业设计场景,这份青少年心理健康科普平台微信小程序项目完整涵盖小程序端与基于Java SSM框架的后台管理端,实现用户注册验证、健康知识浏览、心理医生咨询与回复、公告发布等功能,适用于需要完成前后台联调与MySQL数据交互的本科或专科毕设选题。资源共1452个文件,其中包含320个png图片、207个js逻辑文件、162个svg图标、152个vue页面、144个java后台代码、113个json配置等,整体约67.88MB,rar压缩包内目录结构清晰,便于按模块检索。已有401人学习下载。压缩包内除源码外,还提供说明文档、操作演示录像(mp4)、数据库SQL脚本、启动安装批处理脚本(1-install.bat、2-run.bat、3-build.bat)及项目配置备份,几乎覆盖从环境部署、数据库初始化到功能演示的完整流程,可以直接在此基础上进行二次开发或用于毕业设计答辩参考。
1. 青少年心理健康科普平台:为什么小程序端要配一套Java SSM后台
青少年心理健康这个选题,在小程序生态里很容易做成一个纯前端展示页,调几个接口把文章列表拉下来就完事。但这类毕业设计真正的扣分点往往不在页面,而在「有没有一套完整的用户生命周期」——注册、审核、登录、咨询、回复、公告推送,每一步都要落库、都要有状态流转。这套项目把微信小程序端和 Java SSM + MySQL 后台放在一起,本质上是在模拟一个真实的内容+咨询类产品:C 端用户看到的是小程序,管理端看到的是后台管理系统,两边共用同一套数据库和接口。适合用来做毕业设计或课程项目的人参考,尤其是你需要展示「前后端分离但不脱离业务」的开发能力时,这套拆解能帮你把答辩时的技术点讲清楚。
2. 小程序端与 SSM 后台的账户体系设计:注册、管理员验证、登录态
青少年心理健康科普平台的第一道坎不是页面布局,而是用户身份怎么在小程序和 Java 后台之间对齐。小程序端没有传统网页的 session 机制,wx.login 拿到的 code 只能换 openid,不能直接当业务账号用。所以这里采用「先注册、后审核、再登录」的流程,意味着用户表里必须有一个状态字段来标注账号是否通过管理员验证。
2.1 注册接口设计与字段校验
用户在小程序端填写基本信息,提交到 SSM 后台的注册接口。常见做法是设计一张t_user表,字段包括user_id、username、password、real_name、age、gender、phone、status、create_time。其中password不要存明文,使用 MD5 加盐或 BCrypt 处理。虽然 SSM 项目里很多人直接明文存储,但答辩时面试官问一句「密码怎么保护的」,如果你能答出加密和校验流程,会明显不一样。
小程序端注册页核心代码:
// pages/register/register.js register() { const { username, password, confirmPassword, realName, age } = this.data; if (!username || !password || !confirmPassword) { wx.showToast({ title: '请填写完整信息', icon: 'none' }); return; } if (password !== confirmPassword) { wx.showToast({ title: '两次密码不一致', icon: 'none' }); return; } wx.request({ url: 'http://localhost:8080/api/user/register', method: 'POST', data: { username, password, realName, age, phone: this.data.phone, gender: this.data.gender }, success: (res) => { if (res.data.code === 200) { wx.showToast({ title: '注册成功,等待管理员审核' }); setTimeout(() => wx.navigateBack(), 1500); } else { wx.showToast({ title: res.data.msg, icon: 'none' }); } } }); }这段代码里值得注意的点:wx.request的url在开发阶段直接指向本机localhost:8080,但微信开发者工具默认不允许访问局域网 IP,需要在「详情-本地设置」里勾选「不校验合法域名」。data里没有传status,因为新用户默认值为 0,表示待审核,这个状态由后台在处理注册逻辑时写入。
2.2 管理员审核与状态字段
后台管理员登录后,在用户管理列表里可以看到所有status = 0的注册申请。审核通过的操作本质是执行一条 UPDATE 语句,把status从 0 改成 1。这个逻辑放在 SSM 的 Service 层,Controller 只接收用户 ID:
// UserController.java @Controller @RequestMapping("/admin/user") public class UserController { @Autowired private UserService userService; @RequestMapping("/audit") @ResponseBody public Result audit(@RequestParam Integer userId, @RequestParam Integer status) { // status: 1通过 2拒绝 User user = userService.findById(userId); if (user == null) { return Result.error("用户不存在"); } user.setStatus(status); userService.update(user); return Result.success(); } }这里没有用@RequestBody而是@RequestParam,是因为后台管理端用的是传统表单提交,这样对接更简单。Result是统一返回对象,包含code、msg、data三个字段,小程序端收到后直接判断code是否为 200。
2.3 wx.login 与登录态保持
审核通过后,用户在小程序端输入用户名密码登录。SSM 后台校验成功后返回一个 token,小程序端把 token 存到wx.setStorageSync,后续所有需要身份的接口都在请求头里带token。这里不强制依赖微信的wx.login换 openid,因为业务系统本身有自己的账号体系。
// pages/login/login.js login() { const { username, password } = this.data; wx.request({ url: 'http://localhost:8080/api/user/login', method: 'POST', data: { username, password }, success: (res) => { if (res.data.code === 200) { wx.setStorageSync('token', res.data.data.token); wx.setStorageSync('userInfo', res.data.data.user); wx.switchTab({ url: '/pages/index/index' }); } else { wx.showToast({ title: '账号或密码错误,或账号未审核', icon: 'none' }); } } }); }token 生成可以直接用 UUID,也可以引入 JWT。SSM 项目里建议用 JWT,因为答辩时可以讲无状态认证,还能把用户 ID 和角色编码进 token 里。拦截器里校验 token 的过期时间和签名,如果校验失败返回 401,小程序端在响应拦截里统一跳到登录页。
| 状态值 | 含义 | 用户可操作 |
|---|---|---|
| 0 | 待审核 | 不可登录 |
| 1 | 正常 | 可登录、查看知识、咨询医生 |
| 2 | 已拒绝 | 不可登录,需重新注册 |
| 3 | 已禁用 | 不可登录,后台可强制退出 |
注意,status = 2和status = 3在登录接口里都要被拦截,不能只判断用户名密码是否正确。我一般会在校验密码后加一句if (user.getStatus() != 1) return Result.error("账号状态异常"),避免用户误以为密码错了。
3. 健康知识库与心理医生咨询:表结构设计到双向通信
平台的核心内容有两块:心理健康科普知识和心理医生咨询。前者是静态内容展示,后者是带状态的交互流程。这两块功能如果只做「列表-详情」和「提交-展示」,难度不高,但一旦涉及分类筛选、咨询状态流转、医生端回复,就需要把表结构和接口设计先想清楚。
3.1 健康知识分类表与列表接口
健康知识建议设计两张表:t_health_category(分类表)和t_health_article(文章表)。分类表字段为category_id、category_name、sort_order。文章表字段为article_id、category_id、title、content、cover_url、publish_time、views。小程序首页展示分类 tab,点击分类后请求对应分类下的文章列表。
MySQL 建表语句:
CREATE TABLE `t_health_category` ( `category_id` INT NOT NULL AUTO_INCREMENT, `category_name` VARCHAR(50) NOT NULL, `sort_order` INT DEFAULT 0, PRIMARY KEY (`category_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `t_health_article` ( `article_id` INT NOT NULL AUTO_INCREMENT, `category_id` INT NOT NULL, `title` VARCHAR(200) NOT NULL, `content` TEXT, `cover_url` VARCHAR(255), `publish_time` DATETIME, `views` INT DEFAULT 0, PRIMARY KEY (`article_id`), KEY `idx_category` (`category_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;这里把category_id建了索引,是因为首页查询会频繁按分类过滤。publish_time用DATETIME而不是TIMESTAMP,避免 2038 年问题,也方便管理端在表单里直接用datetime-local控件回填。小程序端请求文章列表时,每次加载 10 条,带lastId或pageNum做分页。
// ArticleController.java @RequestMapping("/list") @ResponseBody public Result list(@RequestParam(required = false) Integer categoryId, @RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize) { PageHelper.startPage(pageNum, pageSize); ArticleQuery query = new ArticleQuery(); query.setCategoryId(categoryId); List<Article> list = articleService.listByCondition(query); PageInfo<Article> pageInfo = new PageInfo<>(list); // 这里只返回必要字段,content 不放入列表接口 return Result.success(pageInfo); }注意列表接口不要把content全文返回,否则首屏加载会消耗大量流量。我一般会用SELECT article_id, title, cover_url, publish_time, views FROM ...手动指定列,详情接口再查全文。
3.2 医生信息展示与咨询消息表
心理医生在首页展示,点进医生详情后可以发起咨询。医生表t_doctor字段包括doctor_id、name、avatar、title(职称)、intro、good_at(擅长方向)。咨询表t_consult字段为consult_id、user_id、doctor_id、content、reply、status、create_time、reply_time。
status字段是咨询流程的核心:
- 0:待医生回复
- 1:医生已回复
- 2:用户已关闭
小程序端提交咨询:
submitConsult() { const { doctorId, content } = this.data; if (!content.trim()) { wx.showToast({ title: '请输入咨询内容', icon: 'none' }); return; } wx.request({ url: 'http://localhost:8080/api/consult/add', method: 'POST', header: { token: wx.getStorageSync('token') }, data: { doctorId, content }, success: (res) => { if (res.data.code === 200) { wx.showToast({ title: '提交成功,等待医生回复' }); this.setData({ content: '' }); } } }); }这里有一个容易忽略的坑:header里传 token 时,key 名称要和后台拦截器取的一致。如果后台用request.getHeader("token"),小程序端就必须写token,不能写成Authorization,否则拦截器会直接放行或报空指针。建议前后端约定统一的 header 名称,比如X-Token。
3.3 医生端回复与小程序端刷新
医生端不是独立 App,而是后台管理系统里的一个角色。管理员可以把t_user里的用户设置为role = 'doctor',医生登录后台后进入「咨询管理」菜单,看到所有status = 0的咨询列表,点击后填写回复内容。
回复更新逻辑:
// ConsultController.java @RequestMapping("/reply") @ResponseBody public Result reply(@RequestParam Integer consultId, @RequestParam String reply) { Consult consult = consultService.findById(consultId); if (consult == null || consult.getStatus() != 0) { return Result.error("咨询不存在或已被处理"); } consult.setReply(reply); consult.setStatus(1); consult.setReplyTime(new Date()); consultService.update(consult); return Result.success(); }校验status != 0是典型的并发场景防护:如果两个医生同时打开同一条咨询,后提交的人这里会拿到错误提示。小程序端查看回复的方式有两种:一是进入咨询记录页面时重新请求详情列表;二是下拉刷新时重新拉取。不需要做 WebSocket 推送,因为毕业设计里轮询或手动刷新已经能完整表达业务。
我一般会在咨询列表页的onShow里重新加载数据,而不是在onLoad里加载一次。原因是用户从详情页返回列表页时,小程序会触发onShow,如果只写onLoad,刚提交的咨询或医生刚回复的内容不会刷新出来。
4. 后台管理端的公告推送与用户管理:从表单提交到小程序展示
后台管理端用的是 Java SSM + JSP 或 Thymeleaf 模板,这套项目里保留了.vue.bak和.css.bak文件,说明开发过程中曾经尝试过 Vue 重构,但最终交付版还是以服务端渲染为主。后台的核心功能有三个:管理员登录与权限拦截、用户管理、公告发布。
4.1 管理员登录与权限拦截
管理员表可以复用t_user表加role字段,也可以单独建t_admin表。如果复用,注意role字段取值建议用字符串admin、doctor,不要用数字 1、2,因为迁移到其他系统时字符串语义更清晰。登录成功后把管理员 ID 和 role 放进 session。
拦截器实现:
public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session = request.getSession(); Object admin = session.getAttribute("adminInfo"); if (admin == null) { // 未登录,重定向到登录页 response.sendRedirect(request.getContextPath() + "/admin/login"); return false; } return true; } }登录接口里要区分用户角色,如果普通用户尝试登录后台,直接拒绝。不要只靠前端隐藏菜单来控制权限,因为后台接口一旦被人猜到 URL,没有拦截器保护就会被越权访问。
4.2 公告发布与小程序端展示
公告功能是管理端发布消息,小程序端在首页或者我的页面展示。表结构t_notice字段为notice_id、title、content、create_time、is_top。管理端发布公告后,小程序端调接口获取公告列表。
// NoticeController.java @RequestMapping("/latest") @ResponseBody public Result latest() { List<Notice> list = noticeService.findLatest(5); return Result.success(list); }小程序端在首页的onLoad里请求这个接口,把公告标题渲染成一个可横向滑动的公告栏。这里有一个性能细节:公告接口不需要每次都全量查询,可以在 SQL 里按create_time DESC LIMIT 5,只拿最新五条。
发布公告时,管理端表单里有一个「是否置顶」的复选框,对应is_top字段。排序时用ORDER BY is_top DESC, create_time DESC,这样置顶公告始终排在最上面。
4.3 用户列表与禁用操作
后台的用户管理页面需要展示所有用户,并且支持按状态筛选。用户列表页面建议实现三个功能:搜索(按用户名或手机号)、审核、禁用/启用。禁用操作不是删除记录,而是把status改成 3,小程序端登录时校验status不等于 1 就提示账号异常。
// admin/user/list 部分 HTML(Thymeleaf) <table class="table table-bordered"> <thead> <tr> <th>ID</th> <th>用户名</th> <th>状态</th> <th>操作</th> </tr> </thead> <tbody> <tr th:each="u : ${pageInfo.list}"> <td th:text="${u.userId}"></td> <td th:text="${u.username}"></td> <td th:text="${u.status == 0 ? '待审核' : (u.status == 1 ? '正常' : '禁用')}"></td> <td> <a th:href="@{/admin/user/audit(userId=${u.userId},status=1)}" th:if="${u.status == 0}">通过</a> <a th:href="@{/admin/user/audit(userId=${u.userId},status=3)}" th:if="${u.status == 1}">禁用</a> </td> </tr> </tbody> </table>这里用 GET 请求做审核操作,只是为了演示方便。生产环境里应该用 POST 或 POST+表单,避免重复提交。后台页面里所有涉及状态变更的操作,都要在操作成功后跳回列表页,否则刷新页面会重复提交。
5. 部署与录像复现:从 bat 脚本到微信开发者工具调试的五个检查点
项目压缩包里带了1-install.bat、2-run.bat、3-build.bat三个脚本,这是典型的 Windows 环境一键启动方案。在答辩或验收前,建议按下面五个顺序检查,能减少大半运行环境问题。
检查点 1:MySQL 版本与编码。SSM 项目默认使用 MySQL 5.7 或 8.0。导入数据库脚本时注意检查脚本文件头的CHARACTER SET是否和实际数据库一致。如果出现中文乱码,在连接字符串里追加?useUnicode=true&characterEncoding=utf8。
检查点 2:端口冲突。后台默认跑在 8080 端口,微信开发者工具的url必须和后台实际端口一致。如果 8080 被占用,优先改后台 Tomcat 端口,而不是改小程序代码,因为改前端代码要重新编译。
检查点 3:本地设置里的域名校验。微信开发者工具默认不允许访问http://localhost,在「详情-本地设置」勾选「不校验合法域名」。如果你的演示录像里省掉这一步,换一台电脑录制时很可能请求直接失败。检查点 4:token 存储与清理。调试多次登录后,wx.getStorageSync('token')可能是旧值。建议在登录接口成功回调里重新写入 token,退出登录时调用wx.removeStorageSync('token')。
检查点 5:文件后缀 .bak 的处理。项目里保留了main.css.bak、update-password.vue.bak等文件,这些文件不会影响运行,但如果你用 IDE 打开整个目录,可能会出现重复文件提示。建议直接忽略,不要删,因为在答辩展示时如果提到项目开发过程中尝试过 Vue 单页改造,这些文件可以作为佐证。
整个项目跑通后,演示录像建议这样录制:先启动后台,管理端登录发布公告并审核一个测试用户;再打开小程序注册新账号、等待审核;登录后浏览健康知识,选择医生提交咨询;切回管理端以医生角色回复;切回小程序查看回复。这个顺序能把注册、审核、登录、内容浏览、咨询、回复、公告展示七个核心模块串成一条业务闭环,答辩时也更容易讲清楚数据流转。
本文还有配套的精品资源,点击获取