Phase 1 安全热修复: - P0-1: /uploads 文件服务添加 JWT 认证中间件(支持 header + query param) - P0-2: analytics/batch 路由从 public 移到 protected_routes - P0-3: plugin engine SQL 注入修复(format! → 参数化查询) - P0-new: stats_service compute_avg_field 字段白名单 + FLOAT8 类型转换 Phase 2 数据完整性: - P0-4: 组织删除级联检查(添加部门存在性校验) - P0-5: 部门删除级联检查(添加岗位 + 用户存在性校验) - P0-8: workflow on_tenant_deleted 实现 5 实体批量删除 - P0-7: 并行网关 race condition 修复(consumed → completed 原子转换) Phase 3 P1 后端 Bug: - P1-12: plugin host 表名消毒(使用 sanitize_identifier) - P1-10: workflow deprecated 状态转换(published → deprecated) - P1-11: workflow 更新验证条件(nodes/edges 任一变化即验证) - P0-9: 小程序 .gitignore 添加 .env/.env.*/日志 - P1-19: 小程序加密密钥替换为 64 字符强密钥 Phase 4 消息模块: - P1-5: 通知偏好 GET 路由 + handler - P1-4: 消息模板 update/delete CRUD + version - P2-8: mark_all_read SQL 添加 version + 1 - P2-7: markAsRead 改为乐观更新 + 失败回滚 Phase 5 前端修复: - P2-9: 通知面板点击导航到 /messages - P2-1: 随访任务患者名批量 ID 解析(替代 UUID 显示) - P2-5: AppointmentList 分离 patient_id/doctor_id 分别调用 API - P2-17: PluginMarket installed 字段修正(name → id) - P3-3: 路由标题 fallback 改为模式匹配(支持 :id 动态路径) - P2-15: workflow updateDefinition 添加 version 字段 - P3-9: Kanban 版本使用记录实际 version - P2-21: secure-storage 生产环境无密钥时阻止存储 - P3-11: destroyOnHidden → destroyOnClose - P3-13: PendingTasks 深色模式 Tag 颜色适配 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
10 KiB
10 KiB
HMS 审计问题全量修复计划
日期: 2026-04-26 | 基于 audit-report-2026-04-26.md 的 72 个问题 分 7 个 Phase 按优先级执行,每个 Phase 完成后提交验证
Context
全系统审计发现 72 个问题(9 P0 / 18 P1 / 22 P2 / 15 P3 / 6 P4 + 2 额外发现)。本计划将所有问题按修复复杂度和依赖关系分 7 个阶段执行。
修正后的审计计数
经代码验证,部分 P0 发现需要修正:
- P0-4/5 部分存在:组织删除已检查子组织(
org_service.rs:241-252),部门删除已检查子部门(dept_service.rs:264-275),但均未检查关联的岗位/用户 - 新增 P0:
stats_service.rs:423-444的compute_avg_field函数也有 SQL 注入(format!拼接field参数) - P3-5 误报:登录默认 tenant_id 是开发环境行为,非 bug
Phase 1: 安全热修复(P0 安全,4 个问题)
1.1 P0-1: 上传文件认证
- 文件:
crates/erp-server/src/main.rs:543-546 - 现状:
ServeDir在 auth middleware 之外 - 修复: 将
/uploads移到 protected_routes,或添加axum_middleware::from_fnJWT 检查 - 方案: 创建
serve_file_with_auth中间件,从 query param 或 header 取 token 验证
1.2 P0-2: analytics/batch 认证
- 文件:
crates/erp-server/src/main.rs:497-500 - 现状: 在
public_routes中 - 修复: 移到
protected_routes(加.merge(...)到 line 514 的 protected_routes)
1.3 P0-3: plugin engine SQL 注入
- 文件:
crates/erp-plugin/src/engine.rs:630-637 - 现状:
format!()拼接pid - 修复: 改用
Statement::from_sql_and_values+$1,$2参数化
1.4 P0-new: stats_service compute_avg_field SQL 注入
- 文件:
crates/erp-health/src/service/stats_service.rs:423-444 - 现状:
format!("SELECT AVG({field}) AS avg_val...")直接拼接字段名 - 修复: 添加字段名白名单验证 + 使用
CAST(AVG(...) AS FLOAT8)同时解决类型问题
Phase 2: 数据完整性修复(P0 数据,4 个问题)
2.1 P0-4: 组织删除级联(补充检查)
- 文件:
crates/erp-auth/src/service/org_service.rs:240-252 - 现状: 已检查子组织,未检查部门
- 修复: 在 line 252 后添加部门存在性检查
// Check for departments under this org
let depts = department::Entity::find()
.filter(department::Column::OrganizationId.eq(id))
.filter(department::Column::DeletedAt.is_null())
.one(db).await?;
if depts.is_some() {
return Err(AuthError::Validation("该组织下存在部门,无法删除".into()));
}
2.2 P0-5: 部门删除级联(补充检查)
- 文件:
crates/erp-auth/src/service/dept_service.rs:264-275 - 现状: 已检查子部门,未检查岗位
- 修复: 添加岗位存在性检查
2.3 P0-6: 岗位删除级联
- 文件:
crates/erp-auth/src/service/position_service.rs:214-249 - 现状: 无关联检查
- 修复: 检查 user_position 关联表中是否有用户分配到此岗位
2.4 P0-8: workflow on_tenant_deleted
- 文件:
crates/erp-workflow/src/module.rs:148-154 - 现状: 空操作
Ok(()) - 修复: 实现 5 个实体的批量软删除(process_definition → instance → task/token/variable)
- 实体:
process_definitions,process_instances,tasks,tokens,process_variables
Phase 3: 并行网关 + P1 后端 Bug(7 个问题)
3.1 P0-7: 并行网关 token 关联
- 文件:
crates/erp-workflow/src/engine/executor.rs:369-425 - 修复: 在 token 表添加
fork_id字段(或使用 token 创建时间窗口),区分不同 fork 产生的 token - 轻量方案: 使用
SELECT ... FOR UPDATE加行锁 + 检查 token 的consumed_at时间窗口
3.2 P1-1: 统计报表 SQL 类型修复
- 文件:
crates/erp-health/src/service/stats_service.rs - 修复: SQL 中使用
CAST(AVG(...) AS FLOAT8)或AVG(...)::FLOAT8 - 同时修复:
compute_avg_field的字段名白名单
3.3 P1-12: plugin host 表名消毒
- 文件:
crates/erp-plugin/src/host.rs:339 - 修复: 使用已有的
sanitize_identifier()函数
3.4 P1-10: workflow deprecated 状态
- 文件:
crates/erp-workflow/src/service/definition_service.rs - 修复: 添加
deprecate方法,实现published → deprecated转换
3.5 P1-11: workflow 更新验证
- 文件:
crates/erp-workflow/src/service/definition_service.rs:174-181 - 修复: nodes 或 edges 任一存在即执行验证
3.6 P0-9: 小程序 .gitignore
- 文件:
apps/miniprogram/.gitignore - 修复: 添加
.env,.env.*,*.log
3.7 P1-19: 小程序加密密钥
- 文件:
apps/miniprogram/.env - 修复: 生成 64 字符 hex 强密钥替换
Phase 4: 消息模块修复(P1,5 个问题)
4.1 P1-5: 通知偏好 GET + version
- 后端:
crates/erp-message/src/module.rs添加GET /message-subscriptions路由 - 后端:
crates/erp-message/src/handler/subscription_handler.rs添加get_subscriptionhandler - 前端:
apps/web/src/pages/messages/NotificationPreferences.tsx- useEffect 中调用 GET API 加载已有配置
- 保存时发送 version 字段
4.2 P1-4: 消息模板 CRUD
- 后端:
template_service.rs添加update,delete方法 - 后端:
module.rs添加PUT /message-templates/{id},DELETE /message-templates/{id}路由 - 前端:
MessageTemplates.tsx添加编辑/删除按钮
4.3 P2-6/7/8: 消息 store + mark_all_read 修复
stores/message.ts: markAsRead 改为乐观更新 + 失败回滚stores/message.ts: 添加 markAllRead action,重置 unreadCount 为 0message_service.rs:298-326: mark_all_read SQL 中添加version = version + 1
4.4 P2-9: 通知面板点击导航
NotificationPanel.tsx:81-85: 添加navigate('/messages')跳转
4.5 P1-20: urlCheck 配置
- 文件:
apps/miniprogram/project.config.json:6 - 修复: 添加注释说明仅开发环境使用 false,或改用条件配置
Phase 5: 前端 P2-P3 Bug 修复(15 个问题)
5.1 P2-1: 随访任务患者名 UUID
- 文件:
apps/web/src/pages/health/FollowUpTaskList.tsx - 修复: 在
fetchTasks后添加AppointmentList风格的批量 ID 解析循环
5.2 P2-5: AppointmentList 冗余请求
- 文件:
apps/web/src/pages/health/AppointmentList.tsx:103-121 - 修复: 分离 patient_id 和 doctor_id,分别调用对应 API
5.3 P3-3: PatientDetail 标题"页面"
- 文件:
apps/web/src/layouts/MainLayout.tsx:84-95, 387 - 修复: 将
routeTitleFallback查找改为路径模式匹配(用startsWith+ 动态段替换)
5.4 P3-1: 已完成任务显示操作按钮
- 文件: 健康模块各列表页
- 修复: 根据状态条件渲染按钮
5.5 P2-17: PluginMarket installed 字段
- 文件:
apps/web/src/pages/PluginMarket.tsx:68 - 修复:
Set改为result.data.map(p => p.id)而非p.name
5.6 P3-6: 审计日志 UUID
- 文件:
apps/web/src/pages/settings/AuditLogViewer.tsx:123-135 - 修复: 添加用户 ID → 用户名解析(批量查询或缓存)
5.7 P3-7: 审计日志资源类型过滤
- 文件:
AuditLogViewer.tsx:7-18 - 修复: 添加 plugin 相关类型到 RESOURCE_TYPE_OPTIONS
5.8 P3-9: Kanban version 硬编码
- 文件:
apps/web/src/pages/PluginKanbanPage.tsx:113 - 修复: 使用 record 的实际 version 字段
5.9 P3-11: destroyOnHidden → destroyOnClose
- 文件:
ProcessDefinitions.tsx:192 - 修复: 替换 prop 名
5.10 P3-13: 深色模式 Tag
- 文件:
PendingTasks.tsx:95-101 - 修复: 使用
isDark条件判断颜色
5.11 P2-21: secure-storage 明文回退
- 文件:
apps/miniprogram/src/utils/secure-storage.ts:12 - 修复: 生产环境下密钥为空时阻止存储,抛出错误
5.12 P3-12: InstanceMonitor node_id
- 文件:
InstanceMonitor.tsx:149 - 修复: 从 definition 的 nodes 中查找 node_name
5.13 P2-14: 委派 UUID 输入
- 文件:
PendingTasks.tsx:207-213 - 修复: 替换为用户搜索选择组件
5.14 P2-15: UpdateDefinition version
- 文件:
apps/web/src/api/workflowDefinitions.ts:45-51 - 修复: 添加 version 字段
5.15 P3-4: any 类型替换
- 搜索
apps/web/src/中 4 处 any,替换为具体类型
Phase 6: 功能补全(P1 功能缺失,8 个问题)
6.1 P1-3: 消息 SSE 推送(最小可行方案)
- 后端: 添加
GET /api/v1/messages/streamSSE 端点 - 后端: EventBus 订阅
message.sent事件推送给对应用户 - 前端: NotificationPanel 中连接 SSE,收到事件立即更新
- 注意: 先实现 SSE(比 WebSocket 简单),后续可升级
6.2 P1-6/7/8/9: 工作流引擎功能补全
- P1-6 ServiceTask: 添加 HTTP 调用能力(基础版:支持 GET/POST URL)
- P1-7 事件注册: 实现
register_event_handlers,监听user.deleted等事件 - P1-8 任务认领: 添加
claim方法,支持 candidate_groups 过滤列表 - P1-9 超时升级: timeout checker 中添加自动通知发布
6.3 P1-15: 名称唯一性
- 文件:
org_service.rs,dept_service.rs - 修复: create/update 时检查同 tenant 下名称唯一性
6.4 P1-18: 消息群发 fan-out
- 文件:
message_service.rs - 修复: 当 recipient_type 为 role/dept/all 时,查询对应用户列表,批量创建消息
Phase 7: P3-P4 收尾 + 优化(6 个问题)
- P4-1: PluginAdmin purge 按钮状态
- P4-3: recover_plugins tenant 过滤
- P4-4: LanguageManager 编辑弹窗
- P4-5: ChangePassword 后端验证
- P4-6: Settings API URL 编码
- P3-2: ArticleEditor 图片上传(标记为未来任务)
- P3-14: 偏好设置 DND 时间范围验证
- P3-15: 小程序空状态处理
验证计划
每个 Phase 完成后执行:
cargo check— 编译通过cargo test --workspace— 所有测试通过- 浏览器验证 — 启动服务,操作对应页面确认修复生效
git commit— 提交修复git push— 推送到远程
关键验证点
| Phase | 验证方式 |
|---|---|
| Phase 1 | curl 无 token 访问 /uploads 应 401 |
| Phase 2 | 尝试删除含部门的组织应返回错误 |
| Phase 3 | 统计报表页面应正常加载数据 |
| Phase 4 | 偏好设置保存后重载应显示已有配置 |
| Phase 5 | FollowUpTaskList 患者名应显示而非 UUID |
| Phase 6 | 发送角色消息,该角色用户应收到 |
| Phase 7 | 全量回归测试 |