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 页面的"管理"按钮(确认/处置告警)永远不显示,因为 health.alert.manage 权限码不存在于系统。用户虽然可以通过 API 直接操作,但 UI 层面无法触发管理动作。
修复:将 health.alert.manage 改为 health.alerts.manage(复数形式)。
3.5 前端路由级权限控制
Web 前端通过 PrivateRoute 组件做路由守卫,当前仅检查:
- 是否已认证(
isAuthenticated)
/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
| 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)支持 systolic_bp_evening/diastolic_bp_evening 指标查询,危急值检测(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 和配置健壮,权限码和数据模型映射有差距 |