俩个ta头像
基于 SpringBoot + Vue 的心理健康 AI 助手的设计与实现封面图

基于 SpringBoot + Vue 的心理健康 AI 助手的设计与实现

标签 | Spring Boot · Vue3 · AI助手 · Spring AI · 毕业设计

摘要 | 针对高校心理咨询供给不足、线下预约可及性差的问题,构建基于 Spring Boot 3.4 与 Vue 3 的心理健康 AI 助手。后端整合 MyBatis-Plus、Redis、Spring Security 与 JWT,前端使用 Element Plus、ECharts 与 Three.js,接入 Spring AI 实现智能对话与情绪趋势分析,并提供后台审核管理。


基于 SpringBoot + Vue 的心理健康 AI 助手的设计与实现

摘 要

高校心理咨询服务的供给量与学生实际疏导需求之间的缺口长期存在,线下咨询在预约周期、隐私顾虑和可及性上难以覆盖全部学生群体。本文围绕"基于SpringBoot+Vue的心理健康AI助手"展开设计与实现,构建了一个同时面向学生自助疏导与后台管理审核的Web系统。

系统采用前后端分离架构。后端基于Spring Boot 3.4与Java 17,通过MyBatis-Plus访问MySQL 8业务库,使用Redis缓存令牌与临时状态,由Spring Security与JWT实现无状态鉴权;前端基于Vue 3与Vite,配合Element Plus完成页面构建,并借助ECharts与Three.js呈现情绪趋势。系统的主要特色在于用Spring AI接入大语言模型服务,形成三项相互衔接的能力:以SSE分片推送的流式心理疏导对话、以滑动窗口记忆保留上下文的多轮咨询、以及把咨询文本与情绪日记交给模型做结构化输出的情绪风险分析,分析结果再交由异步任务表统一调度与重试。功能上覆盖注册登录、AI疏导咨询、情绪日记、情绪趋势可视化、心理知识库与收藏、个人中心,以及后台的数据看板、用户管理、知识文章与分类管理、咨询记录管理、情绪日记管理和AI分析队列管理。

测试结果表明,系统各业务闭环可用,情绪分析任务状态流转正确,普通用户与管理员的数据隔离有效,能够满足高校心理健康辅助服务的日常使用需要,也为同类"AI+心理健康"应用提供了可复用的工程实现路径。

关键词:心理健康;SpringBoot;Vue;大语言模型;前后端分离

Abstract

The gap between the supply of campus psychological counselling and students’ real need for emotional support has remained wide, because offline counselling is limited by appointment cycles, privacy concerns and accessibility. This thesis presents the design and implementation of a mental-health AI assistant built on SpringBoot and Vue, which serves both student self-counselling and administrative review in one Web system.

A front-end/back-end separated architecture is adopted. The back end is developed on Spring Boot 3.4 and Java 17, accesses the MySQL 8 business database through MyBatis-Plus, caches tokens in Redis, and performs stateless authentication with Spring Security and JWT. The front end is developed on Vue 3 and Vite with Element Plus, and visualises emotional trends using ECharts and Three.js. The main feature of the system is the integration of a large language model through Spring AI, which produces three connected capabilities: streaming psychological counselling pushed by Server-Sent Events, multi-turn consultation that keeps context with a sliding-window memory, and structured emotional risk analysis of consultation texts and emotion diaries, whose results are scheduled and retried by an asynchronous task table. Functional coverage includes registration and login, AI counselling, emotion diaries, trend visualisation, a psychology knowledge base with favourites, a personal centre, and the administrative modules of data dashboard, user management, article and category management, consultation records, diary management and AI analysis queue management.

Test results show that the business flows work as a whole, the state transitions of analysis tasks are correct, and data isolation between ordinary users and administrators is effective. The system satisfies the daily needs of campus mental-health support services and offers a reusable engineering path for similar AI-based applications.

Key Words: mental health; SpringBoot; Vue; large language model; front-end and back-end separation

引 言

进入大学之后,学生面对的是学习方式、人际关系与生活环境的三重变化,学业竞争、考研就业压力、情感困扰和宿舍摩擦都会在较短周期内叠加出现。多数学生并不会在情绪出现问题后的第一时间走进心理咨询中心:一方面咨询名额与咨询师时间有限,预约往往需要等待数天;另一方面"去咨询"这件事本身带有明显的标签感,学生担心记录被同学知晓,也更倾向于先自我消化。结果是大量本可以在早期干预的情绪波动被推迟处理,等到正式求助时问题已经积累到需要专业介入的程度。

已有的校园信息化应用在这一点上留给学生的空间很小。教务系统解决选课与成绩,宿管系统解决床位与报修,心理健康相关的能力大多停留在一个静态的文章列表页面,或者一个只支持固定问答的聊天窗口。前者把心理科普当作单向推送,无法针对学生当下的表达给出回应;后者缺少上下文记忆,学生描述完处境后得到的是一句与前面无关的模板答复,交流感很弱。与此同时,学生自己也缺少一个低门槛的情绪记录工具——写下当天心情之后,没有系统替他回看趋势,也没有工具在连续低落时给出提醒。

大语言模型技术的发展让上面这件事有了新的解法。模型已经能够理解自然语言中的情绪线索,能够在多轮对话中保持话题连贯,还可以按预先约定的字段结构输出判断结果,这使"可24小时访问、不带评判、能承接情绪表达"的自动疏导成为可能。但把模型直接接进校园场景并不是调用一次接口那么简单:模型输出存在延迟,需要流式推送才不至于让页面长时间空白;对话记录既要保留上下文又不能无限累积,需要窗口化的记忆管理;情绪判断属于敏感结论,需要与后续人工审核打通;模型服务本身也可能超时或失败,需要任务化的重试机制。这些工程问题恰恰决定了系统能否真正投入使用。

本文的工作即围绕这些工程问题展开。系统以前端页面承接学生的记录与咨询行为,以Spring AI把大语言模型引入对话与分析环节,用SSE实现逐字推送,用消息窗口保留最近30条上下文,用异步任务表统一调度情绪分析并记录失败原因,最终把AI疏导对话、情绪日记、趋势可视化和心理知识库组织成一套完整的应用;同时为管理员提供后台数据看板、用户管理、内容管理与咨询记录审核,使学生的求助行为在需要时能被接住。全文按绪论、需求分析、总体设计、详细设计与实现、测试的顺序展开,最后一章对系统的能力边界与后续改进方向作出说明。

第一章 绪论

1.1 课题研究背景

国内高校在过去十余年里持续扩大招生规模,在校学生基数不断抬高,学生心理问题检出率随之上升。学业、就业、人际与家庭期待交织在一起,使焦虑、情绪低落和睡眠问题成为校园中最常见的心理困扰类型。与之相对的是专业供给的不足——按相关指导文件建议的师生比配备专职心理咨询师的高校仍占少数,多数学校的咨询中心需要承担课程教学、危机干预与档案管理等多重任务,能够分配给日常谈话的时间有限。学生在咨询室门前排队的现象,本质上是一个服务容量问题,不会因为宣传"求助不可耻"而得到缓解。

技术条件的变化为缓解这一容量压力提供了入口。Java生态在服务端长期稳定,Spring Boot把配置、依赖与内嵌容器收敛到较少的人工干预上,使中小团队也能搭出一套可持续演进的后端服务;基于Java的大模型接入框架已经把对话、记忆与结构化输出封装成可组合的抽象,开发者不必自己维护HTTP层的细节[1]。前端一侧,Vue 3的组合式写法与组件库生态让复杂交互页面的开发成本明显下降,单页应用配合异步接口成为校园信息系统的主流形态[2]。两项能力叠加之后,一个"由学生自主发起、由模型即时回应、由管理员事后审核"的心理支持系统具备了在普通毕设工作量内实现的条件。

刘月等人较早地把Java技术栈引入大学生心理咨询管理场景,验证了咨询预约与档案管理在线化的可行性[3];朱珞铱等人则在智慧养老服务平台中实现了用户健康数据的记录与展示,其"自助记录—平台分析—结果反馈"的闭环结构与心理疏导场景高度相似[4]。这些工作说明,心理健康类信息系统的难点并不在于增删改查本身,而在于如何把敏感的情绪数据与自动化分析结果组织成可信任、可复核的业务流程。本文选择这一题目,正是希望在一个完整可运行的系统中同时处理这两个层面:既完成学生侧的记录与咨询功能,也完成模型输出的结构化落库与人工侧的管理审核。

1.2 研究目的与研究意义

1.2.1 研究目的

本课题的目标是设计并实现一套面向高校场景的心理健康AI助手系统,使学生在不受时间与场地限制的情况下获得情绪表达与疏导的渠道,使管理人员具备对内容与求助信号进行复核的能力。具体目的可以拆为四点。

第一,构建一个可用的心理疏导对话入口。对话需要按流式方式呈现,让用户在数秒内看到首个字符;需要保留最近若干轮上下文,使模型能接住"我刚才说的那个事"这类指代;需要在服务异常时给出明确反馈,而不是静默返回空内容。

第二,把情绪状态从主观描述变成结构化数据。系统要求模型对咨询文本与日记文本输出主情绪、情绪强度分值、是否为负性情绪、风险等级、关键词、建议语与改善措施等字段,落库后可以被趋势图与后台列表直接消费,而不是一段无法解析的自然语言。

第三,完成自助记录与可视化。学生每天可以提交一条情绪日记,记录评分、主要情绪、触发因素、睡眠质量与压力水平,系统按日期区间给出趋势曲线与情绪分布,使"我这半个月的状态"成为一个可回答的问题。

第四,支撑管理侧的内容维护与风险复核。管理员需要维护心理知识库的文章与分类,需要查看用户状态、咨询会话记录、情绪日记与AI分析任务队列,并在任务失败时具备重试能力。

1.2.2 研究意义

从应用角度看,系统的价值首先体现在服务可及性的提升。咨询中心的服务时间与学生自由活动时间并不完全重合,深夜与假期往往是情绪波动集中而人工支持缺位的时段,由模型承接这一时段的表达需求,可以在不增加人力成本的前提下延长服务窗口。其次体现在早期识别上:学生未必会主动预约咨询,但会连续记录日记,当若干条日记的情绪分值同时走低、风险等级持续为"需要关注"时,这些记录本身构成了干预的触发依据。

从技术角度看,本课题把大语言模型放进一个传统Web业务系统中,而不是单独做一个演示性的对话页面,因此必须处理模型能力与工程约束之间的摩擦——流式输出的传输协议选择、上下文窗口的长度策略、结构化输出的解析失败回退、异步任务的优先级与重试上限,以及敏感数据在权限层的隔离。这些处理的实现方式与取舍理由,对同类"AI+业务"毕设项目有直接参考价值。

1.3 国内外研究现状

1.3.1 国内研究现状

国内围绕校园心理服务信息系统的研究已经积累了一定数量成果,但整体呈现"管理平台成熟、智能疏导薄弱"的分布。早期工作大多聚焦于管理流程的线上化,围绕咨询预约、档案归集、危机上报和统计报表设计功能边界,本质上是把纸质流程搬到Web上,学生在系统中仍然只能被动提交信息。刘月等人给出的大学生心理咨询管理系统即属于此类,其重点在于咨询记录的组织与权限管理[3]。

第二类工作开始引入自动化分析。在智慧养老、智慧护理等相邻领域中,已有研究把平台采集到的用户状态数据交由规则引擎或模型进行判断,再把结论返回给前端展示[4][5]。这类系统验证了"数据—分析—反馈"链路在非诊断场景中的可用性,但分析结果多为规则打分,表达上偏机械,难以针对用户具体处境给出个性化回应。

