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