Files
hms/docs/audits/04-parameter-config.md
iven d712ad78c3 docs: 审计报告(8 份) + 讨论记录(4 份)
审计报告: 基线快照/功能清单/后端完整性/事件系统/参数配置/
差距模式/错误处理/测试覆盖/审计总结报告
讨论记录: 设备管线/端到端测试/三端审计/工作台重构
2026-05-03 19:32:15 +08:00

335 lines
17 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.
# 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 和配置健壮,权限码和数据模型映射有差距 |