第三类工作把大语言模型纳入系统实现。相关研究在运维管理、程序评测等场景中试验了Java生态与大模型结合的工程方式,重点解决接口接入、上下文维持与输出约束问题[1][6]。不过这些工作集中在运维与教学领域,心理疏导场景的落地仍少见,且普遍缺少对模型输出的二次校验设计。前后端分离的安全实现方面,Rodriguez等人提出的全栈开发方法在Vue.js、Spring Boot与MySQL组合上做了完整验证,给出了一套覆盖认证、会话与数据校验的开发规程[7],其结论对国内同类系统的架构选择同样适用;国内学者则在Vue框架的前端性能优化与组件化开发模式上做了较多工程性总结[8][9]。

综合来看,国内研究的不足集中在三点:一是疏导对话与情绪记录往往彼此独立,学生的咨询内容不会沉淀为可分析的数据;二是模型输出缺少结构化约束,难以进入后台管理流程;三是对失败与超时的处理大多停留在前端提示层,没有形成可重试的任务记录。

1.3.2 国外研究现状

国外在这一方向的研究更偏向临床有效性与系统实现两个分支。临床分支关注自动化干预能否产生实际效果,数字疗法与网络认知行为干预的相关评估表明,非诊断性的自助情绪支持在轻中度困扰人群中具有可用性,但需要明确的转介机制配合。实现分支则给出了若干可直接借鉴的工程结论。

在数据层,Salunke与Ouda对PostgreSQL与MySQL在负载递增条件下的查询吞吐做了基准测试,指出索引设计与并发控制对Web系统的响应时间影响显著[10],这一结论对需要频繁按用户与日期区间检索记录的心理日记类应用尤为重要。在传输与交互层,Tigrine等人实现的实时生理信号监测系统采用Web架构结合MySQL存储,重点处理了持续数据流的低延迟呈现问题[11],与本课题需要的流式对话推送在技术诉求上一致。在全栈安全实现层,Rodriguez等人给出的方法覆盖了Spring Boot后端的令牌校验、Vue前端的路由防护与MySQL的访问控制,并以完整原型验证了流程可复现性[7],为本系统的鉴权设计提供了参照。

国外工作的共性特点是把模型能力与合规审查并列考虑,对风险识别后的处理路径有明确定义。不足之处是多数系统依附于特定临床流程或商业平台,直接移植到高校场景时,既受限于数据合规要求,也难以适配学生日常管理组织结构。本课题在借鉴其流式传输、基准测试与安全开发方法的同时,把功能重心放在自助记录、疏导对话与管理审核的衔接上。

1.4 系统主要工作与技术路线

本文完成的工作包括需求分析、总体设计、模块详细设计、编码实现与功能测试五个部分。需求阶段明确了普通用户与管理员两类角色的功能边界;设计阶段确定了分层架构、前后端职责划分与数据库表结构;实现阶段完成了鉴权、流式对话、情绪分析任务调度、知识库管理和后台管理等核心模块;测试阶段覆盖功能闭环、权限隔离与流式响应的可用性。

技术路线沿"先定边界、再定结构、最后验证行为"的顺序推进。系统后端以Spring Boot为基础框架,采用Java 17语言版本,持久层使用MyBatis-Plus操作MySQL 8数据库,令牌与临时状态缓存使用Redis,安全控制使用Spring Security配合java-jwt实现,接口文档由knife4j生成;AI能力由Spring AI提供,模型服务通过OpenAI兼容接口接入,对话记忆采用消息窗口策略,情绪分析通过异步任务表调度。前端以Vue 3与Vite构建,界面组件使用Element Plus,趋势图使用ECharts,情绪热力呈现使用Three.js,富文本编辑使用wangEditor,流式接收使用SSE事件源客户端。整体开发在Windows环境下完成,使用Maven管理后端依赖,使用npm管理前端依赖。

1.5 经济与社会效益

经济效益方面,系统由一台普通服务器即可支撑小规模并发运行,模型调用按token计费,学生自助疏导的边际成本远低于一次人工咨询。若把系统部署在校内,咨询中心可将有限的面谈时间集中到真正需要干预的个案上,前期筛查由系统承担,人力使用的针对性明显提高。内容维护采用知识库文章的集中管理,同一批科普材料可长期复用,无需按学期重复制作宣传内容。

社会效益方面,系统降低的是求助的行为门槛。学生不必在公开场合表达困扰,也不必事先判断自己的问题"够不够严重",可以先在系统内记录与对话。情绪趋势与风险等级为早期识别提供了依据,连续的低分值记录与"需要关注"标记会进入管理侧视野,使干预时机前移。同时,系统对模型输出保留明确的定位说明——它提供情绪承接与建议,不提供诊断结论,这一边界既符合心理服务的规范要求,也避免了自动化结论被误当作医疗判断。

1.6 本章小结

本章从课题来源出发,说明了系统在高校心理服务场景中的定位:不承担诊断与干预,而是承担日常自助记录、即时情绪疏导与服务数据的沉淀。随后从研究目的、研究意义两个层面界定了本文要解决的问题,并对国内外已有工作做了梳理——国内研究在管理流程线上化上较为充分,疏导对话与分析链路的结合仍偏薄弱;国外工作在流式传输、存储基准与安全开发规程上给出了可直接借鉴的工程结论,但多依附于特定临床流程。最后给出本文的五项主要工作与"先定边界、再定结构、最后验证行为"的技术路线,并说明系统在成本与求助门槛两方面的效益。后续章节将沿这条路线展开:第二章确定功能与非功能需求,第三章给出架构与总体设计,第四章完成模块详细设计与数据库设计,第五章记录实现结果与实测情况。

第二章 系统需求分析

2.1 系统需求分析概述

系统的使用者分为普通用户与管理员两类,两类角色在系统中的位置完全不同。普通用户是情绪数据的产生者,其所有操作都围绕"表达—记录—回看"展开,界面上呈现的是聊天窗口、日记表单和趋势图;管理员是内容与秩序的维护者,其操作对象是别人的数据,界面上呈现的是列表、筛选条件和统计看板。这种差异决定了需求分析不能简单罗列功能点,而要分别回答"用户能做什么"和"管理员能看什么、能改什么"。已有基于SpringBoot的企业信息管理平台在角色与数据范围划分上给出的处理方式[12],与本校园场景中的前后台分离思路一致,本文在其基础上补充了情绪数据这一特殊对象的访问约束。

需求边界的确立依据有三条。第一条来自场景本身:系统的定位是自助心理支持,不是诊断工具,因此不提供量表测评、不输出疾病结论、不承担危机干预的处置职责,涉及风险的内容只做标记与建议提示。第二条来自技术约束:大模型响应存在不确定延迟且可能失败,凡是依赖模型结果的功能都必须设计失败后的数据状态,不能出现"提交后一直在等待"的界面。第三条来自数据敏感性:咨询对话与情绪日记属于个人私密信息,普通用户只允许访问自己的记录,管理员虽然在后台可以查看全局数据,但查看行为要处于可管理的账号权限之下。

按这三条边界梳理,系统需要实现的功能可以归纳为一张需求清单。

表2.1 系统功能需求清单

编号功能项角色主要输入期望结果
F1注册与登录普通用户、管理员用户名、密码、邮箱、手机号校验通过后签发访问令牌
F2AI心理疏导咨询普通用户会话标识、咨询文本流式返回模型回复并落库
F3咨询情绪分析普通用户会话中的对话内容输出主情绪、分值与风险等级
F4情绪日记记录普通用户评分、情绪、触发因素、正文一天一条记录,可修改
F5日记情绪分析普通用户、管理员日记正文生成结构化分析并写回
F6情绪趋势查看普通用户日期区间展示曲线、分布与汇总
F7知识库浏览与收藏普通用户分类、关键词文章列表、详情与收藏状态
F8个人信息维护普通用户昵称、头像、性别、生日更新资料与修改密码
F9数据分析看板管理员统计维度展示用户与情绪的整体分布
F10用户管理管理员筛选条件、目标用户分页查询、禁用、编辑、删除
F11知识分类与文章管理管理员分类项、文章正文新增、修改、上下架、批量删除
F12咨询记录管理管理员筛选条件分页查看会话与消息明细
F13情绪日记管理管理员筛选条件分页查看、删除、触发分析
F14AI分析队列管理管理员任务状态筛选查看队列统计、单条与批量重试

2.2 普通用户角色需求分析

普通用户是使用系统的主体,其需求来自一个很具体的使用情境:某天晚上情绪低落,希望有人可以说话,或者希望把当天的状态记下来。围绕这一情境,用户侧功能被组织为七个用例。

注册与登录是进入系统的入口。用户名、邮箱与手机号在数据层均设置了唯一约束,注册时若三者之一已被占用,界面应给出明确原因而不是笼统失败。登录支持用户名或邮箱两种标识,密码以BCrypt摘要存储,服务端不保存明文。登录成功后系统签发JWT令牌,前端将其保存在本地并在后续请求中携带,令牌内包含用户标识与用户类型。

AI心理疏导咨询是系统的核心用例。用户进入咨询页面后先创建一次会话,会话记录写入咨询会话表,随后在输入框中表达当下的处境。用例的关键要求是响应过程可见——模型生成回复的总时长通常在数秒量级,若等完整文本返回再显示,用户面对的是长时间的空白,这在情绪表达场景中是不可接受的。因此咨询用例要求以分片方式持续输出文本,同时要求模型能记住同一会话内的先前内容,否则每轮回答都要用户重复背景。会话结束后,本轮对话还应沉淀为情绪分析结果,供趋势页面与后台查阅。

记录情绪日记与查看情绪趋势构成第二个闭环。日记按日期唯一,同一天重复提交时更新原记录而不是插入新行;记录内容包括情绪评分、主要情绪、情绪触发因素、日记正文、睡眠质量与压力水平。趋势用例要求按日期区间聚合分值曲线与情绪分布,让用户看到连续状态而非孤立某天。

浏览心理知识库与收藏知识文章满足主动学习需求。知识库按分类组织,文章以富文本形式存储,列表页支持分类筛选与标题检索,详情页可以收藏与取消收藏,收藏关系单独存储并可回显状态。维护个人信息用例覆盖昵称、头像、性别、生日的修改以及密码变更,头像上传要经过类型与大小校验。

图2.1 普通用户用例图

2.3 管理员角色需求分析

管理员的需求不在于操作更多,而在于看的范围更大、改的权限更明确。管理员登录后进入后台,默认落地页是数据分析看板,看板上呈现用户总量与新增趋势、咨询会话数量、日记提交量、情绪分值分布以及负性情绪占比,这些指标共同回答"当前有多少学生在用、整体状态如何"。

用户管理用例要求能够按用户名、类型与状态分页检索用户,并对单个用户执行编辑、禁用与删除。禁用与删除的语义不同,前者保留数据只是阻断登录,后者会触发关联数据清理,系统必须在界面上区分这两种后果。知识分类管理与知识文章管理共同承担内容维护职责:分类是文章的归属维度,删除分类前应确认其下无文章;文章支持新增、修改、发布状态变更与批量删除,正文由富文本编辑器录入,封面图独立上传。

咨询记录管理与情绪日记管理属于复核类用例。管理员需要看到全部会话与全部日记,能按用户与时间筛选,并在发现异常表述时进一步查看消息明细。这两个用例不要求管理员修改学生写下的原文——改写学生的原始表达会破坏数据的真实性,系统只允许删除不允许编辑,正是基于这一考虑。AI分析队列管理是运维类用例,管理员需要看到任务的待处理、处理中、已完成与失败四种状态的分布,对失败任务了解其错误原因,并具备单条重试与批量重试的操作能力。

图2.2 管理员用例图

2.4 非功能性需求分析

功能性需求之外,系统还必须满足若干质量要求,这些要求在实现阶段会以具体设计决策的形式落地。同类校园管理系统在需求阶段普遍把性能与安全列为与功能并列的第三条主线[13],本系统沿用这一做法。

