Files
hms/docs/audits/03-event-system.md
iven d712ad78c3 docs: 审计报告(8 份) + 讨论记录(4 份)
审计报告: 基线快照/功能清单/后端完整性/事件系统/参数配置/
差距模式/错误处理/测试覆盖/审计总结报告
讨论记录: 设备管线/端到端测试/三端审计/工作台重构
2026-05-03 19:32:15 +08:00

215 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# HMS 功能审计 — Phase 3: 事件系统审计
> 日期: 2026-04-30 | 审计范围: 全系统事件总线
## 总览
| 指标 | 值 |
|------|-----|
| 事件类型常量定义 | 25 个event.rs |
| 事件发布调用 | 44 处 |
| 事件消费者 | 14 个11 个 tokio::spawn 任务) |
| 已知未实现事件 | 2 个PATIENT_VERIFIED / PATIENT_DECEASED标记 KNOWN |
---
## 1. 事件发布方清单
### erp-health 模块30 处发布)
| 事件类型 | 发布者 Service | 函数 | Payload 字段 |
|----------|---------------|------|-------------|
| `patient.created` | patient_service.rs | create_patient | patient_id, name, phone |
| `patient.updated` | patient_service.rs | update_patient | patient_id, updated_fields |
| `appointment.created` | appointment_service.rs | create_appointment | appointment_id, patient_id, doctor_id, scheduled_at |
| `appointment.{status}` | appointment_service.rs | update_appointment_status | appointment_id, patient_id, doctor_id, status |
| `appointment.reminder` | appointment_service.rs | send_reminders | appointment_id, patient_id, doctor_id, scheduled_at |
| `follow_up.created` | follow_up_service.rs | create_task | task_id, patient_id, assigned_to |
| `follow_up.batch_created` | follow_up_service.rs | batch_create_tasks | count, task_ids |
| `follow_up.completed` | follow_up_service.rs | batch_complete_tasks | task_ids, completed_count |
| `follow_up.assigned` | follow_up_service.rs | batch_assign_tasks | task_ids, assigned_to |
| `follow_up.overdue` | follow_up_service.rs | check_overdue_tasks | task_id, assigned_to, patient_id |
| `consultation.opened` | consultation_service.rs | create_session | session_id, patient_id, doctor_id |
| `consultation.closed` | consultation_service.rs | close_session | session_id, patient_id, doctor_id |
| `consultation.new_message` | consultation_service.rs | create_message | session_id, sender_id, sender_type |
| `device.readings.synced` | device_reading_service.rs | sync_readings | patient_id, device_type, reading_count |
| `vital_signs.created` | health_data_service.rs | create_vital_signs | patient_id, record_id, indicators |
| `lab_report.uploaded` | health_data_service.rs | create_lab_report | patient_id, report_id, indicator_count |
| `lab_report.reviewed` | health_data_service.rs | review_lab_report | report_id, reviewer_id, status |
| `health_data.critical_alert` | health_data_service.rs | create_vital_signs | patient_id, alert_type, metric_name, metric_value, threshold_value |
| `daily_monitoring.created` | daily_monitoring_service.rs | create_daily_monitoring | patient_id, monitoring_id, record_date |
| `alert.triggered` | alert_engine.rs | evaluate_rules | patient_id, severity, rule_name, alert_id |
| `article.published` | article_service.rs | publish_article | article_id, title, author_id |
| `article.rejected` | article_service.rs | reject_article | article_id, reviewer_id, reason |
| `consent.granted` | consent_service.rs | grant_consent | patient_id, consent_type |
| `consent.revoked` | consent_service.rs | revoke_consent | patient_id, consent_type, reason |
| `points.earned` | points_service.rs | daily_checkin | patient_id, points, balance |
| `points.exchanged` | points_service.rs | exchange_product | patient_id, product_id, order_id, points |
| `points.expired` | points_service.rs | expire_points | count, expired_points_total |
| `doctor.online_status_changed` | doctor_service.rs | update_online_status | doctor_id, old_status, new_status |
### erp-ai 模块2 处发布)
| 事件类型 | 发布者 | 函数 |
|----------|--------|------|
| `ai.analysis.failed` | handler/mod.rs | stream_lab_report 等 |
| `ai.analysis.completed` | handler/mod.rs | stream_lab_report 等 |
### erp-dialysis 模块1 处发布)
| 事件类型 | 发布者 | 函数 |
|----------|--------|------|
| `dialysis.record.created` | dialysis_service.rs | create_record |
### erp-auth 模块1 处发布)
| 事件类型 | 发布者 | 函数 |
|----------|--------|------|
| `user.login` | auth_service.rs | login |
### erp-workflow 模块2+ 处发布)
| 事件类型 | 发布者 | 函数 |
|----------|--------|------|
| `workflow.instance.*` | instance_service.rs | start_instance 等 |
| `workflow.task.timeout` | module.rs | 后台超时检测 |
### erp-plugin 模块3 处发布)
| 事件类型 | 发布者 | 函数 |
|----------|--------|------|
| `plugin.data.*` | data_service.rs | CRUD 操作 |
| `plugin.trigger.*` | data_service.rs | 通知触发 |
| 动态事件 | engine.rs | 插件自定义事件 |
---
## 2. 事件消费方清单
### erp-health 消费者11 个 tokio::spawn 任务)
| # | 消费者名称 | 订阅前缀 | 处理事件 | 业务动作 |
|---|-----------|---------|---------|---------|
| 1 | workflow_task_consumer | `workflow.task.` | `workflow.task.completed` | 更新随访任务状态为 completed |
| 2 | message_consumer | `message.` | `message.sent` | 日志记录(预留扩展) |
| 3 | device_reading_consumer | `device.readings.` | `device.readings.synced` | 触发告警引擎评估 |
| 4 | alert_notifier | `alert.` | `alert.triggered` | 发送应用内告警通知 |
| 5 | patient_welcome | `patient.` | `patient.created` | 发送欢迎消息 |
| 6 | appointment_notifier | `appointment.` | `appointment.confirmed` | 发送预约确认通知 |
| 7 | appointment_cancel_handler | `appointment.` | `appointment.cancelled` | 日志记录(号源释放) |
| 8 | follow_up_escalator | `follow_up.` | `follow_up.overdue` | 发送逾期升级通知 |
| 9 | critical_alert_consumer | `health_data.` | `health_data.critical_alert` | 创建危急值告警记录 |
| 10 | ai_analysis_notifier | `ai.` | `ai.analysis.completed` | 通知关联医生 |
| 11 | dialysis_notifier | `ai.` | `dialysis.record.created` | 日志记录 |
| 12 | consent_notifier | `consent.` | `consent.granted` | 发送授予通知 |
| 13 | consent_revoked_notifier | `consent.` | `consent.revoked` | 发送撤回通知给医护 |
### 其他模块消费者
| 模块 | 消费者 | 订阅范围 |
|------|--------|---------|
| erp-message | SSE handler | 全部事件(转发给前端) |
| erp-plugin | notification handler | `plugin.trigger.` |
| erp-plugin | engine | 按插件 manifest 的 pattern |
---
## 3. 事件常量 vs 发布方 vs 消费方矩阵
| 事件常量 | 事件类型 | 发布方 | 消费方 | 状态 |
|----------|---------|--------|--------|------|
| APPOINTMENT_CREATED | `appointment.created` | ✓ appointment_service | ✓ appointment_notifier前缀匹配 | **ALIVE** |
| ALERT_TRIGGERED | `alert.triggered` | ✓ alert_engine | ✓ alert_notifier | **ALIVE** |
| CONSENT_GRANTED | `consent.granted` | ✓ consent_service | ✓ consent_notifier | **ALIVE** |
| CONSENT_REVOKED | `consent.revoked` | ✓ consent_service | ✓ consent_revoked_notifier | **ALIVE** |
| ARTICLE_PUBLISHED | `article.published` | ✓ article_service | — | **NO CONSUMER** |
| ARTICLE_REJECTED | `article.rejected` | ✓ article_service | — | **NO CONSUMER** |
| CONSULTATION_OPENED | `consultation.opened` | ✓ consultation_service | — | **NO CONSUMER** |
| CONSULTATION_CLOSED | `consultation.closed` | ✓ consultation_service | — | **NO CONSUMER** |
| CONSULTATION_NEW_MESSAGE | `consultation.new_message` | ✓ consultation_service | — | **NO CONSUMER** |
| DEVICE_READINGS_SYNCED | `device.readings.synced` | ✓ device_reading_service | ✓ device_reading_consumer | **ALIVE** |
| DOCTOR_ONLINE_STATUS_CHANGED | `doctor.online_status_changed` | ✓ doctor_service | — | **NO CONSUMER** |
| FOLLOW_UP_CREATED | `follow_up.created` | ✓ follow_up_service | — | **NO CONSUMER** |
| FOLLOW_UP_COMPLETED | `follow_up.completed` | ✓ follow_up_service | — | **NO CONSUMER** |
| FOLLOW_UP_OVERDUE | `follow_up.overdue` | ✓ follow_up_service | ✓ follow_up_escalator | **ALIVE** |
| DAILY_MONITORING_CREATED | `daily_monitoring.created` | ✓ daily_monitoring_service | — | **NO CONSUMER** |
| LAB_REPORT_UPLOADED | `lab_report.uploaded` | ✓ health_data_service | — | **NO CONSUMER** |
| LAB_REPORT_REVIEWED | `lab_report.reviewed` | ✓ health_data_service | — | **NO CONSUMER** |
| HEALTH_DATA_CRITICAL_ALERT | `health_data.critical_alert` | ✓ health_data_service | ✓ critical_alert_consumer | **ALIVE** |
| PATIENT_CREATED | `patient.created` | ✓ patient_service | ✓ patient_welcome | **ALIVE** |
| PATIENT_UPDATED | `patient.updated` | ✓ patient_service | — | **NO CONSUMER** |
| PATIENT_VERIFIED | `patient.verified` | — | — | **KNOWN** 未实现 |
| PATIENT_DECEASED | `patient.deceased` | — | — | **KNOWN** 未实现 |
| POINTS_EXPIRED | `points.expired` | ✓ points_service | — | **NO CONSUMER** |
| POINTS_EARNED | `points.earned` | ✓ points_service | — | **NO CONSUMER** |
| POINTS_EXCHANGED | `points.exchanged` | ✓ points_service | — | **NO CONSUMER** |
---
## 4. 分析结论
### 4.1 活跃事件(有发布+有消费11 个
这是系统核心业务链路,全部正常:
- `workflow.task.completed` → 随访自动完成
- `device.readings.synced` → 告警评估
- `alert.triggered` → 告警通知
- `patient.created` → 欢迎消息
- `appointment.confirmed/cancelled` → 预约通知
- `follow_up.overdue` → 逾期升级
- `health_data.critical_alert` → 危急值告警
- `ai.analysis.completed` → 医生通知
- `dialysis.record.created` → 日志
- `consent.granted/revoked` → 知情同意通知
### 4.2 有发布无消费事件14 个
这些事件被发布到 EventBus 并持久化到 domain_events 表,但没有业务消费者。它们可能仅用于审计追踪或 SSE 前端推送。
**风险评估**
| 事件 | 风险 | 说明 |
|------|------|------|
| `patient.updated` | LOW | 信息性事件,更新操作已直接处理 |
| `vital_signs.created` | LOW | 数据已写入 DB无需后续触发 |
| `lab_report.uploaded` | LOW | 同上 |
| `lab_report.reviewed` | LOW | 同上 |
| `follow_up.created` | LOW | 任务已创建,无需后续触发 |
| `follow_up.completed` | LOW | 状态已更新,无需后续触发 |
| `consultation.*` | LOW | 会话管理已在 service 内直接处理 |
| `article.published/rejected` | LOW | 状态已更新,无需后续触发 |
| `points.earned/exchanged/expired` | LOW | 积分操作已在 service 内完成 |
| `daily_monitoring.created` | LOW | 数据已写入 |
| `doctor.online_status_changed` | LOW | 状态已更新 |
### 4.3 已知未实现事件KNOWN2 个
- `PATIENT_VERIFIED` — 患者认证流程未实现
- `PATIENT_DECEASED` — 死亡记录流程未实现
### 4.4 潜在问题
1. **message.sent 消费者仅为日志记录** — event.rs:119 的消费者仅做 tracing::info实际业务逻辑如更新 consultation last_message_at已在 service 层直接处理,此消费者预留扩展但当前无实质作用。
2. **SSE 全事件转发** — erp-message 的 SSE handler 监听所有事件,意味着所有事件(包括 points.earned 等低优先级事件)都会被推送到前端,可能导致通知噪音。
---
## 5. Payload Schema 一致性验证(抽样 5 个)
| 事件 | 发布方字段 | 消费方解析 | 一致性 |
|------|-----------|-----------|--------|
| `workflow.task.completed` | `task_id` | `task_id` → Uuid | ✅ |
| `device.readings.synced` | `patient_id` | `patient_id` → Uuid | ✅ |
| `health_data.critical_alert` | `patient_id, alert_type, metric_name, metric_value, threshold_value` | 全部解析 | ✅ |
| `follow_up.overdue` | `task_id, assigned_to` | 全部解析 | ✅ |
| `appointment.confirmed` | `doctor_id, patient_id` | 全部解析 | ✅ |
**结论**:所有抽样的关键事件 payload schema 发布方与消费方完全一致。所有消费者都使用幂等检查(`is_event_processed`),防止重复处理。
---
## 6. 事件系统评分
| 检查项 | 评分 | 说明 |
|--------|------|------|
| 事件定义完整性 | 95% | 25/27 常量有发布者2 个 KNOWN 未实现) |
| 消费者覆盖率 | 44% | 11/25 事件有活跃消费者 |
| Payload 一致性 | 100% | 抽样 5 个全部一致 |
| 幂等性保证 | 100% | 所有消费者使用 `is_event_processed` 检查 |
| 死信处理 | 100% | 消费失败自动进入 dead_letter_event 表 |