- Wiki 7 文件关键数字刷新:迁移 96→103、实体 45→46、前端 163→225、测试 5→36 - 修复 architecture.md PostgreSQL 版本不一致(18→16) - 修复 erp-ai.md 实体数 3→6、erp-health.md 实体数 45→46 - 更新 index.md 文档索引:specs 41、plans 38、discussions 18 - 新增事件注册表/方法论/分析报告引用 - 新增页面/组件测试设计规格(模式化工厂方案) - 新增 Q2 路线图规格(技术债 + 新功能并行 8 周)
10 KiB
title, updated, status, tags
| title | updated | status | tags | |||
|---|---|---|---|---|---|---|
| 架构决策记录 | 2026-04-28 | stable |
|
架构决策记录
从 index 导航。关联: erp-core erp-server database wasm-plugin erp-health
1. 设计决策
模块化单体 + 渐进式拆分
模块间零直接依赖,跨模块通信通过事件总线和 trait 接口。ErpModule trait 天然支持未来按模块拆分为微服务。
HMS 架构:原生模块 + 插件并存
HMS 继承 ERP 底座的所有基础模块,erp-health 作为原生 Rust 模块承载医疗业务。WASM 插件系统保留但非 HMS 主要扩展方式。
HMS 平台
├── 基础模块(继承 ERP): auth, config, workflow, message, plugin
├── 核心业务模块: erp-health(原生 Rust,46 实体/39 权限/25+ 页面)★ 已实现
├── AI 模块: erp-ai(6 实体,SSE 流式分析,Phase 1 MVP)
├── 透析模块: erp-dialysis(已拆分为独立 crate)
└── 可选插件: crm, inventory, freelance, itops, assessment(WASM)
为什么 erp-health 用原生模块?
医疗业务需要 46 强类型实体、自定义 API(趋势分析/统计报表)、PII 数据加密(AES-256-GCM + KEK/DEK 分层)、文件上传、未来 AI 集成。WASM 插件的 JSONB 动态存储和 20 实体上限无法满足。详见 erp-health。
为什么用 UUIDv7?
时间排序 + UUID 唯一性 + 接近自增 ID 的索引性能。多租户 SaaS 下不同租户数据不会因 ID 冲突互相影响。
为什么 tenant_id 不在 API 路径中?
从 JWT 提取,中间件注入 TenantContext。防止:手动改 URL 越权 / API 暴露租户信息 / 忘记检查权限。管理员接口例外。
为什么错误类型跨 crate 用 thiserror?
anyhow 无类型信息,无法精确映射 HTTP 状态码。thiserror → AppError → 400/401/403/404/409/500。
为什么预约用原子 CAS?
防止并发创建预约时超额。事务内 UPDATE current_appointments + 1 WHERE current < max,CAS 成功后才 INSERT 预约记录。
2. 项目结构
目录布局
hms/
├── crates/ # Rust Workspace
│ ├── erp-core/ # L1: 基础类型、错误、事件、模块 trait
│ ├── erp-auth/ # L2: 身份与权限模块
│ ├── erp-workflow/ # L2: 工作流引擎模块
│ ├── erp-message/ # L2: 消息中心模块
│ ├── erp-config/ # L2: 系统配置模块
│ ├── erp-health/ # L2: 健康管理模块 ★ HMS 核心
│ └── erp-server/ # L3: Axum 服务入口,组装所有模块
│ └── migration/ # SeaORM 数据库迁移
├── apps/
│ └── web/ # Vite + React 19 SPA (主力前端)
├── packages/
│ └── ui-components/ # React 共享组件库
├── desktop/ # (可选) Tauri 桌面端
├── docker/ # Docker 开发环境配置
├── docs/
│ ├── superpowers/specs/ # 设计规格文档
│ └── discussions/ # 讨论记录
├── wiki/ # 项目知识库
└── Cargo.toml # Workspace root
模块依赖图
erp-core (L1)
|
+--------------+--------------+--------------+-----------+
| | | | |
erp-auth erp-config erp-workflow erp-message erp-health erp-ai (L2)
| | | | |
|
erp-dialysis
+--------------+--------------+--------------+-----------+
|
erp-server (L3: 唯一组装点)
|
erp-plugin (WASM 插件运行时)
禁止: L2 间直接依赖 / L1 依赖业务模块 / 绕过事件总线
模块实现状态
| 模块 | 状态 | 实体数 | 权限数 | 页面数 | 测试覆盖 |
|---|---|---|---|---|---|
| erp-auth | ✅ 完成 | 11 表 | 23(种子数据) | 用户/角色/组织 | 41 单元 + 3 集成 |
| erp-config | ✅ 完成 | 6 表 | 18(种子数据) | 设置/字典/菜单 | 78 单元 |
| erp-workflow | ✅ 完成 | 5 表 | 8(种子数据) | 工作流管理 | 63 单元 + 4 集成 |
| erp-message | ✅ 完成 | 3 表 | 5(种子数据) | 消息中心 | 72 单元 |
| erp-plugin | ✅ 完成 | 4 表 | 2(种子数据) | 插件管理/市场 | 78 单元 + 2 集成 |
| erp-health | ✅ 完成 | 46 表 | 39 | 25+ 页面 + 工作台 | 159 单元 + 144 集成 |
| erp-ai | 🔄 Phase 1 | 6 表 | 6 | AI 分析/Prompt/建议/风险/用量 | 36 单元 |
| erp-dialysis | 🔄 已拆分 | - | 5 | - | 10 单元 + 15 集成 |
技术选型
| 选择 | 理由 |
|---|---|
| Axum 0.8 | Tokio 团队维护,tower 生态,类型安全路由 |
| SeaORM 1.1 | 异步、类型安全、迁移工具完善 |
| PostgreSQL 16 | 企业级,JSON 支持,扩展丰富 |
| Redis 7 | 缓存 + 限流 token bucket |
| React 19 + Ant Design 6 | 企业后台 UI 标配 |
| Zustand 5 | 极简状态管理 |
| Wasmtime 43 | WASM 沙箱,Component Model,Fuel 限制 |
集成契约
| 方向 | 模块 | 触发时机 |
|---|---|---|
| 定义 → | erp-core | 所有模块的 trait 和类型 |
| 组装 ← | erp-server | 6 模块注册和启动 |
| 扩展 ← | wasm-plugin | 插件通过 Host Bridge 桥接 |
| 业务 ← | erp-health | 健康模块原生集成 |
3. 模块开发规范
新建业务模块清单
每个新模块必须包含:
Cargo.toml— 依赖erp-coresrc/lib.rs— 模块入口,实现ErpModuletraitsrc/error.rs— 模块错误类型,wrapAppErrorsrc/entity/— SeaORM Entity 定义src/service/— 业务逻辑层src/handler/— Axum 路由处理器src/event.rs— 模块事件定义和处理器
ErpModule trait 实现
pub struct AuthModule;
impl ErpModule for AuthModule {
fn name(&self) -> &str { "auth" }
fn version(&self) -> &str { env!("CARGO_PKG_VERSION") }
fn dependencies(&self) -> Vec<&str> { vec![] }
fn register_routes(&self, router: Router) -> Router {
router.nest("/api/v1", auth_routes())
}
fn register_event_handlers(&self, bus: &EventBus) { /* 订阅其他模块事件 */ }
async fn on_tenant_created(&self, tenant_id: Uuid) -> AppResult<()> { Ok(()) }
}
数据库迁移规范
- 迁移文件放在
crates/erp-server/migration/src/ - 命名格式:
m{YYYYMMDD}_{6位序号}_{描述}.rs - 必须可回滚(实现
down方法) - 新增表必须包含所有标准字段(id, tenant_id, created_at, updated_at, created_by, updated_by, deleted_at, version)
- 必须幂等(使用
if_not_exists)
4. 安全注意事项
认证安全
- 密码存储: Argon2 哈希,禁止明文
- JWT: access token 15min + refresh token 7d
- Refresh Token 轮换: 每次使用后签发新的,旧的作废
- Token 存储: 桌面端使用 Tauri secure store
- 密码修改: 使所有已签发的 JWT 失效
多租户安全
- 中间件注入:
tenant_id从 JWT 中提取,应用层不可伪造 - 数据隔离: 所有查询自动过滤
tenant_id - 越权防护: 禁止跨租户数据访问
- 租户 provisioning:
on_tenant_created钩子初始化数据
通用安全
- 不硬编码密钥 — 使用环境变量或配置文件
- 用户输入验证 — 所有 API 端点验证输入
- SQL 注入防护 — SeaORM 参数化查询
- 限流 — Redis token bucket(用户 100/min,IP 分级)
- CORS — 白名单制,默认拒绝
- 审计日志 — 所有关键操作记录变更前后状态 + 哈希链防篡改
- 动态表 SQL — 使用
sanitize_identifier防注入 - PII 加密 — AES-256-GCM + KEK/DEK 分层管理(每租户独立 DEK)
- HMAC 盲索引 — 支持加密字段的等值查询
- RLS 行级安全 — PostgreSQL Row Level Policy,中间件 SET
app.current_tenant_id - Dead Letter — 失败事件自动写入 dead_letter_events 表
5. 代码逻辑
⚡ 不变量: 模块间只通过 EventBus 和 trait 通信,无直接依赖
⚡ 不变量: 所有数据表必须含 tenant_id,查询自动过滤
⚡ 不变量: UUID v7 作为主键
⚡ 不变量: 软删除,不硬删除
⚡ 不变量: 所有 API 使用 /api/v1/ 前缀
⚡ 不变量: 预约创建必须走原子 CAS,不能用 read-then-write
⚡ 不变量: PII 数据(身份证、手机号)加密存储 + 脱敏展示
6. 活跃问题 + 陷阱
⚠️ 当前共享数据库 + tenant_id 过滤,未来可扩展为 Schema 隔离或数据库隔离 ⚠️ EventBus 内存 broadcast 需 outbox 持久化保障(已通过后台任务实现) ⚠️ 微信登录固定到 default_tenant_id — 多租户场景需设计解析策略
2026-04-30 审计发现
| 发现 | 严重性 | 说明 |
|---|---|---|
| 前端权限码拼写错误 | CRITICAL | health.alert.manage → health.alerts.manage(缺 s),告警管理按钮永远不显示 |
| 56 个基础模块权限码未通过 PermissionDescriptor 声明 | MEDIUM | auth/config/workflow/message/plugin 通过种子数据手动注册,新增易遗漏 |
| 14 个事件无业务消费者 | LOW | 发布到 EventBus 但无后端消费者,SSE 推送仍有价值 |
| Health service 层运行时日志极缺 | HIGH | 26 个 service 文件仅 11 处 tracing,patient_service(949 行)0 处 |
| 基础模块 ErpModule trait 实现度低 | LOW | 5/8 模块使用默认值(auth/config/workflow/message/plugin) |
7. 变更记录
| 日期 | 变更 |
|---|---|
| 2026-05-01 | 审计数据更新:模块状态表刷新(772 测试 / 328 路由 / 50 权限码)、审计发现清单 |
| 2026-04-26 | 从 CLAUDE.md 迁移:目录结构、模块开发规范(§5)、安全注意事项(§7) |
| 2026-04-25 | 全面更新:6 模块已实现状态表、预约 CAS 决策、PII 加密不变量、健康模块集成 |
| 2026-04-23 | 重构为 5 节结构,删除 erp-common 引用,精简技术选型表 |