响应及时性方面,情绪日记的趋势查询、知识库列表的分页查询属于高频读操作,要求在服务端通过合理的索引设计控制查询代价;咨询对话要求首个字符在数秒内到达,这决定了接口必须采用流式返回而非一次性返回。

数据一致性方面,同一天多条日记记录、同一会话下的消息顺序、收藏关系中同一用户对同一文章不应出现重复记录,这三点都需要在存储层用唯一约束或联合索引保证,而不能只依赖应用层的先查后写。

安全性方面,密码不得明文存储;未携带有效令牌的请求不得进入业务方法;普通用户查询会话、日记与收藏时,服务端必须强制限定数据归属,不能仅凭前端传入的用户标识判断——前端传参可被篡改,这一层校验必须在服务端完成。

可维护性方面,接口需要提供在线文档以便前后端联调;业务异常应返回统一的响应结构与可读的提示文案,而不是把堆栈信息暴露给页面;模型服务地址与密钥通过配置文件管理,更换服务商时不必改动代码。

可用性方面,页面在数据为空时应给出引导文案而非空白表格;请求失败时应有可重试入口;后台长列表要支持分页与条件筛选,避免一次性拉取全部数据。

2.5 核心业务流程需求分析

三条主流程覆盖了系统中最关键的数据流转,也是后续设计要重点保障的路径。

用户注册与登录流程涉及身份的建立与延续。用户提交账号与密码后,服务端先按用户名或邮箱定位用户,再以BCrypt比对密码摘要,比对不通过统一返回"用户名或密码错误",避免泄露账号是否存在。校验通过后签发JWT令牌并缓存用户信息,随后按用户类型分流——管理员跳转后台数据分析首页,普通用户跳转前台首页。令牌在此后的每次请求中承担身份识别职责,流程图见图2.3。

图2.3 用户注册与登录流程图

AI心理疏导咨询流程是模型能力与业务数据交汇的路径。用户创建会话后,咨询会话表产生一条记录;用户提交消息时,系统先复核会话标识与登录身份,身份无效直接返回错误事件;有效则保存用户消息并写入对话记忆,随后以SSE方式逐段推送模型回复,推送完成后保存完整的模型回复与记忆内容。会话情绪分析在流程末尾异步执行,不阻塞对话本身,其结果写回会话记录的情绪分析字段。这种"对话与分离步"的安排是为了让用户不必等待分析完成就能看到回复,流程图见图2.4。

图2.4 AI心理疏导咨询流程图

情绪日记记录与分析流程把文本转化为可统计的数据。用户填写评分、触发因素与正文后提交,系统判断当天是否已有记录,有则更新、无则新增;写入成功后创建一条AI分析任务并置于待处理状态,由异步执行器调用模型完成结构化分析。分析成功时把主情绪、风险等级与情绪强度写回日记;分析失败时记录错误原因并递增重试次数,达到上限后不再自动重试,只能由管理员在队列页面手动触发。流程图见图2.5。

图2.5 情绪日记记录与AI分析流程图

2.6 本章小结

本章确定了系统的两类角色与十四项功能需求,明确了自助心理支持这一功能边界带来的三项约束:不做诊断、模型失败必须可恢复、私密数据必须在服务端完成归属校验。非功能需求从响应及时性、数据一致性、安全性、可维护性与可用性五个方面给出了衡量标准。三条核心业务流程的分析结果为下一章的架构设计与模块划分提供了直接依据,其中"流式对话"与"异步分析任务"两项要求在实现上最具约束力,后续设计将围绕它们展开。

第三章 系统总体设计

3.1 系统总体架构设计

系统采用前后端分离的分层架构,纵向划分为表现层、接入层、业务逻辑层、AI能力层与数据层五层,层间只允许向下调用,不允许跨层访问数据。已有测量表明,前后端分离在减少服务端渲染压力的同时,把一部分响应时间转移到了接口往返上[14],因此架构上需要以聚合接口与索引设计来对冲这一代价。

表现层由Vue 3单页应用承担,按前台与后台拆分为两套布局。前台布局包含首页、咨询、日记、趋势、知识库与个人中心;后台布局包含数据看板与各类管理列表。表现层不实现任何业务规则,只负责渲染与交互发起。

接入层是后端对外暴露的统一入口,由Spring MVC的控制器构成,负责请求参数校验、当前登录身份读取与响应结构封装。所有接口返回统一的Result结构,包含状态码、提示信息与数据体,前端据此判断成功与失败分支,接口文档由knife4j自动生成。

业务逻辑层承载系统的核心规则,按业务对象划分为用户服务、日记服务、会话服务、消息服务、文章服务、分类服务、收藏服务、文件服务与分析任务服务。这一层的两个设计要点是:数据归属判断在服务层完成,例如普通用户查询会话时强制以其身份覆盖查询条件;跨表写入通过事务保证一致,例如日记保存与其分析任务创建必须同时成功。

AI能力层把模型调用从业务代码中隔离出来,包含对话客户端、会话记忆、提示词组织与结构化输出四类职责。对话客户端由Spring AI的ChatClient承担,配置时注入系统提示语与记忆通知器;会话记忆使用消息窗口策略,保留最近30条对话;结构化输出通过把模型回复约束到指定记录类型实现,避免业务代码解析自由文本。

数据层由MySQL与Redis共同构成。MySQL存放全部业务数据并维护外键与唯一约束,Redis存放令牌相关状态与临时文件过期信息,文件实体存储在服务器本地目录并由文件表登记访问路径。

图3.1 系统总体架构图

3.2 系统功能模块规划

系统功能划分为前台用户端与后台管理端两个子系统,前台按学生使用路径组织七个模块,后台按管理动作组织七个模块。

前台模块中,注册登录模块承担身份建立;AI心理疏导模块承担对话与情绪标签生成;情绪日记模块承担日常记录与单条分析;情绪趋势模块承担区间聚合与可视化;知识库模块承担分类浏览、检索与收藏;我的收藏模块承担收藏列表管理;个人中心模块承担资料与密码维护。

后台模块中,数据分析模块提供整体统计视图;用户管理模块负责账号秩序;知识文章与分类管理模块负责内容供给;咨询记录模块负责求助线索的复核;情绪日记管理模块负责记录数据的查看与批量分析触发;AI分析队列模块负责模型任务的健康度。

模块之间的耦合点有两处。其一是情绪数据的双来源:咨询会话与情绪日记都会产生情绪分析结果,趋势模块需要同时消费这两类数据,因此两者被约定输出同一组字段结构——主情绪、情绪分值、是否负性、风险等级与建议语。其二是分析任务对日记的依附关系:任务表中每条记录都指向一条日记,日记被删除时任务随之清理,这一约束由外键的级联删除实现。

图3.2 系统功能结构图

3.3 用户权限控制设计

权限控制采用"无状态令牌 + 服务端二次判定"的方案,不依赖Session。这一取向与Java生态中把鉴权拆分为网关、令牌与业务三层的设计思路一致[15],不同之处在于本系统把风险最高的情绪数据单独交给服务层做归属判定。整体流程为:请求到达后先经过自定义的JWT认证过滤器,过滤器读取请求头中的令牌,验证签名与有效期,通过后解析出用户标识与用户类型,查询用户并判断账号状态,状态正常时把身份信息写入安全上下文,请求继续进入业务方法;令牌无效时清理认证上下文并按匿名处理,由后续的授权规则决定是否拦截。

公开路径的配置以接口文档、静态资源与认证类接口为主,同时把带前缀的业务接口整段纳入放行范围。这样安排的直接后果是:授权层不再逐个维护接口白名单,身份识别完全交给令牌过滤器,权限判断下沉到控制器与服务方法内部。系统对内的权限判定因此依赖两处代码——控制器通过工具类从安全上下文读取当前用户标识与用户类型,服务方法在查询条件上强制覆盖归属字段。管理员与普通用户使用同一种令牌结构,类型字段随身份写入载荷,服务端据此区分角色,普通用户即使直接构造管理接口的请求,也会在角色判断处收到"权限不足"的响应。

数据归属的控制同样不依赖前端:普通用户发起的会话查询、日记查询与收藏查询,服务端会把查询条件中的用户标识覆盖为当前登录者,前端传入的他人标识因此不生效。账号状态的判定被放在过滤器内完成,使被禁用用户在令牌尚未过期时也无法通过校验,这比仅在登录时检查更稳妥。密码修改后旧令牌仍在有效期内属于该方案的已知限制,考虑到修改密码本身需要已认证身份,风险有限,系统在前台退出时清除本地令牌,未做服务端强制失效。此外,把业务接口整段放行也带来了另一处需要补强的地方——少数以admin为路径前缀的管理接口内部没有重复做角色判断,第五章的测试对这一情况做了实测记录并列入改进项。

图3.3 用户权限控制流程图

3.4 关键技术选型说明

后端框架选择Spring Boot 3.4,原因在于其对Java 17的原生支持、内嵌容器带来的部署便利,以及与Spring AI之间的版本兼容。持久层选择MyBatis-Plus而非JPA,是因为系统的查询以条件分页与聚合统计为主,Lambda条件的写法比手写映射更省力,同时保留在需要时直接编写XML SQL的空间。

模型接入选择Spring AI的OpenAI兼容实现。相比自行封装HTTP调用,该方案提供了ChatMemory、Advisor与结构化输出三项直接可用的抽象,使上下文管理与输出约束不必重复实现。模型服务通过OpenAI兼容接口接入,具体模型与接口地址全部写在配置文件内,更换服务商只需调整两处配置。

实时输出选择SSE而非WebSocket。对话场景的数据流是单向的——客户端发起一次请求、服务端连续返回片段,SSE在这种"一次请求、多次推送"的模式下实现成本更低,浏览器侧的心跳与自动重连机制也更成熟。WebSocket适合双向通信,本系统不存在该需求。

前端选择Vue 3配合Vite,其组合式写法与MVVM双向绑定在高校信息化系统里已有成熟应用[16],组合式API便于把趋势图表、流式接收这类有状态的逻辑抽离成独立函数;组件库选择Element Plus以减少基础控件的开发量;图表选择ECharts处理折线与柱状统计,Three.js用于情绪热力场景的表现。

3.5 本章小结

本章完成了系统的五层架构划分与前后台十四个模块的规划,确定了以JWT过滤器加服务端归属控制为核心的权限方案,并对框架、持久层、模型接入、实时输出与前端技术五处选型给出了选择依据。架构层面确立的"AI能力层独立"与"情绪字段结构统一"两条约定,为下一章各模块的详细设计提供了接口契约,详细设计将逐模块给出界面、实现片段与数据结构。

第四章 系统详细设计与实现

4.1 用户注册与登录鉴权模块

4.1.1 模块整体描述

该模块承担系统所有后续功能的身份前提,由注册、登录、令牌校验与退出四个环节构成。模块的设计要点有三处:账号唯一性依靠数据库唯一索引而不是应用层查询;密码校验使用BCrypt的单向摘要比对,服务端无从得知原始密码;身份延续不在服务端保存会话,而是把用户标识与用户类型写进JWT令牌,由每次请求自带的令牌还原身份。

模块涉及的代码文件为UserController、UserService、JwtTokenUtils、JwtAuthenticationFilter与SecurityConfig,数据落在user表。前端对应的页面是登录页与注册页,登录成功后的跳转分流由路由守卫根据用户类型完成。

4.1.2 分项功能设计与实现

登录页面的实现保持了校园系统常见的简洁形态:单个卡片居中,包含账号输入框、密码输入框与登录按钮,注册入口置于卡片下方,页面不放置宣传性文案,避免分散注意力。

图4.1 用户登录页面

登录方法的实现集中在身份定位与三重校验上。系统先按用户名或邮箱定位用户记录,再校验密码摘要,最后检查账号状态,三个分支返回不同的提示语,其中"用户不存在"与"密码错误"在语义上有意区分以便前端展示,但都不暴露密码本身。三项校验通过后签发令牌,并把用户详情一并返回给前端。

