9.6 KiB
HMS 功能审计 — Phase 5: 五种差距模式识别
日期: 2026-04-30 | 审计范围: 全系统差距模式分析
总览
基于 Phase 0-4 的审计数据,系统性检测五种常见差距模式。
1. 模式 1:"写了没接" — 后端路由无前端调用者
检测方法
集合差运算:backend_routes - web_api_calls - mp_api_calls
发现
| 路由 | 模块 | 说明 | 严重性 |
|---|---|---|---|
POST /health/patients/{id}/trends/generate |
erp-health | 趋势报告生成,无前端调用。可能为后台定时任务触发 | LOW |
POST /ai/analyze/vital-signs |
erp-ai | SSE 端点,Web 和小程序均无 UI 调用 | MEDIUM |
POST /ai/analyze/lab-report |
erp-ai | 同上 | MEDIUM |
POST /ai/analyze/health-trend |
erp-ai | 同上 | MEDIUM |
POST /ai/analyze/health-summary |
erp-ai | 同上 | MEDIUM |
AI 分析 SSE 端点说明:4 个 AI 分析端点通过 SSE 流式返回结果。前端未调用是因为 AI 分析功能可能仅通过 API 工具(如 Swagger UI)直接测试。这些端点的后端实现完整(含权限检查 ai.analysis.manage),但缺少前端管理界面的触发入口。
趋势生成说明:POST .../trends/generate 可能设计为后台任务调用(如定时生成趋势报告),而非前端直接触发。
统计
| 类别 | 数量 |
|---|---|
| 孤立后端路由(无任何前端调用) | 5 |
| 其中 AI SSE 端点 | 4 |
| 其中后台任务路由 | 1 |
2. 模式 2:"接了没传" — 前端调用但参数传递不完整
检测方法
搜索前端 API 调用中空参数传递、缺失 header、字段遗漏。
发现
2.1 小程序 vital_signs 字段遗漏(6 个)
已在 Phase 4 §4.3 详细分析。核心问题:indicator_type 模型无法传递晚间血压、体温、血氧等字段。
2.2 小程序 X-Patient-Id / X-Tenant-Id Header
小程序通过 request.ts 拦截器统一注入 X-Patient-Id 和 X-Tenant-Id header。经检查:
- 所有患者端 API 调用均通过
request.ts封装,header 注入正常 - 医护端 API 调用使用独立的
doctorRequest实例,header 注入机制一致
2.3 后端 DTO 字段前端未传递(低风险)
| 端点 | 后端 DTO 字段 | 前端行为 | 风险 |
|---|---|---|---|
POST /vital-signs |
blood_sugar_type | 小程序不传,后端默认 NULL | LOW |
POST /vital-signs |
source | 小程序不传,后端默认 "manual" | LOW |
POST /device-readings/batch |
raw_data | 前端传递完整 JSON | ✓ |
统计
| 类别 | 数量 |
|---|---|
| 前端字段遗漏(HIGH) | 2(晚间血压) |
| 前端字段遗漏(MEDIUM) | 2(体温、血氧) |
| 前端字段遗漏(LOW) | 2(血糖类型、来源) |
| Header 注入问题 | 0 |
3. 模式 3:"传了没存" — 数据传递但未完整持久化
检测方法
追踪 4 条关键写入路径:小程序体征录入、Web 创建预约、BLE 设备同步、每日签到。
路径 1:小程序体征录入
inputVitalSign() → POST /health/patients/{id}/vital-signs
→ health_data_handler::create_vital_signs()
→ health_data_service::create_vital_signs()
→ INSERT vital_signs
结果:✅ 晨间血压/心率/体重/血糖/饮水量/尿量正常写入。❌ 晚间血压/体温/血氧未写入(前端未传)。
路径 2:Web 创建预约
AppointmentList → POST /health/appointments
→ appointment_handler::create_appointment()
→ appointment_service::create_appointment()
→ CAS 原子检查 slot_available → INSERT appointment
结果:✅ 完整。含 CAS 并发控制、乐观锁、事务保证。
路径 3:BLE 设备同步
BLE scan → uploadReadings() → POST /health/patients/{id}/device-readings/batch
→ device_reading_handler::batch_create()
→ device_reading_service::sync_readings()
→ INSERT device_readings + UPSERT vital_signs_hourly
结果:✅ 完整。批量插入 + 小时聚合 + 事件发布。
路径 4:每日签到
checkin() → POST /health/points/checkin
→ points_handler::daily_checkin()
→ points_service::daily_checkin()
→ INSERT/UPDATE points_checkin + INSERT points_transaction
结果:✅ 完整。幂等检查(同日不可重复签到)+ 事务保证 + 事件发布。
统计
| 路径 | 结果 |
|---|---|
| 体征录入 | 部分数据丢失(晚间血压等) |
| 创建预约 | ✅ 完整 |
| BLE 同步 | ✅ 完整 |
| 每日签到 | ✅ 完整 |
4. 模式 4:"存了没用" — 数据持久化但未被消费
检测方法
检查写入了但从未被读取或展示的数据。
发现
4.1 事件无消费者(14 个)
已在 Phase 3 §4.2 详细分析。14 个事件被发布到 EventBus 但没有业务消费者:
patient.updated— 信息性事件,更新已直接处理consultation.*(3 个) — 会话管理已在 service 内直接处理article.published/rejected— 状态已更新points.earned/exchanged/expired(3 个) — 积分操作已完成- 其余 5 个同类型
评估:这些事件通过 SSE 推送到前端,用于实时通知,不完全是"没用"。但没有专门的后端消费者处理后续业务逻辑。
4.2 数据库字段未读取
| 字段 | 表 | 写入场景 | 读取场景 | 状态 |
|---|---|---|---|---|
| raw_data | device_readings | BLE 同步时写入 | 无专门查询 | MEDIUM |
| revoke_reason | consent | 撤回知情同意时写入 | 列表查询返回 | ✓ 已用 |
| model_config | ai_prompt | 创建/更新 Prompt 时写入 | 分析时读取 model/temperature/max_tokens | ✓ 已用 |
| article_revision | article_revision | 文章编辑时写入 | GET .../revisions 端点返回 | ✓ 已用 |
raw_data 字段说明:device_readings.raw_data 存储 BLE 设备原始 JSONB 数据。当前无专门的读取/分析页面。价值在于数据溯源和异常排查,但在 UI 层面无入口。
4.3 entity 字段从未构造
Phase 2 发现 RefRow struct 从未构造(编译器警告),属于重构残留。
统计
| 类别 | 数量 | 影响 |
|---|---|---|
| 无消费者事件 | 14 | LOW(SSE 推送 + 审计追踪仍有价值) |
| 未读取数据库字段 | 1(raw_data) | MEDIUM(数据溯源需要但无 UI 入口) |
| 死代码 struct | 1(RefRow) | LOW(编译器警告,重构残留) |
5. 模式 5:"双系统不同步" — Web 与小程序实现差异
检测方法
对比共享功能在 Web 和小程序的实现差异,区分"角色分叉(正常)"和"实现缺陷"。
5.1 角色分叉(正常差异)
| 功能 | Web(管理端) | 小程序(患者/医护端) | 差异类型 |
|---|---|---|---|
| 积分管理 | 规则 CRUD + 商品管理 + 订单核销 | 签到 + 兑换 + 查看订单 | 角色分叉 ✓ |
| 文章管理 | CMS 全流程(创建/编辑/审核/发布) | 只读列表(仅 published) | 角色分叉 ✓ |
| 告警管理 | 规则配置 + 仪表盘 + 处置 | 查看自身告警 + 处理 | 角色分叉 ✓ |
| 设备管理 | 列表 + 解绑 | BLE 扫描 + 同步 | 平台差异 ✓ |
| 预约管理 | 全 CRUD + 排班配置 | 创建/查看/取消预约 | 角色分叉 ✓ |
| 医生管理 | 档案 CRUD + 排班管理 | 查看排班日历 | 角色分叉 ✓ |
5.2 实现缺陷(需修复)
| 功能 | Web | 小程序 | 差异类型 | 优先级 |
|---|---|---|---|---|
| 体征录入 | 结构化表单(所有字段独立输入) | indicator_type 映射(丢失晚间血压/体温/血氧) | 数据模型差异 | P1 |
| 咨询消息 | 列表 + 详情 + 导出 | 列表 + 详情(无导出) | 功能缺失 | P3 |
| 透析管理 | 完整 CRUD + 审阅 | 无任何入口 | 功能缺失 | P1 |
| 知情同意 | 完整 CRUD | 无任何入口 | 功能缺失 | P1 |
| 健康记录 | 完整 CRUD | 无任何入口 | 功能缺失 | P2 |
| 诊断记录 | 完整 CRUD | 无任何入口 | 功能缺失 | P2 |
| 药物管理 | 完整 CRUD | 有页面但 API 调用不明确 | 待验证 | P2 |
| 权限码拼写 | health.alerts.manage(正确) |
—(前端用 health.alert.manage) |
拼写错误 | P1 |
5.3 透析管理 — 最大差距
透析是唯一一个后端完整实现但小程序完全空白的功能域:
- 后端:12 个路由(记录 CRUD + 处方 CRUD + 审阅 + 统计)
- Web:完整管理界面(DialysisManageList)
- 小程序:0 个 API 调用、0 个页面
影响:透析患者无法在移动端查看自己的透析记录和处方,医护无法在小程序端录入透析数据。
统计
| 差异类型 | 数量 |
|---|---|
| 角色分叉(正常) | 6 |
| 数据模型差异 | 1 |
| 功能缺失(P1) | 3(透析/知情同意/体征映射) |
| 功能缺失(P2) | 3(健康记录/诊断/药物) |
| 功能缺失(P3) | 1(咨询导出) |
| 拼写错误 | 1 |
6. 差距模式汇总评分
| 模式 | 严重程度 | 发现数 | 评分 |
|---|---|---|---|
| 模式 1: 写了没接 | LOW-MEDIUM | 5 | 95%(仅 AI SSE 和趋势生成孤立) |
| 模式 2: 接了没传 | HIGH-LOW | 6 | 75%(晚间血压数据丢失是关键缺陷) |
| 模式 3: 传了没存 | LOW | 1 | 95%(仅体征部分字段未传导致未存) |
| 模式 4: 存了没用 | LOW | 16 | 85%(大部分事件有 SSE 消费者,raw_data 待利用) |
| 模式 5: 双系统不同步 | HIGH-LOW | 9 | 65%(透析/知情同意完全缺失,体征映射丢失) |