Files
hms/wiki/architecture.md
iven b05b7c27a0
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled
feat: 审计修复 Phase 6-7 — SSE 推送/工作流补全/消息群发/前端收尾
Phase 6 功能补全:
- P1-3: 消息 SSE 实时推送端点 + 前端 EventSource 连接
- P1-6: ServiceTask HTTP 调用能力 (reqwest GET/POST)
- P1-7: user.deleted 事件处理 — 终止相关流程实例
- P1-8: 任务认领 (claim) 端点 + handler
- P1-9: 超时检查器发布 task.timeout 事件
- P1-15: 组织/部门名称唯一性校验 (create + update)
- P1-18: 消息群发 fan-out (role/department/all 批量投递)

Phase 7 P3-P4 收尾:
- PluginAdmin purge 按钮状态修复
- ChangePassword 最小 8 字符 + 新旧密码不同验证
- AuditLogViewer 用户名缓存 + 扩展资源类型
- InstanceMonitor 通过 definition 缓存解析 node_name
- NotificationPreferences DND 时间范围校验
2026-04-26 19:44:04 +08:00

219 lines
8.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: 架构决策记录
updated: 2026-04-25
status: stable
tags: [architecture, decisions, design-principles]
---
# 架构决策记录
> 从 [[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原生 Rust18 实体/14 权限/13 页面)★ 已实现
└── 可选插件: crm, inventory, freelance, itopsWASM
```
### 为什么 erp-health 用原生模块?
医疗业务需要 18 强类型实体、自定义 API趋势分析/统计报表、PII 数据加密AES-256-GCM、文件上传、未来 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. 项目结构
### 目录布局
```text
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 (L2)
| | | | |
+--------------+--------------+--------------+-----------+
|
erp-server (L3: 唯一组装点)
|
erp-plugin (WASM 插件运行时)
```
**禁止**: L2 间直接依赖 / L1 依赖业务模块 / 绕过事件总线
### 模块实现状态
| 模块 | 状态 | 实体数 | 权限数 | 页面数 |
|------|------|--------|--------|--------|
| erp-auth | ✅ 完成 | 11 表 | - | 用户/角色/组织 |
| erp-config | ✅ 完成 | 6 表 | - | 设置/字典/菜单 |
| erp-workflow | ✅ 完成 | 5 表 | - | 工作流管理 |
| erp-message | ✅ 完成 | 3 表 | - | 消息中心 |
| erp-plugin | ✅ 完成 | 4 表 | - | 插件管理/市场 |
| erp-health | ✅ 完成 | 18 表 | 14 | 13 页面 + 11 组件 |
### 技术选型
| 选择 | 理由 |
|------|------|
| Axum 0.8 | Tokio 团队维护tower 生态,类型安全路由 |
| SeaORM 1.1 | 异步、类型安全、迁移工具完善 |
| PostgreSQL 18 | 企业级JSON 支持,扩展丰富 |
| Redis 7 | 缓存 + 限流 token bucket |
| React 19 + Ant Design 6 | 企业后台 UI 标配 |
| Zustand 5 | 极简状态管理 |
| Wasmtime 43 | WASM 沙箱Component ModelFuel 限制 |
### 集成契约
| 方向 | 模块 | 触发时机 |
|------|------|---------|
| 定义 → | [[erp-core]] | 所有模块的 trait 和类型 |
| 组装 ← | [[erp-server]] | 6 模块注册和启动 |
| 扩展 ← | [[wasm-plugin]] | 插件通过 Host Bridge 桥接 |
| 业务 ← | [[erp-health]] | 健康模块原生集成 |
## 3. 模块开发规范
### 新建业务模块清单
每个新模块**必须**包含:
1. `Cargo.toml` — 依赖 `erp-core`
2. `src/lib.rs` — 模块入口,实现 `ErpModule` trait
3. `src/error.rs` — 模块错误类型wrap `AppError`
4. `src/entity/` — SeaORM Entity 定义
5. `src/service/` — 业务逻辑层
6. `src/handler/` — Axum 路由处理器
7. `src/event.rs` — 模块事件定义和处理器
### ErpModule trait 实现
```rust
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
- CORS — 白名单制,默认拒绝
- 审计日志 — 所有关键操作记录变更前后状态
- 动态表 SQL — 使用 `sanitize_identifier` 防注入
## 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 — 多租户场景需设计解析策略
## 7. 变更记录
| 日期 | 变更 |
|------|------|
| 2026-04-26 | 从 CLAUDE.md 迁移目录结构、模块开发规范§5、安全注意事项§7 |
| 2026-04-25 | 全面更新6 模块已实现状态表、预约 CAS 决策、PII 加密不变量、健康模块集成 |
| 2026-04-23 | 重构为 5 节结构,删除 erp-common 引用,精简技术选型表 |