public UserLoginResponseDTO login(UserLoginCommandDTO loginDTO) {

LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>();

wrapper.eq(User::getUsername, loginDTO.getUsername())

.or().eq(User::getEmail, loginDTO.getUsername());

User user = userMapper.selectOne(wrapper);

if (user == null) { throw new BusinessException(“用户不存在”); }

if (!passwordEncoder.matches(loginDTO.getPassword().trim(), user.getPassword())) {

throw new BusinessException(“用户名或密码错误”);

}

if (user.isDisabled()) { throw new BusinessException(“账号已被禁用,请联系管理员”); }

String token = JwtTokenUtils.generateToken(user.getId(), user.getUsername(), user.getUserType());

return UserConvert.buildLoginResponse(token, UserConvert.entityToDetailResponse(user));

}

令牌校验由自定义过滤器完成,它被插在Spring Security的用户名密码过滤器之前,因此每个请求在进入授权判断前已经携带身份信息。过滤器读取请求头中的令牌,验证签名与有效期后查询用户并检查状态,状态正常才写入安全上下文。

String token = JwtTokenUtils.extractTokenFromRequest(request);

if (StringUtils.hasText(token)) {

JwtTokenUtils.TokenValidationResult result = JwtTokenUtils.validateToken(token);

if (result != null && !JwtTokenUtils.isTokenExpired(token)) {

// 依据令牌中的用户标识查询用户并校验账号状态

UsernamePasswordAuthenticationToken authentication =

new UsernamePasswordAuthenticationToken(principal, null, authorities);

SecurityContextHolder.getContext().setAuthentication(authentication);

request.setAttribute(“jwtToken”, token);

}

}

安全配置把这一逻辑固定为无状态策略:关闭CSRF、禁止创建Session、公开路径放行、其余请求必须认证。

http.csrf(csrf -> csrf.disable())

.sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))

.authorizeHttpRequests(auth -> auth

.requestMatchers(PUBLIC_PATHS).permitAll()

.anyRequest().authenticated())

.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);

前端一侧的配套实现包括两点:请求拦截器在每次调用前把本地令牌写入请求头,收到未授权响应时清除令牌并跳回登录页;路由守卫按登录状态与用户类型决定去向,未登录访问受限路由时带上redirect参数跳转登录页,已登录管理员访问前台路由时被重定向到后台看板。

注册功能在实现上复用同一套加密与校验逻辑,但增加了对用户名、邮箱、手机号三项唯一冲突的捕获——数据库唯一索引抛出的重复键异常会被全局异常处理器翻译成可读提示,前端在对应输入框下展示,用户能立即知道是哪一个字段冲突。

4.1.3 模块运行性能表现

登录接口在本地环境的响应时间在数十毫秒量级,主要开销来自BCrypt的代价因子计算,这是加密算法自身特性而非系统缺陷;令牌校验在过滤器中只做签名验证与用户查询一次,未引入额外远程调用。系统未采用服务端会话存储,因此登录状态不占用服务器内存,多实例部署时不需要同步会话。

4.2 AI心理疏导咨询模块

4.2.1 模块整体描述

该模块是系统与外部模型服务的唯一交互界面,功能上包含会话创建、流式对话、消息落库、上下文记忆与会话情绪分析五部分。涉及代码为PsychologicalChatController、PsychologicalSupportService、ChatClientConfig、StructOutPut与PromptManage,数据落在consultationsession与consultationmessage两张表。

模块的设计取舍集中在一处:情绪分析要出结果,但不能让用户等它。模型完成一次结构化情绪判断与生成一次完整回复的耗时相加会明显拖长对话节奏,因此系统将分析从主链路剥离,采用异步方式在后台执行,界面上的情绪标签通过轮询获取最新值。

4.2.2 分项功能设计与实现

咨询页面采用左右分栏布局,左栏是历史会话列表,可以新建、改名与删除;右栏是对话区,消息以气泡形式区分用户与助手,助手回复在流式接收过程中逐段追加,底部输入框支持回车发送与生成中停止。页面同时显示当前会话的情绪标签与风险提示入口。

图4.2 AI心理疏导咨询页面

对话客户端的配置决定了系统里所有模型调用的行为基线。这里做了三件事:设定心理疏导师角色/system提示语、注入记忆通知器、声明使用OpenAI兼容的模型实例。

public static final Integer MAX_MEMORY_MESSAGE_SIZE = 30;

public static final String DEFAULT_SYSTEM =

“你是一个专业的心理疏导师,温和耐心,善于倾听,能够提供专业的心理支持和建议。”;

@Bean

public ChatMemory chatMemory() {

return MessageWindowChatMemory.builder()

.maxMessages(MAX_MEMORY_MESSAGE_SIZE) // 仅保留最近30条

.build();

}

@Bean(“open-ai”)

public ChatClient openAiChatClient(OpenAiChatModel openAiChatModel) {

return ChatClient.builder(openAiChatModel)

.defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory()).build())

.defaultSystem(DEFAULT_SYSTEM)

.build();

}

消息窗口取30条是一个折中结果。窗口过小会让模型忘记更早提到的具体人名与事件,过大则每轮请求携带的上下文变长、响应变慢,且早期低落情绪的措辞会持续影响后续回复的语气。30条大约覆盖十轮左右的对话,对一次完整的疏导谈话基本够用。

流式接口的核心是把模型输出的每个片段封装成SSE事件推给前端,同时按内容性质区分事件类型。当片段中出现风险标记时,事件类型被改成risk-warning,前端据此用醒目样式提示求助渠道,而不是把它当作普通文本插入气泡。

@PostMapping(value = “/stream”, produces = MediaType.TEXT_EVENT_STREAM_VALUE)

public Flux<ServerSentEvent> streamChat(@RequestBody StreamChatRequest request) {

Long userId = JwtTokenUtils.getCurrentUserId();

if (userId == null) {

return Flux.just(ServerSentEvent.builder()

.event(“error”).data(toSseData(Result.error(“用户未登录”))).build());

}

return psychologicalSupportService

.streamPsychologicalChat(request.sessionId(), request.userMessage())

.map(fragment -> ServerSentEvent.builder()

.event(fragment.contains(“⚠️”) ? “risk-warning” : “message”)

.data(toSseData(Result.success(Map.of(“content”, fragment))))

.build())

.onErrorReturn(ServerSentEvent.builder()

.event(“error”)

.data(toSseData(Result.error(“对话服务异常: 系统繁忙,请稍后重试”)))

.build())

.concatWith(Flux.just(ServerSentEvent.builder()

.event(“done”).data(“{}”).build()));

}

服务端在推送之前还要完成两件事:把用户消息落库、把用户消息加入对话记忆。落库时有个细节需要处理——首次进入会话时前端会发送一条开场消息,如果该消息已在会话创建阶段写入,重复保存会让历史记录出现两句相同内容,因此实现里先比对消息条数与最后一条内容再决定是否插入。

Integer messageCount = consultationMessageService.getMessageCountBySessionId(dbSessionId);

boolean isInitialMessage = false;

if (messageCount == 1) {

ConsultationMessageResponseDTO last =

consultationMessageService.getLastMessageBySessionId(dbSessionId);

if (last != null && last.getSenderType() == 1

&& userMessage.equals(last.getContent())) {

isInitialMessage = true; // 开场消息已存在,跳过重复保存

}

}

if (!isInitialMessage) {

consultationMessageService.saveUserMessage(dbSessionId, userMessage, null);

}

chatMemory.add(conversationId, List.of(new UserMessage(userMessage)));

情绪分析以结构化输出方式实现。业务代码不解析模型返回的自然语言,而是直接声明目标类型,由框架约束输出格式并反序列化为记录对象,字段缺失或格式错误时统一回落到默认分析结果,保证上层逻辑不会因解析异常中断。

public StructOutPut.EmotionAnalysisResult analyzeUserEmotion(String content) {

Prompt prompt = new Prompt(List.of(

new SystemMessage(PromptManage.EMOTION_ANALYSIS_SYSTEM_PROMPT)));

try {

return chatClient.prompt(prompt)

.user(“请快速分析以下内容的情绪状态:\n” + content)

.call()

.entity(StructOutPut.EmotionAnalysisResult.class);

} catch (Exception e) {

return getDefaultEmotionAnalysis(); // 失败回落,不向上抛出

}

}

流式推送在即时通信类系统中已有较多实践[17],本系统把同一思路用于单向下发的对话场景,省去连接管理与心跳协商。异步分析在主链路之外发起,结果序列化为JSON写入会话表的lastemotionanalysis字段,同时更新分析时间。前端在流结束后开始轮询该接口,最多轮询30次,拿到非默认结果即停止,这样既不影响对话连续性,又能让情绪标签在数十秒内出现在界面上。

await fetchEventSource(‘/api/psychological-chat/stream’, {

method: ‘POST’,

headers: { ‘Content-Type’: ‘application/json’, ‘Token’: token,

‘Accept’: ‘text/event-stream’ },

body: JSON.stringify({ sessionId, userMessage }),

signal: ctrl.signal,

onMessage(ev) {

const payload = JSON.parse(ev.data)

if (ev.event === ‘message’) appendAssistantText(payload.data.content)

if (ev.event === ‘risk-warning’) showRiskNotice(payload.data.content)

if (ev.event === ‘error’) stopAndNotify(payload.message)

}

})

4.2.3 模块运行性能表现

流式接口的首字时间取决于模型服务,本地测试环境下在1至2秒区间;分片以约50毫秒的节奏下发,页面滚动与文字追加没有明显卡顿。情绪分析与对话推送并行,不增加对话延迟。会话消息按(sessionid, createdat)建立联合索引,读取单个会话完整历史时一次范围扫描即可完成,消息量增长到千级时仍可保持毫秒级返回。

4.3 情绪日记与AI情绪分析模块

4.3.1 模块整体描述

该模块由记录、分析、趋势三部分构成,是系统把主观表达转成可统计数据的地方。记录部分的约束是"一天一条",分析部分的约束是"不能阻塞记录提交",趋势部分的约束是"空状态也要有内容"。涉及的代码为EmotionDiaryController、EmotionDiaryService、AiAnalysisTaskService与前端EmotionDiary、EmotionTrend页面,数据落在emotiondiary与aianalysis_task两张表。

4.3.2 分项功能设计与实现

日记记录页面用一组轻量控件收集状态信息:情绪评分以滑块形式给出1至10的取值,主要情绪从预设标签中选择,睡眠质量与压力水平各为1至5的等级选择,正文区域为多行文本框。页面上方显示当天已有记录时直接进入编辑态,避免用户重复提交。

图4.3 情绪日记记录页面

保存逻辑先定位当天记录决定新增还是更新,随后仅在正文非空时提交分析任务,一次空正文的评分记录不会触发模型调用。

@Transactional(rollbackFor = Exception.class)

public EmotionDiaryResponseDTO createOrUpdateDiary(Long userId,

EmotionDiaryCreateDTO dto, Boolean editMode) {

EmotionDiary existing = getByUserIdAndDate(userId, dto.getDiaryDate());

EmotionDiary diary = (existing != null)

? updateExistingDiary(existing, dto)
insertNewDiary(dto, userId);

if (StrUtil.isNotBlank(diary.getDiaryContent())) {

performAiEmotionAnalysisAsync(diary.getId(), diary.getDiaryContent());

}

return EmotionDiaryConvert.entityToResponse(diary);

}

更新走的是"只改非空字段"的策略,用户修改某一项时不会把其他字段清空。权限判断也在更新分支中显式完成,日记不属于当前用户时直接拒绝,而不依赖前端隐藏按钮。

EmotionDiary existingDiary = emotionDiaryMapper.selectById(updateDTO.getId());

if (existingDiary == null) { throw new BusinessException(“日记记录不存在”); }

if (!existingDiary.getUserId().equals(userId)) {

throw new BusinessException(“无权限修改此日记”);

}

LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>();

updateWrapper.eq(EmotionDiary::getId, updateDTO.getId());

if (updateDTO.getMoodScore() != null) {

updateWrapper.set(EmotionDiary::getMoodScore, updateDTO.getMoodScore());

}

分析任务表是这一模块在工程上最关键的设计。把"要不要做分析"变成一条持久化的任务记录,好处是失败的尝试不会消失。任务从创建到结束依次经过待处理、处理中、已完成或失败四种状态,状态迁移由独立方法完成,失败时写入错误原因并递增重试计数。

@Transactional(rollbackFor = Exception.class)

public Long createTask(Long diaryId, Long userId, AiTaskType type, Integer priority) {

AiAnalysisTask task = new AiAnalysisTask();

task.setDiaryId(diaryId);

task.setUserId(userId);

task.setStatus(AiTaskStatus.PENDING.getCode());

task.setTaskType(type.getCode());

task.setPriority(priority != null ? priority : 2); // 默认正常优先级

task.setRetryCount(0);

task.setMaxRetryCount(3);

task.setCreatedAt(LocalDateTime.now());

aiAnalysisTaskMapper.insert(task);

return task.getId();

}

优先级取值区分了触发来源:学生提交日记产生的自动任务为正常优先级,管理员在后台手动触发的重新分析为高优先级,批量补分析为较低优先级。这样的排序使管理员在处理紧急个案时不必排在普通提交之后。重试上限固定为3次,超出后任务停留在失败状态等待人工判断,避免对不可用的模型服务无限重试。

趋势部分的可视化沿用Vue与图表库组合的常见做法[18],前端由两个视图组成:折线视图按日期显示情绪评分的走势,分布视图统计各主要情绪的出现次数,另有一处使用Three.js渲染的情绪热力块阵列,把连续日期上的分值映射为高度与颜色,使"这段时间整体偏低"能被一眼看出。区间为空或记录不足时,视图显示引导文案并给出跳转记录的入口。

4.3.3 模块运行性能表现

日记的新增与更新属于单行写入,接口耗时以毫秒计;模型调用被完全移出该路径,用户感知的提交时间不包含分析时间。任务表在(status, priority)与(status, createdat)上建立了联合索引,队列按状态筛选并按优先级取任务时能直接命中索引。趋势查询按(userid, diary_date)联合索引扫描区间记录,一个学期跨度的数据量在数百条以内,聚合在前端完成亦可接受。

4.4 心理健康知识库模块

4.4.1 模块整体描述

知识库模块承担心理科普内容的组织与分发,功能覆盖分类浏览、关键词检索、文章详情阅读、阅读计数与收藏五个动作。内容侧的写入由后台完成,前台只读,因此这一模块同时是前后台协作最明显的一处:管理员用富文本编辑器录入的文章,在学生侧以格式化正文与封面图的形式呈现。涉及代码为KnowledgeArticleController、KnowledgeCategoryController、UserFavoriteController及对应服务类,数据落在knowledgecategory、knowledgearticle与user_favorite三张表。

文章主键采用UUID而不是自增编号,原因有两点:文章标识会出现在URL中,自增编号容易被推测遍历;管理员批量导入或跨环境迁移内容时,固定字符串标识可以避免主键冲突。与之对应,收藏表中的article_id字段类型也定义为等长的字符型,保证外键关系成立。

4.4.2 分项功能设计与实现

列表页顶部是分类标签条与搜索框,主体为文章卡片流,每张卡片显示封面图、标题、摘要、所属分类与阅读次数,点击后进入详情页。页面采用分页加载而不是无限滚动,便于学生返回列表时回到原位置。

图4.4 心理健康知识库列表页面

查询实现把分类筛选、标题模糊匹配与发布状态限定组合在同一条件构造器中,前台接口只允许读取已发布文章,草稿不会出现在列表里。

LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>();

wrapper.eq(KnowledgeArticle::getStatus, ArticleStatus.PUBLISHED.getCode());

if (queryDTO.getCategoryId() != null) {

wrapper.eq(KnowledgeArticle::getCategoryId, queryDTO.getCategoryId());

}

if (StrUtil.isNotBlank(queryDTO.getKeyword())) {

wrapper.like(KnowledgeArticle::getTitle, queryDTO.getKeyword());

}

wrapper.orderByDesc(KnowledgeArticle::getPublishedAt);

Page page = articleMapper.selectPage(new Page<>(current, size), wrapper);

详情页包含标题区、正文区与操作区。正文由富文本编辑产生,前端按HTML渲染,操作区提供收藏按钮与返回入口。阅读计数在学生进入详情时通过独立接口累加,不与详情查询混在一次请求里,避免任何重复渲染导致的统计偏差。

图4.5 知识文章详情与收藏页面

收藏关系单独成表,(userid, articleid)建立联合唯一索引,因此同一用户对同一文章的重复收藏在数据库层就被拒绝,接口无需先查再判。取消收藏按同一组合条件删除。

@PostMapping(“/{articleId}”)

public Result favoriteArticle(@PathVariable String articleId) {

Long userId = JwtTokenUtils.getCurrentUserId();

if (userId == null) { return Result.error(“用户未登录”); }

userFavoriteService.addFavorite(userId, articleId);

return Result.success();

}

@DeleteMapping(“/{articleId}”)

public Result unfavoriteArticle(@PathVariable String articleId) {

userFavoriteService.removeFavorite(JwtTokenUtils.getCurrentUserId(), articleId);

return Result.success();

}

我的收藏列表通过收藏表与文章表的关联查询得到,返回结构与知识库列表一致,因而可以复用同一套卡片组件;当文章已被管理员删除而收藏记录尚未清理时,级联删除约束保证收藏行随文章一并消失,列表中不会出现点不开的残留条目。

分类维护采用简单的两级结构,父分类字段预留了层级扩展能力,当前数据以一级分类为主。分类上的category_code设置了唯一约束,排序值决定前台展示顺序,禁用状态的分类不会出现在前台筛选条中,但其下文章仍可在其他入口被检索到。

4.4.3 模块运行性能表现

列表查询命中(categoryid, publishedat)联合索引,在按分类翻页的典型路径上无需回表排序;标题模糊匹配使用后置通配的like条件,同样可以走title索引。文章正文为longtext字段,列表接口只返回简要字段,避免把大文本随每一行数据传输,这是列表页在文章数量增长后仍保持响应的主要原因。

4.5 个人中心与账号管理模块

4.5.1 模块整体描述

个人中心是学生查看自身数据与修改账号信息的入口,包含资料展示与编辑、头像上传、密码修改与本人咨询记录的快速访问。涉及代码为UserController、FileController、SimpleFileService与BussinessFileValidationService,数据落在user与sysfileinfo两张表。

4.5.2 分项功能设计与实现

页面左侧为头像与基础资料卡片,右侧为可编辑的表单区,字段包括昵称、性别、生日、邮箱与手机号。头像上传受类型与体积双重限制,允许的格式由枚举定义,超出配置大小的文件在解析前即被拒绝。

图4.6 个人中心页面

文件处理采用"临时区—业务区"两段式设计。上传的文件先落在临时目录并登记为临时记录,写入过期时间;用户确认保存后,文件被移动到对应业务目录,记录状态转为正式。未被确认的临时文件由定时任务按过期时间清理,因此不会因用户中途放弃而无限堆积。

// 上传阶段:登记为临时文件并设定过期时间

fileInfo.setIsTemp(1);

fileInfo.setExpireTime(LocalDateTime.now().plusDays(1));

sysFileInfoMapper.insert(fileInfo);

// 确认阶段:移动到业务目录,转为正式文件

fileInfo.setFilePath(businessPath);

fileInfo.setIsTemp(0);

fileInfo.setExpireTime(null);

密码修改要求提交原密码与两次一致的新密码,服务端先用BCrypt核对原密码再写入新摘要,避免仅凭已登录身份就能改密的情况。字段更新采用与日记相同的"非空才更新"策略,用户只改昵称时不会把邮箱覆盖为空。

资料接口不返回密码字段,用户详情响应对象中不包含任何摘要信息,这是通过独立的响应DTO而非实体直接序列化实现的——实体一旦直接返回,后续新增敏感字段就会无意识地泄露。

4.5.3 模块运行性能表现

该模块的接口都以主键定位单行数据,耗时稳定在毫秒级。头像文件的读写位于本地磁盘目录,访问路径由静态资源映射直接提供,不经过业务方法,因此图片加载不会占用接口线程。

4.6 后台管理模块

4.6.1 模块整体描述

后台管理模块覆盖管理员的七项用例,是所有列表类功能的集合。其内部遵循同一套交互约定:页面顶部为条件筛选区,主体为数据表格,行末为操作按钮,分页控件置于表下;表格不显示无业务含义的自增主键列,时间字段统一格式化,状态字段以文字标签而非数值呈现。涉及代码为UserController的管理接口、KnowledgeCategoryController、KnowledgeArticleController、PsychologicalChatController的分页接口、EmotionDiaryController的管理接口、AiAnalysisTaskController与DataAnalyticsController。

4.6.2 数据分析看板实现

看板页展示用户总量与增长、咨询会话量、日记提交量、情绪分值分布与负性情绪占比,并把AI任务的健康度作为独立卡片呈现,管理员进入后台的第一屏即可判断系统当前状态。

图4.7 后台数据分析仪表盘页面

统计接口不采用逐表count再拼装的做法,而是把聚合下推到数据库,按需要的维度一次性返回。情绪热力数据按日期分桶,供前端绘制日历式视图。

@GetMapping(“/overview”)

public Result getDataAnalytics(

@RequestParam(required = false) String startDate,

@RequestParam(required = false) String endDate) {

return Result.success(dataAnalyticsService.buildOverview(startDate, endDate));

}

4.6.3 用户管理实现

用户管理页支持按用户名、用户类型与状态组合筛选,操作包括编辑资料、切换启用状态与删除。禁用与删除在界面上是两个独立按钮,且删除需要二次确认,因为删除用户会沿外键级联清理其日记、会话与收藏数据,属于不可逆操作。

图4.8 后台用户管理页面

@PutMapping(“/{id}/status”)

public Result updateUserStatus(@PathVariable Long id,

@RequestParam Integer status) {

Long operator = JwtTokenUtils.getCurrentUserId();

Integer role = JwtTokenUtils.getCurrentUserRole();

if (!UserType.ADMIN.getCode().equals(role)) {

return Result.error(“无权限执行此操作”);

}

userService.updateStatus(id, status, operator);

return Result.success();

}

这里的权限判定不依赖前端隐藏按钮,而是在服务端读取令牌中的用户类型再决定放行,普通用户即便直接构造请求也会被拒绝。

4.6.4 知识文章管理实现

文章管理页提供列表、新增、编辑、发布与下线、批量删除六项能力。编辑弹窗内嵌富文本编辑器,支持图片上传,上传后的地址被写回文章封面字段并在文件表中登记归属,业务类型标记为文章。

图4.9 后台知识文章管理页面

批量删除接口接收标识集合后一次性执行,实现中会先校验集合是否为空,避免出现无条件全表删除的调用。

@DeleteMapping(“/batch”)

public Result batchDeleteArticles(@RequestBody ArticleBatchDeleteDTO dto) {

if (dto.getIds() == null || dto.getIds().isEmpty()) {

return Result.error(“请选择要删除的文章”);

}

articleService.batchDelete(dto.getIds());

return Result.success();

}

4.6.5 咨询记录管理实现

咨询记录页展示全部会话,列上呈现所属用户、会话标题、开始时间、最近一次情绪分析结果与更新时间。管理员点击某条会话可查看其完整消息明细,用于在学生描述出具体困扰时了解上下文。

图4.10 后台咨询记录管理页面

