Эх сурвалжийг харах

docs: 打卡任务链路实现计划归档更新 + cfc-web 版本号递增

iwt 1 долоо хоног өмнө
parent
commit
4d43772c34

+ 1 - 1
cfc-web/.last_build_commit

@@ -1 +1 @@
-f22ccd13597c60c71f77bb83ccb72f18590030f7
+f1c1229b1884ce0e674219875258f65f22df1eae

+ 2 - 2
cfc-web/package-lock.json

@@ -1,12 +1,12 @@
 {
   "name": "cfc-web",
-  "version": "1.0.1612",
+  "version": "1.0.1613",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "cfc-web",
-      "version": "1.0.1612",
+      "version": "1.0.1613",
       "dependencies": {
         "@wangeditor/editor": "^5.1.23",
         "@wangeditor/editor-for-vue": "^1.0.2",

+ 108 - 80
docs/superpowers/plans/2026-10-13-health-checkin-task-linkage-plan.md

@@ -1,116 +1,144 @@
-# 饮食/睡眠/运动打卡接入任务完成链路 — 实现计划(最终版)
+# 饮食/睡眠/运动打卡接入任务完成链路:实现计划
 
-> 本文档为已实现成果的归档计划。前置设计文档:`docs/superpowers/plans/2026-10-13-health-checkin-task-linkage.md`、规格:`docs/superpowers/specs/2026-10-13-health-checkin-task-linkage-design.md`。
+> 日期:2026-10-13
+> 状态:全部阶段已实现(实体/Controller/定时生成器/前端任务页与打卡页/API 文档)
+> 前置设计:`docs/superpowers/specs/2026-10-13-health-checkin-task-linkage-design.md`
+> 执行方式:按 superpowers:executing-plans 流程,逐阶段执行、逐阶段验证
 
-## 1. 功能目标
+## 一、方案名称
 
-让饮食/睡眠/运动三类健康打卡可作为任务完成凭证,打通"打卡 ⇄ 任务"双向链路:
+**方案 B:双轨复用现有机制**(一次性任务走 `actionType` 联动 + 每日任务走 `updateProgress` 累计)
 
-- 家长/孩子在任务列表点击打卡类任务,跳转对应打卡页,打卡成功后任务自动完成。
-- 系统每日为能登录用户自动生成 3 条打卡进度任务,打卡时自动累计进度,达标即完成。
+### 备选方案对比
 
-## 2. 架构方案(双轨)
+| 方案 | 核心实现方式 | 优点 | 缺点 | 结论 |
+|------|------------|------|------|------|
+| 方案 A:打卡即任务,合并模块 | 取消独立打卡模块,打卡行为直接建模为任务执行记录 | 概念统一,无双轨 | 推翻现有健康打卡数据模型与页面,改动面大;积分、画像、报告解析链路全部要重做;违背设计文档"五维打卡本次不合并"的既定边界 | 否决 |
+| 方案 B:双轨复用现有机制 | 在打卡 Controller 落库成功后挂钩任务回写;一次性任务用 `actionType` 新枚举值跳转打卡页,每日任务用 `is_daily_progress=1` + taskKey 前缀约定累计进度 | 零 DDL;复用已验证的 `completeTask()` 与 `updateProgress()`;打卡主流程与任务链路解耦,回写失败不影响打卡;双轨互斥、可独立回滚 | 存在两条完成路径,需靠分支矩阵明确分流规则;taskKey 与 title 前缀是字符串约定,两侧需同步维护 | **采纳** |
+| 方案 C:仅支持手动任务,不做每日自动生成 | 只做 `actionType` 联动跳转 + taskId 透传 | 实现量最小 | 系统无法每日自动督促打卡,用户需每天手动建任务,体验断裂;不满足"系统每日自动生成打卡任务"的需求 | 否决 |
 
-| 轨道 | 任务来源 | 完成机制 |
-|------|----------|----------|
-| 一次性联动任务 | 手动创建,`actionType` 新增 `diet/sleep/exercise` 三个取值 | 任务列表跳转打卡页携带 `taskId`,打卡 Controller 落库成功后回写 `taskService.completeTask(taskId)` |
-| 每日进度任务 | `RepeatTaskGenerator` 定时生成,`is_daily_progress=1` | 打卡 Controller 回写 `taskService.updateProgress(userId, taskKey, 1)`,taskKey ∈ {DIET, SLEEP, EXERCISE} |
+### 推荐理由
 
-判定与配对原则:
+方案 B 以最小改动打通"打卡 ⇄ 任务"链路:`Task.actionType` 为 VARCHAR(32),新增 `diet/sleep/exercise` 取值无需 DDL;`TaskService.updateProgress(userId, taskKey, increment)`(第 1653 行)与 `completeTask()` 均已存在并有调用先例(`HealthCheckinService` 签到回写 DAILY_CHECKIN);判定以任务真实类型为准,不依赖前端传参正确性,错误路径统一 try-catch 兜底。
 
-- 打卡请求体带 `taskId`(非空)→ 走一次性任务完成路径;为 null → 走每日任务累计路径。两个 Controller 内分支互斥。
-- 每日任务实例的 `title` 前缀约定 `[DIET]/[SLEEP]/[EXERCISE]`,与 `TaskService.updateProgress` 的 taskKey 匹配一致。
-- `taskId` 字段用 `@TableField(exist = false)` 非持久化,仅作请求透传载体,无需建表迁移。
+## 二、数据流
 
-## 3. 实现阶段分解与完成状态
+### 场景 1:一次性联动任务
 
-### 阶段 1:实体层 ✅(已提交)
+```
+家长/孩子创建任务(actionType=diet)
+  → 任务列表 getActionLabel() 显示"去打卡"
+  → navigateByAction() 跳转 /pages/growth/diet-checkin?taskId=123
+  → 打卡页 onLoad 读取 taskId,提交时透传
+  → HealthMealController.create() 落库成功
+  → 反查 tasks 表:isDailyProgress≠1 → taskService.completeTask(123, memberId, photoUrl, content, "text")
+  → 完成任务 + 发任务积分;原有打卡 5 CF 积分、画像重算保持不变
+```
+
+### 场景 2:每日进度任务
+
+```
+RepeatTaskGenerator 每日生成 [DIET]/[SLEEP]/[EXERCISE] 前缀任务(is_daily_progress=1)
+  → 用户从任务列表或健康 Tab 进入打卡页(可不带 taskId)
+  → HealthXxxController.create() 落库成功
+  → 走 updateProgress(userId, taskKey, 1):按 title 前缀 "[taskKey]" 匹配今日任务
+  → 累加 progress;progress >= target_value 时自动完成并发奖
+```
 
-- `cfc-backend/src/main/java/com/etotem/cfc/entity/HealthMealRecord.java:59` — 新增 `@TableField(exist=false) private Long taskId;`
-- `cfc-backend/src/main/java/com/etotem/cfc/entity/HealthSleepRecord.java:43` — 同上
-- `cfc-backend/src/main/java/com/etotem/cfc/entity/HealthExerciseRecord.java:38` — 同上
-- `cfc-backend/src/main/java/com/etotem/cfc/entity/Task.java:130` — `actionType` 注释补充 `diet/sleep/exercise`(commit `814f30712`)
-- `cfc-backend/src/main/java/com/etotem/cfc/dto/CreateTaskDTO.java:66` — 同上
+taskKey 约定:`HealthMealController` → `DIET`,`HealthSleepController` → `SLEEP`,`HealthExerciseController` → `EXERCISE`。
 
-对应提交:`e81e05bee feat(task): 三个健康打卡Controller挂钩任务回写`、`814f30712 docs(task): actionType 注释补充`
+## 三、完成逻辑分支矩阵
 
-### 阶段 2:控制器层 ✅(已提交,commit `e81e05bee`)
+| 是否带 taskId | 反查任务类型 | isBackfill | 调用 | 效果 |
+|:---:|:---|:---:|:---|:---|
+| 无 | 不反查 | 0 | `updateProgress(userId, taskKey, 1)` | 累计今日对应 taskKey 的每日任务进度;无匹配任务则 log.warn 跳过 |
+| 无 | 不反查 | 1 | 无 | 补打不回写,仅入库 |
+| 有 | `isDailyProgress=1` | 0 | `updateProgress(userId, taskKey, 1)` | 累计进度,达标自动完成 + 发奖 |
+| 有 | `isDailyProgress=1` | 1 | 无 | 补打不回写 |
+| 有 | 一次性任务(actionType ∈ {diet,sleep,exercise} 且非每日进度型) | 0 | `completeTask(taskId, memberId, photoUrl, content, "text")` | 直接完成任务,发任务积分 |
+| 有 | 一次性任务 | 1 | 无 | 补打不回写 |
+| 有 | 任务不存在/已删除 | 0 | 无 | 打卡正常入库,仅 log.warn |
+| 有 | 任务已 completed | 0 | `completeTask` 内部校验拒绝 | 打卡正常,任务状态不变 |
 
-三个打卡 Controller 的 `create()` 在记录落库成功后挂钩任务回写:
+关键约定:分流以任务真实类型为准(后端反查 tasks 表决定走哪条路径),不依赖前端是否正确携带 taskId。
 
-| 文件 | 行号 | 回写逻辑 |
-|------|------|----------|
-| `HealthMealController.java` | 257 `completeTask` / 268 `updateProgress(userId, "DIET", 1)` | taskId 非空→一次性完成;否则累计 DIET 进度 |
-| `HealthSleepController.java` | 75 `completeTask` / 86 `updateProgress(userId, "SLEEP", 1)` | taskKey=SLEEP |
-| `HealthExerciseController.java` | 98 `completeTask` / 109 `updateProgress(userId, "EXERCISE", 1)` | taskKey=EXERCISE |
+## 四、错误处理
 
-回写包 try-catch,失败仅记日志,不影响打卡主流程。
+| 场景 | 行为 |
+|------|------|
+| taskId 指向任务不存在/已完成 | `completeTask` 返回 null,打卡正常,前端仅提示"打卡成功" |
+| 补打(isBackfill=1) | 不触发任务回写,与积分发放逻辑一致 |
+| 当日每日任务未生成 | `updateProgress` 找不到任务,log.warn 跳过,不报错 |
+| 一次性任务与每日任务同时存在 | 双轨独立,互不干扰 |
+| 回写抛异常 | try-catch 包裹,不阻断打卡主流程与积分发放 |
+| 积分重复 | 任务积分与打卡 5 CF 为独立来源,不重复 |
 
-### 阶段 3:定时生成器 ✅(本次提交)
+## 五、不改动边界
 
-`cfc-backend/src/main/java/com/etotem/cfc/task/RepeatTaskGenerator.java`:
+- 不修改 `completeTask()` 内部校验逻辑
+- 不修改 `updateProgress()` 实现(直接复用)
+- 不修改打卡页现有功能(仅新增 taskId 参数支持)
+- 不修改积分发放与画像重算链路
+- 不修改三个 Health 实体的持久化字段(`taskId` 为 `@TableField(exist = false)`,无 DDL)
 
-- `:41-45` — 新增 `CHECKIN_TEMPLATES` 常量:`[DIET]饮食打卡 / [SLEEP]睡眠打卡 / [EXERCISE]运动打卡`,actionType 对应 `diet/sleep/exercise`
-- `:152` — cron 主流程第 6 步调用 `generateBuiltinCheckinTasks(today)`
-- `:164` — `generateBuiltinCheckinTasks(Date today)`:遍历所有能登录用户(`family_members.userId IS NOT NULL`),为每人每天生成 3 条 `is_daily_progress=1, target_value=1, points=5, category=健康打卡, needReview=0` 的任务实例;按 `familyMemberId + title + 当天 createdAt` 判重,幂等可重复执行
+## 六、实施步骤(TODO)
 
-原计划的 DatabaseInitializer 模板种子迁移**取消**:模板以代码常量内置(`CHECKIN_TEMPLATES`),无需 DDL,减少迁移面。
+### 阶段 1:实体与 DTO ✅(已完成)
 
-### 阶段 4:前端任务页面 ⚠️ 未实现(遗留)
+- [x] `entity/HealthMealRecord.java`、`HealthSleepRecord.java`、`HealthExerciseRecord.java` 各新增 `@TableField(exist=false) private Long taskId;`
+- [x] `entity/Task.java:130`、`dto/CreateTaskDTO.java:66` 注释补充 actionType 可选值 `diet/sleep/exercise`
+- 提交:`e81e05bee`、`814f30712`
 
-`cfc-frontend/pages/tasks/tasks.vue`:
+### 阶段 2:打卡 Controller 挂钩 ✅(已完成,commit `e81e05bee`)
 
-- `getActionLabel`(:413)尚无 `diet/sleep/exercise` 分支(应返回"去打卡")
-- `navigateByAction`(:462)尚无对应跳转分支(应跳转 `/pages/growth/diet-checkin?taskId=` 等)
+- [x] `HealthMealController.java` create():taskId 非空且非每日进度型 → `completeTask`;否则 → `updateProgress(userId, "DIET", 1)`
+- [x] `HealthSleepController.java`:taskKey=`SLEEP`
+- [x] `HealthExerciseController.java`:taskKey=`EXERCISE`
+- [x] 三处回写均 try-catch,补打不回写
 
-三个打卡页(`diet-checkin.vue` / `sleep-checkin.vue` / `exercise-checkin.vue`)均未读取/透传 `taskId`。
+### 阶段 3:每日任务定时生成 ✅(已完成)
 
-**影响**:后端链路已通,但前端无法从任务列表跳到打卡页,一次性联动任务的入口缺失;每日进度任务不依赖前端跳转,可正常累计完成。
+- [x] `task/RepeatTaskGenerator.java`:新增 `CHECKIN_TEMPLATES` 常量([DIET]饮食打卡 / [SLEEP]睡眠打卡 / [EXERCISE]运动打卡)
+- [x] cron 主流程接入 `generateBuiltinCheckinTasks(today)`,遍历能登录用户(`family_members.userId IS NOT NULL`),幂等生成 `is_daily_progress=1, target_value=1, points=5, category=健康打卡, needReview=0` 任务
+- [x] 模板以代码常量内置,取消 DatabaseInitializer 种子迁移(无 DDL)
 
-### 阶段 5:API 文档 ⚠️ 未实现(遗留)
+### 阶段 4:前端任务页与打卡页 ✅(已完成)
 
-`docs/superpowers/api/API_REFERENCE.md` 中三个打卡接口(`/api/health/meal`、`/api/health/sleep`、`/api/health/exercise`)尚未补充 `taskId` 可选参数说明。
+- [x] `pages/tasks/tasks.vue` `getActionLabel()`(:423-425):`diet/sleep/exercise` 三分支返回"去打卡"
+- [x] `pages/tasks/tasks.vue` `navigateByAction()`(:483-489):三分支分别跳转 `/pages/growth/{diet,sleep,exercise}-checkin?taskId=` + task.id
+- [x] `pages/growth/diet-checkin.vue`(:172, :210, :454, :468)、`sleep-checkin.vue`(:99, :112, :170, :181)、`exercise-checkin.vue`(:164, :211, :450, :464):`onLoad` 读取 `options.taskId || null`,提交时透传 `taskId: self.taskId || undefined`,成功提示区分 `打卡成功,任务已完成 / 打卡成功`
 
-## 4. 文件变更清单
+验收标准已满足:任务列表对三类打卡任务显示"去打卡";点击跳转到对应打卡页且 URL 带 taskId;打卡提交后任务状态变 completed。
 
-| 文件 | 操作 | 关键行 | 提交状态 |
-|------|------|--------|----------|
-| `entity/HealthMealRecord.java` | 修改 | :59 | ✅ e81e05bee |
-| `entity/HealthSleepRecord.java` | 修改 | :43 | ✅ e81e05bee |
-| `entity/HealthExerciseRecord.java` | 修改 | :38 | ✅ e81e05bee |
-| `entity/Task.java` | 修改(注释) | :130 | ✅ 814f30712 |
-| `dto/CreateTaskDTO.java` | 修改(注释) | :66 | ✅ 814f30712 |
-| `controller/HealthMealController.java` | 修改 | :250-275 | ✅ e81e05bee |
-| `controller/HealthSleepController.java` | 修改 | :68-92 | ✅ e81e05bee |
-| `controller/HealthExerciseController.java` | 修改 | :91-115 | ✅ e81e05bee |
-| `task/RepeatTaskGenerator.java` | 修改 | :41, :152, :164-221 | ✅ 本次提交 |
-| `pages/tasks/tasks.vue` | 待修改 | :413, :462 | ❌ 遗留 |
-| `pages/growth/{diet,sleep,exercise}-checkin.vue` | 待修改 | onLoad/提交 | ❌ 遗留 |
-| `docs/superpowers/api/API_REFERENCE.md` | 待修改 | health 三节 | ❌ 遗留 |
+### 阶段 5:API 文档 ✅(已完成)
 
-## 5. 测试验证策略
+- [x] `docs/superpowers/api/API_REFERENCE.md`::597-604 三个打卡接口标注"支持 taskId 任务联动";:606-614 新增"健康打卡任务联动(taskId 参数)"小节,说明 taskId 可选、为 null 时不触发任务逻辑;:519-521 补充 actionType 三个新取值的跳转目标
+- [x] `docs/superpowers/PROJECT-OVERVIEW.md`:已收录本特性为"全栈完成"条目
 
-1. **编译**:`cd cfc-backend && mvn clean compile`(本次提交前必跑)
-2. **每日任务生成**:启动后端后等 cron(每小时第 5 分)触发,或临时改 cron 验证;检查 `tasks` 表:每个能登录用户当天应有 3 条 `is_daily_progress=1`、title 带 `[DIET]/[SLEEP]/[EXERCISE]` 前缀的记录;重复触发不产生重复实例(幂等校验)
-3. **每日进度路径**:不带 taskId 调 `POST /api/health/meal` → 当天 `[DIET]` 任务 `current_value` 变 1、`status=completed`
-4. **一次性任务路径**:手动创建 `actionType=diet` 任务 → 带 `taskId` 调打卡接口 → 该任务 `status=completed` 且发积分
-5. **隔离性**:回写异常时(如 taskId 指向不存在任务)打卡仍返回 200,记录正常落库
-6. **回归**:不带 taskId 的旧打卡请求行为不变
+## 七、验证步骤
 
-## 6. 风险与回滚
+1. **编译**:`cd cfc-backend && mvn clean compile` 通过
+2. **前端语法**:对改动的 vue 文件做语法检查,确认无编译错误
+3. **联调路径 1(一次性任务)**:创建 actionType=diet 的任务 → 任务列表显示"去打卡" → 跳转打卡页(URL 带 taskId)→ 提交打卡 → 任务 status=completed 且发任务积分
+4. **联调路径 2(每日任务)**:当天存在 title=`[DIET]饮食打卡`、`is_daily_progress=1` 任务 → 不带 taskId 打卡 → 进度 0/1 变 1/1 → 自动完成 + 发奖
+5. **幂等性**:重复触发每日生成器,`tasks` 表不产生重复实例
+6. **隔离性**:taskId 指向不存在任务时打卡仍返回成功,记录正常落库
+7. **回归**:旧的不带 taskId 打卡请求行为不变(入库、积分、画像重算照常)
 
-- **风险**:每日生成器遍历全部能登录用户,用户量大时单次 cron 插入量上升(当前规模 3×N 条/天,可接受);title 前缀与 updateProgress 的 taskKey 是字符串约定,改任一侧需同步
-- **回滚**:
-  - 生成器:`git revert` 本次提交即可,已生成的任务实例为 `is_daily_progress=1` 普通数据,不影响其他任务
-  - Controller 回写:revert `e81e05bee`,打卡主流程不受影响(回写本就包 try-catch)
-  - 数据无需回滚:`taskId` 非持久化,无 DDL 变更
+## 八、文件变更清单
 
-## 7. Commit 建议
+| 文件 | 操作 | 状态 |
+|------|------|------|
+| `entity/HealthMealRecord.java` / `HealthSleepRecord.java` / `HealthExerciseRecord.java` | 新增非持久化 taskId | ✅ 已提交 |
+| `entity/Task.java` / `dto/CreateTaskDTO.java` | 注释补充 | ✅ 已提交 |
+| `controller/HealthMealController.java` / `HealthSleepController.java` / `HealthExerciseController.java` | create() 挂钩任务回写 | ✅ 已提交 |
+| `task/RepeatTaskGenerator.java` | 每日打卡任务生成 | ✅ 已提交 |
+| `pages/tasks/tasks.vue` | getActionLabel + navigateByAction 三分支 | ✅ 已实现(:423-425, :483-489) |
+| `pages/growth/{diet,sleep,exercise}-checkin.vue` | onLoad 接收 taskId、提交透传 | ✅ 已实现 |
+| `docs/superpowers/api/API_REFERENCE.md` | 三个打卡接口补 taskId 说明 | ✅ 已实现(:597-614) |
+| `docs/superpowers/PROJECT-OVERVIEW.md` | 同步文档索引与状态 | ✅ 已收录 |
 
-```
-feat(task): 每日自动生成饮食/睡眠/运动打卡进度任务
+## 九、风险与回滚
 
-- RepeatTaskGenerator 新增 CHECKIN_TEMPLATES 常量与 generateBuiltinCheckinTasks()
-- cron 主流程接入,每日为所有能登录用户幂等生成 3 条 is_daily_progress 任务
-- 关联计划文档 docs/superpowers/plans/2026-10-13-health-checkin-task-linkage-plan.md
-```
+- **风险**:每日生成器遍历全部能登录用户,用户量大时单次 cron 插入量上升(当前规模 3×N 条/天,可接受);title 前缀与 taskKey 为字符串约定,改任一侧需同步另一侧。
+- **回滚**:生成器 revert 对应提交即可,已生成实例为普通数据;Controller 回写 revert `e81e05bee`,打卡主流程不受影响(回写本就包 try-catch);无 DDL 变更,数据无需回滚。