山东大学软件学院创新实训--“智愈医院自助服务系统“-(8)-测试与总结
一、引言
经过数周的持续开发与迭代,智愈医疗系统(Smart Medicine)已按计划完成全部核心功能的设计、编码、测试与部署。本文作为整个项目的收官博客,将从以下三个方面进行总结:
- 功能模块总览 — 回顾系统实现的全部功能,按模块分类说明
- 系统测试 — 功能测试、集成测试、兼容性测试的结果与问题修复
- 项目结项 — 交付物清单、技术指标达成情况、经验总结
二、功能模块总览
系统共实现 多个核心功能模块,覆盖"诊前查询 → 诊中分析 → 诊后管理 → 在线支付"的完整就医闭环。
2.1 智能医生对话系统
这是本项目的核心创新功能,实现了类似 DeepSeek、ChatGPT 等大模型产品的多轮对话体验。
主要功能:
- 基于通义千问大模型的医疗咨询对话,提供 7×24 小时在线问诊
- SSE 流式输出(EventSource 逐 token 呈现),用户可实时看到 AI 的思考过程
- 多会话管理:左侧边栏展示历史对话列表,支持新建、切换、删除会话
- 会话上下文隔离:每个会话的对话历史独立存储,AI 严格按会话读取上下文,不同会话互不干扰
- 快捷追问:AI 回复下方显示"用药建议/预防建议/饮食建议/康复周期"四个快捷按钮,一键追问
- 会话标题自动生成:取首条用户消息的前 50 字作为标题
- 流式停止控制:输出过程中发送按钮变为"■ 停止",点击可随时中断 AI 回复
- 后台流缓冲:切换会话时原 AI 流在后台继续运行,切回时可实时看到已累积的回复内容
技术实现:
前端 (EventSource) ↔ 后端 (SseEmitter) ↔ DashScope SDK (streamCall) ↔ 通义千问
核心代码 — 流式端点异步处理用户消息并保存到数据库:
@GetMapping("/query/stream")
public SseEmitter queryStream(
@RequestParam String content,
@RequestParam String conversationId,
HttpSession session) {
User loginUser = getLoginUser(session);
chatHistoryService.saveMessage(loginUser.getId(), conversationId, Role.USER.getValue(), content);
List<Message> messages = buildAIMessages(conversationId, loginUser.getId(), null);
SseEmitter emitter = new SseEmitter(180000L);
executor.execute(() -> {
apiService.queryStream(messages,
token -> emitter.send(SseEmitter.event().data(token)),
() -> {
chatHistoryService.saveMessage(userId, conversationId, Role.ASSISTANT.getValue(), fullResponse.toString());
emitter.complete();
},
e -> emitter.completeWithError(e));
});
return emitter;
}
2.2 诊断书 AI 分析模块
实现了诊断书的异步上传与分析全流程,包含文件存储、异步任务调度、AI 识别、状态追踪。
主要功能:
- 文件上传:支持 JPG/PNG/JPEG/PDF 格式,大小限制 10MB
- 阿里云 OSS 存储:私有权访问 + Presigned URL 签名(有效期 30 分钟)
- 异步任务队列:ThreadPoolTaskExecutor 线程池(core=2, max=4),提交即返回 taskId
- 五状态状态机:PENDING → PROCESSING → SUCCESS / WARNING / FAILED
- 指数退避重试:10s → 30s → 60s,最多 3 次
- 前端轮询进度:每 2 秒轮询
/api/analysis-history/status - 分析结果结构化展示 + PDF 报告导出
- 历史记录管理(列表/详情/删除/清空/批量删除/存储统计)
状态机设计:
┌──────────┐
│ PENDING │
└────┬─────┘
│ 线程池调度
┌────▼─────┐
┌──────│PROCESSING│
│ └────┬─────┘
│ │
┌──────▼──┐ ┌─────▼────┐ ┌────────┐
│ SUCCESS │ │ WARNING │ │ FAILED │
│ (成功) │ │(部分识别) │ │ (失败) │
└─────────┘ └──────────┘ └────────┘
2.3 疾病与药品知识库
- 疾病分类管理:支持 11 个科室分类,按分类浏览疾病
- 疾病详情页:病因、主要症状、特殊症状、关联药品推荐
- 药品信息:品名、关键字、功效、品牌、相互作用、禁忌、剂型、价格
- 多维度搜索:按疾病名称、症状关键词、药品名称等搜索
- 浏览量追踪:记录每个疾病的查看次数
三、系统测试
3.1 功能测试
| 模块 | 测试项 | 结果 | 备注 |
|---|---|---|---|
| 用户登录 | 正常登录、密码错误、空账号 | ✅ 通过 | |
| 用户注册 | 邮箱验证码、重复账号检测 | ✅ 通过 | |
| 智能医生 | 多轮对话、上下文记忆 | ✅ 通过 | 经测试 10 轮内上下文准确 |
| 流式输出 | 逐 token 渲染、停止按钮 | ✅ 通过 | |
| 多会话切换 | 创建/切换/删除/标题生成 | ✅ 通过 | |
| 快捷追问 | 四个按钮均正常触发追问 | ✅ 通过 | |
| 诊断书分析 | 图片上传、异步分析、结果展示 | ✅ 通过 | 图片/PDF 格式均测试 |
| 进度轮询 | 前端 2s 轮询状态更新 | ✅ 通过 | |
| 重试机制 | 失败任务手动重试 | ✅ 通过 | |
| PDF 导出 | 诊断报告 PDF 生成与下载 | ✅ 通过 | |
| 支付流程 | 统一下单 → 扫码 → 回调处理 | ✅ 通过 | 沙箱环境 |
| 药品查询 | 名称搜索、分类浏览 | ✅ 通过 | |
| 反馈提交 | 意见反馈保存 | ✅ 通过 |
3.2 Bug 修复记录
B1 — EventSource 连接丢失
- 问题:流式请求 URL 拼接了
conversationId但 EventSource 创建时用了不含该参数的硬编码 URL,后端返回 400 导致连接立即失败 - 修复:
new EventSource(streamUrl)使用正确拼接的完整 URL - 文件:
custom.js
B2 — 跨会话 AI 回复被误删
- 问题:删除消息对时仅按
userId过滤,未限制conversationId。切换到新会话后,若editMessageIds中的 ID 属于旧会话,删除时会误删旧会话的消息 - 修复:
deleteByIds()增加conversationId参数,SQL 中追加AND conversation_id = ?双重隔离 - 文件:
ChatHistoryService.java
B3 — 切换会话后原 AI 回复丢失
- 问题:切换会话时
close()关闭了 EventSource,后台线程虽保存了 AI 回复,但用户切回时无通知机制,必须手动刷新 - 修复:切换时不关闭 EventSource,标记为
_abandoned仅解引用。引入window.streamBuffers缓冲区,切回时从缓冲区实时渲染 - 文件:
doctor.html、custom.js
B4 — 终端中文乱码
- 问题:Windows Git Bash 终端编码 GBK,JVM 输出 UTF-8 导致中文日志显示乱码
- 修复:
application.yml中配置logging.charset.console: GBK - 文件:
application.yml
B5 — 删除所有会话后重复创建
- 问题:
deleteConversation→newConversation()内部已调用loadConversationList(),外层又调一次,两个 AJAX 并发执行导致创建两个新会话 - 修复:区分当前会话删除与非当前会话删除,只在非当前会话删除时单独刷新列表
- 文件:
doctor.html
B6 — 实体类主键未使用包装类
- 问题:
IllnessKind和Pageview实体的id字段使用原始类型int而非包装类型Integer - 修复:
int id→Integer id,避免默认值 0 的问题 - 文件:
IllnessKind.java、Pageview.java
3.3 编码规范修复
对所有 Controller 和 Service 层进行了 Eclipse 空安全检查,修复了以下问题:
| 问题类型 | 修复方式 | 涉及文件 |
|---|---|---|
@NonNull 缺失 |
addViewControllers(@NonNull ViewControllerRegistry) |
MvcConfig.java |
| Bean 命名冲突 | @Resource 字段名从 historyService 改为 analysisHistoryService |
TaskService.java |
| Null 类型安全转换 | 增加 token == null 守卫 + 局部变量提取 |
MessageController.java |
四、技术指标达成情况
4.1 功能覆盖
| 模块 | 计划功能 | 实现功能 | 扩展功能 | 完成度 |
|---|---|---|---|---|
| 诊断书分析 | 5 项 | 5 项 | 批量删除、存储统计 | 100% |
| 智能医生对话 | 4 项 | 6 项 | 多会话、快捷追问、停止控制 | 100% |
| 疾病药品查询 | 3 项 | 3 项 | — | 100% |
| 支付集成 | 3 项 | 3 项 | — | 100% |
| 用户管理 | 3 项 | 3 项 | — | 100% |
4.2 代码规模
| 指标 | 数值 |
|---|---|
| Java 代码文件 | 50+ 个 |
| 页面模板 | 18 个 |
| 数据库表 | 13 张 |
| RESTful API 端点 | 30+ 个 |
| 核心 JavaScript | ~700 行 |
4.3 质量评分(ISO/IEC 25010)
| 维度 | 评分 | 说明 |
|---|---|---|
| 功能适用性 | 4.5/5 | 需求覆盖全面,扩展功能超出预期 |
| 性能效率 | 4.0/5 | 异步架构优化良好,流式输出延迟可控 |
| 可用性 | 4.5/5 | 多会话边栏、快捷追问提升交互效率 |
| 可靠性 | 4.5/5 | 异步重试、状态机保障数据一致性 |
| 安全性 | 4.0/5 | OSS 签名 URL、XSS 防护、参数校验 |
五、经验总结
6.1 技术收获
-
异步架构设计:从同步阻塞到异步队列的改造,深刻理解了"提交即返回、状态可追踪、失败可重试"的设计理念。内存队列 + 线程池在单体应用中性价比极高。
-
SSE 流式交互:EventSource + SseEmitter 的组合实现了类 DeepSeek 的实时输出体验。流式设计中需特别注意连接生命周期管理、中断恢复、缓冲区同步等问题。
-
状态机实践:五状态模型配合指数退避重试,有效提升了系统的容错性。WARNING 状态的引入为部分识别场景提供了更友好的用户体验。
-
多会话架构:会话表 + 消息表的分离设计支持独立上下文管理,AI 上下文严格按
conversation_id隔离的设计经验可推广到任何多轮对话系统中。
6.2 项目管理收获
-
迭代增量开发:三次迭代逐步扩展功能,每次迭代都产出可运行版本,有效降低了项目风险。
-
问题驱动开发:开发过程中遇到的 Bug(EventSource 连接丢失、跨会话污染、编码问题等)都成为了改进系统的契机,每次修复都让系统更加健壮。
-
团队协作:Git 分支管理 + 代码审查机制保障了多人协作的代码质量。遇到合并冲突时通过沟通确认而非强行覆盖。
6.3 改进方向
| 方向 | 当前方案 | 改进方案 |
|---|---|---|
| 任务队列 | 内存队列 + 线程池 | 引入 RabbitMQ 实现持久化队列 |
| 文档格式 | 图片 + PDF | 支持 Word、DICOM 医学影像 |
| 缓存 | 无 | 引入 Redis 缓存热点数据 |
| 移动端 | 桌面端为主 | 移动端适配 / 响应式改造 |
| 部署 | 本地运行 | Docker Compose 容器化部署 |
七、结项语
智愈医疗系统从最初的需求分析、架构设计,到逐步编码实现、测试修复,再到最终的功能完善与结项,走完了软件项目管理的完整生命周期。系统实现了从诊断书上传、AI 识别分析、智能医患对话到在线支付的完整闭环,覆盖了患者就医的核心场景。
本项目的意义不仅在于产出了一个可运行的医疗辅助系统,更在于通过实践验证了异步架构、状态机设计、流式交互等技术方案在 Web 应用中的有效性。希望后续开发者能在此基础上继续迭代,打造更好的产品。
更多推荐

所有评论(0)