Files
smartmate/docs/功能决策记录/任务四象限_自动平移_决策记录.md
Losita 84371e2ff8 Version: 0.6.3.dev.260316
 feat(task): 新增四象限任务懒触发自动平移链路(读时派生 + Outbox 异步收敛)

- 🧩 为 `Task` 模型新增 `urgency_threshold_at` 字段,并补充复合索引 `user_id,is_completed,urgency_threshold_at,priority` 及相关事件 payload
- ♻️ 重构 `TaskService.GetUserTasks`:调整为“缓存/DB 读取原始任务 -> 读时派生优先级(`2 -> 1`、`4 -> 3`)-> 通过 `SETNX` 去重后发布平移事件”的处理链路
- 🚚 新增任务平移事件链路:
  - `service/events/task_urgency_promote.go`
  - 事件类型:`task.urgency.promote.requested`
  - 支持 `Publish` + `RegisterHandler` + `ConsumeAndMarkConsumed` 的事务化消费流程
- 🛡️ 为 `TaskDAO` 新增幂等批量更新能力 `PromoteTaskUrgencyByIDs`,采用条件更新策略,仅对“达到阈值且未完成”的任务生效
- 🔌 更新启动接线逻辑:注册任务平移 handler,并将 `eventBus` 注入 `NewTaskService`
- 🧹 修复并升级任务缓存层,统一为 `[]model.Task` 原始模型缓存;同时清理误导性注释,并补充详细中文步骤化注释
- 🔗 打通 `QuickNote` 链路中的 `urgency_threshold_at` 透传与校验,覆盖 `state` / `tool` / `nodes` / `prompt` / `agent_quick_note` 全链路
- 💾 写库时补充落库 `task.UrgencyThresholdAt`
- 📝 新增功能决策记录

之前画的饼正在一块块填上~这一块饼填上之后,第一批开发的后端部分基本已经搞定了。后面的功能全都是天马行空的拓展功能。
2026-03-16 20:33:33 +08:00

5.7 KiB
Raw Blame History

功能决策记录FDR任务四象限自动平移懒触发

