状态:设计稿 · 待评审 日期:2026-08-19 范围:cfc-backend(Spring Boot 2.7.18)+ cfc-frontend(uni-app Vue 2)+ 关联子系统
现有 tasks 体系是「家长给孩子布置任务 + 积分激励」模型:创建 → 完成 → 审核 → 积分/能量/打卡。其核心价值(积分/streak/审核/五维能量/小游戏)全部保留。
本次升级目标:把任务系统提升为调度用户行为的行为中心——任务成为填写问卷、购买商品、阅读文章、参加活动、玩小游戏、专注训练等功能入口,支持前置依赖、灵活频率、填写式完成、开始-完成两阶段与购物里程碑。
tasks 表、积分/审核/streak/五维能量链路全部复用,只做增量扩展。RepeatTaskGenerator 已确立「模板(is_template=1)→ 实例」模式,本次将频率/前置/联动全部建模在模板与实例两层。TaskEngine.completeTask(),保证积分/能量/前置解锁逻辑唯一。| 能力 | 现状 | 结论 |
|---|---|---|
| 任务 CRUD / 状态机 | tasks 表,7 状态,完整 |
✅ 复用 |
| 完成计分 / streak / 审核 / 五维能量 | TaskService.completeTask 全链路 | ✅ 复用 |
| 小游戏联动 | category=小游戏类 + minigame_code + complete-minigame 接口 | ✅ 复用,补 start |
| 频率任务 | 仅 daily/weekly,RepeatTaskGenerator 凌晨5点扫描 repeatType | ⚠️ 扩展 frequency 语义 |
| 前置任务 | 无 | ❌ 新增 |
| 填写式完成 | complete_types 字段有,无强制校验 | ❌ 补校验 |
| 购物任务 | 无联动;product_orders 有 paid/completed、fulfill_status=received_by_user | ❌ 新增 |
| 文章阅读任务 | article_reading_records 有 task_id 字段 | ⚠️ 补自动判定 |
| 专注训练 | focus_sessions 存在(child 维度) | ⚠️ 补任务挂钩 |
| 活动任务 | activity_registrations/activity_orders 存在 | ❌ 新增 |
tasks 表新增字段(迁移)-- 迁移N: tasks 表任务系统升级扩展字段
ALTER TABLE tasks
ADD COLUMN prerequisite_task_id BIGINT NULL COMMENT '前置任务ID:完成后才解锁本任务' AFTER template_id,
ADD COLUMN action_type VARCHAR(32) NULL COMMENT '联动动作: minigame/focus/survey/article/activity/cart/order' AFTER source_id,
ADD COLUMN action_config JSON NULL COMMENT '联动参数: {"gameCode":"schulte","productId":123,"minSeconds":30,"completeMilestone":"paid"/"received","surveyId":99}' AFTER action_type,
ADD COLUMN require_input TINYINT DEFAULT 0 COMMENT '1=需填写内容才能完成' AFTER need_review,
ADD COLUMN start_required TINYINT DEFAULT 0 COMMENT '1=需先开始再完成(两阶段)' AFTER require_input,
ADD COLUMN start_count INT DEFAULT 0 COMMENT '已开始次数(累计)' AFTER completed_at,
ADD COLUMN min_duration_seconds INT DEFAULT 0 COMMENT '最短持续秒数(专注/游戏类,未达标不计完成)' AFTER duration;
字段语义:
| 字段 | 说明 |
|---|---|
prerequisite_task_id |
前置任务。本任务完成条件之一:该任务已 completed。支持链式。 |
action_type |
联动动作类型(见 §4)。null/空 = 纯手动任务(现有行为不变)。 |
action_config |
动作参数 JSON,按 action_type 解析。 |
require_input |
=1 时 completeTask 必须带 content(文本/图片/语音等),否则拒绝。 |
start_required |
=1 时必须先调 /start 进入 in_progress,再调 complete。小游戏/专注/购物任务的统一开关。 |
start_count |
开始次数累计,供频率判定(如"每日最多开始 N 次")。 |
min_duration_seconds |
开始到完成的最短间隔;不足则拒绝完成(防秒完成)。 |
兼容性:所有新字段可空,存量任务(action_type=NULL)走旧逻辑,零迁移风险。
现状:repeat_type ENUM('none','daily','weekly') + frequency(daily/weekly/monthly,无逻辑消费)。
改造:以 frequency 为唯一频率字段,repeat_type 保留兼容。
| frequency 值 | 含义 | 生成策略 |
|---|---|---|
none |
一次性 | 不生成 |
hourly |
每小时 | RepeatTaskGenerator 每小时执行 |
daily |
每天 | 凌晨5点生成当日实例 |
every_other_day |
隔天 | 按模板 created_at 奇偶日判定 |
weekly |
每周 | 按模板 deadline 星期几判定 |
monthly |
每月 | 按模板 deadline 日期判定 |
custom |
自定义(配合 frequency_cron) |
由 action_config 内 cron 或 intervalHours 驱动 |
实例生成器改造(RepeatTaskGenerator 重构):
// 每小时调度入口(原每日5点改为频率分派器)
@Scheduled(cron = "0 5 * * * ?") // 每小时第5分
public void generateByFrequency() {
// 按 frequency 分组扫描 is_template=1 的任务模板
// daily/weekly/monthly 沿用现有 hasTaskForDate 去重逻辑
// hourly: 检查最近1小时无实例则生成(去重键:title+child+family+1小时窗口)
// every_other_day: 计算模板创建日至今的间隔天数,偶数天生成
}
去重键统一收敛为 title + childId + familyId + 时间窗口,与现有 hasTaskForDate 一致,避免重复实例。
两阶段(开始-完成)与购物里程碑需要记录进度快照,现有 tasks 表一个状态字段不够。新增:
-- 迁移N: 任务执行进度表(两阶段/里程碑/填写内容记录)
CREATE TABLE IF NOT EXISTS task_executions (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
task_id BIGINT NOT NULL COMMENT '任务实例ID',
member_id BIGINT NOT NULL COMMENT '执行成员ID(family_members.id)',
status VARCHAR(20) NOT NULL DEFAULT 'started' COMMENT 'started/finished/failed',
started_at DATETIME NOT NULL COMMENT '开始时间',
finished_at DATETIME COMMENT '完成时间',
milestone VARCHAR(32) COMMENT '购物任务里程碑: cart_added/paid/received(当前进度)',
content TEXT COMMENT '填写内容(require_input 任务)',
content_type VARCHAR(20) COMMENT 'content 类型: text/image/audio/video',
extra JSON COMMENT '扩展数据: {gameScore, focusMinutes, orderId, articleId, surveyId}',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_task_member (task_id, member_id),
INDEX idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='任务执行进度表';
task_executions是 task 的进度明细(1:N:一个实例任务可被多次开始/执行),tasks.status仍是权威状态。
| action_type | 触发点 | 完成判定 | 里程碑 |
|---|---|---|---|
minigame |
小游戏页(schulte/1a2b/sudoku) | 游戏结束 → complete-minigame(现有) | — |
focus |
专注计时器页 | 计时结束 → complete,需 ≥ min_duration_seconds | — |
survey |
问卷/测评页 | 问卷提交 → 回写任务 | — |
article |
文章详情页 | 阅读 ≥ 指定秒数(minSeconds)→ 自动/手动完成 |
— |
activity |
活动详情页 | 活动报名成功 → 完成 | — |
cart |
任务卡片"去购物"按钮 | 点开始:自动加购 → 跳购物车页 | cart_added |
order |
购物车→结算页 | 点开始:自动加购+跳转;支付成功 → 里程碑 paid;确认收货 → 里程碑 received |
paid / received(由 action_config.completeMilestone 决定哪个算完成) |
任务卡"开始购物" → POST /api/tasks/{id}/start
→ 后端校验前置/频率 → 状态 pending→in_progress → 创建 task_executions(started)
→ 按 action_config.productId 调 CartController 加入购物车 → milestone=cart_added
→ 返回 {executionId, cartJumpUrl}
前端跳 /pages/shop/cart(或直接下单页)
→ 用户结算支付
→ PaymentController 支付成功回调(现成 hooks)→ 查 task_executions 关联
→ 若 completeMilestone=paid → TaskEngine.completeTask()
→ 若 completeMilestone=received → milestone=paid,等收货
→ 用户确认收货(ProductOrderController 确认收货接口现成)
→ 若 completeMilestone=received → TaskEngine.completeTask()
关联方式:task_executions.extra.orderId 记录订单号,支付/收货回调按 orderId → executionId → taskId 反查。在 PaymentController 支付成功分支和订单确认收货分支各插入一行事件发布(taskEventPublisher.publish(paid/received, orderId)),不动订单主流程。
article_reading_records 已有 task_id 字段,但当前无自动判定。改造:
POST /api/tasks/{id}/progress-article)focus_sessions 已有完整记录(started_at/completed_at/duration)。在专注结束保存处插入:若该会话绑定任务(focus_sessions 增加 task_id 字段,迁移),结束后调 TaskEngine 判定(duration ≥ min_duration_seconds)。
completeTask() 完成前校验(TaskEngine 内统一):
if (task.getPrerequisiteTaskId() != null) {
Task prereq = taskMapper.selectById(task.getPrerequisiteTaskId());
if (prereq == null || !"completed".equals(prereq.getStatus())) {
throw new BizException("需先完成前置任务:" + (prereq != null ? prereq.getTitle() : ""));
}
}
/api/tasks/today 返回任务时,带 lockedByPrerequisite 标记:前置未完成 → 前端灰态 + "🔒 完成前置任务后解锁"提示,不展示完成按钮(但展示任务内容供了解)。TaskEngine.checkUnlock() 把后置任务 status 从 locked(新状态)改为 pending。状态机扩展:tasks.status ENUM 增加 locked(仅用于有前置且前置未完成的任务;无前置任务不受影响)。
创建/更新任务时校验 prerequisite_task_id 不成环(BFS 沿 prerequisite_task_id 上溯,遇到自身/超过 20 层即拒绝)。
require_input=1 + complete_types 声明允许的内容类型(text/image/audio/video,复用现有字段)。POST /api/tasks/{id}/complete 增强:require_input=1 时必传 content + contentType,否则 Result.error("该任务需填写内容后完成")。task_executions.content / content_type(兼容:现有 media_records 表继续可用,新逻辑优先写 executions)。need_review=1 时,填写内容随审核流走(现有 task_reviews 逻辑不变)。| 接口 | 方法 | 说明 |
|---|---|---|
POST /api/tasks/{id}/start |
POST | 开始任务:校验前置/频率/会员 → pending→in_progress → 创建 execution;购物任务触发加购;返回 executionId + 跳转参数 |
POST /api/tasks/{id}/progress-article |
POST | 文章阅读进度上报(article 联动) |
POST /api/tasks/{id}/unlock-check |
POST | 检查并解锁后置任务(前置完成时由 completeTask 自动调用,此接口供前端手动刷新) |
POST /api/tasks/template/create |
POST | 模板任务创建(含 frequency/action/前置配置;复用 CreateTaskDTO 扩展) |
POST /api/tasks/template/list |
POST | 模板列表(管理端/规划师端复用) |
| 接口 | 变更 |
|---|---|
POST /api/tasks/create |
CreateTaskDTO 增加 prerequisiteTaskId/actionType/actionConfig/requireInput/startRequired/minDurationSeconds 字段 |
POST /api/tasks/{id}/complete |
校验前置 + require_input 强校验 + 两阶段任务必须已 start(start_required=1 且无 in_progress execution → 拒绝) |
POST /api/tasks/{id}/complete-minigame |
增加 minDurationSeconds 校验 |
POST /api/tasks/today |
返回 lockedByPrerequisite、actionType、actionConfig、canStart(供前端渲染联动按钮) |
POST /api/tasks/history |
返回 execution 摘要(进度/里程碑) |
| 现有位置 | 插入点 | 事件 |
|---|---|---|
| PaymentController 支付成功分支 | 成功回调内 | TaskEventPublisher.orderPaid(orderId) |
| 订单确认收货接口 | received_by_user 更新处 | TaskEventPublisher.orderReceived(orderId) |
| ArticleController 阅读上报 | 时长更新处 | 由 progress-article 直接驱动 |
| 专注结束保存 | focus_sessions.completed_at 更新处 | 直接驱动 |
按 action_type 渲染不同主按钮:
| action_type | 按钮文案 | 行为 |
|---|---|---|
| null(纯手动) | 完成 | 现有逻辑 + require_input 弹填写面板 |
| minigame | 开始游戏 | navigateTo 游戏页带 taskId(现有)+ 先调 /start |
| focus | 开始专注 | navigateTo 专注页带 taskId |
| survey | 去填写 | navigateTo 问卷页带 taskId |
| article | 去阅读 | navigateTo 文章页带 taskId |
| activity | 去报名 | navigateTo 活动页带 taskId |
| cart / order | 去购物 | 调 /start(自动加购)→ navigateTo 购物车/下单页 |
| 前置未解锁(locked) | 🔒 已完成前置解锁 | 灰态禁用 + 提示 |
新增交互:
require_input 任务:点完成 → 弹出填写面板(文本/拍照/录音,按 complete_types 显示)→ 提交 content。start_required 任务:未 start 时按钮为"开始",已 start 返回后按钮变"完成"(依据 task_executions 状态)。| 文件 | 说明 |
|---|---|
components/task-input-panel.vue |
填写式完成面板(文本/图片/语音/视频切换,复用现有上传组件) |
pages/tasks/task-detail.vue |
任务详情(前置链展示、进度、里程碑、执行历史)——可选,MVP 可先不做 |
admin_task_templates 已有表结构,管理端任务模板编辑页增加:频率选择(hourly/daily/隔天/weekly/monthly)、联动动作配置、前置任务选择器。此项可排二期。
| 现有特性 | 影响 | 处理 |
|---|---|---|
| 积分/streak/审核/五维能量 | 无影响 | completeTask 逻辑原样保留,TaskEngine 只在其前后加校验 |
| RepeatTaskGenerator | 重构 | 保留 daily/weekly 现有行为,扩展频率分派;去重逻辑不变 |
| 套餐任务(task_plan_instances) | 无影响 | 其生成的 Task 不带 action_type,走手动完成 |
| 小游戏任务 | 兼容 | 现有 complete-minigame 保留;start_required=0 时行为不变 |
| member 任务(assign/accept/reject) | 无影响 | accept→in_progress 已存在,与 start 语义并存(start 是执行态,accept 是接收态) |
/start 接口 + task_executions 记录locked 状态仅新任务使用。