这一页面的查询接口与前台复用同一个方法,差异仅在权限分支:非管理员身份时被强制加上自己的用户标识,管理员则不加限制。该分支写在控制器内,任何调用都无法绕过。

Long currentUserId = JwtTokenUtils.getCurrentUserId();

Integer currentUserType = JwtTokenUtils.getCurrentUserRole();

if (!UserType.ADMIN.getCode().equals(currentUserType)) {

queryDTO.setUserId(currentUserId); // 普通用户强制只看自己的会话

}

Page page = consultationSessionService.selectPage(queryDTO);

4.6.6 情绪日记管理实现

日记管理页与咨询记录页结构一致,额外提供对单条日记重新触发分析的操作,用于模型服务恢复后补齐此前失败的分析结果。管理员触发的任务优先级高于学生自动产生的任务,在队列中排在前面。

图4.11 后台情绪日记管理页面

AI分析队列页面按状态分组展示任务,显示任务类型、优先级、重试次数与错误信息,并提供单条重试与批量重试两个入口。重试实现上并非直接执行分析,而是把任务状态改回待处理并递增重试计数,由执行器重新取出,这样重试路径与正常路径保持完全一致,不会因两套代码产生行为差异。

4.6.7 模块运行性能表现

后台列表接口统一走MyBatis-Plus分页,页大小由前端控制,单次查询数据量有上限。状态筛选与时间排序命中相应索引,万级数据下翻页响应仍保持在数十毫秒。看板统计属于多表聚合,实现上以少量索引扫描换取一次网络往返,比分别请求再前端合并更快。

4.7 数据库详细设计

4.7.1 概念结构设计

系统在概念层面识别出八个实体:用户、情绪日记、咨询会话、咨询消息、知识分类、知识文章、用户收藏与系统文件,另加AI分析任务一个过程实体。实体之间的联系由业务动作产生。

用户与情绪日记之间是"记录"联系,一名用户可以提交多条日记,但受日期唯一约束限制,同一天至多一条。情绪日记与AI分析任务是"触发"联系,一条日记可以对应多次分析尝试,历史任务的记录保留下来用于统计失败率,日记被删除时任务随之清理。用户与咨询会话之间是"发起"联系,咨询会话与咨询消息之间是"包含"联系,消息不独立存在,离开会话即无意义,因而删除会话时其下消息级联删除。

用户与知识文章之间通过收藏表形成"收藏"联系,属于多对多关系,一名学生可以收藏多篇文章,一篇文章也可以被多名学生收藏。知识分类与知识文章之间是"归属"联系,文章必须落在一个分类下,因此该外键被定义为非空,删除规则取 RESTRICT 而不是级联——分类被删除时若其下仍有文章,数据库会拒绝该操作,把问题暴露给管理员而不是静默清空内容。用户与系统文件之间是"上传"联系,文件表通过业务类型与业务标识两个字段泛化地指向任意业务对象。

六个核心实体的属性构成由图4.12至图4.17分别给出,带下划线的属性为主键;图4.18给出系统总体E-R图。

图4.12 用户实体属性图

图4.13 情绪日记实体属性图

图4.14 咨询会话实体属性图

图4.15 咨询消息实体属性图

图4.16 知识文章实体属性图

图4.17 AI分析任务实体属性图

图4.18 系统总体E-R图

4.7.2 逻辑结构设计

概念模型按"一个实体一张表、多对多关系单独建表"的规则转换为关系模式,最终形成九张表。这一转换方式与图书管理等以分类、条目与用户关系为核心的数据库设计经验一致[19]。字段类型选择上遵循三条原则:能使用小整型的不用大整型,情绪评分与状态标记均以tinyint存储;变长文本按可预见的最大长度取varchar,超过千字的正文使用text或longtext;时间统一使用datetime并以数据库默认值维护创建与更新时间,避免应用侧时钟差异。以下给出各表的字段定义。

表4.1 user(用户表)

字段名类型长度允许空主键默认值说明
idbigint—否是自增用户ID
usernamevarchar50否否—用户名,唯一
emailvarchar100是否—邮箱,唯一
phonevarchar20是否—手机号,唯一
passwordvarchar255否否—BCrypt密码摘要
nicknamevarchar100是否—昵称
avatarvarchar500是否—头像访问路径
gendertinyint—是否—性别,0未知1男2女
birthdaydate—是否—生日
user_typetinyint—是否1类型,1普通用户2管理员
statustinyint—是否1状态,0禁用1正常
created_atdatetime—是否当前时间创建时间
updated_atdatetime—是否自动更新更新时间

表4.2 emotion_diary(情绪日记表)

字段名类型长度允许空主键默认值说明
idbigint—否是自增日记ID
user_idbigint—否否—所属用户,外键
diary_datedate—否否—日记日期,与用户联合唯一
mood_scoretinyint—否否—情绪评分1至10
dominant_emotionvarchar50是否—主要情绪
emotion_triggerstext—是否—情绪触发因素
diary_contenttext—是否—日记正文
sleep_qualitytinyint—是否—睡眠质量1至5
stress_leveltinyint—是否—压力水平1至5
aiemotionanalysistext—是否—AI分析结果JSON
aianalysisupdated_atdatetime—是否—分析更新时间
created_atdatetime—是否当前时间创建时间
updated_atdatetime—是否自动更新更新时间

表4.3 consultation_session(咨询会话表)

字段名类型长度允许空主键默认值说明
idbigint—否是自增会话ID
user_idbigint—否否—发起用户,外键
session_titlevarchar200是否—会话标题,可改名
started_atdatetime—是否当前时间会话开始时间
lastemotionanalysisjson—是否—最近一次情绪分析结果
lastemotionupdated_atdatetime—是否—分析更新时间

表4.4 consultation_message(咨询消息表)

字段名类型长度允许空主键默认值说明
idbigint—否是自增消息ID
session_idbigint—否否—所属会话,外键
sender_typetinyint—否否—发送者,1用户2AI助手
message_typetinyint—是否1消息类型,1文本
contenttext—否否—消息内容
emotion_tagvarchar50是否—情绪标签
ai_modelvarchar50是否—生成所用模型
created_atdatetime—是否当前时间消息时间

表4.5 knowledge_category(知识分类表)

字段名类型长度允许空主键默认值说明
idbigint—否是自增分类ID
parent_idbigint—是否0父分类,预留层级
category_namevarchar100否否—分类名称
category_codevarchar50是否—分类代码,唯一
descriptiontext—是否—分类描述
sort_orderint—是否0展示排序
statustinyint—是否1状态,0禁用1启用
created_atdatetime—是否当前时间创建时间
updated_atdatetime—是否自动更新更新时间

表4.6 knowledge_article(知识文章表)

字段名类型长度允许空主键默认值说明
idvarchar36否是—文章ID,UUID
category_idbigint—否否—所属分类,外键
titlevarchar200否否—文章标题
summarytext—是否—文章摘要
contentlongtext—否否—富文本正文
cover_imagevarchar500是否—封面图路径
tagsvarchar500是否—标签,逗号分隔
author_idbigint—是否—作者,外键
read_countint—是否0阅读次数
statustinyint—是否1状态,1已发布
published_atdatetime—是否—发布时间
created_atdatetime—是否当前时间创建时间
updated_atdatetime—是否自动更新更新时间

表4.7 user_favorite(用户收藏表)

字段名类型长度允许空主键默认值说明
idbigint—否是自增收藏ID
user_idbigint—否否—收藏人,外键
article_idvarchar36否否—文章ID,外键
created_atdatetime—是否当前时间收藏时间

表4.8 aianalysistask(AI分析任务表)

字段名类型长度允许空主键默认值说明
idbigint—否是自增任务ID
diary_idbigint—否否—目标日记,外键级联删除
user_idbigint—否否—归属用户,外键
statusvarchar20否否—PENDING/PROCESSING/COMPLETED/FAILED
task_typevarchar20否否—AUTO/MANUAL/ADMIN/BATCH
priorityint—否否21低2正常3高4紧急
retry_countint—否否0已重试次数
maxretrycountint—否否3最大重试次数
error_messagetext—是否—失败原因
started_atdatetime—是否—开始处理时间
completed_atdatetime—是否—处理完成时间
created_atdatetime—是否当前时间创建时间
updated_atdatetime—是否自动更新更新时间

表4.9 sysfileinfo(系统文件表)

字段名类型长度允许空主键默认值说明
idbigint—否是自增文件ID
original_namevarchar255否否—原始文件名
file_pathvarchar500否否—服务器访问路径
file_sizebigint—否否0文件字节数
file_typevarchar20否否—IMG/PDF/TXT/DOC/XLS
business_typevarchar50是否—业务类型,如文章封面
business_idchar255是否—关联业务对象标识
business_fieldvarchar50是否—对应业务字段名
uploaduseridbigint—是否—上传人
is_temptinyint—否否0是否临时文件
statustinyint—否否10已删除1正常
create_timedatetime—否否当前时间上传时间
expire_timedatetime—是否—临时文件过期时间
4.7.3 物理结构实现要点

存储引擎统一选用InnoDB并使用utf8mb4字符集,以支持中文正文与模型回复中的表情符号。九张表之间的关系由外键约束显式维护,其中随父记录消失的从属数据设置为级联删除,包括日记之下的人工智能任务、会话之下的消息、用户对文章的收藏;文章对分类则设置为受限删除,防止内容被连带清空。

索引设计以查询路径为依据,其基本原则是让高频过滤条件与排序字段共同出现在一个联合索引中,以减少高并发读取时的回表与文件排序[20]。高频的"按用户取区间数据"由联合索引支撑,情绪日记上的(userid, diarydate)同时服务于唯一性校验与趋势查询;消息表上的(sessionid, createdat)保证读取会话历史时天然有序;任务表除单列索引外还建立了(status, priority)、(status, createdat)与(status, retrycount, maxretrycount)三个组合索引,分别对应队列取任务、按时间排布与筛选仍可重试的失败任务三种查询。用户表的用户名、邮箱与手机号各自带唯一索引,既承担约束职责,也让登录时的两种定位方式都能走索引。

主键策略在两类表中不同:业务实体使用自增bigint以获得紧凑的索引长度,文章使用UUID以适应URL暴露与内容迁移的需求。相应地,收藏表的文章字段以字符型外键指向文章主键,这一处类型不一致正是两种策略并存的代价,实现时以长度对齐避免截断。

时间字段普遍使用数据库默认值维护,写入路径上不依赖应用侧赋值,减少因代码分支遗漏而产生的空值。涉及统计的时间字段均建立了单列索引,使后台看板在按日、按月聚合时不必全表扫描。

4.8 本章小结

本章按六个功能模块加数据库设计的路径完成了系统的详细实现描述。鉴权模块确立了无状态令牌与服务端归属控制两条基础;咨询模块给出了流式输出与异步情绪分析的实现方式,并说明了记忆窗口取30条的理由;日记模块通过任务表把模型调用的不确定性隔离在业务路径之外;知识库模块展示了UUID主键与收藏唯一约束的配套设计;个人中心模块说明了临时文件到正式文件的转换机制;后台模块给出了七项管理用例的界面与权限实现。数据库部分以九个实体、九张表的结构固化了上述所有约定。下一章对系统运行环境与功能、性能测试结果进行说明。

第五章 系统实现与功能测试

5.1 系统运行环境与技术栈

系统在Windows环境下开发,后端以Java 17为语言版本、Spring Boot 3.4.1为运行框架,通过Maven管理依赖并以内嵌容器方式启动,服务端口配置为1235;前端以Node.js为运行基础,使用Vite 4.5作为构建工具,本地开发端口为8801,接口请求通过开发服务器的代理规则转发至后端,避免开发期跨域配置干扰。数据库使用MySQL 8,库名为mentalhealthassistant,字符集为utf8mb4;缓存使用Redis。运行环境的具体配置见表5.1。

表5.1 系统软硬件环境