1. 基本信息

  • 记录编号FDR-2026-03-TASK-URGENCY-PROMOTE
  • 功能名称:任务四象限自动平移(不紧急 -> 紧急)
  • 记录日期2026-03-16
  • 决策状态:已采纳
  • 负责人:项目协作实现(你 + Codex
  • 关联模块:
    • backend/service/task.go
    • backend/service/events/task_urgency_promote.go
    • backend/dao/task.go
    • backend/infra/outbox/*
    • backend/middleware/cache_deleter.go

2. 背景与问题

  • 业务背景:任务在创建时属于某个四象限,随着时间推进,部分“不紧急”任务应自动进入“紧急”象限。
  • 关键约束:
    • 不引入全局定时任务(避免资源常驻扫描)。
    • 已有缓存删除机制依赖 cache_deleterGORM 写库后自动删缓存)。
    • 已有 outbox 总线可承载异步更新。
  • 原始问题:
    • 如果“读时直接改库 + 删缓存”,会引入写放大与重复触发;
    • 如果“只靠 outbox 异步更新”,在消费延迟窗口内二次访问可能读到旧缓存;
    • 需要在一致性与性能之间取平衡,且用户体验不能差。

3. 决策目标

  • 目标 1用户访问任务列表时优先看到“符合当前时间语义”的象限结果。
  • 目标 2真实数据修正异步执行避免把写库延迟绑定到读接口。
  • 目标 3避免重复投递与重复更新控制总线与数据库压力。
  • 非目标:
    • 本次不做定时批处理平台;
    • 本次不做分布式多级缓存一致性框架;
    • 本次不做任务系统分库分表改造。

4. 备选方案

方案 A定时任务全量扫描并更新

  • 描述:后台周期扫描 tasks,到线即批量更新优先级。
  • 优点:数据收敛快、语义直观。
  • 缺点:常驻资源开销高;扫描窗口与峰值冲突风险高;运维复杂度增加。
  • 成本:中高(需要调度、监控、限流与容错)。

方案 B读时同步改库请求内直接 UPDATE

  • 描述:用户读任务列表时,检测到到线任务后立即写库并删缓存。
  • 优点:实现简单,数据立刻落地。
  • 缺点:读接口变“读+写”;高并发时放大锁竞争;延迟直接转嫁给用户请求。
  • 成本:中(实现快,但后续稳定性成本高)。

方案 C采纳读时派生 + SETNX 去重 + outbox 异步落库

  • 描述:
    • 读请求先按 urgency_threshold_at 做“派生展示”(仅本次响应生效);
    • 对需要平移的任务做 Redis SETNX 去重;
    • 去重成功后发布 outbox 事件,消费者异步条件更新 DB
    • DB 更新后由 cache_deleter 自动删缓存,完成最终收敛。
  • 优点:
    • 用户先看到正确业务语义,不被异步延迟影响体感;
    • 读链路不阻塞写库;
    • 复用现有 outbox 与 cache_deleter侵入小、可扩展。
  • 缺点:
    • 派生视图与真实落库存在短暂时间差;
    • 需要维护去重键 TTL 与消费可观测性。
  • 成本:中(实现复杂度适中,长期收益更高)。

5. 最终决策

  • 采纳方案:方案 C读时派生 + 异步收敛)。
  • 关键理由:
    • 最符合“性能优先 + 最终一致”目标;
    • 与现有 outbox、缓存失效机制天然兼容
    • 面向后续通用事件总线扩展(更多异步业务)更友好。

6. 影响范围

  • 涉及模块:
    • TaskService.GetUserTasks:新增读时派生与异步触发。
    • TaskDAO.PromoteTaskUrgencyByIDs:新增幂等条件更新。
    • service/events:新增任务平移事件发布与消费注册。
    • cmd/start.go:新增 handler 注册与 service 注入。
  • 数据与存储影响:
    • tasks 新增字段:urgency_threshold_at
    • 新增复合索引:(user_id, is_completed, urgency_threshold_at, priority)
    • outbox 新增事件类型:task.urgency.promote.requested
  • 接口影响:
    • 对外 API 协议不变;
    • 行为层面变为“读时先派生展示、随后异步收敛”。
  • 监控与日志影响:
    • 新增任务平移事件发布/消费日志;
    • 可按 event_type 维度观测积压与重试。

7. 风险与应对

  • 风险 1outbox 消费延迟导致短时间内 DB 仍是旧值。
    • 应对策略:读时派生保证用户看到新语义;最终由异步消费收敛。
  • 风险 2同一任务被多次重复投递。
    • 应对策略:SETNX + TTL 去重DAO 层条件更新保证幂等。
  • 风险 3缓存一致性混乱误删/抖动)。
    • 应对策略:不在读链路主动删缓存;只在真实写库后由 cache_deleter 自动失效。

8. 验证与回滚

  • 验证方式:
    • 用到线前后样例任务验证 GetUserTasks 返回象限变化;
    • 验证 outbox 事件是否成功入队、消费并更新 DB
    • 验证 DB 更新后缓存是否由 cache_deleter 自动清理。
  • 成功判定标准:
    • 用户可在到线后第一时间看到“已平移”结果;
    • DB 在异步窗口后收敛为相同状态;
    • 无明显重复投递和异常写放大。
  • 回滚方案:
    • 关闭任务平移事件发布(保留读时派生)可快速止损;
    • 或回退到“只读不平移”行为(临时关闭派生逻辑)。

9. 里程碑与后续计划

  • 里程碑 1完成懒触发主链路与事件消费者落地已完成
  • 里程碑 2完成联调与基本回归已完成
  • 后续优化项:
    • 增加按 event_type=task.urgency.promote.requested 的积压监控面板;
    • 根据线上情况调优去重 TTL
    • 引入批量聚合发布(按用户或时间窗口)进一步降事件量。

10. 复盘结论(上线后补充)

  • 实际效果:待补充。
  • 与预期偏差:待补充。
  • 是否需要二次决策:待补充。