# 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 分布统计 | 分类 | 数量 | 说明 | |------|------|------| | Create(Create*Req) | 27 | 创建请求 | | Update(Update*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-health(39 声明 → 全部使用) | 权限码 | 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-dialysis(5 声明 → 全部使用) | 权限码 | Handler 使用 | 前端 AuthButton | |--------|-------------|----------------| | health.dialysis.list | ✓ | — | | health.dialysis.manage | ✓ | ✓ DialysisManageList | | health.dialysis-prescription.list | ✓ | — | | health.dialysis-prescription.manage | ✓ | — | | health.dialysis.stats | ✓ | — | #### erp-ai(6 声明 → 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\ | ✓ blood_pressure → extra.systolic | | 6 | diastolic_bp_morning | Option\ | ✓ blood_pressure → extra.diastolic | | 7 | **systolic_bp_evening** | Option\ | **未映射** | | 8 | **diastolic_bp_evening** | Option\ | **未映射** | | 9 | heart_rate | Option\ | ✓ heart_rate | | 10 | weight | Option\ | ✓ weight | | 11 | blood_sugar | Option\ | ✓ blood_sugar | | 12 | **body_temperature** | Option\ | **未映射** | | 13 | **spo2** | Option\ | **未映射** | | 14 | **blood_sugar_type** | Option\ | **未映射** | | 15 | water_intake_ml | Option\ | ✓ water_intake | | 16 | urine_output_ml | Option\ | ✓ urine_output | | 17 | notes | Option\ | ✓ | | 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 和配置健壮,权限码和数据模型映射有差距 |