- .kiro/specs/ → docs/specs/(41 个历史需求 spec 迁移,移除 .config.kiro) - CLAUDE.md 三层拆分:根文件精简 + apps/backend/CLAUDE.md + .claude/commands/ - 新增 /spec-close、/pre-change 两个工作流命令 - DDL 基线刷新(从测试库重新导出 11 个文件,dws 35→38 表,biz 18→21 表) - BD_Manual → BD_manual 命名统一(48 个文件) - 修复 3 处文档与数据库不一致(auth.users.status 默认值、scheduled_tasks 字段、RLS 视图数) - 新增 BD_manual_public_rbac_tables.md(public schema 8 张 RBAC/工作流表) - 合并 biz.trigger_jobs 文档(10→12 字段,归档独立文档) - docs/database/README.md 索引更新 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
302 lines
15 KiB
Markdown
302 lines
15 KiB
Markdown
# 实施计划:小程序数据获取与展示效率优化
|
||
|
||
## 概述
|
||
|
||
按"数据正确性优先于性能"原则,先拍基线快照、再做 DDL 变更、然后后端优化、前端优化,最后收尾验证。后端使用 Python(FastAPI + pytest/hypothesis),前端使用 TypeScript(微信小程序 + fast-check),数据库使用 PostgreSQL DDL。
|
||
|
||
## 任务
|
||
|
||
- [ ] 1. 数据基线快照(优化前)
|
||
- [x] 1.1 创建基线快照测试脚本 `tests/test_perf_optimization_baseline.py`
|
||
- 使用小燕真实数据(`assistant_id: 2964673443302213`、`site_id: 2790685415443269`)
|
||
- 对 `get_coach_detail`、`get_task_list_v2`、`get_task_detail` 各执行一次
|
||
- 将完整返回值序列化为 JSON 存储到 `tests/fixtures/perf_optimization/`
|
||
- 实现逐字段 diff 对比函数,排除 `updated_at`、`data_updated_at` 等时间戳字段
|
||
- diff 覆盖:到手金额、RS 指数、会员余额、服务记录条数/排序、酒水明细、TOP 客户列表、`aiAnalysis`、`talkingPoints`
|
||
- _Requirements: 9.1, 9.2, 9.3, 9.4, 9.5, 9.6, 10.4_
|
||
|
||
- [x] 1.2 执行基线快照采集
|
||
- 运行 `pytest tests/test_perf_optimization_baseline.py -v` 生成基线 JSON 文件
|
||
- 确认三个快照文件已生成并纳入版本控制
|
||
- _Requirements: 9.1, 9.6_
|
||
|
||
- [ ] 2. 数据库 DDL 变更
|
||
- [ ] 2.1 创建迁移脚本 `db/zqyy_app/migrations/YYYY-MM-DD__coach_tasks_updated_at.sql`
|
||
- `ALTER TABLE biz.coach_tasks ADD COLUMN updated_at TIMESTAMPTZ NOT NULL DEFAULT now()`
|
||
- 创建触发器函数 `biz.coach_tasks_set_updated_at()` 和触发器 `trg_coach_tasks_updated_at`
|
||
- 触发器在 `BEFORE UPDATE` 时自动设置 `NEW.updated_at = now()`
|
||
- _Requirements: 6.4, 6.5_
|
||
|
||
- [ ] 2.2 编写属性测试:updated_at 自动更新
|
||
- **Property 9: 任务更新后 updated_at 自动变化**
|
||
- 使用 hypothesis 生成随机任务状态变更
|
||
- 验证 UPDATE 后 `updated_at` 严格大于更新前的值
|
||
- **验证: 需求 6.4**
|
||
|
||
- [ ] 3. 检查点 — DDL 变更验证
|
||
- 执行迁移脚本到测试库,验证新字段和触发器已正确创建
|
||
- 确保 Property 9 测试通过,ask the user if questions arise.
|
||
|
||
- [ ] 4. 后端优化:Performance 页 N+1 查询消除
|
||
- [ ] 4.1 新增 `fdw_queries.get_relation_index_batch()` 函数
|
||
- 接受 `conn, site_id, assistant_id, member_ids` 参数
|
||
- 返回 `dict[int, float]`(member_id → rs_display 映射)
|
||
- `member_ids` 为空时直接返回空字典,不执行 SQL
|
||
- 使用 `WHERE assistant_id = %s AND member_id = ANY(%s)` 查询
|
||
- 复用已有 `_fdw_context` 连接管理
|
||
- _Requirements: 1.2, 1.3_
|
||
|
||
- [ ] 4.2 编写属性测试:批量 RS 查询与逐条查询等价性
|
||
- **Property 1: 批量 RS 查询与逐条查询等价性**
|
||
- 使用 hypothesis `st.lists(st.integers(min_value=1))` 生成 member_ids
|
||
- 验证 `get_relation_index_batch` 返回与逐条 `get_relation_index` 结果完全一致
|
||
- **验证: 需求 1.4**
|
||
|
||
- [ ] 4.3 修改 `coach_service._build_top_customers()` 使用批量查询
|
||
- 将 `for mid in member_ids` 循环替换为单次 `get_relation_index_batch` 调用
|
||
- 返回数据结构不变
|
||
- _Requirements: 1.1, 1.5_
|
||
|
||
- [ ] 4.4 修改 `coach_service.get_coach_detail()` 添加 `data_updated_at` 字段
|
||
- `top_customers` 列表中每个客户新增 `data_updated_at`(取查询时 `now()`)
|
||
- _Requirements: 6.2_
|
||
|
||
- [ ] 4.5 编写单元测试:`member_ids` 为空返回空字典
|
||
- 验证边界条件:空列表不执行 SQL,直接返回 `{}`
|
||
- _Requirements: 1.3_
|
||
|
||
- [ ] 5. 后端优化:Task-List 页服务端过滤
|
||
- [ ] 5.1 验证并完善 `task_manager.get_task_list_v2()` SQL 层过滤
|
||
- 确认 SQL WHERE 子句已包含 `relationship_building` 任务的 RS 范围过滤
|
||
- 验证 `total` 计数与实际过滤后结果集一致
|
||
- 返回每个任务项新增 `updated_at` 字段
|
||
- _Requirements: 2.1, 2.2, 2.4, 2.5, 6.1_
|
||
|
||
- [ ] 5.2 编写属性测试:RS 范围过滤
|
||
- **Property 2: relationship_building 任务 RS 范围过滤**
|
||
- 生成随机 RS 值和 rs_min/rs_max 范围
|
||
- 验证返回的 `relationship_building` 任务 RS 指数在 `(rs_min, rs_max)` 范围内
|
||
- **验证: 需求 2.2**
|
||
|
||
- [ ] 5.3 编写属性测试:分页 total 一致性
|
||
- **Property 3: 分页 total 与实际结果集一致性**
|
||
- 生成随机 page/page_size 参数
|
||
- 验证 `total` 等于遍历所有页累计的 `items` 总数
|
||
- **验证: 需求 2.4**
|
||
|
||
- [ ] 5.4 编写单元测试:RS 排除列表查询失败降级
|
||
- 验证查询失败时降级为不排除任何任务
|
||
- _Requirements: 2.3_
|
||
|
||
- [ ] 6. 后端优化:Task-Detail 页 ETL 查询合并
|
||
- [ ] 6.1 新增 `fdw_queries.batch_query_for_task_detail()` 函数
|
||
- 接受 `conn, site_id, assistant_id, member_id` 参数
|
||
- 单连接批量查询 `member_info`、`balance`、`rs_score`
|
||
- 每个子查询用 try/except 包裹,失败返回默认值
|
||
- 复用 `_fdw_context` 单连接模式
|
||
- _Requirements: 5.1, 5.2, 5.3_
|
||
|
||
- [ ] 6.2 编写属性测试:Task-Detail 批量查询等价性
|
||
- **Property 8: Task-Detail 批量查询与独立查询等价性**
|
||
- 生成随机 member_id
|
||
- 验证 `batch_query_for_task_detail` 返回与三个独立查询结果一致
|
||
- **验证: 需求 5.4**
|
||
|
||
- [ ] 6.3 修改 `task_manager.get_task_detail()` 使用批量查询
|
||
- 将 3 个独立 ETL 查询替换为 `batch_query_for_task_detail` 单次调用
|
||
- 返回数据结构不变,新增 `updated_at` 字段
|
||
- _Requirements: 5.1, 6.3_
|
||
|
||
- [ ] 6.4 编写属性测试:接口返回包含版本时间戳
|
||
- **Property 4: 接口返回包含版本时间戳**
|
||
- 验证 `get_task_list_v2`、`get_task_detail`、`get_coach_detail` 返回数据包含有效 ISO 8601 时间戳
|
||
- **验证: 需求 3.2, 6.1, 6.2, 6.3**
|
||
|
||
- [ ] 6.5 编写单元测试:子查询独立降级
|
||
- 验证某子查询失败时返回默认值,不影响其他子查询
|
||
- _Requirements: 5.3_
|
||
|
||
- [ ] 7. 后端优化:酒水聚合 CTE 预聚合(低优先级)
|
||
- [ ] 7.1 修改 `fdw_queries.get_service_records_for_task()` 添加 CTE 分支
|
||
- 服务记录 > 50 条时使用 CTE 预聚合替代 `LEFT JOIN LATERAL`
|
||
- ≤ 50 条时保持现有方式
|
||
- _Requirements: 8.1, 8.3_
|
||
|
||
- [ ] 7.2 编写属性测试:CTE 预聚合与 LEFT JOIN LATERAL 等价性
|
||
- **Property 12: CTE 预聚合与 LEFT JOIN LATERAL 等价性**
|
||
- 生成随机服务记录集合(> 50 条)
|
||
- 验证两种方式返回的 `drinks` 字段内容完全一致
|
||
- **验证: 需求 8.2**
|
||
|
||
- [ ] 8. 后端回归验证
|
||
- [ ] 8.1 运行基线快照 diff 对比
|
||
- 使用相同参数再次调用三个核心接口
|
||
- 逐字段 diff 对比优化前后返回值
|
||
- 确认 `aiAnalysis`、`talkingPoints` 字段未被改变
|
||
- _Requirements: 9.2, 9.3, 9.4, 10.1, 10.2, 10.3, 10.4_
|
||
|
||
- [ ] 9. 检查点 — 后端优化完成验证
|
||
- 运行 Monorepo 属性测试:`cd C:\NeoZQYY && pytest tests/ -v`
|
||
- 运行后端单元测试:`cd apps/backend && pytest tests/ -v`
|
||
- 确保所有属性测试(Property 1-4, 8-9, 12)和单元测试全部通过
|
||
- 确保基线快照 diff 无数据偏差
|
||
- ask the user if questions arise.
|
||
|
||
- [ ] 10. 前端优化:CacheStore 缓存模块
|
||
- [ ] 10.1 创建 `utils/cache-store.ts` 缓存模块
|
||
- 实现 `CacheEntry<T>` 接口(`data`、`updatedAt`、`cachedAt`)
|
||
- 实现 `CacheStore` 类:`get()`、`set()`、`clear()`、`setTaskPreload()`、`getTaskPreload()`
|
||
- 缓存 key 格式:`{dataType}:{id}`
|
||
- 所有读写操作用 try/catch 包裹,异常时静默降级
|
||
- _Requirements: 3.1, 3.3, 3.4, 3.5, 3.6_
|
||
|
||
- [ ] 10.2 编写属性测试:缓存版本校验行为
|
||
- **Property 5: 缓存版本校验行为**
|
||
- 使用 fast-check `fc.string()` 生成 updatedAt 时间戳
|
||
- 验证缓存命中/过期判定逻辑
|
||
- **验证: 需求 3.3, 3.4**
|
||
|
||
- [ ] 10.3 编写属性测试:缓存 clear 后为空
|
||
- **Property 6: 缓存 clear 后为空**
|
||
- 使用 fast-check `fc.dictionary()` 生成随机缓存条目
|
||
- 验证 `clear()` 后所有 `get()` 返回 `null`
|
||
- **验证: 需求 3.6**
|
||
|
||
- [ ] 10.4 编写属性测试:预加载数据 round-trip
|
||
- **Property 7: 预加载数据 round-trip**
|
||
- 使用 fast-check `fc.record()` 生成 TaskPreloadData
|
||
- 验证 `setTaskPreload` → `getTaskPreload` 数据完全一致
|
||
- **验证: 需求 4.1**
|
||
|
||
- [ ] 10.5 编写单元测试:CacheStore 异常降级
|
||
- 验证缓存读写异常时静默降级为网络请求
|
||
- _Requirements: 3.5_
|
||
|
||
- [ ] 11. 前端优化:跳转传参与骨架渲染
|
||
- [ ] 11.1 修改 Task-List 页跳转传参
|
||
- 点击任务卡片时调用 `cacheStore.setTaskPreload()` 写入预加载数据
|
||
- 传递字段:`customer_name`、`heart_score`、`balance`、`task_type`、`task_type_label`
|
||
- _Requirements: 4.1_
|
||
|
||
- [ ] 11.2 修改 Task-Detail 页骨架渲染
|
||
- `onLoad` 时先从 `cacheStore.getTaskPreload()` 读取预加载数据
|
||
- 有缓存:立即渲染骨架内容,同时异步请求完整详情数据
|
||
- 完整数据返回后覆盖骨架内容,实现无缝过渡
|
||
- 无缓存:回退为当前全量加载模式
|
||
- _Requirements: 4.2, 4.3, 4.4_
|
||
|
||
- [ ] 11.3 修改 Performance 页跳转传参
|
||
- 点击客户卡片跳转 Task-Detail 时,将 `customer_name`、`heart_score` 写入 `cacheStore`
|
||
- _Requirements: 4.5_
|
||
|
||
- [ ] 11.4 集成 `onHide` 和下拉刷新清空缓存
|
||
- 在 App 级 `onHide` 回调中调用 `cacheStore.clear()`
|
||
- 在支持下拉刷新的页面 `onPullDownRefresh` 中调用 `cacheStore.clear()`
|
||
- _Requirements: 3.6_
|
||
|
||
- [ ] 12. 前端优化:折前/折后小时数条件显示
|
||
- [ ] 12.1 实现折前/折后条件显示逻辑
|
||
- `duration_raw` 不为 null 且 `duration_raw ≠ duration` 时显示"折前 XX.Xh"标注
|
||
- `duration_raw` 为 null 或等于 `duration` 时仅显示折后小时数
|
||
- 格式化为一位小数(如"折前 2.5h")
|
||
- _Requirements: 7.1, 7.2, 7.3, 7.4_
|
||
|
||
- [ ] 12.2 编写属性测试:折前/折后条件显示逻辑
|
||
- **Property 10: 折前/折后条件显示逻辑**
|
||
- 使用 fast-check `fc.float()` 生成 duration 和 duration_raw
|
||
- 验证显示/隐藏"折前"标注的条件判断
|
||
- **验证: 需求 7.1, 7.2, 7.3**
|
||
|
||
- [ ] 12.3 编写属性测试:折前小时数格式化
|
||
- **Property 11: 折前小时数格式化**
|
||
- 使用 fast-check `fc.float({min: 0, max: 1000})`
|
||
- 验证输出恰好一位小数且 `parseFloat(formatted) ≈ round(hours, 1)`
|
||
- **验证: 需求 7.4**
|
||
|
||
- [ ] 13. 检查点 — 前端优化完成验证
|
||
- 运行前端测试:`cd apps/miniprogram && npm test`
|
||
- 确保所有属性测试(Property 5-7, 10-11)和单元测试全部通过
|
||
- ask the user if questions arise.
|
||
|
||
- [ ] 14. 前后端联调与集成验证
|
||
- [ ] 14.1 启动后端服务,验证各端点完整请求-响应链路
|
||
- 使用真实 FDW 连接验证 SQL 查询正确性
|
||
- 验证 JSON 响应结构与 Schema 定义一致(camelCase 序列化)
|
||
- 验证 `updated_at` / `data_updated_at` 字段在响应中正确返回
|
||
- _Requirements: 6.1, 6.2, 6.3_
|
||
- [ ] 14.2 前端联调验证
|
||
- 确认前端页面能正确调用优化后的 API 并渲染数据
|
||
- 验证缓存命中/过期场景下前端行为正确
|
||
- 验证空数据/降级场景下前端不崩溃
|
||
- _Requirements: 3.3, 3.4, 3.5, 4.2, 4.3, 4.4_
|
||
|
||
- [ ] 15. 数据库变更审计与 DDL 合并
|
||
- [ ] 15.1 审计本次实现中对数据库的所有改动
|
||
- 检查新增字段(`updated_at`)、新增触发器(`trg_coach_tasks_updated_at`)
|
||
- 确认无遗漏的 DDL 变更
|
||
- _Requirements: 6.4, 6.5_
|
||
- [ ] 15.2 执行迁移脚本到测试库
|
||
- 验证新字段和触发器已正确创建(使用 BD 手册中的验证 SQL)
|
||
- _Requirements: 6.4_
|
||
- [ ] 15.3 合并到主 DDL 基线文件
|
||
- 业务库 → `docs/database/ddl/zqyy_app__biz.sql`
|
||
- _Requirements: 6.5_
|
||
- [ ] 15.4 编写回滚脚本(逆序 DROP TRIGGER / DROP FUNCTION / DROP COLUMN)
|
||
- _Requirements: 6.5_
|
||
|
||
- [ ] 16. BD 手册更新
|
||
- [ ] 16.1 更新 BD 手册 `docs/database/BD_Manual_coach_tasks.md`
|
||
- 新增 `updated_at` 字段明细、触发器说明
|
||
- 包含:字段明细、约束与索引、验证 SQL(≥3 条)、兼容性影响、回滚策略
|
||
- 记录变更原因和影响范围
|
||
- _Requirements: 6.4, 6.5_
|
||
|
||
- [ ] 17. 文档同步更新
|
||
- [ ] 17.1 更新后端 API 参考文档
|
||
- 在 `apps/backend/docs/API-REFERENCE.md` 更新接口返回结构变更(`updated_at`、`data_updated_at` 字段)
|
||
- 更新 `apps/backend/README.md` 相关模块摘要
|
||
- _Requirements: 6.1, 6.2, 6.3_
|
||
- [ ] 17.2 更新小程序接口契约文档
|
||
- 在 `docs/miniprogram-dev/API-contract.md` 更新接口返回结构
|
||
- _Requirements: 3.2, 6.1, 6.2, 6.3_
|
||
- [ ] 17.3 更新文档地图
|
||
- 在 `docs/DOCUMENTATION-MAP.md` 新增本次变更相关条目
|
||
- _Requirements: 6.5_
|
||
|
||
- [ ] 18. 最终检查点 — 全量验证
|
||
- 运行 Monorepo 属性测试:`cd C:\NeoZQYY && pytest tests/ -v`
|
||
- 运行后端单元测试:`cd apps/backend && pytest tests/ -v`
|
||
- 运行前端测试:`cd apps/miniprogram && npm test`
|
||
- 确保所有属性测试(Property 1-12)和单元测试全部通过
|
||
- 确保基线快照 diff 无数据偏差
|
||
- 确保 DDL 迁移已合并到主基线
|
||
- 确保 BD 手册已同步更新
|
||
- 确保 API 文档、后端 README、文档地图均已更新
|
||
- 确保前端页面连接真实后端运行正常
|
||
- ask the user if questions arise.
|
||
|
||
## 备注
|
||
|
||
- 标记 `*` 的子任务为可选(属性测试/单元测试),可跳过以加速 MVP
|
||
- 每个任务引用了具体的需求编号以确保可追溯性
|
||
- 属性测试验证通用正确性属性(Property 1-12),单元测试验证具体边界条件
|
||
- 检查点任务确保增量验证,避免问题累积
|
||
- 需求 10(AI 分析数据校对预留)通过基线快照覆盖(任务 1.1 和 8.1),无需独立实施任务
|
||
|
||
## 硬性规定:属性测试执行规范
|
||
|
||
1. **分组执行**:每次只运行单个属性测试函数(`-k "test_property_N"`),禁止一次性跑全部
|
||
2. **后台 CLI**:使用 `controlPwshProcess` 启动测试,`getProcessOutput` 轮询结果(间隔 ≥ 15 秒)
|
||
3. **确认完成再继续**:必须在输出中看到明确的 `passed`/`failed`/`error` 后才可进入下一步,禁止"启动测试后立即继续编码"
|
||
4. **超时保护**:`executePwsh` 的 `timeout` 设为预估时间 3 倍(最少 120 秒),hypothesis `max_examples=100`
|
||
5. **详见**:`.kiro/steering/testing-env.md` 属性测试执行规范章节
|
||
|
||
## 硬性规定:前后端联调期间的效率优化框架
|
||
|
||
当用户在联调过程中提及"效率优化"或"本页已完成,进行效率优化"时,所有调试和改动必须在本 spec 的框架下进行:
|
||
|
||
1. **数据正确性优先**:任何优化改动前先确认基线快照存在(任务 1),改动后跑 diff 验证(任务 8)
|
||
2. **AI 数据不动**:`aiAnalysis`、`talkingPoints` 的查询逻辑和返回结构保持不变(需求 10),后期单独校对
|
||
3. **联调中的样式/交互调整**:属于联调范畴,不受效率优化 spec 约束,但改动不得破坏优化后的数据链路
|
||
4. **联调中发现的数据问题**:如果是优化引入的偏差,回退到基线快照对比定位;如果是原有问题,单独记录不混入本 spec
|