# 任务系统升级设计文档(行为调度中心) > 状态:设计稿 · 待评审 > 日期:2026-08-19 > 范围:cfc-backend(Spring Boot 2.7.18)+ cfc-frontend(uni-app Vue 2)+ 关联子系统 ## 1. 背景与目标 现有 `tasks` 体系是「家长给孩子布置任务 + 积分激励」模型:创建 → 完成 → 审核 → 积分/能量/打卡。其核心价值(积分/streak/审核/五维能量/小游戏)全部保留。 本次升级目标:把任务系统提升为**调度用户行为的行为中心**——任务成为填写问卷、购买商品、阅读文章、参加活动、玩小游戏、专注训练等功能入口,支持前置依赖、灵活频率、填写式完成、开始-完成两阶段与购物里程碑。 ### 设计原则 1. **不推翻现有模型**:`tasks` 表、积分/审核/streak/五维能量链路全部复用,只做增量扩展。 2. **任务 = 模板 + 实例**:现有 `RepeatTaskGenerator` 已确立「模板(is_template=1)→ 实例」模式,本次将频率/前置/联动全部建模在模板与实例两层。 3. **联动不硬编码**:任务通过「动作类型 action」+「动作参数」声明式关联子系统,子系统侧在关键节点发事件,由 TaskEngine 统一判定完成。 4. **完成判定集中化**:所有完成路径(打卡/填写/游戏/支付/收货/阅读)最终走同一个 `TaskEngine.completeTask()`,保证积分/能量/前置解锁逻辑唯一。 ## 2. 现状差距总览(勘察结论) | 能力 | 现状 | 结论 | |------|------|------| | 任务 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 存在 | ❌ 新增 | ## 3. 数据模型扩展(核心) ### 3.1 `tasks` 表新增字段(迁移) ```sql -- 迁移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)走旧逻辑,零迁移风险。 ### 3.2 频率语义扩展(复用 repeat_type + frequency) 现状:`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 重构):** ```java // 每小时调度入口(原每日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` 一致,避免重复实例。 ### 3.3 任务实例记录(新表:任务执行进度明细) 两阶段(开始-完成)与购物里程碑需要记录进度快照,现有 `tasks` 表一个状态字段不够。新增: ```sql -- 迁移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` 仍是权威状态。 ## 4. 联动动作(action_type)设计 | 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 决定哪个算完成) | ### 4.1 购物任务两阶段流程(§需求5 核心) ``` 任务卡"开始购物" → 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)`),不动订单主流程。 ### 4.2 文章阅读任务 `article_reading_records` 已有 `task_id` 字段,但当前无自动判定。改造: - 文章页 onHide/onUnload 时上报阅读时长(现已有上报接口则复用;无则加轻量 `POST /api/tasks/{id}/progress-article`) - 阅读时长 ≥ action_config.minSeconds 且任务存在 → 自动 complete ### 4.3 专注任务 `focus_sessions` 已有完整记录(started_at/completed_at/duration)。在专注结束保存处插入:若该会话绑定任务(focus_sessions 增加 `task_id` 字段,迁移),结束后调 TaskEngine 判定(duration ≥ min_duration_seconds)。 ## 5. 前置任务(§需求1) ### 判定逻辑 `completeTask()` 完成前校验(TaskEngine 内统一): ```java 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 层即拒绝)。 ## 6. 填写式完成(§需求4) - 任务创建时 `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 逻辑不变)。 ## 7. 接口清单(新增/修改) ### 新增接口 | 接口 | 方法 | 说明 | |---|---|---| | `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 更新处 | 直接驱动 | ## 8. 前端改造点(cfc-frontend) ### 8.1 任务卡渲染(pages/tasks/tasks.vue + daily-tasks.vue) 按 `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 状态)。 ### 8.2 新增页面/组件 | 文件 | 说明 | |---|---| | `components/task-input-panel.vue` | 填写式完成面板(文本/图片/语音/视频切换,复用现有上传组件) | | `pages/tasks/task-detail.vue` | 任务详情(前置链展示、进度、里程碑、执行历史)——可选,MVP 可先不做 | ### 8.3 管理端(cfc-web,可选二期) `admin_task_templates` 已有表结构,管理端任务模板编辑页增加:频率选择(hourly/daily/隔天/weekly/monthly)、联动动作配置、前置任务选择器。此项可排二期。 ## 9. 与现有系统的兼容性 | 现有特性 | 影响 | 处理 | |---|---|---| | 积分/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 是接收态) | ## 10. 分阶段实施计划 ### 阶段一(MVP,核心闭环) 1. 数据库迁移:tasks 新字段 + task_executions 表 + focus_sessions.task_id 2. TaskEngine 重构:completeTask 增加前置校验 / require_input 强校验 / 两阶段校验(start_required) 3. `/start` 接口 + task_executions 记录 4. RepeatTaskGenerator 频率分派(hourly/daily/every_other_day/weekly/monthly) 5. 前端:任务卡 action_type 渲染 + 填写面板 + 开始/完成两阶段 6. 验证:mvn clean compile + node --check + 手工联调 ### 阶段二(外部联动) 7. 购物任务(cart/order):start 自动加购 + 支付/收货事件钩子 + 里程碑判定 8. 文章阅读任务:阅读时长上报与自动完成 9. 专注任务:focus_sessions 挂钩 10. 活动任务:报名成功回写 ### 阶段三(增强) 11. 前置任务链 UI(任务详情页) 12. cfc-web 管理端模板编辑增强 13. 任务编排页(拖拽式前置关系)——远期 ## 11. 风险与注意 - **频率生成器并发**:RepeatTaskGenerator 重构后需保证幂等(去重键 + 唯一约束),防止重复实例。 - **支付回调时序**:购物任务依赖支付回调,测试模式(wechat.test-mode=true)需 mock 事件。 - **前置环检测**:创建任务时 BFS 校验,避免 A→B→A 死锁。 - **存量数据**:新字段全部可空,旧任务零影响;`locked` 状态仅新任务使用。 - **前端兼容**:任务卡渲染改动需保证无 action_type 的旧任务显示原按钮(默认"完成")。