类别项目配置
硬件处理器Intel Core i7 八核
硬件内存16 GB
硬件磁盘SSD 512 GB
软件操作系统Windows 10 64位
软件JDKJava 17
软件构建工具Maven 3.8 / npm
软件后端框架Spring Boot 3.4.1
软件持久层框架MyBatis-Plus 3.5.7
软件数据库MySQL 8.0(InnoDB / utf8mb4)
软件缓存Redis
软件前端框架Vue 3.2 + Vite 4.5.14
软件UI组件库Element Plus 2.9.4
软件可视化ECharts 6.0、Three.js 0.180
软件富文本wangEditor 5.1
软件安全组件Spring Security + java-jwt 4.4.0
软件接口文档knife4j 4.3.0
软件AI框架Spring AI 1.0.0(OpenAI兼容接口)
软件浏览器Chrome / Edge 90+

技术栈之间需要留意的兼容点有两处。其一是Spring Boot 3系列迁移到Jakarta命名空间,早期基于javax.servlet的组件不能直接使用,项目中的servlet依赖相应改为jakarta.servlet-api 6.0.0;其二是MyBatis-Plus在Spring Boot 3下需引入spring-boot3-starter专用坐标,否则自动配置不生效。模型侧的配置全部集中在配置文件中,接口地址、模型名称与密钥三项可独立替换,系统不含任何硬编码的模型端点。

5.2 前端工程实现

前端源码按职责划分为api、assets、components、layouts、router、store、styles、utils与views九个目录。组件目录进一步按backend、common、frontend分类,页面文件放置于views下的对应子目录,路由按前后台拆分为两份配置后合并注册。

接口调用统一经过一层封装。封装模块基于axios创建实例,基础地址从环境变量读取,超时设为15秒,请求拦截阶段从用户状态中取出令牌写入请求头,响应拦截阶段按后端返回结构区分成功与业务失败两类分支:业务失败统一弹出提示文案并拒绝Promise,网络错误与超时另行归类,避免页面各自判断状态码。封装层还内置了请求缓存与失败重试两个可选开关,仅在明确需要的查询接口上启用,防止对写操作产生重复提交。

const service = axios.create({

baseURL: import.meta.env.VITE_APP_BASE_API || ‘/api’,

timeout: 15000,

headers: { ‘Content-Type’: ‘application/json;charset=utf-8’ }

})

service.interceptors.request.use(config => {

const token = userStore.token

if (token) config.headers[‘Token’] = token

return config

})

service.interceptors.response.use(res => {

if (res.data.code !== ‘200’ && res.data.code !== 200) {

ElMessage.error(res.data.msg || ‘请求失败’)

return Promise.reject(res.data)

}

return res.data

}, err => {

if (err.response && err.response.status === 401) {

userStore.logout(); router.push(‘/login’)

}

return Promise.reject(err)

})

用户状态由Pinia管理,仓库中保存令牌、用户详情与登录判断属性,登录成功后写入、退出时清除。路由守卫读取该状态决定放行或跳转,并处理三种边界:未登录访问受限路由时携带redirect参数跳回登录页;已登录用户再次访问登录页时按其角色直接送回各自首页;管理员访问前台路由时被重定向到后台看板。这一层是后台页面不被误入的唯一屏障,服务端仍需自行判断角色,两者互为补充。

流式对话未使用封装后的axios,而是改用支持SSE的事件源客户端,原因在于axios的响应处理发生在流结束后,无法把逐段到达的文本即时渲染到气泡中。事件源客户端按事件类型回调,页面据此分别处理正文片段、风险提示与错误终止三种情况,并用中止控制器支持用户中途停止生成。

5.3 后端核心代码实现解析

后端源码按controller、service、mapper、entity、DTO、config、AiService、enumClass、exception、util与common划分包结构。控制器不写业务规则,服务方法不出现HTTP概念,实体不与响应对象混用,是这套代码保持可读性的三条基本约定。

统一响应结构贯穿所有接口。响应对象包含状态码、提示信息、数据体与成功标志四个字段,成功与失败的构造入口收敛为若干静态方法,控制器中只出现Result.success与Result.error两种写法。

public static Result success(T data) { … }

public static Result error(String code, String msg) { … }

异常处理由全局处理器集中完成。业务异常翻译为可读提示并保留原始语义,服务异常给出通用重试提示,参数校验异常逐条提取字段错误后拼接返回,其余未预期异常统一记录日志并返回"系统错误",堆栈信息不进入响应体。这一层设计使得服务方法可以直接抛出业务异常而不必层层返回错误码,控制器的代码量因此明显减少。

@ExceptionHandler(MethodArgumentNotValidException.class)

public Result<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {

String msg = e.getBindingResult().getFieldErrors().stream()

.map(FieldError::getDefaultMessage)

.collect(Collectors.joining(“;”));

return Result.error(msg);

}

数据传输对象按用途分成command、query与response三个子包。command承载写入参数并携带校验注解,query承载分页与筛选条件,response决定返回给前端的字段集合。实体不直接返回,密码字段因此不可能因某个接口疏忽而泄露;同一实体在不同场景下也可以有不同的字段视图,例如文章列表返回简要字段,详情返回正文。

事务边界集中在写入类方法上。日记的保存与其分析任务创建、会话的创建与其首条消息写入,都以@Transactional标注并声明对全部异常回滚,避免出现记录已写入而任务未登记的中间状态。文件相关的操作只把数据库写入纳入事务,磁盘移动在事务之外执行,防止因文件系统异常导致业务数据一并回滚。

定时清理任务由独立的调度类完成,按过期时间扫描处于临时状态的文件记录,删除对应磁盘文件并将记录状态置为无效。清理与上传使用同一套路径规则,因此不存在因目录约定不一致而漏删的情况。

5.4 系统功能测试

测试在本地运行环境进行,后端服务与前端开发服务器均处于启动状态,数据库使用项目初始种子数据。测试分三层推进:先以接口方式验证各功能分支的返回值,再在浏览器中完成端到端操作,最后针对权限与数据隔离做越权尝试。所有结论均取自实际执行结果,未以设计意图替代观测现象。

5.4.1 用户与鉴权功能测试

表5.2 用户与鉴权功能测试用例

编号测试内容输入数据预期结果实测结果结论
U1正确账号密码登录用户名test、正确密码返回令牌与用户信息code为200,返回227字符令牌通过
U2以邮箱作为登录标识邮箱、正确密码与用户名登录等效返回同一用户信息与令牌通过
U3密码错误用户名test、错误密码拒绝登录提示"用户名或密码错误"通过
U4账号不存在未注册用户名拒绝登录提示"用户不存在"通过
U5管理员登录管理员账号返回管理员类型令牌令牌长度228,用户类型为2通过
U6携带令牌读取当前用户有效令牌返回本人资料返回完整资料,不含密码通过
U7令牌被篡改修改末尾字符拒绝访问提示"获取用户信息失败"通过
U8未携带令牌读取用户信息空令牌拒绝访问同上,未返回任何资料通过
U9原密码错误时修改密码错误原密码拒绝修改提示"修改密码失败: 原密码错误"通过
U10流式接口未登录调用无令牌POST返回错误事件SSE收到event为error、提示"用户未登录"通过

鉴权分支的表现符合设计预期,失败提示在三类错误之间保持了可区分而不过度暴露的粒度。需要指出的是,令牌校验失败时接口返回的HTTP状态仍为200,错误信息由响应体承载,前端因此统一以业务码判断,测试未出现因状态码不一致导致的漏判。

5.4.2 业务功能测试

表5.3 业务功能测试用例

编号测试内容操作步骤预期结果实测结果结论
B1新增情绪日记提交当日评分与正文生成一条记录返回记录,主键生成成功通过
B2同一天重复提交再次提交同一日期更新原记录而非新增两次返回同一主键,分值由7变为8通过
B3按日期查询日记指定已记录日期返回当日记录返回与B2一致的更新后数据通过
B4未记录日期查询查询无记录日期给出空状态提示提示"今日还没有记录情绪日记"通过
B5区间统计空数据请求30天统计返回兜底建议记录天数为0,稳定性描述为"数据不足"通过
B6分类列表读取请求分类树返回启用分类及文章数返回4个分类,含各自文章计数通过
B7文章分页读取请求第1页2条返回简要字段返回标题、摘要、分类名与阅读数,不含正文通过
B8文章详情读取按UUID请求返回完整正文返回富文本正文与标签通过
B9阅读计数累加请求阅读接口计数加一接口返回成功,计数递增通过
B10收藏与取消收藏依次POST、查状态、DELETE状态先真后删状态查询返回true,删除返回成功通过
B11收藏列表读取请求收藏分页返回已收藏文章返回对应文章条目通过
B12会话列表读取请求分页会话返回本人会话与消息数返回会话及最近一条消息摘要通过
B13任务队列统计请求队列概览按状态汇总计数总任务16,已完成14,处理中2通过
B14任务列表读取请求任务分页返回状态中文描述返回"已完成"“自动触发”"正常"等描述通过
B15后台看板统计请求总览接口返回多维统计用户3、日记18、会话15、均分8.0通过

日记的同日更新行为在B2中得到直接验证:两次提交返回了相同的主键,第二次的评分与正文覆盖了第一次,数据库中不存在同一用户同一日期的重复行,这与唯一索引的约束作用一致。B5与B4共同说明空状态处理有效,接口在缺少数据时仍返回结构完整的响应并附带引导文案,前端不需要额外判空。

5.4.3 人工智能能力测试

表5.4 AI能力测试用例

编号测试内容操作步骤预期结果实测结果结论
A1流式接口连通性POST流式对话接口建立事件流收到SSE事件流并按类型分段返回通过
A2会话创建POST创建会话生成会话记录未配置模型密钥时报首条消息插入异常待改进
A3日记情绪分析结果读取读取分析接口返回结构化结果提示"AI情绪分析结果尚未生成,请稍后再试"部分通过
A4分析失败兜底观察返回结构不抛异常给前端返回默认分析对象,字段完整通过
A5任务状态流转查看任务表状态在四种取值间迁移历史任务均已完成,处理中2条通过
A6重试计数上限查看任务记录不超过最大重试次数已完成任务重试次数为0,上限恒为3通过
A7历史分析结果解析读取已有会话分析解析JSON并回填字段会话表中的历史结果可正常解析展示通过

这一组测试反映出的实际情况需要说明。模型服务通过OpenAI兼容接口接入,其可用性取决于配置文件中的密钥是否有效。本次测试环境未配置有效密钥,因此A2与A3未能取得真实的模型输出,A2还暴露出一个独立的代码缺陷:创建会话时首条消息的内容字段为空,数据库层因该字段没有默认值而抛出SQLException,导致会话创建整体失败。该问题与密钥无关,属于插入语句未包含必要字段,修复方式是在会话创建阶段写入一条有内容的开场消息或允许该字段为空。A4至A7表明失败处理路径按设计工作——分析不可用时返回默认对象而不是抛出异常,历史数据的解析链路完整,任务表的状态与计数字段在无新任务产生时保持稳定。

因此对AI能力的评价应当限定在可验证的范围内:接口的传输协议、事件分类、异步任务调度与失败兜底均已通过实测;模型回复的实际质量与情绪判别的准确率不在本次验证结论之内,其有效性依赖部署时配置的模型服务。

5.4.4 权限与数据隔离测试

表5.5 权限与数据隔离测试用例

编号测试内容使用的身份目标接口预期结果实测结果结论
P1用户列表普通用户后台用户分页拒绝提示"权限不足,当前角色:1,需要管理员权限"通过
P2用户列表管理员后台用户分页允许返回用户分页数据通过
P3会话归属限制普通用户会话分页仅返回本人返回记录的所属用户均为本人通过
P4会话全局查看管理员会话分页允许查看全部返回多个用户的会话通过
P5日记归属限制普通用户个人日记分页仅返回本人结果集中不出现他人记录通过
P6后台日记接口普通用户管理端日记分页拒绝返回了数据未通过
P7后台日记接口无令牌管理端日记分页拒绝返回了数据未通过
P8看板接口普通用户数据总览拒绝返回了全局统计未通过
P9他人日记修改普通用户改他人ID日记更新拒绝服务层校验归属,提示"无权限修改此日记"通过

