Files
hms/wiki/erp-core.md
iven ec87ae85cf docs(wiki): 全量 wiki 更新 — C1 晚间血压已修复标记 + 数据校正
- miniprogram.md: 晚间血压 CRITICAL 标记为已修复
- index.md: 症状导航表更新
- architecture/database/erp-core/erp-health/frontend/testing: 同步更新
2026-05-03 19:32:30 +08:00

140 lines
5.9 KiB
Markdown
Raw Permalink 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: erp-core
updated: 2026-04-28
status: stable
tags: [core, error, event-bus, module-trait, shared-types]
---
# erp-core
> 从 [[index]] 导航。关联: [[erp-server]] [[database]] [[wasm-plugin]] [[architecture]] [[erp-health]]
## 1. 设计决策
`erp-core` 是 L1 基础层,所有业务模块的唯一共同依赖。定义**跨模块共享的契约**,不含业务逻辑。
核心决策:
- **AppError 统一错误体系** — 8 种变体映射 HTTP 状态码,`?` 传播 + Axum `IntoResponse` 自动转换
- **EventBus 进程内广播** — broadcast channel (1024) + PostgreSQL Outbox + LISTEN/NOTIFY + 幂等消费 + Dead Letter
- **PiiCrypto 加密体系** — AES-256-GCM + KEK/DEK 分层管理 + HMAC 盲索引 + 数据脱敏
- **ErpModule 插件 trait** — 统一注册路由和事件处理器 + 拓扑排序启动
- **BaseFields 强制多租户** — 所有实体基础字段模板
## 2. 关键文件 + 数据流
### 核心文件
| 文件 | 职责 |
|------|------|
| `crates/erp-core/src/error.rs` | AppError 定义、HTTP 映射、From 转换 |
| `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/crypto/` | PiiCryptoengine/key_manager/masking/hmac_indexAES-256-GCM + KEK/DEK |
| `crates/erp-core/src/sanitize.rs` | strip_html_tags()、sanitize_string() |
| `crates/erp-core/src/entity/` | audit_log, domain_event, processed_event, dead_letter_event |
| `crates/erp-core/src/audit.rs` | 审计日志记录 |
| `crates/erp-core/src/rbac.rs` | require_permission 权限检查 |
| `crates/erp-core/src/lib.rs` | 模块导出入口 |
### 集成契约
| 方向 | 模块 | 接口 | 触发时机 |
|------|------|------|---------|
| 提供 → | erp-auth | ErpModule trait, AppError, EventBus | 模块实现 |
| 提供 → | erp-config | ErpModule trait, AppError | 模块实现 |
| 提供 → | 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 | 运行时 |
## 3. 代码逻辑
### 错误处理链
```
业务 crate (thiserror) → AppError → IntoResponse → HTTP JSON
数据库 (sea_orm::DbErr) → From 转换 → AppError (自动识别 duplicate key → Conflict)
```
响应格式:`{ "error": "not_found", "message": "资源不存在", "details": null }`
### 事件总线
```
EventBus::publish(DomainEvent)
→ INSERT pending → 内存 broadcast(channel 1024) → UPDATE published → NOTIFY outbox_channel
Outbox Relay: PgListener (LISTEN/NOTIFY) + 30s 兜底轮询 + 自动重连
幂等消费: processed_events 表 + consumer_id
Dead Letter: 失败事件 → dead_letter_events 表
```
事件字段: id(UUIDv7), event_type("user.created"), tenant_id, payload(JSON), timestamp
命名规则:`{模块}.{动作}``user.created`, `workflow.task.completed`
健康模块事件:`patient.created`, `appointment.confirmed`, `appointment.cancelled`
### 模块注册
```
ErpModule trait → ModuleRegistry::register() →
build_router(): 折叠所有模块路由 → Axum Router
register_handlers(): 注册事件处理器 → EventBus
```
已注册模块AuthModule → ConfigModule → WorkflowModule → MessageModule → PluginModule → **HealthModule****AiModule**
### 共享类型
- `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/` 前缀
**不变量**: tenant_id 从 JWT 中间件注入,应用层不可伪造
## 4. 活跃问题 + 陷阱
⚠️ crate 内部可用 `anyhow`,但跨 crate 边界必须转 `AppError`
⚠️ EventBus 当前为内存 broadcastoutbox 持久化通过后台任务实现
⚠️ 微信注册路径的 display_name 需调用 `sanitize_string()` 防止 XSS
### 2026-04-30 审计发现
**事件系统评分**
| 检查项 | 评分 | 说明 |
|--------|------|------|
| 事件定义完整性 | 95% | 25/27 常量有发布者2 个 KNOWN 未实现) |
| 消费者覆盖率 | 44% | 11/25 事件有活跃消费者 |
| Payload 一致性 | 100% | 抽样 5 个全部一致 |
| 幂等性保证 | 100% | 所有消费者使用 `is_event_processed` 检查 |
| 死信处理 | 100% | 消费失败自动进入 dead_letter_event 表 |
**错误处理评分**
| 检查项 | 评分 | 说明 |
|--------|------|------|
| 错误变体覆盖 | 95% | 8 AppError + 37 领域错误HealthError 26 + AiError 11 |
| Handler 错误传播 | 100% | 统一 `?` + IntoResponse无手动 match |
| PII 错误安全 | 100% | Internal 错误消息对外隐藏 |
| SSE 挂起风险 | 100% | Provider 不可用不会挂起,有完整清理 |
| 生产 unwrap() 安全性 | 95% | 2 处中等风险PluginHost::db + 信号量 acquire |
**审计日志**140 处调用 / 33 文件 / SHA256 哈希链 / 变更快照
**运行时日志缺口**health service 层仅 11 处 tracing26 个文件),运维排查困难
## 5. 变更记录
| 日期 | 变更 |
|------|------|
| 2026-05-01 | 审计结果更新:事件系统 44% 消费者覆盖率 / 错误处理 95% / 审计日志 140 处 / 运行时日志不足 |
| 2026-04-25 | 添加 erp-health 集成契约、健康模块事件、sanitize 模块引用 |
| 2026-04-23 | 重构为 5 节结构,更新为已完全集成状态 |