Files
hms/wiki/architecture.md
iven 75cd305996
Some checks failed
CI / security-audit (push) Has been cancelled
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
docs(wiki): 全景梳理 — 更新 9 个 wiki + CLAUDE.md scope + 头脑风暴记录
基于 3 个并行探索代理的全面扫描结果,更新 wiki 数据至实际状态:
- index.md: 18 crate / 76 迁移 / 44 实体 / 77k 行 / 409 提交
- erp-health.md: 44 实体 / 21 handler / 22 权限 / 25 事件 / 6 消费者
- erp-server.md: 9 后台任务 / RLS 中间件栈
- architecture.md: 新增 erp-ai/dialysis 到依赖图 / 测试覆盖表
- testing.md: 225 单元 + 159 集成 / 4 模块零测试警告
- database.md: 76 迁移 / RLS+哈希链+盲索引+Dead Letter
- erp-core.md: PiiCrypto 加密体系 / EventBus 完整描述
- frontend.md: 163 文件 / 5 store / 10 API 文件
- CLAUDE.md: 新增 health/ai/dialysis/assessment scope

头脑风暴 4 个议题决策:
- dialysis: 接入激活
- 测试: 按风险排序(workflow > ai > message > config)
- AI: 数据桥接优先
- 路线图: AI 驱动 3 个月 5 Phase
2026-04-28 14:53:04 +08:00

9.4 KiB
Raw Blame History

title, updated, status, tags
title updated status tags
架构决策记录 2026-04-28 stable
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原生 Rust44 实体/22 权限/25+ 页面)★ 已实现
├── AI 模块: erp-ai3 实体SSE 流式分析Phase 1 MVP
├── 透析模块: erp-dialysis已拆分为独立 crate
└── 可选插件: crm, inventory, freelance, itops, assessmentWASM
└── 可选插件: crm, inventory, freelance, itopsWASM

为什么 erp-health 用原生模块?

医疗业务需要 44 强类型实体、自定义 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 状态码。thiserrorAppError → 400/401/403/404/409/500。

为什么预约用原子 CAS

防止并发创建预约时超额。事务内 UPDATE current_appointments + 1 WHERE current < maxCAS 成功后才 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 表 - 用户/角色/组织 38 单元 + 3 集成
erp-config 完成 6 表 - 设置/字典/菜单 ⚠️ 0 测试
erp-workflow 完成 5 表 - 工作流管理 ⚠️ 0 测试
erp-message 完成 3 表 - 消息中心 ⚠️ 0 测试
erp-plugin 完成 4 表 - 插件管理/市场 31 单元 + 2 集成
erp-health 完成 44 表 22 25+ 页面 + 11 组件 104 单元 + 159 集成
erp-ai 🔄 Phase 1 3 表 - AI 分析/Prompt/用量 ⚠️ 0 测试
erp-dialysis 🔄 已拆分 - - - 10 单元

技术选型

选择 理由
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 实现

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/minIP 分级)
  • 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 — 多租户场景需设计解析策略

7. 变更记录

日期 变更
2026-04-26 从 CLAUDE.md 迁移目录结构、模块开发规范§5、安全注意事项§7
2026-04-25 全面更新6 模块已实现状态表、预约 CAS 决策、PII 加密不变量、健康模块集成
2026-04-23 重构为 5 节结构,删除 erp-common 引用,精简技术选型表