对应 5 份设计规格,共 75 个 Task: 1. 性能优化 (12 Task) — 批量INSERT/N+1内联name/合并COUNT/按需重绘/chunk拆分 2. 安全纵深防御 (8 Task) — RLS/行级数据范围/Redis session_key/审计哈希链 3. 事件驱动架构 (10 Task) — 11个缺失事件补发/LISTEN+NOTIFY/schema版本化 4. 前端工程化 (10 Task) — hook统一/组件拆分/Bundle优化 5. 可观测性运维 (10 Task) — 深度健康检查/Prometheus/OTel/生产Docker/告警
125 lines
5.4 KiB
Markdown
125 lines
5.4 KiB
Markdown
# 性能优化实施计划
|
||
|
||
> 设计规格: `docs/superpowers/specs/2026-04-26-performance-optimization-design.md`
|
||
> 日期: 2026-04-26 | 状态: draft | 总周期: 3 周
|
||
|
||
---
|
||
|
||
## Phase 1: 后端批量插入优化(Week 1, P0)
|
||
|
||
### Task 1: device_reading_service batch_insert_readings 改为 SeaORM insert_many
|
||
|
||
**涉及文件**: `crates/erp-health/src/service/device_reading_service.rs`
|
||
|
||
**步骤**: 将 `batch_insert_readings()` 中 for 循环逐条 `model.insert(db).await` 替换为构建 `Vec<ActiveModel>` 后调用 `insert_many()` + `on_conflict(columns([...]).do_nothing())`。补充 50 条模拟数据的批量插入测试。
|
||
|
||
**验收**: `cargo test -p erp-health device_reading` 通过;500 条插入延迟 < 100ms。
|
||
|
||
### Task 2: device_reading_service upsert_hourly_aggregates 批量化
|
||
|
||
**涉及文件**: `crates/erp-health/src/service/device_reading_service.rs`
|
||
|
||
**步骤**: 批量查出已存在的聚合记录(按 patient_id + device_type + hour),分为"新增"和"更新"两组,分别 `insert_many` 和批量 `update`,事务包装。补充同一小时多次 upsert 的聚合精度测试。
|
||
|
||
**验收**: 聚合值(avg/min/max)精度与优化前一致;`cargo test` 通过。
|
||
|
||
---
|
||
|
||
## Phase 2: 前端 N+1 根治(Week 2, P0)
|
||
|
||
### Task 3: 后端 appointment_service list 返回 patient_name/doctor_name
|
||
|
||
**涉及文件**: `crates/erp-health/src/service/appointment_service.rs`, `dto/mod.rs`, `handler/mod.rs`
|
||
|
||
**步骤**: 列表 DTO 增加 `patient_name/doctor_name` 字段,service 查询中 `find_also_related` 或子查询关联 users 表获取 display_name,handler 层映射到响应 DTO。保持 Option 类型向后兼容。
|
||
|
||
**验收**: `GET /api/v1/health/appointments` 每条记录含 name 字段。
|
||
|
||
### Task 4: 后端 consultation_service / follow_up_service 同样内联 name
|
||
|
||
**涉及文件**: `consultation_service.rs`, `follow_up_service.rs`, `dto/mod.rs`
|
||
|
||
**步骤**: consultation list 添加 patient_name/doctor_name,follow_up list 添加 patient_name,复用 Task 3 的 JOIN 模式。
|
||
|
||
**验收**: 两个列表 API 响应均含 name 字段;`cargo test` 通过。
|
||
|
||
### Task 5: 前端 AppointmentList 移除 nameCache,改用内联字段
|
||
|
||
**涉及文件**: `apps/web/src/pages/health/AppointmentList.tsx`
|
||
|
||
**步骤**: 移除 `nameCache` useState 及逐条请求 name 的 useEffect,表格列直接使用后端返回的 `patient_name/doctor_name`,消除 fetchData 对 nameCache 的依赖。
|
||
|
||
**验收**: Network 面板仅 1 个列表 API 请求;首屏 < 500ms(20 条记录)。
|
||
|
||
### Task 6: 前端 ConsultationList / FollowUpTaskList 同样改造
|
||
|
||
**涉及文件**: `ConsultationList.tsx`, `FollowUpTaskList.tsx`
|
||
|
||
**步骤**: 两个页面移除 nameCache,使用内联 name 字段。验证无 N+1 请求。
|
||
|
||
**验收**: 两个页面 Network 面板无 N+1 请求;`pnpm build` 通过。
|
||
|
||
---
|
||
|
||
## Phase 3: 后端查询优化(Week 3, P1)
|
||
|
||
### Task 7: stats_service 合并多次 COUNT 为 GROUP BY
|
||
|
||
**涉及文件**: `crates/erp-health/src/service/stats_service.rs`
|
||
|
||
**步骤**: 6 个统计函数从多次 COUNT 合并为 `SELECT status, COUNT(*) GROUP BY status` + 应用层 HashMap 聚合。`compute_avg_field` 用宏生成静态 SQL 常量替代 `format!` 拼接。编写对比测试确认 GROUP BY 与多次 COUNT 结果一致。
|
||
|
||
**验收**: `get_follow_up_statistics` 查询次数从 4 降为 1;`compute_avg_field` 不再 format! 拼接。
|
||
|
||
### Task 8: patient_service get_health_summary 用 tokio::join! 并行化
|
||
|
||
**涉及文件**: `crates/erp-health/src/service/patient_service.rs`
|
||
|
||
**步骤**: `get_health_summary()` 中 4 次 `.await` 改为 `tokio::join!` 并行执行,各查询错误独立处理(未找到返回 None)。
|
||
|
||
**验收**: 并行化后返回数据与串行一致;`cargo test` 通过。
|
||
|
||
### Task 9: alert_engine 预加载规则批量评估
|
||
|
||
**涉及文件**: `crates/erp-health/src/service/alert_engine.rs`
|
||
|
||
**步骤**: 批量查询患者最近 cooldown 期间所有 alerts 构建 `HashSet<rule_id>`,按 device_type 批量查最新 hourly 记录后在内存匹配规则条件。重构为:批量加载 -> 内存过滤 -> 批量生成告警。
|
||
|
||
**验收**: 10 条规则评估查询次数从 ~20 降为 2-3;`cargo test` 通过。
|
||
|
||
---
|
||
|
||
## Phase 4: 前端渲染优化(Week 3, P2)
|
||
|
||
### Task 10: PluginCRUDPage columns useMemo + 拆分子组件
|
||
|
||
**涉及文件**: `apps/web/src/pages/PluginCRUDPage.tsx`
|
||
|
||
**步骤**: `columns` 包裹 `useMemo(() => [...], [schema])`,搜索栏/分页/表格拆为独立子组件。
|
||
|
||
**验收**: 输入搜索时 columns 不重建;`pnpm build` 通过。
|
||
|
||
### Task 11: PluginGraphPage 按需重绘
|
||
|
||
**涉及文件**: `apps/web/src/pages/PluginGraphPage.tsx`
|
||
|
||
**步骤**: 移除持续 requestAnimationFrame 循环,改为数据变更 useEffect 触发单次重绘 + ResizeObserver 监听容器变化。
|
||
|
||
**验收**: 静态页面时 CPU 占用 < 1%;`pnpm build` 通过。
|
||
|
||
### Task 12: vite.config.ts manualChunks 拆分 heavy deps
|
||
|
||
**涉及文件**: `apps/web/vite.config.ts`
|
||
|
||
**步骤**: `manualChunks` 配置 `vendor-charts`(@ant-design/charts) / `vendor-flow`(@xyflow/react) / `vendor-editor`(@wangeditor/editor),对应路由改用 `React.lazy` 动态加载。
|
||
|
||
**验收**: 主 bundle gzip 体积降低 200KB+;图表/流程图/编辑器按需加载。
|
||
|
||
---
|
||
|
||
## 执行原则
|
||
|
||
1. **每 Task 完成后立即提交** — 不积压,保持可追溯
|
||
2. **Phase 1-2 为 P0** — 批量插入和 N+1 根治直接影响生产性能
|
||
3. **cargo test + pnpm build 必须通过** — 每个 Task 完成后验证
|