docs: 全面更新 wiki 文档至当前实现状态
9 个 wiki 页面全部更新: - index: 关键数字更新(15 crate/48 表/50 迁移/6 模块) - erp-health: 从"规划中"更新为"已实现"(18 实体/14 权限) - erp-server: 6 模块注册/8 环境变量/5 后台任务 - database: 50 迁移/48 表/健康模块迁移(m000042-m000050) - frontend: 10 健康路由/12 共享组件/7 健康 API/3 单元测试 - miniprogram: 20 页面/10 服务/9 组件 - testing: 93 后端测试+3 前端测试/全链路验证结果 - erp-core: 新增 erp-health 集成契约 - architecture: 6 模块实现状态表/预约 CAS/PII 加密
This commit is contained in:
@@ -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 引用,精简技术选型表 |
|
||||
|
||||
@@ -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) |
|
||||
|
||||
@@ -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<DomainEvent>
|
||||
|
||||
命名规则:`{模块}.{动作}` 如 `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<T>` — 分页标准化(每页上限 100)
|
||||
- `ApiResponse<T>` — 统一信封 `{ 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 节结构,更新为已完全集成状态 |
|
||||
|
||||
@@ -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 页,设计规格确认 |
|
||||
|
||||
@@ -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 节结构,更新为当前集成状态 |
|
||||
|
||||
@@ -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 节结构,更新为当前完整前端状态 |
|
||||
|
||||
@@ -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` |
|
||||
|
||||
205
wiki/miniprogram.md
Normal file
205
wiki/miniprogram.md
Normal file
@@ -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 页面,记录登录流程、环境配置、历史陷阱 |
|
||||
@@ -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 审计 + 性能优化 |
|
||||
|
||||
Reference in New Issue
Block a user