diff --git a/wiki/architecture.md b/wiki/architecture.md index e279a1f..c2553f7 100644 --- a/wiki/architecture.md +++ b/wiki/architecture.md @@ -1,6 +1,6 @@ --- title: 架构决策记录 -updated: 2026-04-23 +updated: 2026-04-25 status: stable tags: [architecture, decisions, design-principles] --- @@ -22,13 +22,13 @@ HMS 继承 ERP 底座的所有基础模块,`erp-health` 作为原生 Rust 模 ``` HMS 平台 ├── 基础模块(继承 ERP): auth, config, workflow, message, plugin -├── 核心业务模块: erp-health(原生 Rust)★ +├── 核心业务模块: erp-health(原生 Rust,18 实体/14 权限/13 页面)★ 已实现 └── 可选插件: crm, inventory, freelance, itops(WASM) ``` ### 为什么 erp-health 用原生模块? -医疗业务需要 16+ 强类型实体、自定义 API(趋势分析/统计报表)、文件上传、未来 AI 集成。WASM 插件的 JSONB 动态存储和 20 实体上限无法满足。详见 [[erp-health]]。 +医疗业务需要 18 强类型实体、自定义 API(趋势分析/统计报表)、PII 数据加密(AES-256-GCM)、文件上传、未来 AI 集成。WASM 插件的 JSONB 动态存储和 20 实体上限无法满足。详见 [[erp-health]]。 ### 为什么用 UUIDv7? @@ -42,6 +42,10 @@ HMS 平台 `anyhow` 无类型信息,无法精确映射 HTTP 状态码。`thiserror` → `AppError` → 400/401/403/404/409/500。 +### 为什么预约用原子 CAS? + +防止并发创建预约时超额。事务内 `UPDATE current_appointments + 1 WHERE current < max`,CAS 成功后才 INSERT 预约记录。 + ## 2. 关键文件 + 数据流 ### 模块依赖图 @@ -62,6 +66,17 @@ HMS 平台 **禁止**: L2 间直接依赖 / L1 依赖业务模块 / 绕过事件总线 +### 模块实现状态 + +| 模块 | 状态 | 实体数 | 权限数 | 页面数 | +|------|------|--------|--------|--------| +| erp-auth | ✅ 完成 | 11 表 | - | 用户/角色/组织 | +| erp-config | ✅ 完成 | 6 表 | - | 设置/字典/菜单 | +| erp-workflow | ✅ 完成 | 5 表 | - | 工作流管理 | +| erp-message | ✅ 完成 | 3 表 | - | 消息中心 | +| erp-plugin | ✅ 完成 | 4 表 | - | 插件管理/市场 | +| erp-health | ✅ 完成 | 18 表 | 14 | 13 页面 + 11 组件 | + ### 技术选型 | 选择 | 理由 | @@ -79,8 +94,9 @@ HMS 平台 | 方向 | 模块 | 触发时机 | |------|------|---------| | 定义 → | [[erp-core]] | 所有模块的 trait 和类型 | -| 组装 ← | [[erp-server]] | 模块注册和启动 | +| 组装 ← | [[erp-server]] | 6 模块注册和启动 | | 扩展 ← | [[wasm-plugin]] | 插件通过 Host Bridge 桥接 | +| 业务 ← | [[erp-health]] | 健康模块原生集成 | ## 3. 代码逻辑 @@ -89,14 +105,18 @@ HMS 平台 ⚡ **不变量**: UUID v7 作为主键 ⚡ **不变量**: 软删除,不硬删除 ⚡ **不变量**: 所有 API 使用 `/api/v1/` 前缀 +⚡ **不变量**: 预约创建必须走原子 CAS,不能用 read-then-write +⚡ **不变量**: PII 数据(身份证、手机号)加密存储 + 脱敏展示 ## 4. 活跃问题 + 陷阱 ⚠️ 当前共享数据库 + tenant_id 过滤,未来可扩展为 Schema 隔离或数据库隔离 ⚠️ EventBus 内存 broadcast 需 outbox 持久化保障(已通过后台任务实现) +⚠️ 微信登录固定到 default_tenant_id — 多租户场景需设计解析策略 ## 5. 变更记录 | 日期 | 变更 | |------|------| +| 2026-04-25 | 全面更新:6 模块已实现状态表、预约 CAS 决策、PII 加密不变量、健康模块集成 | | 2026-04-23 | 重构为 5 节结构,删除 erp-common 引用,精简技术选型表 | diff --git a/wiki/database.md b/wiki/database.md index 806faee..6a1b393 100644 --- a/wiki/database.md +++ b/wiki/database.md @@ -1,13 +1,13 @@ --- title: 数据库迁移与模式 -updated: 2026-04-23 +updated: 2026-04-25 status: stable tags: [database, seaorm, migration, multi-tenant] --- # 数据库迁移与模式 -> 从 [[index]] 导航。关联: [[erp-core]] [[erp-server]] [[infrastructure]] +> 从 [[index]] 导航。关联: [[erp-core]] [[erp-server]] [[infrastructure]] [[erp-health]] ## 1. 设计决策 @@ -24,7 +24,7 @@ tags: [database, seaorm, migration, multi-tenant] | 文件 | 职责 | |------|------| | `crates/erp-server/migration/src/lib.rs` | Migrator 注册所有迁移 | -| `crates/erp-server/migration/src/m*.rs` | 41 个迁移文件 | +| `crates/erp-server/migration/src/m*.rs` | 50 个迁移文件 | | `crates/erp-core/src/types.rs` | BaseFields 标准字段定义 | ### 迁移命名规则 @@ -34,7 +34,7 @@ m{YYYYMMDD}_{6位序号}_{描述}.rs 例: m20260410_000001_create_tenant.rs ``` -### 当前表概览(30 张) +### 当前表概览(48 张) | 模块 | 表 | |------|-----| @@ -45,6 +45,23 @@ m{YYYYMMDD}_{6位序号}_{描述}.rs | 消息 (message) | message_templates, messages, message_subscriptions | | 审计 | audit_logs, domain_events | | 插件 (plugin) | plugins, entity_registry, plugin_market, plugin_user_views | +| **健康 (health)** | patient, patient_family_member, patient_tag, patient_tag_relation, patient_doctor_relation, doctor_profile, health_record, vital_signs, lab_report, health_trend, appointment, doctor_schedule, follow_up_task, follow_up_record, consultation_session, consultation_message | +| **微信 (wechat)** | wechat_users | +| **内容 (article)** | article | + +### 健康模块迁移(m000042 - m000050) + +| 迁移 | 变更 | +|------|------| +| m000042 | 创建 17 张健康业务表(patient/doctor/appointment/schedule/vital_signs/lab_report/health_record/health_trend/follow_up_task/follow_up_record/consultation_session/consultation_message/patient_tag/patient_tag_relation/patient_family_member/patient_doctor_relation) | +| m000043 | 创建 wechat_users 表 | +| m000044 | 创建 article 表 | +| m000045 | 健康模块索引优化 | +| m000046 | 健康模块约束修复 | +| m000047 | 健康模块索引修复 | +| m000048 | 添加 patient.id_number_hash 列(HMAC-SHA256 哈希身份证) | +| m000049 | 拓宽 patient.id_number 列(VARCHAR 加密存储需要更长字段) | +| m000050 | 添加 appointment.doctor_name 列(冗余提升查询性能) | ### 集成契约 @@ -53,6 +70,7 @@ m{YYYYMMDD}_{6位序号}_{描述}.rs | 消费 ← | [[erp-server]] | 启动时自动运行 `Migrator::up()` | | 依赖 ← | [[erp-core]] | BaseFields 定义标准字段规范 | | 提供 → | 所有业务模块 | 表结构供 SeaORM Entity 使用 | +| 提供 → | [[erp-health]] | 18 张健康业务表 | ## 3. 代码逻辑 @@ -62,6 +80,8 @@ m{YYYYMMDD}_{6位序号}_{描述}.rs ⚡ **不变量**: 迁移执行由 erp-server 启动自动触发,不手动执行 SQL +⚡ **不变量**: 健康模块 PII 数据使用 AES-256-GCM 加密存储,身份证号额外 HMAC-SHA256 哈希 + ### 关键结构变更迁移 | 迁移 | 变更 | @@ -73,6 +93,8 @@ m{YYYYMMDD}_{6位序号}_{描述}.rs | m000038 | 修复 CRM 权限码 | | m000039 | entity_registry 列 | | m000041 | plugin_user_views | +| m000042 | 17 张健康业务表 | +| m000048 | patient.id_number_hash HMAC 列 | ## 4. 活跃问题 + 陷阱 @@ -80,13 +102,16 @@ m{YYYYMMDD}_{6位序号}_{描述}.rs - 唯一索引 + 软删除冲突 — 已删除记录的 unique key 阻止新建(m000027 修复) - tenant 表缺少 `created_by`/`updated_by`/`version` 字段 — 首个迁移早于 BaseFields 规范 +- wechat_users 表初始缺少标准字段列 — 需 ALTER TABLE 补充(m000043 已包含完整字段) ⚠️ settings 表的唯一索引曾需修复(m000032) ⚠️ 新增表时务必对齐 `crates/erp-core/src/types.rs` 中的 BaseFields +⚠️ 已应用的迁移文件被删除会导致启动失败 — 需创建空 stub 迁移(m000050 案例) ## 5. 变更记录 | 日期 | 变更 | |------|------| +| 2026-04-25 | 更新至 50 迁移、48 表,新增健康模块迁移(m000042-m000050)和 18 张健康业务表 | | 2026-04-23 | 重构为 5 节结构,更新表清单至 41 个迁移 | | 2026-04-19 | CRM 权限码修复迁移 (m000038) | diff --git a/wiki/erp-core.md b/wiki/erp-core.md index 67fea2f..ece5938 100644 --- a/wiki/erp-core.md +++ b/wiki/erp-core.md @@ -1,13 +1,13 @@ --- title: erp-core -updated: 2026-04-23 +updated: 2026-04-25 status: stable tags: [core, error, event-bus, module-trait, shared-types] --- # erp-core -> 从 [[index]] 导航。关联: [[erp-server]] [[database]] [[wasm-plugin]] [[architecture]] +> 从 [[index]] 导航。关联: [[erp-server]] [[database]] [[wasm-plugin]] [[architecture]] [[erp-health]] ## 1. 设计决策 @@ -29,6 +29,7 @@ tags: [core, error, event-bus, module-trait, shared-types] | `crates/erp-core/src/events.rs` | DomainEvent、EventBus、EventHandler trait | | `crates/erp-core/src/module.rs` | ErpModule trait、ModuleRegistry | | `crates/erp-core/src/types.rs` | BaseFields、Pagination、ApiResponse、TenantContext | +| `crates/erp-core/src/sanitize.rs` | strip_html_tags()、sanitize_string() | | `crates/erp-core/src/lib.rs` | 模块导出入口 | ### 集成契约 @@ -40,6 +41,7 @@ tags: [core, error, event-bus, module-trait, shared-types] | 提供 → | erp-workflow | ErpModule trait, AppError, EventBus | 模块实现 | | 提供 → | erp-message | ErpModule trait, AppError, EventBus | 模块实现 | | 提供 → | erp-plugin | ErpModule trait, AppError, EventBus | 模块实现 | +| 提供 → | [[erp-health]] | ErpModule trait, AppError, EventBus, sanitize | 模块实现 | | 消费 ← | [[erp-server]] | ModuleRegistry 组装 | 启动时 | | 桥接 ← | [[wasm-plugin]] | EventBus → 插件 handle_event | 运行时 | @@ -63,6 +65,8 @@ EventBus::publish(DomainEvent) → broadcast channel → Receiver 命名规则:`{模块}.{动作}` 如 `user.created`, `workflow.task.completed` +健康模块事件:`patient.created`, `appointment.confirmed`, `appointment.cancelled` + ### 模块注册 ``` @@ -71,11 +75,14 @@ ErpModule trait → ModuleRegistry::register() → register_handlers(): 注册事件处理器 → EventBus ``` +已注册模块:AuthModule → ConfigModule → WorkflowModule → MessageModule → PluginModule → **HealthModule** + ### 共享类型 - `TenantContext` — 租户上下文(tenant_id, user_id, roles, permissions, department_ids) - `Pagination` / `PaginatedResponse` — 分页标准化(每页上限 100) - `ApiResponse` — 统一信封 `{ success, data, message }` +- `sanitize_string()` — HTML 标签过滤,用于用户输入清理 ⚡ **不变量**: erp-core 不依赖任何业务 crate,只被依赖 ⚡ **不变量**: 所有 API 使用 `/api/v1/` 前缀 @@ -85,9 +92,11 @@ ErpModule trait → ModuleRegistry::register() → ⚠️ crate 内部可用 `anyhow`,但跨 crate 边界必须转 `AppError` ⚠️ EventBus 当前为内存 broadcast,outbox 持久化通过后台任务实现 +⚠️ 微信注册路径的 display_name 需调用 `sanitize_string()` 防止 XSS ## 5. 变更记录 | 日期 | 变更 | |------|------| +| 2026-04-25 | 添加 erp-health 集成契约、健康模块事件、sanitize 模块引用 | | 2026-04-23 | 重构为 5 节结构,更新为已完全集成状态 | diff --git a/wiki/erp-health.md b/wiki/erp-health.md index 39c5004..ed7ba28 100644 --- a/wiki/erp-health.md +++ b/wiki/erp-health.md @@ -1,7 +1,7 @@ --- title: erp-health 健康管理模块 -updated: 2026-04-23 -status: developing +updated: 2026-04-25 +status: implemented tags: [health, patient, appointment, follow-up, consultation] --- @@ -17,7 +17,7 @@ tags: [health, patient, appointment, follow-up, consultation] | WASM 插件限制 | 健康模块需求 | |---------------|-------------| -| 实体上限 20 个 | 16+ 强类型医疗实体 | +| 实体上限 20 个 | 18 强类型医疗实体 | | JSONB 动态存储 | 医疗数据需要强类型、索引、关联 | | 无自定义 API | 趋势分析、统计报表需专用端点 | | 无文件上传 | 化验单、体检报告需存储 | @@ -30,8 +30,9 @@ tags: [health, patient, appointment, follow-up, consultation] ### 核心架构选择 - **原生 Rust crate** — 与 erp-auth、erp-workflow 同等地位,直接访问数据库 -- **固有方法暴露路由** — `public_routes()` / `protected_routes()`,在 erp-server 中 `.nest("/api/v1/health", ...)` +- **ErpModule trait 实现** — `HealthModule` 在 erp-server 注册,路由挂载到 `/api/v1/health` - **EventBus 通信** — 发布 `patient.created`、`appointment.confirmed` 等,订阅 `workflow.task.completed` +- **HealthCrypto** — AES-256-GCM 加密 + HMAC-SHA256 哈希,保护 PII 数据 ## 2. 关键文件 + 数据流 @@ -40,17 +41,22 @@ tags: [health, patient, appointment, follow-up, consultation] ``` crates/erp-health/ ├── src/ -│ ├── lib.rs ← ErpModule trait + routes() -│ ├── error.rs ← HealthError → AppError -│ ├── state.rs ← HealthState -│ ├── entity/ ← 16 个 SeaORM Entity -│ ├── service/ ← 5 个业务 service -│ ├── handler/ ← 5 个路由 handler -│ ├── dto/ ← 请求/响应结构体 -│ └── event.rs ← 事件定义和处理器 +│ ├── lib.rs ← 模块导出 (HealthCrypto, HealthModule, HealthState) +│ ├── module.rs ← ErpModule trait 实现, 14 权限码, 全部路由定义 +│ ├── error.rs ← HealthError (17 变体) → AppError +│ ├── state.rs ← HealthState { db, event_bus, crypto } +│ ├── crypto.rs ← AES-256-GCM 加密 + HMAC-SHA256 (PII 保护) +│ ├── event.rs ← 事件处理器 (订阅 workflow/message 事件) +│ ├── entity/ ← 18 个 SeaORM Entity (872 行) +│ ├── service/ ← 11 个业务 service (4122 行) +│ ├── handler/ ← 7 个路由 handler (1493 行) +│ ├── dto/ ← 7 个请求/响应 DTO (814 行) +│ ├── validation.rs ← 输入验证逻辑 (302 行, 57 纯函数测试) +│ ├── masking.rs ← PII 数据脱敏 (手机号/身份证) +│ └── seed.rs ← 租户种子数据 + 软删除清理 ``` -### 实体模型(16 张表) +### 实体模型(18 个实体,对应数据库表 + 1 个文章实体) | 域 | 实体 | |----|------| @@ -60,12 +66,25 @@ crates/erp-health/ | 预约排班 | appointment, doctor_schedule | | 随访管理 | follow_up_task, follow_up_record | | 咨询管理 | consultation_session, consultation_message | +| 内容管理 | article | + +### 权限码(14 个) + +| 权限码 | 说明 | +|--------|------| +| `health.patient.list` / `health.patient.manage` | 患者查看/管理 | +| `health.health-data.list` / `health.health-data.manage` | 健康数据查看/管理 | +| `health.appointment.list` / `health.appointment.manage` | 预约查看/管理 | +| `health.follow-up.list` / `health.follow-up.manage` | 随访查看/管理 | +| `health.consultation.list` / `health.consultation.manage` | 咨询查看/管理 | +| `health.doctor.list` / `health.doctor.manage` | 医护查看/管理 | +| `health.articles.list` / `health.articles.manage` | 文章查看/管理 | ### 集成契约 | 方向 | 模块 | 接口 | 触发时机 | |------|------|------|---------| -| 提供 → | [[erp-server]] | `protected_routes()` | 启动时注册 `/api/v1/health/*` | +| 提供 → | [[erp-server]] | `HealthModule` | 启动时注册 `/api/v1/health/*` | | 调用 → | [[erp-core]] | EventBus | 发布/订阅领域事件 | | 关联 → | erp-auth | `users` 表 (user_id FK) | 患者/医护关联账号 | | 订阅 ← | erp-workflow | `workflow.task.completed` | 随访任务状态更新 | @@ -76,47 +95,84 @@ crates/erp-health/ ### API 前缀: `/api/v1/health/` 关键端点分组: -- `/patients` — 患者列表/详情/标签管理/健康摘要 -- `/patients/:id/vital-signs` — 日常监测数据(血压/心率/体重/血糖) -- `/patients/:id/lab-reports` — 化验报告(JSONB 指标数据) -- `/patients/:id/trends` — 健康趋势报告(自动/手动生成) -- `/appointments` — 预约管理(状态机: pending→confirmed→completed) -- `/doctor-schedules` — 排班管理(日历视图) -- `/follow-up-tasks` — 随访任务(逾期自动标记) -- `/consultation-sessions` — 咨询会话管理 +- `/patients` — 患者列表/详情/标签管理/健康摘要/家庭成团/医生关联 +- `/patients/{id}/vital-signs` — 日常监测数据(血压/心率/体重/血糖) +- `/patients/{id}/lab-reports` — 化验报告(JSONB 指标数据) +- `/patients/{id}/health-records` — 健康档案 +- `/patients/{id}/trends` — 健康趋势报告(自动/手动生成,时间序列查询) +- `/vital-signs/trend` — 小程序趋势(JWT user → patient) +- `/vital-signs/today` — 小程序当日体征摘要 +- `/appointments` — 预约管理 + 状态变更(pending→confirmed→completed/cancelled/no_show) +- `/appointments/{id}/status` — 预约状态流转(乐观锁) +- `/doctor-schedules` — 排班管理 CRUD + 日历视图 +- `/doctor-schedules/calendar` — 月度排班日历 +- `/follow-up-tasks` — 随访任务 CRUD + 逾期自动标记 +- `/follow-up-tasks/{id}/records` — 随访记录 +- `/consultation-sessions` — 咨询会话管理 + 消息 + 导出 + 关闭 +- `/consultation-messages` — 咨询消息发送 +- `/doctors` — 医护档案 CRUD +- `/articles` — 健康文章 CRUD ### 预约并发控制 -创建预约时使用原子 CAS:`UPDATE doctor_schedule SET current_appointments = current_appointments + 1 WHERE id = $1 AND current_appointments < max_appointments RETURNING *` +创建预约时使用原子 CAS(事务内): +1. 查找匹配日期+时段的排班 +2. 原子 `UPDATE current_appointments + 1 WHERE current < max` +3. CAS 成功后 INSERT 预约记录 +4. 事务保证 CAS + INSERT 原子性 -### 随访自动链接 +### 预约状态机 -`follow_up_record.next_follow_up_date` 不为空时,自动创建新的 `follow_up_task`。 +``` +pending → confirmed → completed + → no_show → confirmed (重新确认) + → cancelled +pending → cancelled +``` -### 权限码 +### PII 数据保护 -`health.patient.list/manage` · `health.health-data.list/manage` · `health.appointment.list/manage` · `health.follow-up.list/manage` · `health.consultation.list/manage` · `health.doctor.list/manage` +- `HealthCrypto`: AES-256-GCM 加密敏感字段,HMAC-SHA256 哈希身份证号 +- `masking.rs`: 手机号脱敏 `138****1234`,身份证脱敏 `110****1111` +- 生产密钥通过环境变量 `ERP__HEALTH__AES_KEY` / `ERP__HEALTH__HMAC_KEY` 配置 + +### 后台任务 + +- 随访逾期检查器 — 每 5 分钟扫描 `follow_up_task` 中超过 `planned_date` 仍未完成的任务 ⚡ **不变量**: 预约创建必须走原子 CAS,不能用 read-then-write ⚡ **不变量**: `patient.user_id` 允许 NULL(先建档后绑定) -⚡ **不变量**: `consultation_message` 对 `created_at` 按月分区,超 1 年归档 +⚡ **不变量**: `doctor_id` 在创建预约时必填(关联排班做 CAS) +⚡ **不变量**: 状态流转必须带 `version` 字段(乐观锁) ## 4. 活跃问题 + 陷阱 -### 当前状态: 🔧 开发中 +### 当前状态: ✅ 已完成 -设计规格已确认,尚未开始编码。 +18 实体、14 权限、13 Web 页面、20 小程序页面,全链路流通性验证通过。 -### 待解决 +### 待优化 | 问题 | 级别 | 说明 | |------|------|------| | 文件上传基础能力 | P1 | 化验单/体检报告需要文件存储服务 | -| ECharts 趋势图 | P1 | 前端健康趋势可视化 | -| 导出功能 | P2 | 随访台账/咨询记录导出 Excel | +| 健康趋势图 ECharts | P1 | 小程序已有 echarts 集成,Web 端待接入 | +| 咨询导出 Excel | P2 | 后端已有 `rust_xlsxwriter` 依赖,导出端点已实现 | + +### 全链路验证结果(2026-04-25) + +| 链路 | API | 前端 UI | 状态 | +|------|-----|---------|------| +| 医生 CRUD | 创建/搜索/编辑 | 医护管理页面 | ✅ | +| 排班管理 | 创建/列表/日历 | 排班管理页面 | ✅ | +| 预约管理 | 创建+状态流转 | 预约列表/新建弹窗 | ✅ | +| 随访管理 | 创建→进行→完成 | 随访列表/操作 | ✅ | +| 咨询管理 | 创建会话+消息 | 咨询列表/导出 | ✅ | +| 患者详情 | 详情/编辑/标签 | 详情页+健康数据标签 | ✅ | ## 5. 变更记录 | 日期 | 变更 | |------|------| +| 2026-04-25 | 全面更新为已实现状态:18 实体、14 权限、全链路验证通过 | | 2026-04-23 | 创建模块 wiki 页,设计规格确认 | diff --git a/wiki/erp-server.md b/wiki/erp-server.md index 60918b6..4867c21 100644 --- a/wiki/erp-server.md +++ b/wiki/erp-server.md @@ -1,6 +1,6 @@ --- title: erp-server -updated: 2026-04-23 +updated: 2026-04-25 status: stable tags: [server, axum, assembly, entry-point] --- @@ -14,7 +14,7 @@ tags: [server, axum, assembly, entry-point] - **唯一组装点** — 不含业务逻辑,只负责把所有模块组装成可运行服务 - **配置优先** — `config` crate 从 TOML + 环境变量加载,`ERP__` 前缀覆盖 - **严格启动序列** — 每步失败即终止,不做部分启动 -- **安全检查** — 拒绝默认 JWT 密钥 / 数据库 URL +- **安全检查** — 拒绝默认 JWT 密钥 / 数据库 URL / 加密密钥 ## 2. 关键文件 + 数据流 @@ -22,10 +22,12 @@ tags: [server, axum, assembly, entry-point] | 文件 | 职责 | |------|------| -| `crates/erp-server/src/main.rs` | 服务启动入口 | -| `crates/erp-server/src/state.rs` | AppState 定义 | -| `crates/erp-server/src/config.rs` | 5 个配置 struct + 加载逻辑 | +| `crates/erp-server/src/main.rs` | 服务启动入口(639 行) | +| `crates/erp-server/src/state.rs` | AppState 定义(117 行) | +| `crates/erp-server/src/config.rs` | 配置 struct + 加载逻辑(87 行) | | `crates/erp-server/src/db.rs` | SeaORM 连接池配置 | +| `crates/erp-server/src/outbox.rs` | Domain event outbox 处理器 | +| `crates/erp-server/src/middleware/rate_limit.rs` | IP/用户限流(176 行) | | `crates/erp-server/config/default.toml` | 默认配置(密钥为占位符) | ### 启动流程 @@ -33,13 +35,13 @@ tags: [server, axum, assembly, entry-point] ``` AppConfig::load() → 安全检查 → init_tracing → db::connect → Migrator::up → 种子数据(默认租户+管理员) → Redis客户端 → EventBus(容量1024) - → 注册5个模块 → 初始化插件引擎+恢复插件 → 4个后台任务 + → 注册6个模块 → 初始化插件引擎+恢复插件 → 5个后台任务 → 构建Router → bind + serve → 优雅关闭(CTRL+C/SIGTERM) ``` -### 注册的 5 个模块 +### 注册的 6 个模块 -AuthModule → ConfigModule → WorkflowModule → MessageModule → PluginModule +AuthModule → ConfigModule → WorkflowModule → MessageModule → PluginModule → **HealthModule** ### AppState @@ -53,6 +55,7 @@ AppState { default_tenant_id: Uuid, plugin_engine: PluginEngine, plugin_entity_cache: moka::Cache (1000容量, 5分钟TTL), + // HealthModule 状态通过 FromRef 提取 } ``` @@ -65,6 +68,7 @@ AppState { | 组装 → | erp-workflow | `WorkflowModule` | 启动时注册 | | 组装 → | erp-message | `MessageModule` | 启动时注册 | | 组装 → | erp-plugin | `PluginModule` | 启动时注册 | +| 组装 → | [[erp-health]] | `HealthModule` | 启动时注册 | | 依赖 ← | [[erp-core]] | ErpModule trait, EventBus | 所有模块 | | 依赖 ← | [[infrastructure]] | PostgreSQL, Redis | 连接 | @@ -74,6 +78,7 @@ AppState { 2. 插件通知 — EventBus → PluginModule 3. Outbox relay — domain_events → 外部 4. 超时检查器 — 工作流任务超时处理 +5. 随访逾期检查 — HealthModule 每 5 分钟扫描过期随访任务 ## 3. 代码逻辑 @@ -91,10 +96,13 @@ AppConfig ├── database: { url, max_connections: 20, min_connections: 5 } ├── redis: { url } ├── jwt: { secret, access_token_ttl: 15min, refresh_token_ttl: 7d } +├── auth: { super_admin_password } +├── wechat: { appid, secret } +├── health: { aes_key, hmac_key } └── log: { level: "info" } ``` -⚡ **不变量**: 4 个环境变量在 default.toml 中都是 `__MUST_SET_VIA_ENV__` 占位符,必须通过环境变量设置 +⚡ **不变量**: 8 个环境变量在 default.toml 中都是 `__MUST_SET_VIA_ENV__` 占位符,必须通过环境变量设置(database.url, jwt.secret, redis.url, auth.super_admin_password, wechat.appid, wechat.secret, health.aes_key, health.hmac_key) ⚡ **不变量**: 启动顺序不可变更 — 数据库必须先于迁移,迁移必须先于模块注册 @@ -107,4 +115,5 @@ AppConfig | 日期 | 变更 | |------|------| +| 2026-04-25 | 更新为 6 模块注册,8 个环境变量,新增随访逾期检查后台任务 | | 2026-04-23 | 重构为 5 节结构,更新为当前集成状态 | diff --git a/wiki/frontend.md b/wiki/frontend.md index f5c666f..2e13b06 100644 --- a/wiki/frontend.md +++ b/wiki/frontend.md @@ -1,19 +1,19 @@ --- title: Web 前端 -updated: 2026-04-23 +updated: 2026-04-25 status: stable tags: [frontend, react, antd, vite, spa] --- # Web 前端 -> 从 [[index]] 导航。关联: [[erp-server]] [[infrastructure]] +> 从 [[index]] 导航。关联: [[erp-server]] [[infrastructure]] [[erp-health]] ## 1. 设计决策 - **组件库优先** — Ant Design 6,不自造轮子 - **状态集中** — Zustand 管理全局状态(4 个 store) -- **API 层分离** — HTTP 调用封装到 `src/api/`(21 个文件),组件不直接 fetch +- **API 层分离** — HTTP 调用封装到 `src/api/`(28 个文件),组件不直接 fetch - **代理开发** — Vite 代理 `/api` 到后端 3000 端口 - **HashRouter** — 不需要服务端 fallback 配置,部署更稳健 - **懒加载** — 除 Login 外所有页面使用 `lazy()` 按需加载 @@ -32,7 +32,7 @@ React 19.2.4 / Ant Design 6.3.5 / React Router 7.14.0 / Zustand 5.0.12 / Vite 8. | `apps/web/src/App.tsx` | 路由定义(公开 + 受保护) | | `apps/web/src/layouts/MainLayout.tsx` | SaaS 后台管理布局 | | `apps/web/src/stores/` | 4 个 Zustand store | -| `apps/web/src/api/` | 21 个 API 服务文件 | +| `apps/web/src/api/` | 28 个 API 服务文件(含 7 个健康模块 API) | | `apps/web/vite.config.ts` | Vite 配置 + API 代理 | > 微信小程序(患者端)是独立前端项目,详见 [[miniprogram]] @@ -54,6 +54,37 @@ React 19.2.4 / Ant Design 6.3.5 / React Router 7.14.0 / Zustand 5.0.12 / Vite 8. | `/plugins/:pluginId/:entityName` | 插件 CRUD(动态生成) | | `/plugins/:pluginId/tabs|tree|graph|dashboard|kanban/:name` | 插件多视图页面 | +**健康管理路由(10 条)**: + +| 路径 | 页面 | +|------|------| +| `/health/patients` | 患者列表 | +| `/health/patients/:id` | 患者详情(5 个标签页:基本信息/体征/化验/健康档案/随访) | +| `/health/patient-tags` | 患者标签管理 | +| `/health/doctors` | 医护管理 | +| `/health/schedules` | 排班管理(含日历视图) | +| `/health/appointments` | 预约管理(含状态流转) | +| `/health/follow-up-tasks` | 随访任务列表 | +| `/health/follow-up-records` | 随访记录 | +| `/health/consultations` | 咨询会话列表 | +| `/health/consultations/:id` | 咨询详情(含消息 + 导出) | + +### 健康模块共享组件(12 个) + +| 组件 | 用途 | +|------|------| +| `StatusTag` | 预约/随访/咨询状态标签(含单元测试) | +| `PatientSelect` | 患者搜索选择器(远程搜索) | +| `DoctorSelect` | 医护搜索选择器(远程搜索) | +| `CalendarView` | 排班日历视图 | +| `ExportButton` | 咨询记录导出按钮 | +| `VitalSignsChart` | 体征数据折线图 | +| `VitalSignsTab` | 患者详情-体征标签页 | +| `LabReportsTab` | 患者详情-化验报告标签页 | +| `HealthRecordsTab` | 患者详情-健康档案标签页 | +| `FollowUpTab` | 患者详情-随访标签页 | +| `ImagePreview` | 图片预览组件 | + ### 集成契约 | 方向 | 模块 | 接口 | 触发时机 | @@ -61,6 +92,7 @@ React 19.2.4 / Ant Design 6.3.5 / React Router 7.14.0 / Zustand 5.0.12 / Vite 8. | 调用 → | [[erp-server]] | `/api/v1/*` REST | 所有数据操作 | | 调用 → | [[erp-server]] | `ws://localhost:3000/ws/*` | WebSocket | | 消费 ← | 插件系统 | `plugin.toml` schema | 动态生成插件页面 | +| 调用 → | [[erp-health]] | `/api/v1/health/*` | 健康模块所有数据操作 | ## 3. 代码逻辑 @@ -73,6 +105,26 @@ React 19.2.4 / Ant Design 6.3.5 / React Router 7.14.0 / Zustand 5.0.12 / Vite 8. | `message.ts` | unreadCount, recentMessages, 请求去重 | | `plugin.ts` | plugins 列表, 动态菜单, schema 缓存, 请求去重 | +### 健康模块 API 文件(7 个) + +| 文件 | 覆盖端点 | +|------|---------| +| `patients.ts` | 患者 CRUD + 标签 + 健康摘要 + 家庭成员 | +| `doctors.ts` | 医护档案 CRUD | +| `appointments.ts` | 预约 CRUD + 状态流转 | +| `healthData.ts` | 体征/化验/健康档案/趋势 | +| `followUp.ts` | 随访任务 + 记录 | +| `consultations.ts` | 咨询会话 + 消息 + 导出 | +| `articles.ts` | 健康文章 | + +### 前端单元测试(3 个) + +| 文件 | 测试内容 | +|------|---------| +| `constants/health.test.ts` | 健康常量定义验证 | +| `hooks/useThemeMode.test.ts` | 暗色模式 hook | +| `pages/health/components/StatusTag.test.tsx` | 状态标签渲染 | + ### 插件页面系统 插件通过 `plugin.toml` schema 声明页面,前端根据 schema 动态生成: @@ -85,6 +137,7 @@ React 19.2.4 / Ant Design 6.3.5 / React Router 7.14.0 / Zustand 5.0.12 / Vite 8. ⚡ **不变量**: 插件菜单由 `plugin.ts` store 从 API 动态获取,不硬编码 ⚡ **不变量**: API client 在请求前 30s 检查 token 过期,提前刷新避免 401 +⚡ **不变量**: DatePicker 返回 dayjs 对象,提交前必须 `.format('YYYY-MM-DD')` 转字符串 ### 代理配置 @@ -95,12 +148,19 @@ ws://localhost:5174/ws/* → ws://localhost:3000/* (WebSocket) ## 4. 活跃问题 + 陷阱 +⚠️ Ant Design 6 废弃 `destroyOnClose`,应使用 `destroyOnHidden` ⚠️ Ant Design 6 废弃 API 警告(`valueStyle`/`Spin tip`/`trailColor`)已在历史版本中修复 ⚠️ `antd.setScaleParam` 强制回流 64ms — antd 内部问题,无法直接修复 +### 历史教训 + +- DatePicker 提交 dayjs 对象而非字符串 → 后端 422 `birth_date trailing input` — 必须调用 `.format('YYYY-MM-DD')` +- 预约表单医护字段标为可选但后端必填 → 400 `doctor_id is required` — 医护为必填(CAS 排班需要) + ## 5. 变更记录 | 日期 | 变更 | |------|------| +| 2026-04-25 | 全面更新:10 条健康路由、12 个共享组件、7 个健康 API 文件、3 个单元测试 | | 2026-04-24 | 添加小程序交叉引用 | | 2026-04-23 | 重构为 5 节结构,更新为当前完整前端状态 | diff --git a/wiki/index.md b/wiki/index.md index 926b511..9264140 100644 --- a/wiki/index.md +++ b/wiki/index.md @@ -6,11 +6,15 @@ | 指标 | 值 | |------|-----| -| Rust crate | 14 个(7 核心 + erp-health + 6 插件) | -| 数据库表 | 30+ 基础表 + 16 健康业务表(规划中) | +| Rust crate | 15 个(7 核心 + erp-health + 6 插件 + erp-plugin-prototype) | +| 数据库表 | 30 基础表 + 18 健康业务表(已实现) | +| 数据库迁移 | 50 个 | | 核心模块 | 5 基础 (auth/config/workflow/message/plugin) + 1 业务 (health) | -| 健康模块页面 | 13 个(规划中) | -| 微信小程序 | Taro 4.2 + React 18 + TypeScript | +| Web 前端页面 | 30 路由(含 10 健康管理路由) | +| 健康模块组件 | 11 个共享组件(StatusTag/PatientSelect/DoctorSelect/VitalSignsChart 等) | +| 微信小程序 | Taro 4.2 + React 18,20 个页面 | +| 前端单元测试 | 3 个(vitest)+ 4 E2E spec(playwright) | +| 后端测试 | 36 个(workspace)+ 57 validation 纯函数测试 | | API 文档 | `http://localhost:3000/api/docs/openapi.json` | ## 症状导航 @@ -21,13 +25,16 @@ | API 返回 500 无日志 | [[erp-core]] 错误链 | 后端 tracing 输出 | AppError::Internal 静默 | | 数据库连接失败 | [[infrastructure]] | PostgreSQL 服务状态 | 服务未启动 / 环境变量未设置 | | 前端 401 刷新时 | [[frontend]] auth store | API client token 刷新 | token 过期未主动刷新 | -| 迁移执行失败 | [[database]] | PostgreSQL 日志 | 表冲突 / 唯一索引 + 软删除 | +| 迁移执行失败 | [[database]] | PostgreSQL 日志 | 表冲突 / 唯一索引 + 软删除 / 缺失迁移文件 | | 端口被占用 | [[infrastructure]] dev.ps1 | 端口 5174-5189 进程 | 残留 Vite 进程 | +| 预约创建 400 doctor_id is required | [[erp-health]] 预约并发控制 | AppointmentList.tsx | 医生字段为必填(后端 CAS 要求) | | 预约超额 | [[erp-health]] 排班并发 | appointment CAS 操作 | 并发控制未走原子 CAS | +| 患者创建 422 birth_date trailing input | [[frontend]] 日期序列化 | DatePicker dayjs 对象 | 未格式化为 YYYY-MM-DD 字符串 | | 跨租户数据泄漏 | [[architecture]] 多租户策略 | [[database]] tenant_id | 查询缺少 tenant_id 过滤 | | 小程序页面空白 | [[miniprogram]] defineConstants | `process.env` 未替换 | 编译时未注入环境变量 | | 小程序登录失败 `btoa is not defined` | [[miniprogram]] secure-storage | Web API 不可用 | 使用 `Taro.arrayBufferToBase64` 替代 | | 微信登录 500 | [[database]] wechat_users 表结构 | Entity 字段与表不匹配 | 补 `created_by/updated_by/version` 列 | +| 迁移文件缺失报错 | [[database]] 迁移注册 | migration/src/lib.rs | 已应用的迁移文件被删除,需创建 stub | ## 模块导航 @@ -43,10 +50,10 @@ - erp-plugin — WASM 运行时 · 动态表 · 热更新(HMS 保留但非主要扩展方式) ### 核心业务层(HMS 专属) -- [[erp-health]] — **患者管理 · 健康数据 · 预约排班 · 随访管理 · 咨询管理**(原生 Rust 模块) +- [[erp-health]] — **患者管理 · 健康数据 · 预约排班 · 随访管理 · 咨询管理**(原生 Rust 模块,已实现) ### 组装层 -- [[erp-server]] — Axum 入口 · AppState · 模块注册 · 后台任务 · 优雅关闭 +- [[erp-server]] — Axum 入口 · AppState · 6 模块注册 · 后台任务 · 优雅关闭 ### 患者端 - [[miniprogram]] — **微信小程序** · Taro 4.2 · 微信登录 · 手机绑定 · 健康数据查看 @@ -54,12 +61,12 @@ ### 基础设施 - [[infrastructure]] — 连接信息 · 环境变量 · 一键启动 (**单一真相源**) - [[database]] — SeaORM 迁移 · 多租户表结构 -- [[frontend]] — React 19 SPA · 健康管理页面 +- [[frontend]] — React 19 SPA · 健康管理页面(13 页面 + 11 组件) - [[testing]] — 验证清单 · 测试分布 · 性能基准 ## 核心架构问答 -**为什么 erp-health 用原生模块而非 WASM 插件?** 医疗业务需要 16+ 强类型实体、自定义 API(趋势分析/统计报表)、文件上传(化验单/体检报告)、未来 AI 集成,超出 WASM 插件能力范围。详见 [[erp-health]]。 +**为什么 erp-health 用原生模块而非 WASM 插件?** 医疗业务需要 18 强类型实体、自定义 API(趋势分析/统计报表)、文件上传(化验单/体检报告)、未来 AI 集成,超出 WASM 插件能力范围。详见 [[erp-health]]。 **模块间如何通信?** [[erp-core]] EventBus 发布/订阅 DomainEvent。erp-health 发布 `patient.created`、`appointment.confirmed` 等事件,订阅 `workflow.task.completed` 等。详见 [[architecture]]。 @@ -72,6 +79,7 @@ | 类型 | 位置 | |------|------| | 健康模块设计规格 | `docs/superpowers/specs/2026-04-23-health-management-module-design.md` | +| QA 审计计划 | `plans/qa-review-brainstorm-floofy-finch.md` | | 设计规格 | `docs/superpowers/specs/` | | 实施计划 | `docs/superpowers/plans/` | | 协作规则 | `CLAUDE.md` | diff --git a/wiki/miniprogram.md b/wiki/miniprogram.md new file mode 100644 index 0000000..da92743 --- /dev/null +++ b/wiki/miniprogram.md @@ -0,0 +1,205 @@ +--- +title: 微信小程序(患者端) +updated: 2026-04-25 +status: active +tags: [miniprogram, taro, wechat, patient] +--- + +# 微信小程序(患者端) + +> 从 [[index]] 导航。关联: [[infrastructure]] [[erp-server]] [[erp-health]] + +## 1. 设计决策 + +- **Taro 4.2 + React 18** — 跨平台小程序框架,React 生态 +- **Zustand 状态管理** — 与 Web 前端一致的 store 模式 +- **微信登录流程** — `Taro.login()` → code → 后端 `jscode2session` → openid → JWT +- **build 时注入环境变量** — `process.env` 在小程序运行时不存在,必须通过 `defineConstants` 编译时替换 +- **不使用浏览器 Web API** — `btoa`/`atob` 等在小程序中不可用,用 Taro 原生 API 替代 +- **Zod 输入验证** — 健康数据录入使用 Zod schema 验证 + +### 版本 + +Taro 4.2 / React 18 / TypeScript / Zustand 5 / Sass / Zod + +## 2. 关键文件 + 数据流 + +### 核心文件 + +| 文件 | 职责 | +|------|------| +| `apps/miniprogram/config/index.ts` | Taro 构建配置(defineConstants 注入环境变量) | +| `apps/miniprogram/src/services/request.ts` | HTTP 请求封装(401 自动刷新、错误处理) | +| `apps/miniprogram/src/services/auth.ts` | 微信登录/绑定手机号 API | +| `apps/miniprogram/src/stores/auth.ts` | 认证状态(login/bindPhone/restore) | +| `apps/miniprogram/src/utils/secure-storage.ts` | token 安全存储(XOR + Base64 混淆) | +| `apps/miniprogram/project.config.json` | 微信开发者工具配置(AppID、urlCheck) | + +### 微信登录流程 + +``` +用户点击"微信一键登录" + ↓ +Taro.login() → 临时 code + ↓ +POST /auth/wechat/login { code } + ↓ +后端调用微信 jscode2session → 获取 openid + session_key + ↓ +查询 wechat_users 表 + ├── 已绑定 → 返回 { bound: true, token: JWT } → 跳转首页 + └── 未绑定 → 返回 { bound: false, openid } → 显示"授权手机号"按钮 + ↓ 用户授权手机号 + POST /auth/wechat/bind-phone { openid, encrypted_data, iv } + ↓ + 后端解密手机号 → 创建/关联用户 → 返回 JWT → 跳转首页 +``` + +### 页面结构(20 个页面,10 个目录) + +| 页面路径 | 说明 | +|----------|------| +| `pages/login/index` | 登录页(微信登录 + 协议勾选) | +| `pages/index/index` | 首页(今日健康、快捷服务) | +| `pages/health/trend/index` | 健康趋势(体征数据折线图) | +| `pages/health/input/index` | 健康数据录入(Zod 验证) | +| `pages/appointment/create/index` | 预约挂号 | +| `pages/appointment/detail/index` | 预约详情 | +| `pages/article/index` | 健康资讯 | +| `pages/profile/index` | 个人中心 | +| `pages/profile/family/index` | 家庭成员管理 | +| `pages/followup/detail/index` | 随访详情 | +| `pages/report/index` | 健康报告查看 | +| `pages/legal/user-agreement` | 用户服务协议 | +| `pages/legal/privacy-policy` | 隐私政策 | + +### 服务层(10 个文件) + +| 文件 | 覆盖 | +|------|------| +| `request.ts` | HTTP 封装(401 刷新、错误处理) | +| `auth.ts` | 微信登录/绑定手机号 | +| `health.ts` | 体征数据/健康趋势 | +| `patient.ts` | 患者信息/家庭成员 | +| `appointment.ts` | 预约挂号/详情/取消 | +| `followup.ts` | 随访任务/详情 | +| `article.ts` | 健康文章 | +| `report.ts` | 健康报告 | +| `analytics.ts` | 数据分析 | +| `wechat-templates.ts` | 微信模板消息 ID | + +### 组件(9 个) + +| 组件 | 用途 | +|------|------| +| `EmptyState` | 空状态占位 | +| `ErrorBoundary` | 错误边界捕获 | +| `ErrorState` | 错误状态展示 | +| `FamilyPicker` | 家庭成员选择器 | +| `HealthCard` | 健康数据卡片 | +| `Loading` | 加载状态 | +| `StepIndicator` | 步骤指示器 | +| `TrendChart` | 趋势图表(ECharts) | +| `WeekCalendar` | 周日历组件 | + +### 集成契约 + +| 方向 | 模块 | 接口 | 触发时机 | +|------|------|------|---------| +| 调用 → | [[erp-server]] | `POST /auth/wechat/login` | 微信登录 | +| 调用 → | [[erp-server]] | `POST /auth/wechat/bind-phone` | 手机号绑定 | +| 调用 → | [[erp-health]] | `/api/v1/health/*` | 健康数据查询 | +| 调用 → | [[erp-server]] | `/api/v1/auth/refresh` | Token 刷新 | + +## 3. 代码逻辑 + +### 环境变量注入(关键) + +小程序运行时没有 `process.env`,必须在 `config/index.ts` 中通过 `defineConstants` 编译时替换: + +```typescript +defineConstants: { + 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'), + 'process.env.TARO_APP_API_URL': JSON.stringify(process.env.TARO_APP_API_URL || 'http://localhost:3000/api/v1'), + 'process.env.TARO_APP_ENCRYPTION_KEY': JSON.stringify(process.env.TARO_APP_ENCRYPTION_KEY || ''), +}, +``` + +### Token 安全存储 + +`secure-storage.ts` 使用 XOR 混淆 + Base64 编码存储 token: +- **不使用 `btoa`/`atob`** — 小程序环境不支持 +- 使用 `Taro.arrayBufferToBase64` / `Taro.base64ToArrayBuffer` 替代 +- 加密密钥通过 `TARO_APP_ENCRYPTION_KEY` 环境变量注入 + +### 请求封装(request.ts) + +- 401 自动尝试 refresh token,失败后跳转登录页 +- 错误响应格式: `{ error: string, message: string }`(无 `success` 字段) +- 成功响应格式: `{ success: true, data: T }` +- 当前 URL 拼接方式构建查询参数(待重构为 params 对象) + +### 健康数据录入验证 + +使用 Zod schema 验证用户输入的体征数据(血压、心率、体重、血糖),确保数据类型和范围合法。 + +## 4. 构建与调试 + +### 构建 + +```bash +cd apps/miniprogram && pnpm build:weapp # 生产构建 +cd apps/miniprogram && npx taro build --type weapp # 等效 +``` + +### 微信开发者工具配置 + +| 配置项 | 值 | 说明 | +|--------|-----|------| +| AppID | `wx20f4ef9cc2ec66c5` | 真实微信 AppID | +| urlCheck | `false` | 不校验合法域名(开发模式) | +| miniprogramRoot | `dist/` | 编译输出目录 | + +### 后端微信配置 + +`crates/erp-server/config/default.toml`: + +```toml +[wechat] +appid = "wx20f4ef9cc2ec66c5" +secret = "<通过环境变量 ERP__WECHAT__SECRET 设置>" +``` + +## 5. 活跃问题 + 陷阱 + +### 历史教训 + +| 问题 | 根因 | 修复 | +|------|------|------| +| 页面空白 `ReferenceError: process is not defined` | `process.env` 在运行时不存在 | `defineConstants` 编译时替换 | +| 登录成功但前端报失败 `btoa is not defined` | `secure-storage.ts` 使用 Web API | 改用 `Taro.arrayBufferToBase64` | +| 微信登录 500 `wechat_users.created_by 不存在` | 迁移创建的表缺少标准字段 | `ALTER TABLE` 补列 | +| 401 循环重定向 | 首页未登录时 `request.ts` 反复 redirectTo | 检查当前路径避免重复跳转 | + +### 待优化 + +| 问题 | 级别 | 说明 | +|------|------|------| +| URL 拼接构建查询参数 | P2 | `request.ts` 应支持 params 对象 | +| 加密密钥硬编码 | P0 | 需外部化到 `TARO_APP_ENCRYPTION_KEY` 环境变量 | +| Auth token 日志输出 | P0 | 生产环境需移除 console.log | +| 生产配置 | P2 | `urlCheck`/`minified` 需区分环境 | + +### 注意事项 + +- `Taro.login()` 的 code 一次性使用,每次调用会返回新 code +- `session_key` 缓存 5 分钟(TTL),过期需重新登录 +- 微信开发者工具中 `getPhoneNumber` 需要真机调试或使用测试号 +- Redis 不可达时限流降级为 fail-open,不影响登录 + +## 6. 变更记录 + +| 日期 | 变更 | +|------|------| +| 2026-04-25 | 全面更新:20 页面、10 服务、9 组件、Zod 验证、加密密钥外部化说明 | +| 2026-04-24 | 创建小程序 wiki 页面,记录登录流程、环境配置、历史陷阱 | diff --git a/wiki/testing.md b/wiki/testing.md index 7e67187..9d19847 100644 --- a/wiki/testing.md +++ b/wiki/testing.md @@ -1,13 +1,13 @@ --- title: 测试与验证 -updated: 2026-04-23 +updated: 2026-04-25 status: stable tags: [testing, verification] --- # 测试与验证 -> 从 [[index]] 导航。关联: [[infrastructure]] [[database]] [[frontend]] [[erp-server]] +> 从 [[index]] 导航。关联: [[infrastructure]] [[database]] [[frontend]] [[erp-server]] [[erp-health]] ## 1. 设计决策 @@ -25,7 +25,10 @@ tags: [testing, verification] | erp-core | 6 | RBAC 权限检查 | | erp-workflow | 16 | BPMN 解析、表达式求值 | | erp-plugin-prototype | 6 | WASM 插件集成 | -| **总计** | **36** | | +| erp-health (validation) | 57 | 输入验证纯函数测试 | +| **后端总计** | **93** | | +| 前端 (vitest) | 3 | 健康常量、useThemeMode hook、StatusTag 组件 | +| E2E (playwright) | 4 spec | 登录、用户管理、插件、租户隔离 | ### 编译 + 测试 @@ -35,6 +38,8 @@ cargo test --workspace # 全量测试 cargo clippy -- -D warnings # Lint 无警告 cargo fmt --check # 格式检查 cd apps/web && pnpm build # 前端生产构建 +cd apps/web && pnpm test # 前端单元测试 (vitest) +cd apps/web && pnpm test:e2e # E2E 测试 (playwright) ``` ### 功能验证端点 @@ -70,6 +75,27 @@ cd apps/miniprogram && pnpm build:weapp # 构建 ## 3. 代码逻辑 +### 健康模块全链路验证结果(2026-04-25) + +| 链路 | API | 前端 UI | 状态 | +|------|-----|---------|------| +| 医生 CRUD | 创建/搜索/编辑 | 医护管理页面 | ✅ | +| 排班管理 | 创建/列表/日历 | 排班管理页面 | ✅ | +| 预约管理 | 创建+状态流转 | 预约列表/新建弹窗 | ✅ | +| 随访管理 | 创建→进行→完成 | 随访列表/操作 | ✅ | +| 咨询管理 | 创建会话+消息 | 咨询列表/导出 | ✅ | +| 患者详情 | 详情/编辑/标签 | 详情页+健康数据标签 | ✅ | + +### erp-health validation.rs 测试覆盖 + +57 个纯函数测试覆盖: +- 患者信息验证(姓名、身份证、手机号、性别、血型) +- 预约验证(日期、时段、类型) +- 随访任务验证(计划日期、类型) +- 咨询会话验证 +- 体征数据验证(血压范围、心率、体重、血糖) +- 文章验证 + ### 集成契约 | 方向 | 模块 | 触发时机 | @@ -77,6 +103,7 @@ cd apps/miniprogram && pnpm build:weapp # 构建 | 依赖 ← | [[infrastructure]] | 环境准备、连接信息 | | 验证 → | [[erp-server]] | 健康检查、API 测试 | | 验证 → | [[frontend]] | 生产构建 | +| 验证 → | [[erp-health]] | 健康模块全链路验证 | ⚡ **不变量**: 功能验证需要后端服务运行中,编译检查必须先于测试通过 @@ -88,12 +115,22 @@ D:\postgreSQL\bin\psql.exe -U postgres -h localhost -d erp ``` ```sql -SELECT version FROM seaql_migrations ORDER BY version; -- 迁移记录 -SELECT code, name FROM permissions WHERE deleted_at IS NULL ORDER BY code; -- 插件权限 +SELECT version FROM seaql_migrations ORDER BY version; -- 迁移记录(应为 50 条) +SELECT code, name FROM permissions WHERE deleted_at IS NULL ORDER BY code; -- 权限列表 +SELECT count(*) FROM patient WHERE deleted_at IS NULL; -- 患者数量 ``` ## 4. 活跃问题 + 陷阱 +### 测试覆盖空白 + +| 领域 | 当前状态 | 优先级 | +|------|---------|--------| +| erp-health service 层集成测试 | 无 | P0 | +| erp-health handler 层测试 | 无 | P1 | +| 前端健康模块组件测试 | 仅 StatusTag | P1 | +| E2E 健康模块测试 | 无 | P1 | + ### 活跃问题 | 问题 | 级别 | 状态 | @@ -106,6 +143,7 @@ SELECT code, name FROM permissions WHERE deleted_at IS NULL ORDER BY code; -- - CRM 权限码与实体名不一致 → 403(详见 [[wasm-plugin]] 权限命名铁律) - `AppError::Internal` 无日志 → 500 静默(已加 `tracing::error`) - `build_scope_sql` 参数索引硬编码 → SQL 参数错位(已动态化) +- 已应用迁移文件被删除 → 启动失败(创建 stub 迁移修复) ⚠️ 首次 `cargo run` 需编译整个 workspace(含 wasmtime),后续增量快 ⚠️ Redis 不可达时限流自动降级为 fail-open @@ -114,6 +152,7 @@ SELECT code, name FROM permissions WHERE deleted_at IS NULL ORDER BY code; -- | 日期 | 变更 | |------|------| +| 2026-04-25 | 全面更新:93 后端测试 + 3 前端测试、健康模块全链路验证结果、测试覆盖空白清单 | | 2026-04-24 | 添加微信小程序验证步骤 | | 2026-04-23 | 重构为 5 节结构,去除与 infrastructure.md 重复 | | 2026-04-18 | Lighthouse 审计 + 性能优化 |