docs: 审计报告(8 份) + 讨论记录(4 份)

审计报告: 基线快照/功能清单/后端完整性/事件系统/参数配置/
差距模式/错误处理/测试覆盖/审计总结报告
讨论记录: 设备管线/端到端测试/三端审计/工作台重构
This commit is contained in:
iven
2026-05-03 19:32:15 +08:00
parent 78c783d332
commit d712ad78c3
13 changed files with 2683 additions and 0 deletions

View File

@@ -0,0 +1,334 @@
# HMS 功能审计 — Phase 4: 参数传递与配置审计
> 日期: 2026-04-30 | 审计范围: DTO 覆盖率、配置参数、权限码、数据模型映射
## 总览
| 指标 | 值 |
|------|-----|
| DTO 结构体 | 105 个17 文件) |
| 配置参数 | 30 个字段12 配置节) |
| 未使用配置字段 | 6 个(均在 AiConfig |
| 声明权限码 | 50 个3 模块) |
| Handler 实际使用权限码 | ~106 个 |
| 前端 AuthButton 引用 | 13 个唯一码 |
| 小程序未映射字段 | 6 个vital_signs |
---
## 1. DTO 覆盖率检查
### 1.1 DTO 分布统计
| 分类 | 数量 | 说明 |
|------|------|------|
| CreateCreate*Req | 27 | 创建请求 |
| UpdateUpdate*Req | 19 | 更新/审核/撤销/核销请求 |
| Query*Query/*Params | 8 | 列表查询参数 |
| Response*Resp/*Response | 38 | 响应结构体 |
| Other批量操作/辅助) | 13 | BatchReq、ExchangeReq、数据点等 |
| **总计** | **105** | 17 个文件 |
### 1.2 按 Handler 的 DTO 覆盖矩阵
| Handler | Create DTO | Update DTO | Query DTO | Response DTO | 状态 |
|---------|-----------|-----------|----------|-------------|------|
| patient_handler | CreatePatientReq | UpdatePatientReq | PatientListQuery | PatientResp | ✓ |
| doctor_handler | CreateDoctorReq | UpdateDoctorReq | DoctorListQuery | DoctorResp | ✓ |
| appointment_handler | CreateAppointmentReq | UpdateAppointmentStatusReq | AppointmentListQuery/CalendarQuery | AppointmentResp/ScheduleResp | ✓ |
| consultation_handler | CreateSessionReq/CreateMessageReq | — | SessionQuery | SessionResp/MessageResp | ✓ |
| follow_up_handler | CreateFollowUpTaskReq/BatchCreateTasksReq | UpdateFollowUpTaskReq | FollowUpTaskListQuery/FollowUpRecordListQuery | FollowUpTaskResp/FollowUpRecordResp | ✓ |
| follow_up_template_handler | CreateFollowUpTemplateReq | UpdateFollowUpTemplateReq | FollowUpTemplateListQuery | FollowUpTemplateResp | ✓ |
| health_data_handler | CreateVitalSignsReq/CreateLabReportReq/CreateHealthRecordReq | UpdateVitalSignsReq/UpdateLabReportReq/UpdateHealthRecordReq/ReviewLabReportReq | MiniTrendQueryParams | VitalSignsResp/LabReportResp/HealthRecordResp/TrendResp | ✓ |
| daily_monitoring_handler | CreateDailyMonitoringReq | UpdateDailyMonitoringReq | — | DailyMonitoringResp | ✓ |
| diagnosis_handler | CreateDiagnosisReq | UpdateDiagnosisReq | — | DiagnosisResp | ✓ |
| medication_record_handler | CreateMedicationRecordReq | UpdateMedicationRecordReq | — | MedicationRecordResp | ✓ |
| medication_reminder_handler | CreateMedicationReminderReq | UpdateMedicationReminderReq | — | MedicationReminderResp | ✓ |
| alert_handler | CreateAlertRuleRequest | UpdateAlertRuleRequest/AcknowledgeAlertRequest | — | AlertRuleResponse/AlertResponse | ✓ |
| consent_handler | CreateConsentReq | RevokeConsentReq | — | ConsentResp | ✓ |
| points_handler | CreatePointsRuleReq/CreatePointsProductReq/CreateOfflineEventReq | UpdatePointsRuleReq/UpdatePointsProductReq/UpdateOfflineEventWithVersion/VerifyOrderReq | — | PointsRuleResp/PointsProductResp/PointsOrderResp/PointsAccountResp | ✓ |
| article_handler | CreateArticleReq/CreateCategoryReq/CreateTagReq | UpdateArticleReq/UpdateCategoryReq/UpdateTagReq/ReviewArticleReq | ArticleListParams | ArticleResp/CategoryResp/TagResp/ArticleRevisionResp | ✓ |
| stats_handler | — | — | — | DashboardStatsResp/PatientStatisticsResp/...9 个) | ✓ |
**结论:所有 23 个 handler 都有完整的 DTO 覆盖。每个写入端点有 Create/Update DTO列表端点有 Query DTO所有端点有 Response DTO。**
### 1.3 DTO 传递链完整性(抽样验证 5 个)
| 端点 | Handler 输入 DTO | Service 函数签名 | 一致性 |
|------|-----------------|-----------------|--------|
| `POST /patients` | CreatePatientReq | `create_patient(req: CreatePatientReq)` | ✅ |
| `POST /appointments` | CreateAppointmentReq | `create_appointment(req: CreateAppointmentReq)` | ✅ |
| `POST /vital-signs` | CreateVitalSignsReq | `create_vital_signs(patient_id, req: CreateVitalSignsReq)` | ✅ |
| `POST /follow-up/tasks` | CreateFollowUpTaskReq | `create_task(req: CreateFollowUpTaskReq)` | ✅ |
| `POST /consultation-sessions` | CreateSessionReq | `create_session(req: CreateSessionReq)` | ✅ |
---
## 2. 配置参数使用率
### 2.1 配置结构总览
AppConfig 包含 12 个子配置节、30 个字段:
| 配置节 | 字段数 | 用途 |
|--------|-------|------|
| ServerConfig | 3 | 服务端口、指标端口 |
| DatabaseConfig | 3 | 连接字符串、连接池大小 |
| RedisConfig | 1 | 连接字符串 |
| JwtConfig | 3 | 密钥、Token TTL |
| AuthConfig | 1 | 超级管理员密码 |
| LogConfig | 1 | 日志级别 |
| CorsConfig | 1 | 允许的 Origin |
| WechatConfig | 3 | 微信小程序 AppID/Secret/开发模式 |
| HealthConfig | 2 | AES/HMAC 密钥PII 加密) |
| CryptoConfig | 1 | KEK 主密钥 |
| AiConfig | 8 | AI 提供商配置 |
| StorageConfig | 2 | 文件上传目录/大小限制 |
### 2.2 字段使用情况
| 配置节 | 字段 | 使用次数 | 状态 |
|--------|------|---------|------|
| ServerConfig | `host` | 1 | ✓ 正常 |
| ServerConfig | `port` | 1 | ✓ 正常 |
| ServerConfig | `metrics_port` | 2 | ✓ 正常 |
| DatabaseConfig | `url` | 5 | ✓ 正常 |
| DatabaseConfig | `max_connections` | 1 | ✓ 正常 |
| DatabaseConfig | `min_connections` | 1 | ✓ 正常 |
| RedisConfig | `url` | 2 | ✓ 正常 |
| JwtConfig | `secret` | 5 | ✓ 正常 |
| JwtConfig | `access_token_ttl` | 1 | ✓ 正常 |
| JwtConfig | `refresh_token_ttl` | 1 | ✓ 正常 |
| AuthConfig | `super_admin_password` | 1 | ✓ 正常 |
| LogConfig | `level` | 1 | ✓ 正常 |
| CorsConfig | `allowed_origins` | 1 | ✓ 正常 |
| WechatConfig | `appid` | 2 | ✓ 正常 |
| WechatConfig | `secret` | 3 | ✓ 正常 |
| WechatConfig | `dev_mode` | 2 | ✓ 正常 |
| HealthConfig | `aes_key` | 2 | ✓ 正常(启动校验 + CryptoService 初始化) |
| HealthConfig | `hmac_key` | 2 | ✓ 正常(启动校验 + HMAC 盲索引) |
| CryptoConfig | `kek` | 2 | ✓ 正常KEK/DEK 密钥体系) |
| AiConfig | `api_key` | 1 | ✓ 正常 |
| AiConfig | `base_url` | 1 | ✓ 正常 |
| **AiConfig** | **`default_provider`** | **0** | **未使用** |
| **AiConfig** | **`model`** | **0** | **未使用** |
| **AiConfig** | **`max_tokens`** | **0** | **未使用** |
| **AiConfig** | **`temperature`** | **0** | **未使用** |
| **AiConfig** | **`cache_ttl_seconds`** | **0** | **未使用** |
| **AiConfig** | **`rate_limit_patient_daily`** | **0** | **未使用** |
| StorageConfig | `upload_dir` | 3 | ✓ 正常 |
| StorageConfig | `max_file_size` | 1 | ✓ 正常 |
### 2.3 未使用字段分析
6 个未使用字段全部集中在 `AiConfig`
| 字段 | 原设计意图 | 当前替代机制 |
|------|-----------|------------|
| `default_provider` | 全局默认 AI 提供商 | 硬编码使用 Claude |
| `model` | 全局默认模型 | 每个 Prompt 模板通过 `model_config` JSONB 字段配置 |
| `max_tokens` | 全局默认最大 Token 数 | 同上,从 `model_config.max_tokens` 读取,默认 2048 |
| `temperature` | 全局默认温度 | 同上,从 `model_config.temperature` 读取,默认 0.3 |
| `cache_ttl_seconds` | AI 结果缓存时间 | 未实现缓存层(每次为独立 SSE 流) |
| `rate_limit_patient_daily` | 每患者每日 AI 调用限制 | 未实现限流 |
**影响评估**LOW。这些字段是预留的全局默认值实际业务已通过更灵活的 per-prompt `model_config` 实现相同能力。但 `cache_ttl_seconds``rate_limit_patient_daily` 代表缺失的功能(缓存和限流),属于 P3 待实现。
---
## 3. 权限码审计
### 3.1 权限码声明总览
| 模块 | 声明数 | 权限码前缀 |
|------|--------|-----------|
| erp-health | 39 | `health.*` |
| erp-ai | 6 | `ai.*` |
| erp-dialysis | 5 | `health.dialysis*` |
| **合计** | **50** | |
其余 5 个模块auth/config/workflow/message/plugin**未声明任何 PermissionDescriptor**,但 handler 中使用了约 56 个权限码。
### 3.2 声明 vs 使用覆盖矩阵
#### erp-health39 声明 → 全部使用)
| 权限码 | Handler 使用 | 前端 AuthButton |
|--------|-------------|----------------|
| health.patient.list | ✓ | — |
| health.patient.manage | ✓ | ✓ PatientList/PatientDetail/PatientTagManage |
| health.health-data.list | ✓ | — |
| health.health-data.manage | ✓ | ✓ VitalSignsTab/LabReportsTab/HealthRecordsTab/DailyMonitoringTab |
| health.appointment.list | ✓ | — |
| health.appointment.manage | ✓ | ✓ AppointmentList |
| health.follow-up.list | ✓ | — |
| health.follow-up.manage | ✓ | ✓ FollowUpTaskList |
| health.consultation.list | ✓ | — |
| health.consultation.manage | ✓ | ✓ ConsultationList/ConsultationDetail |
| health.doctor.list | ✓ | — |
| health.doctor.manage | ✓ | ✓ DoctorList/DoctorSchedule |
| health.articles.list | ✓ | — |
| health.articles.manage | ✓ | ✓ ArticleManageList/ArticleEditor/ArticleCategoryManage/ArticleTagManage |
| health.articles.review | ✓ | ✓ ArticleManageList |
| health.points.list | ✓ | — |
| health.points.manage | ✓ | ✓ PointsRuleList/PointsProductList/PointsOrderList/OfflineEventList |
| health.device-readings.list | ✓ | — |
| health.device-readings.manage | ✓ | — |
| health.devices.list | ✓ | — |
| health.devices.manage | ✓ | — |
| health.alerts.list | ✓ | — |
| health.alerts.manage | ✓ | ⚠️ 前端拼写错误(见 §3.4 |
| health.alert-rules.list | ✓ | — |
| health.alert-rules.manage | ✓ | — |
| health.critical-alerts.list | ✓ | — |
| health.critical-alerts.manage | ✓ | — |
| health.critical-value-thresholds.list | ✓ | — |
| health.critical-value-thresholds.manage | ✓ | — |
| health.follow-up-templates.list | ✓ | — |
| health.follow-up-templates.manage | ✓ | — |
| health.daily-monitoring.list | ✓ | — |
| health.daily-monitoring.manage | ✓ | — |
| health.consent.list | ✓ | — |
| health.consent.manage | ✓ | — |
| health.medication-records.list | ✓ | — |
| health.medication-records.manage | ✓ | — |
| health.medication-reminders.list | ✓ | — |
| health.medication-reminders.manage | ✓ | — |
#### erp-dialysis5 声明 → 全部使用)
| 权限码 | Handler 使用 | 前端 AuthButton |
|--------|-------------|----------------|
| health.dialysis.list | ✓ | — |
| health.dialysis.manage | ✓ | ✓ DialysisManageList |
| health.dialysis-prescription.list | ✓ | — |
| health.dialysis-prescription.manage | ✓ | — |
| health.dialysis.stats | ✓ | — |
#### erp-ai6 声明 → 5 使用)
| 权限码 | Handler 使用 | 前端 AuthButton |
|--------|-------------|----------------|
| ai.analysis.list | ✓ | — |
| ai.analysis.manage | ✓ | — |
| ai.prompt.list | ✓ | ✓ AiPromptList |
| ai.prompt.manage | ✓ | ✓ AiPromptList |
| ai.usage.list | ✓ | — |
| **ai.provider.manage** | **—** | **已声明但无 Handler 调用** |
### 3.3 未声明权限码(基础模块)
以下 5 个模块的 handler 使用了 `require_permission` 但未实现 `permissions()` 方法,导致权限码不会通过 `sync_module_permissions` 自动注册到数据库:
| 模块 | 未声明权限数 | 权限码示例 |
|------|------------|-----------|
| erp-auth | 23 | user.list, role.create, organization.update, department.delete, position.list |
| erp-config | 18 | dictionary.list, menu.update, setting.read, numbering.generate, theme.update |
| erp-workflow | 8 | workflow.list, workflow.start, workflow.approve, workflow.delegate |
| erp-message | 5 | message.list, message.send, message.template.create |
| erp-plugin | 2 | plugin.admin, plugin.list |
| **合计** | **56** | |
**影响评估**这些权限码通过种子数据super_admin 角色)手动注册到数据库,而非通过 `ErpModule::permissions()` 自动注册。功能上可以正常工作,但:
- 新增权限需要手动 SQL 插入,容易遗漏
- 与 health/ai/dialysis 模块的自动注册机制不一致
- 建议在后续迭代中为这 5 个模块补充 `permissions()` 实现
### 3.4 前端权限码拼写错误BUG
| 文件 | 前端代码 | 后端声明 | 差异 |
|------|---------|---------|------|
| [AlertList.tsx:240](apps/web/src/pages/health/AlertList.tsx#L240) | `health.alert.manage` | `health.alerts.manage` | 缺少 `s` |
**影响**AlertList 页面的"管理"按钮(确认/处置告警)永远不显示,因为 `health.alert.manage` 权限码不存在于系统。用户虽然可以通过 API 直接操作,但 UI 层面无法触发管理动作。
**修复**:将 `health.alert.manage` 改为 `health.alerts.manage`(复数形式)。
### 3.5 前端路由级权限控制
Web 前端通过 `PrivateRoute` 组件做路由守卫,当前仅检查:
1. 是否已认证(`isAuthenticated`
2. `/users``/roles``/organizations` 路径需要 `auth.*` 前缀权限
健康模块的路由**没有前端路由级权限守卫**,依赖后端 API 返回 403 拒绝未授权请求。这在 SPA 架构中是可接受的做法,但用户可能看到空白页面而非友好的"无权限"提示。
---
## 4. 小程序数据模型映射验证
### 4.1 后端 vital_signs Entity 字段24 个,含标准字段)
| # | 字段名 | 类型 | 小程序映射 |
|---|--------|------|-----------|
| 1 | id | Uuid | — |
| 2 | tenant_id | Uuid | — |
| 3 | patient_id | Uuid | — |
| 4 | record_date | NaiveDate | ✓ |
| 5 | systolic_bp_morning | Option\<i32\> | ✓ blood_pressure → extra.systolic |
| 6 | diastolic_bp_morning | Option\<i32\> | ✓ blood_pressure → extra.diastolic |
| 7 | **systolic_bp_evening** | Option\<i32\> | **未映射** |
| 8 | **diastolic_bp_evening** | Option\<i32\> | **未映射** |
| 9 | heart_rate | Option\<i32\> | ✓ heart_rate |
| 10 | weight | Option\<Decimal\> | ✓ weight |
| 11 | blood_sugar | Option\<Decimal\> | ✓ blood_sugar |
| 12 | **body_temperature** | Option\<Decimal\> | **未映射** |
| 13 | **spo2** | Option\<i32\> | **未映射** |
| 14 | **blood_sugar_type** | Option\<String\> | **未映射** |
| 15 | water_intake_ml | Option\<i32\> | ✓ water_intake |
| 16 | urine_output_ml | Option\<i32\> | ✓ urine_output |
| 17 | notes | Option\<String\> | ✓ |
| 18 | source | String | —(后端默认 "manual" |
| 19-24 | 标准字段 | — | — |
### 4.2 小程序 indicator_type 映射表
来源:[health.ts:30-59](apps/miniprogram/src/services/health.ts#L30-L59)
| indicator_type | 映射字段 | 转换逻辑 |
|---------------|---------|---------|
| `blood_pressure` | systolic_bp_morning + diastolic_bp_morning | extra.systolic / extra.diastolic |
| `heart_rate` | heart_rate | Math.round(value) |
| `weight` | weight | value |
| `blood_sugar` | blood_sugar | value |
| `water_intake` | water_intake_ml | Math.round(value) |
| `urine_output` | urine_output_ml | Math.round(value) |
| *(default)* | — | 丢弃(不发送) |
### 4.3 差异清单
#### A. 后端有但小程序未映射6 个业务字段)
| 字段 | 严重性 | 说明 |
|------|--------|------|
| **systolic_bp_evening** | **HIGH** | 后端趋势服务和危急值检测均支持晚间血压,但小程序所有血压数据一律写入 `*_morning` 字段,晚间数据丢失 |
| **diastolic_bp_evening** | **HIGH** | 同上 |
| **body_temperature** | MEDIUM | 后端 entity 和 DTO 均支持体温录入,但小程序无 `body_temperature` indicator_type |
| **spo2** | MEDIUM | 后端支持血氧饱和度,小程序无对应类型 |
| **blood_sugar_type** | LOW | 后端支持区分空腹/餐后/随机/OGTT 血糖类型,小程序仅传数值不传类型 |
| **source** | LOW | 后端默认 "manual",小程序未区分来源标识 |
#### B. 小程序映射了但后端不存在的字段
**无此类差异。** 所有小程序映射的目标字段在后端 CreateVitalSignsReq 中均存在。
### 4.4 根因分析
小程序 `inputVitalSign()``indicator_type` 模型设计为"每种体征一个类型",但血压实际上需要区分时段(晨间/晚间)。当前 `blood_pressure` 类型固定映射到 `*_morning` 字段,没有逻辑可以写入 `*_evening` 字段。
后端设计是完善的——趋势服务([trend_service.rs](crates/erp-health/src/service/trend_service.rs))支持 `systolic_bp_evening`/`diastolic_bp_evening` 指标查询,危急值检测([health_data_service.rs](crates/erp-health/src/service/health_data_service.rs))使用 `morning.or(evening)` 做回退检查。
**修复建议**:新增 `blood_pressure_evening` indicator_type或修改 UI 让用户选择时段。
---
## 5. 评分
| 检查项 | 评分 | 说明 |
|--------|------|------|
| DTO 覆盖率 | 100% | 105 个 DTO 完整覆盖所有 handler传递链一致 |
| 配置参数使用率 | 80% | 24/30 字段活跃使用6 个 AiConfig 字段预留未接入 |
| 权限码声明覆盖率 | 47% | 仅 50/106 个使用的权限码通过 PermissionDescriptor 声明 |
| 权限码一致性 | 98% | 1 处前端拼写错误alert vs alerts |
| 前端权限按钮覆盖 | 26% | 13/50 声明码有 AuthButton其余依赖 API 403 |
| 数据模型映射完整性 | 63% | 6/16 业务字段未映射2 个 HIGH + 2 个 MEDIUM |
| **综合评分** | **69%** | DTO 和配置健壮,权限码和数据模型映射有差距 |