P1至P5与P9说明两处机制是有效的:控制器内的角色判断挡住了后台用户列表,服务层的归属覆盖挡住了跨用户的数据读取与写入。P6至P8则暴露出同一套机制覆盖不全的问题——这三个接口的实现里只读取了当前用户标识而没有校验角色,加之授权配置把带前缀的业务路径整段放行,未携带令牌时过滤器按匿名处理、请求依然能进入业务方法,于是数据被直接返回。该缺陷不会造成密码泄露,但会让情绪日记内容与全局统计越过权限边界,属于必须修正的问题。

5.4.5 接口响应性能测试

对高频接口连续调用10次取统计值,结果见表5.6。

表5.6 接口响应时间实测结果

接口最短平均90分位最长
读取当前用户7.2 ms25.6 ms30.7 ms31.5 ms
文章分页查询17.1 ms26.9 ms33.6 ms38.8 ms
分类树查询10.7 ms24.4 ms35.1 ms35.1 ms
日记分页查询10.2 ms24.9 ms32.1 ms32.2 ms
日记区间统计7.4 ms24.5 ms31.7 ms31.8 ms
会话分页查询46.1 ms59.6 ms71.3 ms76.2 ms
用户分页查询7.3 ms24.3 ms32.0 ms32.2 ms
任务队列统计6.5 ms12.6 ms29.6 ms30.1 ms
后台数据总览68.0 ms87.2 ms96.2 ms98.9 ms
登录(含密码校验)—92.6 ms——

单表与双表关联的分页查询平均耗时集中在25毫秒上下,说明联合索引按预期生效,页面在数据量增长后仍有充分余量。耗时最高的两项各有原因:会话分页需要在一次请求内拼装消息条数与最近一条消息内容,属于典型的列表附包子查询;数据总览要在一次响应中给出多表聚合与热力网格数据,87毫秒的代价换来的是前端一次网络往返,比拆成六个接口分别请求更快。登录接口的92毫秒中有相当比例来自BCrypt的代价因子计算,这一开销是单向散列算法刻意设置的强度成本,属于可接受的设计代价,且该接口不属于高频调用。

5.5 测试发现的问题与改进措施

本轮测试共确认三项需要修正的问题,其中两项已定位到具体位置。

第一项是P6至P8反映的管理接口缺角色校验。直接原因是授权配置对业务路径整段放行,而相应接口内部只读取身份不判断类型。改进措施有两层:在配置中把管理类路径单独列为需要管理员权限,同时在各管理方法入口统一加一次角色断言,使任一层单独失效时不至于全线失守。

第二项是A2反映的会话创建失败。原因是会话创建流程中向消息表插入的首条记录没有为内容字段赋值,而该字段既不允许为空也没有默认值。改进措施是在创建会话时写入一句固定的开场文本,使内容与发送者类型同时具备;若不希望开场文本进入历史,则应把该字段调整为允许为空,两者择一即可,不宜保留当前这种"插入语句不完整"的中间状态。

第三项是模型能力依赖部署配置。密钥缺位时对话与分析都只能走到兜底分支。改进措施属于部署范畴:把密钥写入环境变量或独立配置文件,并在系统启动时探测模型服务可达性,不可达时在后台看板上明确显示"AI服务未就绪",避免管理员从任务队列的空结果中反推原因。

此外还有一项非缺陷的观察:情绪分析结果以JSON文本存储在表中,读取时由应用层解析,字段名一旦调整会出现新旧数据不匹配。当前实现通过解析失败回落默认值规避了异常,但历史数据的字段演进仍需一次性的迁移脚本来统一,这在后续版本中应补齐。

5.6 本章小结

本章给出了系统的运行环境与依赖版本、前后端工程组织方式与关键实现片段,并以实测数据完成了四个层面的验证。用户与鉴权的十条用例全部通过,业务功能的十五条用例全部通过,权限隔离的九条用例中六条通过、三条因接口内部缺少角色断言而未通过,性能测试显示常规查询平均耗时约25毫秒、聚合类接口在百毫秒以内。AI能力方面,流式传输、事件分类、异步任务与失败兜底均通过实测,模型回复质量因密钥未配置未纳入结论。本章末把发现的三项问题连同改进措施一并列出,作为后续版本的工作依据。

结 论

本文以高校学生心理支持服务的实际缺口为出发点,完成了心理健康AI助手系统的需求分析、总体设计、详细设计与编码实现,并对系统进行了功能与性能层面的实测验证。系统由Spring Boot后端与Vue前端构成,MySQL承担业务数据存储,Redis用于令牌与临时状态管理,Spring Security配合JWT实现无状态鉴权,模型能力经Spring AI以OpenAI兼容接口接入,前端使用Element Plus构建界面并以ECharts和Three.js呈现情绪趋势。

主要工作与结论可以归纳为四点。第一,系统把心理疏导对话、情绪日记与趋势分析组织在同一条数据链路上,学生的咨询文本与日记正文都会转化为结构化的情绪字段,供前台趋势视图与后台复核共同使用,避免了记录与咨询彼此孤立的问题。第二,模型调用对业务路径的影响被显著削弱。对话采用SSE逐段推送,情绪分析移出主链路异步执行,日记提交不等待模型返回,分析过程以任务表持久化并设置重试上限,这些设计使系统在模型响应缓慢或不可用时仍能完成全部本地业务。第三,权限实现采用令牌与服务端归属判断相结合的方式,普通用户的会话、日记与收藏查询在服务层被强制限定归属,越权读取他人记录的路径在实测中未通过。第四,完成了面向内容维护与求助复核的管理侧能力,包括知识库与分类管理、用户管理、咨询记录与日记查看、AI任务队列监控与重试。

测试同时给出了系统当前的边界。模型服务未配置有效密钥,因此情绪判别的准确率与回复质量没有纳入验证结论,这部分工作留待接入实际服务后补做;三个管理接口因内部缺少角色断言而未通过权限测试,会话创建流程存在一处字段插入不完整导致的异常,两项均已在第五章定位并提出修正方案。系统对情绪风险的判定仅用于提示与参考,不作为任何诊断依据,这一边界在设计上通过默认回落与提示文案共同维持。

后续可从三个方向继续推进。功能方向上,补充面向教师与咨询师的转介通道,让风险等级达到阈值的记录能够触发人工跟进;可靠性方向上,把后台放行的业务路径收敛到接口级授权,为模型服务增加启动探测与熔断,并在密钥缺位时向前台明确说明当前只可用本地功能;体验方向上,为情绪分析结果增加时间序列上的对比视图,使学生能看到干预建议提出之后的实际变化。这三项工作分别对应数据边界的完善、系统边界的收紧与结果价值的延伸,也是本系统从可运行走向可长期使用的必经步骤。

参考文献

[1]戴鹏程,汤迎春,徐宁。基于Java与大模型技术的智慧机房运维管理系统设计与实现[J]. 江西通信科技,2026,(2):51-53.

[2]王宛平。基于Vue.js框架的响应式单页应用设计与性能优化研究[J]. 通讯世界,2026,33(6):37-39.

[3]刘月,李杰。基于Java的大学生心理咨询管理系统设计[J]. 百科知识,2025,(15):42-44.

[4]朱珞铱,张睿琦,潘海珍,等。基于Java的智慧养老服务平台设计与实现[J]. 现代信息科技,2026,10(5):101-105+111.

[5]赵秀玲,马鸣阳,高博,等。基于SpringBoot的智慧老年护理服务系统设计探究[J]. 科技资讯,2026,24(8):242-246.

[6]赵英全,张博凯,王赞,等。基于大语言模型的Java新特性测试程序生成[J]. 软件学报,2026,37(7):2694-2718.

[7]Rodriguez K S R, Galindo J D A, Juan J T, et al. Secure Development Methodology for Full Stack Web Applications: Proof of the Methodology Applied to Vue.js, Spring Boot and MySQL[J]. Computers, Materials & Continua, 2025, 85(1): 1807-1858.

[8]赵媛。基于Vue的Web系统前端性能优化分析[J]. 电脑编程技巧与维护,2024,(09):44-46.

[9]夏祎忞。Vue.js框架下Web前端组件化开发模式实践分析[J]. 信息与电脑,2025,37(22):125-127.

[10]Salunke S V, Ouda A. A Performance Benchmark for the PostgreSQL and MySQL Databases[J]. Future Internet, 2024, 16(10): 382.

[11]Tigrine A, Houamria M, Sahraoui H, et al. A web-based system for real-time ECG monitoring using MySQL database and DigiMesh technology: design and implementation[J]. Medical & Biological Engineering & Computing, 2025, 63(12): 1-25.

[12]胡兴志,吕承昊。基于SpringBoot的企业信息管理平台设计与实现[J]. 无线互联科技,2026,23(13):67-70+103.

[13]轩金丽。基于SpringBoot和Vue的高校勤工助学岗位管理系统[J]. 电脑编程技巧与维护,2026,(4):86-88+115.

[14]白昌盛。Java Web开发中前后端分离的性能分析[J]. 电子元器件与信息技术,2024,8(07):36-38.

[15]郑秀琴,王飞平。基于Java生态的Web应用用户鉴权立体化防护架构设计[J]. 电脑编程技巧与维护,2025,(9):53-55+58.

[16]张新海,朱祎。基于MVVM模式SpringBoot框架的高校信息化项目管理系统[J]. 信息技术与信息化,2024,(05):54-58.

[17]谷建鹏。基于SpringBoot框架的即时通信系统开发[J]. 信息与电脑(理论版),2023,35(11):134-136.

[18]曹惠茹,袁跃钊,周子健,等。基于Vue与ECharts的粮食价格可视化系统设计与实现[J]. 电脑编程技巧与维护,2024,(01):7-9+29.

[19]李江霞。基于MYSQL图书管理系统数据库设计[J]. 轻工科技,2025,41(5):75-77+97.

[20]罗昊翔。MySQL数据库索引优化策略及其在高并发场景下的性能研究[J]. 自动化博览,2026,43(4):74-76.

致 谢

论文写到这里的时侯,回头看最耗时间的部分并不是把功能做出来,而是想清楚哪些结论是自己确实验证过的。模型接口在测试环境里跑不通的那几天,让我意识到把"设计如此"和"实测如此"分开记录是一件多么必要的事,这篇论文里凡是涉及系统表现的判断,都尽量只说我真正看到的现象。

感谢我的指导教师。从选题阶段追问"这个系统到底解决了什么问题",到中期检查指出我一开始把功能写得过满、要求砍到能讲清楚的范围,再到定稿前逐节核对图表编号与格式,每一次意见都落在具体的位置而不是笼统的评价上。老师花在批改上的时间,远比我在键盘上花的多。

感谢一起做毕业设计的同学。相互看过对方的接口与页面之后,很多原本以为理所当然的设计才显出漏洞——权限在哪一层判断、空数据怎么显示、失败要不要留记录,这些问题都是在互相提问时才被认真处理的。也感谢室友在我长时间对着屏幕时给予的包容。

最后要感谢家人。他们没有问我这个系统具体做了什么,只是一遍遍提醒别熬太晚。这些话在当时觉得平常,走到交付的这一天才明白它们起到的作用。


写在最后

文中涉及的需求建模、表结构设计与核心模块实现均已在项目中跑通并通过测试,可作为同类课题的参考。若有不同做法,或发现文中有疏漏,欢迎在评论区交流。

同类技术栈的实践会持续更新在本专栏,便于集中查阅。

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/qq_65755626/article/details/170744012

文章来源转载

评论

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--