# 健康管理模块全面迭代设计 > **文档版本**: 1.0 > **日期**: 2026-04-24 > **状态**: 待评审 > **基于**: 5 位专家(后端架构/前端架构/医疗业务/安全质量/产品策略)深度审查 --- ## 0. 审查发现总览 ### 0.1 V1 发布阻塞项 | # | 阻塞项 | 来源 | 影响 | |---|--------|------|------| | B1 | Web 健康模块 10 页面未实现 | 前端架构/产品策略 | 无法演示和交付 | | B2 | 医疗数据安全不合规 | 安全质量 | 零 sanitize / 零审计 / 身证明文 / 零测试 | | B3 | 数据一致性缺陷 | 医疗业务/后端架构 | 排班可超额 / 名额释放可能失败 / 随访逾期未实现 | | B4 | 事件处理器空壳 | 后端架构 | 随访状态/咨询消息不联动 | ### 0.2 当前完成度 | 层级 | 模块 | 完成度 | |------|------|--------| | 后端 | erp-health(16 实体/8 服务/7 handler/40+ API) | 95% | | 后端 | 事件处理器业务逻辑 | 0%(框架已搭建,需填充 db 操作) | | 后端 | sanitize / 审计 / 加密 | 0% | | 后端 | 测试覆盖 | 0% | | Web 前端 | 健康模块页面 | 0% | | Web 前端 | 健康模块 API 服务层 | 0% | | 小程序 | 初版 21 页面 | 85% | --- ## 1. 安全省基(阶段 1,1.5-2 周) ### 1.1 sanitize 全覆盖 **问题**: erp-health 模块没有任何对 `strip_html_tags` 的调用,攻击者可在患者姓名、病史等字段注入 XSS payload。 **参考实现**: `crates/erp-auth/src/dto.rs` 第 96-118 行,`CreateUserReq` 和 `UpdateUserReq` 已实现 `sanitize()` 方法。 **修复方案**: 为每个 DTO 的字符串输入字段添加 sanitize。 **覆盖字段清单**: | DTO 文件 | 字段 | |----------|------| | `patient_dto.rs` CreatePatientReq / UpdatePatientReq | name, notes, allergy_history, medical_history_summary, emergency_contact_name, source | | `patient_dto.rs` FamilyMemberReq(create + update 共用) | name, notes | | `patient_handler.rs` AssignDoctorReq(位于 handler 非 dto) | — (无字符串字段) | | `health_data_dto.rs` CreateVitalSignsReq | notes | | `health_data_dto.rs` CreateLabReportReq | doctor_interpretation | | `health_data_dto.rs` CreateHealthRecordReq | source, overall_assessment, notes | | `appointment_dto.rs` CreateAppointmentReq | notes, cancel_reason | | `follow_up_dto.rs` CreateFollowUpTaskReq / UpdateFollowUpTaskReq | content_template | | `follow_up_dto.rs` CreateFollowUpRecordReq | patient_condition, medical_advice | | `consultation_dto.rs` CreateMessageReq | content | | `consultation_dto.rs` CreateSessionReq | — (无字符串字段) | | `doctor_dto.rs` CreateDoctorReq / UpdateDoctorReq | department, title, specialty, bio | **实现模式**: ```rust // 封装 sanitize 辅助函数(与 erp-auth 的 sanitize_option 模式一致) fn sanitize_option_string(opt: Option) -> Option { opt.map(|s| strip_html_tags(&s)) } // 在每个 DTO 的 impl 中添加 sanitize 方法 impl CreatePatientReq { pub fn sanitize(&mut self) { self.name = strip_html_tags(&self.name); self.notes = sanitize_option_string(self.notes.take()); self.allergy_history = sanitize_option_string(self.allergy_history.take()); self.medical_history_summary = sanitize_option_string(self.medical_history_summary.take()); // ... } } // 在 handler 调用 service 前执行 async fn create_patient(/* ... */) -> AppResult>> { let mut req: CreatePatientReq = Json(req).0; req.sanitize(); // ... } ``` **前端安全**: ChatBubble 组件必须使用 React 默认 JSX 转义渲染文本内容(不使用 `dangerouslySetInnerHTML`),图片消息 URL 需做白名单校验。 ### 1.2 审计日志注入 **问题**: erp-health 整个模块没有任何对 `audit_service::record` 的调用。 **参考实现**: `crates/erp-auth/src/service/auth_service.rs` 第 168-177 行。 **修复方案**: 在所有写入操作的 service 层添加审计记录。 **覆盖操作清单**: | Service | 操作 | 审计 action | |---------|------|------------| | patient_service | create_patient | `patient.created` | | patient_service | update_patient | `patient.updated` | | patient_service | delete_patient | `patient.deleted` | | patient_service | manage_patient_tags | `patient.tags_updated` | | health_data_service | create_vital_signs | `vital_signs.created` | | health_data_service | create_lab_report | `lab_report.created` | | health_data_service | create_health_record | `health_record.created` | | appointment_service | create_appointment | `appointment.created` | | appointment_service | update_appointment_status | `appointment.status_changed` | | follow_up_service | create_task | `follow_up_task.created` | | follow_up_service | create_record | `follow_up_record.created` | | consultation_service | create_session | `consultation.opened` | | consultation_service | close_session | `consultation.closed` | | consultation_service | create_message | `consultation.message_sent` | | doctor_service | create/update/delete_doctor | `doctor.*` | **审计日志内容**: tenant_id、user_id、action、resource_type、resource_id、变更前后值摘要。 **注意**: 当前 `audit_service::record` 是 fire-and-forget,审计日志丢失对医疗合规不可接受。修复方案: 1. 新增 `record_in_txn(log: AuditLog, txn: &DatabaseTransaction)` 方法,在事务内 await 写入 2. 保留原 `record` 方法用于不要求事务保证的场景 3. erp-health 的关键写入操作使用 `record_in_txn`,失败时回滚整个事务 4. 需要改为事务包裹的 service 方法:create_patient、update_patient、delete_patient、create_appointment、update_appointment_status、create_record(随访)、create_message(咨询) ### 1.3 身份证号加密存储 **问题**: `patient.id_number` 明文存储在数据库中,违反《个人信息保护法》。 **方案**: AES-256-GCM 应用层加密。 **新增文件**: `crates/erp-health/src/crypto.rs` ```rust pub struct HealthCrypto { key: [u8; 32] } impl HealthCrypto { pub fn from_env() -> Self { /* 从 ERP__HEALTH__ENCRYPTION_KEY 读取 */ } pub fn encrypt(&self, plaintext: &str) -> AppResult { /* AES-256-GCM + Base64 */ } pub fn decrypt(&self, ciphertext: &str) -> AppResult { /* 解密 */ } } ``` **集成点**: - `patient_service::create_patient` — 加密 id_number 后存储 - `patient_service::update_patient` — 同上 - `patient_service::get_patient` — 解密后返回 - `patient_service::list_patients` — 列表不返回 id_number(脱敏) **密钥管理**: 环境变量 `ERP__HEALTH__ENCRYPTION_KEY`(32 字节 hex),必须在 `default.toml` 中标记为 `__MUST_SET_VIA_ENV__`。 **搜索兼容**: `patient.id_number` 的模糊搜索(`contains`)改为精确匹配(`eq`),在加密后使用 HMAC 索引做等值查询。 **HMAC 索引详情**: - 新增数据库列 `id_number_hash VARCHAR(64)`,存储 HMAC-SHA256 哈希 - HMAC 密钥独立于 AES 密钥,从环境变量 `ERP__HEALTH__HMAC_KEY` 读取 - 创建/更新患者时同时写入 hash 列,等值查询使用 `WHERE id_number_hash = hmac(输入值)` - 迁移 SQL:新增列 → 批量加密现有明文 → 删除原明文列(可选) **数据迁移方案**: 1. 停机窗口(预估 1-2 小时,视数据量) 2. 迁移脚本:`SELECT id, id_number FROM patients WHERE id_number IS NOT NULL AND deleted_at IS NULL` → 批量加密 → `UPDATE patients SET id_number = $encrypted WHERE id = $id` 3. 同步写入 `id_number_hash` 列 4. 验证脚本:抽样解密比对原值 5. 回滚方案:保留明文备份表 `patients_id_number_backup`,72 小时后确认无误再删除 **问题**: 列表接口直接返回完整身份证号、病史等敏感字段。 **修复方案**: 拆分响应 DTO。 ```rust // 列表用 — 不含敏感字段 pub struct PatientListResp { pub id: Uuid, pub name: String, pub gender: Option, pub birth_date: Option, pub status: String, pub tags: Vec, // 无 id_number, allergy_history, medical_history_summary, emergency_contact_phone 等 } // 详情用 — 敏感字段掩码 pub struct PatientDetailResp { // ... 全部字段 pub id_number: Option, // "320***********1234" pub emergency_contact_phone: Option, // "138****1234" } ``` --- ## 2. 后端补完(阶段 2,1.5 周) ### 2.1 事件处理器实现 **问题**: `event.rs` 中两个事件处理器只有 `tracing::info`,无实际业务逻辑。且 handler 中没有 `DatabaseConnection`,无法执行数据库操作。 **方案**: 在 `HealthModule::on_startup` 中创建 `HealthState` 并注册需要数据库访问的事件处理器。将现有 `register_event_handlers` 中的空壳代码迁移到 `on_startup`,`register_event_handlers` 改为空实现。 **修改 `crates/erp-health/src/module.rs`**: ```rust // register_event_handlers 改为空实现 fn register_event_handlers(&self, _bus: &EventBus) { // 事件处理器迁移到 on_startup,此处不再注册 } // on_startup 中注册带 db 的事件处理器 async fn on_startup(&self, ctx: &ModuleContext) -> AppResult<()> { let state = HealthState { db: ctx.db.clone(), event_bus: ctx.event_bus.clone(), }; crate::event::register_handlers_with_state(state); Ok(()) } ``` **修改 `crates/erp-health/src/event.rs`**: 新增 `register_handlers_with_state(state: HealthState)` 函数替代原有 `register_handlers`。 **事件处理器业务逻辑**: `workflow.task.completed`: 1. 从 payload 中提取 `task_id` 2. 查询 `follow_up_task WHERE related_appointment_id` 或通过 payload 映射 3. 更新随访任务状态为 `completed` `message.sent`: 1. 从 payload 中提取 `session_id`(或通过 sender/recipient 关联) 2. 更新 `consultation_session SET last_message_at = NOW(), unread_count = unread_count + 1` 3. 使用 `check_version` 乐观锁 ### 2.2 数据一致性修复 #### 2.2.1 排班名额保护 **问题**: `update_schedule` 可以将 `max_appointments` 改为小于 `current_appointments` 的值。 **修复**: 在 `appointment_service.rs` 的 `update_schedule` 方法中增加校验: ```rust if req.max_appointments < model.current_appointments { return Err(HealthError::Validation( "max_appointments 不能小于当前已预约数".into() ).into()); } ``` #### 2.2.2 取消预约名额释放 **问题**: `update_appointment_status` 中取消时名额释放失败只 log error 不回滚。 **修复**: 将名额释放作为事务的一部分,失败时回滚整个操作(包括状态更新)。 #### 2.2.3 咨询消息原子性 **问题**: `create_message` 中消息已插入,但后续 CAS 更新 session 失败时返回错误 — 消息已持久化但 session 元数据未更新。 **修复**: 将消息 INSERT + session CAS 更新放在同一个事务中。 ### 2.3 随访逾期定时任务 **问题**: 设计规格定义了 `overdue` 状态和定时任务自动标记,但代码中: - `validation.rs` 不允许转换到 `overdue` - 没有后台定时任务 **修复**: 1. 在 `validation.rs` 中添加 `overdue` 转换规则:`pending -> overdue`(仅限系统自动触发) 2. 在 `erp-server/src/main.rs` 后台任务区增加逾期检查器,使用与现有 `start_timeout_checker` 一致的 `tokio::spawn` + `loop` + `tokio::time::interval` 模式(每 6 小时执行一次,非 cron 表达式): ```rust // erp-server/src/main.rs 后台任务区 tokio::spawn(async move { let mut interval = tokio::time::interval(Duration::from_secs(6 * 3600)); loop { interval.tick().await; // 调用 health module 的 check_overdue_tasks } }); ``` 3. 在 `erp-health` module 中添加一个公开方法 `check_overdue_tasks` 供定时任务调用。 ### 2.4 article 管理 CRUD **问题**: 权限声明中有 `health.articles.manage`,但 service/handler 只有 list 和 get。 **修复**: 在 `article_service.rs` 和 `article_handler.rs` 中补充 create/update/delete 方法。在 `module.rs` 中添加路由。**工时估算**: 0.5 天。 --- ## 3. Web 前端 10 页面(阶段 3,3.5-4 周) ### 3.1 页面文件组织 ``` apps/web/src/ ├── api/health/ │ ├── patients.ts # 12 端点 │ ├── healthData.ts # 13 端点 │ ├── appointments.ts # 6 端点 │ ├── followUp.ts # 6 端点 │ ├── consultations.ts # 6 端点 │ └── doctors.ts # 4 端点 ├── pages/health/ │ ├── PatientList.tsx # 患者列表 │ ├── PatientDetail.tsx # 患者详情(5 Tab) │ ├── PatientTagManage.tsx # 标签管理 │ ├── DoctorList.tsx # 医护列表 │ ├── AppointmentList.tsx # 预约管理 │ ├── DoctorSchedule.tsx # 排班管理 │ ├── FollowUpTaskList.tsx # 随访任务 │ ├── FollowUpRecordList.tsx # 随访台账 │ ├── ConsultationList.tsx # 会话管理 │ ├── ConsultationDetail.tsx # 对话详情 │ └── components/ │ ├── StatusTag.tsx # 通用状态标签 │ ├── PatientSelect.tsx # 患者搜索选择器 │ ├── DoctorSelect.tsx # 医护选择器 │ ├── VitalSignsChart.tsx # ECharts 趋势图 │ ├── CalendarView.tsx # 日历视图 │ ├── ChatBubble.tsx # 聊天气泡 │ ├── ImagePreview.tsx # 图片预览 │ └── ExportButton.tsx # 导出按钮 ``` ### 3.2 API 服务层设计 每个 service 文件遵循现有 `api/users.ts` 的解构模式: ```typescript // api/health/patients.ts import client from '../client'; export interface Patient { id: string; name: string; gender?: string; birth_date?: string; status: string; tags: Tag[]; // ... } export interface CreatePatientReq { name: string; gender?: string; // ... } export const patientApi = { list: async (params: ListParams) => { const { data } = await client.get<{ success: boolean; data: PaginatedResponse }>( '/health/patients', { params } ); return data.data; }, get: async (id: string) => { const { data } = await client.get<{ success: boolean; data: Patient }>( `/health/patients/${id}` ); return data.data; }, create: async (req: CreatePatientReq) => { const { data } = await client.post<{ success: boolean; data: Patient }>( '/health/patients', req ); return data.data; }, // ... }; ``` ### 3.3 路由注册 在 `App.tsx` 中新增: ```typescript // lazy imports const PatientList = lazy(() => import('./pages/health/PatientList')); const PatientDetail = lazy(() => import('./pages/health/PatientDetail')); // ... 共 10 个路由组件 // Routes 内 } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> ``` ### 3.4 侧边栏菜单 在 `MainLayout.tsx` 中新增 `healthMenuItems` 数组(参照现有 `bizMenuItems` 模式),使用 `@ant-design/icons` 图标(如 `MedicineBoxOutlined`、`HeartOutlined`、`CalendarOutlined`、`PhoneOutlined`、`CommentOutlined`、`TagsOutlined`): ``` 侧边栏布局: ├── 首页 (HomeOutlined) ├── 用户管理 (UserOutlined) ├── 权限管理 (SafetyOutlined) ├── 工作流 (ApartmentOutlined) ├── 消息中心 (BellOutlined) ├── ───────── ├── 健康管理 (MedicineBoxOutlined) ← 新增组 │ ├── 患者管理 (TeamOutlined) │ ├── 医护管理 (HeartOutlined) │ ├── 预约排班 (CalendarOutlined) │ ├── 随访管理 (PhoneOutlined) │ ├── 咨询管理 (CommentOutlined) │ └── 标签管理 (TagsOutlined) ├── ───────── ├── 插件管理 (AppstoreOutlined) ├── 系统设置 (SettingOutlined) ``` ### 3.5 前端权限集成 后端已有完整权限体系(14 个权限码),前端 V1 阶段采用以下策略: 1. **路由级权限**: 所有健康模块路由在 `PrivateRoute` 内(已实现),后端 `require_permission` 拦截无权限请求返回 403 2. **按钮级权限(V1 简化)**: 不做前端按钮级权限控制,依赖后端 403 响应。后续可扩展 `usePermission` hook 3. **菜单可见性**: 健康模块菜单组始终显示,但无权限用户点击任何页面会收到 403 提示 ### 3.5 13 页面逐一设计 #### PatientList.tsx(中复杂度,1.5 天) - Ant Design `Table` 组件(与 Users.tsx 模式一致,不使用 ProTable) - 搜索:姓名模糊 + 状态筛选 + 标签多选筛选 - 每行显示患者标签为 `Tag` 组件列表 - 行点击跳转 `/health/patients/:id` - 批量操作:批量打标 - 导出功能 #### PatientDetail.tsx(高复杂度,3 天) - 顶部:患者摘要卡片(姓名/性别/年龄/状态/标签) - Ant Design `Tabs` 5 个 Tab: 1. **基本信息** — `Descriptions` 展示 + 编辑 Modal 2. **健康趋势** — `VitalSignsChart` 组件 + 时间范围选择器 3. **化验报告** — 报告卡片列表 + `ImagePreview` 指标详情 4. **就诊记录** — 嵌套列表(体检/门诊/住院) 5. **随访记录** — 嵌套列表 + 关联的随访记录 #### PatientTagManage.tsx(低复杂度,0.5 天) - 标准 CRUD 表格 - 颜色选择器(Ant Design `ColorPicker`) - 批量打标功能 #### DoctorList.tsx(低复杂度,0.5 天) - 标准 CRUD 表格 - 科室筛选 + 在线状态 Badge(online=绿/busy=黄/offline=灰) - 详情 Drawer #### AppointmentList.tsx(中复杂度,2 天) - `Segmented` 切换列表/日历视图 - 列表模式:表格 + 状态筛选 + 日期筛选 - 日历模式:`Calendar` + `cellRender` 显示当日预约数 - 状态流转 Dropdown(pending → confirmed → completed/no_show/cancelled) - 创建预约 Modal(选择患者 + 医生 + 日期时段 + 检查排班余量) #### DoctorSchedule.tsx(高复杂度,2.5 天) - 选择医生后展示其排班 - 周视图(自定义 7 列网格,每列显示一天的排班时段) - 月视图(Ant Design Calendar) - 批量创建排班(选择日期范围 + 时段模板) - 显示已预约/最大预约数 #### FollowUpTaskList.tsx(中复杂度,1.5 天) - 表格 + 状态筛选(pending/in_progress/completed/overdue/cancelled) - 分配给医护(`DoctorSelect`) - 创建任务 Modal - 快捷"填写随访记录"按钮打开子 Modal #### FollowUpRecordList.tsx(低复杂度,0.5 天) - 纯只读台账 - 筛选:日期范围、患者、任务、结果 - 导出功能(`ExportButton`) #### ConsultationList.tsx(中复杂度,1 天) - 表格 + 状态筛选(waiting/active/closed) - 未读消息数 Badge - 最后消息时间 - 关闭会话操作 - 点击跳转 `/health/consultations/:id` #### ConsultationDetail.tsx(高复杂度,2 天) - `ChatBubble` 组件渲染聊天气泡 - 根据 `sender_role` 区分左右对齐 - 支持内容类型:text / image(`ImagePreview`)/ voice / file - 消息按时间排列,支持滚动加载更多(分页) - 导出按钮 ### 3.6 技术难点方案 #### ECharts 趋势图 使用已安装的 `@ant-design/charts` 的 `Line` 组件。 - 后端 API `/patients/:id/trends/:indicator` 返回时序数据 - 前端转换为 `{ date: string, value: number }[]` - 支持多指标叠加(血压收缩压/舒张压双线) - 封装为 `VitalSignsChart`,接收 `patientId` + `indicators` 参数 - 时间范围选择器(7天/30天/90天) #### 日历视图 Ant Design `Calendar` + 自定义 `cellRender`: - DoctorSchedule:每个日期格显示排班时段标签 - AppointmentList:每个日期格显示预约数量气泡 #### 聊天 UI 自定义 `ChatBubble` 组件,基于 Ant Design `Typography.Paragraph` + `Avatar`: - 根据 `sender_role` 区分样式 - 只读模式(PC 后台只查看不发送) - 图片消息使用 `Image.PreviewGroup` #### 导出 后端 blob 导出 + 前端触发下载,参照 `PluginCRUDPage` 中已有的 `exportPluginDataAsBlob` 模式。 #### 文件上传/预览 - 上传:Ant Design `Upload.Dragger`,上传到后端文件接口 - 图片预览:Ant Design `Image.PreviewGroup` - PDF 预览:新窗口打开(V1 简化方案) ### 3.7 开发顺序 | Phase | 内容 | 天数 | 依赖 | |-------|------|------|------| | 1 | API 层 6 文件 + 通用组件 + 路由菜单 | 1.5 | 无 | | 2 | PatientList + PatientTagManage + PatientDetail 基本信息Tab | 2 | Phase 1 | | 3 | VitalSignsChart + 健康趋势 Tab + LabReportList + HealthRecordList | 3 | Phase 2 | | 4 | DoctorList + AppointmentList + DoctorSchedule | 3 | Phase 1 | | 5 | FollowUpTaskList + FollowUpRecordList + ConsultationList + ConsultationDetail | 3 | Phase 1 | | 6 | 打磨(暗色主题 + 响应式 + 联调) | 1 | Phase 2-5 | | **合计** | | **13.5 天** | | --- ## 4. 测试策略(阶段 2-3 交叉进行) ### 4.1 优先级排序 | 优先级 | 测试目标 | 预估用例数 | 工作量 | |--------|---------|-----------|--------| | P0 | `validation.rs` 纯函数 | 20-30 | 1 天 | | P0 | `appointment_service` CAS + 状态流转 | 15-20 | 2 天 | | P0 | `patient_service` CRUD + 状态机 | 15-20 | 2 天 | | P1 | `consultation_service` 消息原子性 | 10-15 | 2 天 | | P1 | `health_data_service` 指标数据 | 10-15 | 1 天 | | P2 | `follow_up_service` 链式任务 | 10 | 1 天 | ### 4.2 测试基础设施 在 `erp-health/Cargo.toml` 中添加 `[dev-dependencies]`: - `tokio` 的 `test` 和 `macros` feature - `sea-orm` 的 `mock` feature(用于简单单元测试,如 validation 纯函数) 对于涉及事务和 CAS 的集成测试(预约并发、消息原子性),使用 testcontainers-postgreSQL 做真实数据库测试,因为 SeaORM 的 `MockDatabaseConnection` 不支持复杂事务模拟。 创建 `tests/test_helpers.rs` 提供: - `create_test_health_state()` — 带 mock db 的 HealthState(单元测试用) - `create_integration_db()` — testcontainers PostgreSQL 实例(集成测试用) - 共享 fixture 工厂 ### 4.3 关键测试场景 **预约 CAS 并发**: - 排班已满 → 创建预约失败 - 排班有余 → CAS 成功 + 名额减 1 - 并发创建 → 只有 max_appointments 个成功 **状态机转换**: - 合法转换:pending → confirmed → completed - 非法转换:completed → pending → 拒绝 - 取消:任意状态 → cancelled(填 cancel_reason) **随访链式任务**: - next_follow_up_date 不为空 → 自动创建新任务 - 新任务的 assigned_to 沿用当前医护 - next_follow_up_date 为空 → 不创建新任务 --- ## 5. 实施路线图 ### 5.1 总时间线(调整为 7 周) ``` Week 1-2 | 安全地基(1.5-2 周) | ├── sanitize 全覆盖(2 天) | ├── 审计日志注入(2 天) | ├── 身份证号加密 + HMAC 索引 + 数据迁移(3-4 天) | └── 字段级脱敏(1-2 天) Week 2-4 | 后端补完 + 测试(1.5-2 周) | ├── 事件处理器实现(2 天) | ├── 数据一致性修复(2 天) | ├── 随访逾期定时任务(1 天) | ├── article CRUD(0.5 天) | └── 核心路径测试(5-6 天) Week 4-7 | Web 前端(3.5-4 周) | ├── Phase 1: API 层 + 通用组件 + 路由菜单(1.5 天) | ├── Phase 2: 核心入口页面(2 天) | ├── Phase 3: 健康数据页面(3 天) | ├── Phase 4: 预约排班页面(3 天) | ├── Phase 5: 随访咨询页面(3 天) | └── Phase 6: 打磨联调(1 天) Week 7-8 | 端到端验证(1 周) | ├── 小程序联调 | ├── 种子数据填充 | ├── Docker 演示环境 | └── 文档更新 ``` ### 5.2 里程碑 | 里程碑 | 交付物 | 验收标准 | |--------|--------|---------| | M1 | 安全省基完成 | sanitize + 审计 + 加密 + 脱敏全部到位,cargo test 通过 | | M2 | 后端功能完整 | 事件处理器 + 数据一致性 + 测试覆盖,cargo test 通过 | | M3 | Web 3 核心页面 | PatientList + AppointmentList + DoctorSchedule 可操作 | | M4 | Web 10 页面完成 | 所有页面功能可用,pnpm build 通过 | | M5 | 端到端验证 | Web + 小程序 + 后端全链路可演示 | ### 5.3 风险和缓解 | 风险 | 概率 | 缓解 | |------|------|------| | ECharts 集成复杂度高 | 中 | 使用 @ant-design/charts 已安装,降低自研成本 | | 身份证加密影响现有查询 | 中 | HMAC 索引 + 数据迁移脚本 + 备份表 + 回滚方案 | | 10 页面开发时间超预期 | 高 | 按优先级裁剪,MVP 先做 3 核心页面 | | 文件上传能力未就绪 | 中 | V1 先支持 URL 存储,文件上传推迟到 V1.1 | --- ## 6. 不在本设计范围内(推迟到 V2) - 积分商城 - 数据统计中心 / 运营驾驶舱 - AI 辅助诊断/报告解读 - 实时 WebSocket 在线咨询 - 咨询消息按月分区 - 事件幂等性(processed_events 去重表) - Polling Outbox 重试机制 - HealthState 扩展 Redis 缓存 - 国际化(英文等多语言) - 小程序医护端