状态:已完成修复并通过编译(
mvn clean compile退出码 0) 范围:仅改动任务回写链路相关文件(1 个 Service + 3 个 Controller),未触碰 BASIC 会员配额相关文件(BasicQuotaService / ProductOrderService / MembershipService 等),未改动 dist / unpackage,未执行 git commit(遵守任务护栏)。
三个健康打卡 Controller(睡眠 / 运动 / 饮食)的 create 方法在插入打卡记录后都有「任务回写挂钩」:
taskId 透传到请求体(如 sleep-checkin.vue 的 doCheckin,taskId: self.taskId || undefined)。linkedTask = taskMapper.selectById(record.getTaskId())。linkedTask.getIsDailyProgress() == 1(每日进度型):原来调用
taskService.updateProgress(fm.getUserId(), fm.getId(), "SLEEP"/"EXERCISE"/"DIET", 1) 只做进度累加。taskService.completeTask(...) 直接完成。TaskService.updateProgress(Long userId, Long familyMemberId, String taskKey, int increment)(TaskService.java ~1679 行):
(familyMemberId, is_daily_progress=1, created_at ∈ 今日, title 精确匹配 taskKey OR title LIKE '[taskKey]%') 查询当日任务实例,LIMIT 1 且无排序。task_executions.extra.progress,累加 increment。task_executions(extra 记录 {"progress":x,"target":y})。if (progress >= target) awardTaskCompletionRewards(task, memberId, now) —— 该私有方法(~893 行)内部会 task.setStatus("completed") 并 taskMapper.updateById(task),同时发积分/能量/CF/连胜。结论:updateProgress 在进度达标时确实会把任务状态置为 completed(每日内置打卡任务 targetValue=1,一次打卡即可达标)。
问题不在「达标后是否置 completed」,而在于 updateProgress 不是按前端透传的 record.getTaskId() 定位任务,而是按 title 前缀 [taskKey] + LIMIT 1 无排序匹配:
RepeatTaskGenerator.createTaskFromTemplate 从 admin_task_templates 生成的「deadline 型」实例与内置 [SLEEP]睡眠打卡 实例并存,或历史重复数据),selectOne(LIMIT 1) 完成的是任意一条,用户点击的那条任务卡片可能永远保持 pending。getTodayTasks 会通过「deadline 今日」分支返回每日进度型任务(created_at 可能是昨天生成的),而 updateProgress 只按「created_at ∈ 今日」查找 → task == null → log.warn 后直接 return,点击的实例永不被关闭。try { } catch { log.warn } 包裹,失败静默,前端不会再重试。前端侧已排除:index-home/index.vue 的 onShow → _refreshOnShow → loadFamilyData → loadTodayTasks 会重新拉 /api/tasks/today,并按 t.status !== 'pending' 过滤 —— 只要后端把那条任务置为 completed,卡片就会消失。问题确实出在后端「关闭的不是用户点的那条」。
cfc-backend/src/main/java/com/etotem/cfc/service/TaskService.java新增公开方法 updateCheckinTaskProgress(Long taskId, Long userId, Long familyMemberId, int increment)(紧随 updateProgress(familyMemberId, taskKey, increment) 之后):
taskId 精确加载任务实例(taskMapper.selectById),而非 title 前缀匹配。familyMemberId 必须属于 userId(多家庭防串号,与既有约定一致)。familyMemberId 必须等于打卡成员;非每日进度型直接跳过(避免误关一次性任务)。status=completed 直接 return(防重复发奖)。updateProgress 完全一致(读 task_executions.extra.progress、累加、写回)。awardTaskCompletionRewards(task, memberId, now),把这条具体的任务置为 completed,并复用积分/能量/CF/连胜奖励链路。cfc-backend/src/main/java/com/etotem/cfc/controller/HealthSleepController.java(create,~75-80 行)每日进度型分支由:
taskService.updateProgress(fm.getUserId(), fm.getId(), "SLEEP", 1);
改为:
taskService.updateCheckinTaskProgress(record.getTaskId(), fm.getUserId(), fm.getId(), 1);
未带 taskId 的兜底分支(else)保持 updateProgress(..., "SLEEP", 1) 不变(此时没有具体实例可关)。
cfc-backend/src/main/java/com/etotem/cfc/controller/HealthExerciseController.java(create,~97-102 行)同样把 updateProgress(fm.getUserId(), fm.getId(), "EXERCISE", 1) 改为 updateCheckinTaskProgress(record.getTaskId(), fm.getUserId(), fm.getId(), 1);无 taskId 兜底分支不变。
cfc-backend/src/main/java/com/etotem/cfc/controller/HealthMealController.java(create,~257-262 行)同样把 updateProgress(fm.getUserId(), fm.getId(), "DIET", 1) 改为 updateCheckinTaskProgress(record.getTaskId(), fm.getUserId(), fm.getId(), 1);isBackfill(补打)守卫与无 taskId 兜底分支不变。
说明:
HealthWaterController使用独立的updateProgress(userId, "WATER", 1)且不接收 taskId,不属于本次三打卡(睡眠/运动/饮食)范围,未改动;如后续需要可单独评估。
| 任务类型 | 判定 | 处理 | 语义 |
|---|---|---|---|
| 每日进度型 | is_daily_progress = 1 |
updateCheckinTaskProgress(taskId, ...):按具体实例累计进度,达标后置 completed 并发奖 |
每天达成后当天关闭;次日 RepeatTaskGenerator.generateBuiltinCheckinTasks 按 (member, title, 当日) 幂等判断,重新生成当天新实例(targetValue=1、status=pending),卡片次日重新出现。关闭今天实例不影响明天实例生成。 |
| 一次性任务 | is_daily_progress != 1 |
原 completeTask(...) 链路(不变) |
一次性完成即关闭,不参与每日重置。 |
兼容性:
taskId 的旧调用路径仍走 title-key updateProgress,行为完全不变。LambdaQueryWrapper、Calendar、JSON、@Transactional 等既有设施)。无。 前端(index-home/index.vue 的 onShow 刷新、sleep-checkin.vue / diet-checkin.vue / exercise-checkin.vue 透传 taskId)已满足需求:打卡页已传 taskId,首页返回后 onShow 会重新拉取 /api/tasks/today 并按 status 过滤。本次仅修复后端「关闭的不是用户点击的那条任务」的问题,不涉及 dist / unpackage,无需重新打包。
cd E:/cfc/cfc-backend && mvn clean compile
[INFO] BUILD SUCCESS
exitcode=0
mvn spring-boot:run,9082)与测试库(192.168.16.251:3306/zxyj)。RepeatTaskGenerator 每小时第 5 分执行;如当日未生成可等下一个整点或手工触发)。pending,显示「去完成」。tasks.status='completed',前端按 pending 过滤)。task_executions 出现该任务的执行记录,extra.progress=1, target=1;tasks.status='completed'、completed_at 有值。awardTaskCompletionRewards 链路),且再次重复打卡不会重复发奖(completed 幂等 return)。RepeatTaskGenerator)确认三条任务重新以 pending 出现,可再次打卡。completeTask 关闭且发奖正常。