Skip to content

用户任务系统技术方案 ​

1. 设计目标与定位 ​

用户任务系统是一套通用、可扩展、事件驱动的任务奖励引擎,用于驱动用户行为、提升活跃度和留存。业务方可通过配置任务模板(任务组 + 任务),并在关键业务节点触发对应事件,即可自动完成任务进度判定与奖励发放。

1.1 设计原则 ​

原则说明
配置驱动任务类型、条件、奖励均在数据库中配置,无需改代码即可上线新任务
事件驱动业务侧只负责发事件,任务系统监听事件并判定进度,解耦业务与任务
阶梯奖励支持累计型阶梯任务(如"累计观看30秒/60秒/120秒"),一次行为可同时推进多阶
奖励可扩展奖励类型通过 reward_type 区分,支持金币、积分、VIP 时长等,新增奖励类型无需改表结构
幂等安全同一任务同一周期内只奖励一次,并发场景下依靠唯一索引 + 事务保证
通用骨架作为骨架项目能力,提供基础任务类型(签到、邀请、完善资料等),业务项目按需扩展

1.2 与参考项目的差异 ​

参考 drama-forge-base 的任务系统是面向短剧业务定制的(看剧、看广告等),本系统作为通用骨架项目,采用以下策略:

  • 保留任务组 + 任务 + 完成日志的三层核心结构
  • 保留事件驱动 + 监听器判定的架构模式
  • 移除短剧业务特有的任务类型(看剧、视频广告、激励视频)
  • 新增通用业务常用任务类型(签到、完善资料、邀请好友、每日登录、绑定手机/邮箱等)
  • 奖励体系对齐本项目现有金币 / 积分 / VIP 三种虚拟资产,不再引入新资产类型
  • 奖励发放统一通过 CoinHelper / PointHelper / VIP 能力,禁止直接操作余额字段

2. 核心概念与数据模型 ​

2.1 核心实体 ​

TaskGroup(任务组) ──1:N── Task(任务) ──1:N── TaskProgress(用户任务进度)
                                              └──1:N── TaskLog(完成/领取日志)

2.2 数据表设计 ​

2.2.1 任务组表 (task_groups) ​

按业务场景组织任务的分组容器,如"新手任务""每日任务""成就任务"。

字段类型说明
idBIGINT PK主键
nameVARCHAR(64)任务组名称,如"每日任务"
codeVARCHAR(64) UNIQUE任务组唯一标识,如 daily / newbie / achievement
descriptionVARCHAR(255)描述
reset_typeVARCHAR(20)重置周期:none(不重置) / daily(每日) / weekly(每周) / monthly(每月)
visibilityTINYINT前台是否可见:0 不可见 1 可见
statusTINYINT状态:0 禁用 1 启用
sortINT排序
task_countINT任务数(冗余)
created_at / updated_atTIMESTAMP时间戳
deleted_atTIMESTAMP软删除

索引:(status, sort)、code 唯一索引。

2.2.2 任务表 (tasks) ​

单个任务的定义,包含类型、条件、奖励。

字段类型说明
idBIGINT PK主键
group_idBIGINT所属任务组 ID
nameVARCHAR(64)任务名称,如"每日签到"
codeVARCHAR(64)任务唯一标识(同组内唯一),如 daily_sign_in
typeVARCHAR(32)任务类型,见 TaskType 枚举
descriptionVARCHAR(255)任务描述
conditionJSON任务条件,见下方"条件结构"
target_countINT DEFAULT 1目标次数/数量(累计型任务用,1 表示一次性任务)
reward_typeVARCHAR(32)奖励类型:coin / point / vip_days / none
reward_amountINT DEFAULT 0奖励数量(金币数/积分数/VIP天数)
claim_modeVARCHAR(20)领取方式:auto(达成自动发放) / manual(用户手动领取)
iconVARCHAR(255)任务图标 URL(可选)
statusTINYINT状态:0 禁用 1 启用
sortINT排序
log_countINT完成次数(冗余统计)
created_at / updated_atTIMESTAMP时间戳
deleted_atTIMESTAMP软删除

索引:(group_id, status, sort)、(type, status)、code 唯一索引。

条件结构(condition JSON):

不同任务类型使用不同字段,通过 AsTaskCondition Cast 提供默认值:

json
{
  "serial_days": 0,              // 连续天数(签到类)
  "target_value": 0,             // 目标值(数值累计类,如邀请人数)
  "completeness_threshold": 100  // 资料完整度阈值,百分比(完善资料类)
}

2.2.3 用户任务进度表 (task_progress) ​

记录每个用户在每个任务上的当前进度,用于累计型任务的中间状态。一次性任务也写入一条记录表示已完成。

字段类型说明
idBIGINT PK主键
user_idBIGINT用户 ID
task_idBIGINT任务 ID
group_idBIGINT任务组 ID(冗余,便于按组查询)
current_valueINT DEFAULT 0当前进度值
is_completedTINYINT DEFAULT 0是否已完成(达成目标)
claimed_atTIMESTAMP NULL领取时间(manual 模式用)
period_keyVARCHAR(32)周期标识,如 2026-09-09(日) / 2026-W37(周) / 2026-09(月) / all(永久)
completed_atTIMESTAMP NULL完成时间
created_at / updated_atTIMESTAMP时间戳

唯一索引:uniq_user_task_period (user_id, task_id, period_key) — 同一用户同一任务同一周期只能有一条进度记录。

索引:(user_id, group_id, period_key)、(task_id, is_completed)。

2.2.4 任务完成日志表 (task_logs) ​

记录用户完成任务并领取奖励的流水,用于审计和去重。

字段类型说明
idBIGINT PK主键
user_idBIGINT用户 ID
task_idBIGINT任务 ID
group_idBIGINT任务组 ID(冗余)
reward_typeVARCHAR(32)奖励类型(冗余快照)
reward_amountINT奖励数量(冗余快照)
reward_trade_idBIGINT NULL奖励关联的交易记录 ID(coin_trade_id / point_trade_id 等)
period_keyVARCHAR(32)周期标识
claimed_byVARCHAR(20)领取触发方:system(自动) / user(手动)
created_atTIMESTAMP完成时间

唯一索引:uniq_user_task_period (user_id, task_id, period_key) — 保证同一周期同一任务只发放一次奖励。

索引:(user_id, created_at)、(task_id, created_at)、(group_id, created_at)。


3. 任务类型体系 ​

3.1 TaskType 枚举 ​

枚举值值说明重置周期建议
SIGN_INsign_in签到任务daily
COMPLETE_PROFILEcomplete_profile完善资料none(一次性)
FOLLOW_OFFICIAL_ACCOUNTfollow_official_account关注公众号none(一次性)
INVITE_USERinvite_user邀请好友注册none(累计)
DAILY_LOGINdaily_login每日登录daily
CUSTOM_ACTIONcustom_action自定义行为事件按业务而定

CUSTOM_ACTION 为兜底类型,业务方可通过事件名 + target_count 快速接入新任务,无需新增枚举值。

3.2 各类型任务判定逻辑 ​

3.2.1 签到任务(sign_in) ​

  • 触发事件:UserSignedIn(携带 user_id、serial_days 连续签到天数)

  • 实现方式:多任务 + serial_days 条件匹配(方案A)— 每个连续签到天数对应一条独立任务,由运营在后台配置。

  • 判定逻辑:

    1. 监听 UserSignedIn 事件,获取当前用户的连续签到天数 serial_days
    2. 查询所有 type = sign_in 且 status = 1 的任务
    3. 遍历任务,按 condition.serial_days == serial_days 精确匹配
    4. 匹配成功则发放对应奖励(claim_mode = auto 自动发放)
  • period_key 策略:

    • 每日签到基础奖励(serial_days = 0 表示每日签到通用奖励):period_key = Y-m-d,每日可领一次
    • 连续签到阶梯奖励(serial_days > 0):period_key = sign_in_day_{serial_days},同一连续天数一生只领一次(断签重来也不重复发放,避免刷奖励)
  • 奖励模式:auto(自动发放)

  • 后台配置示例:

    任务名称condition.serial_daysreward_typereward_amount
    每日签到0coin10
    连续签到第2天2coin15
    连续签到第3天3coin20
    连续签到第7天7coin100
    连续签到第30天30coin500
  • 注意:签到任务不走通用的 current_value >= target_count 判定流程,由 SignInTaskListener 特殊处理。condition.serial_days = 0 的任务表示"每日签到"基础奖励,每天都能领;serial_days > 0 表示连续签到第 N 天的阶梯奖励,一生只领一次。

3.2.2 完善资料任务(complete_profile) ​

  • 触发事件:UserProfileUpdated(携带 user_id,由 UserController@modifyProfile 和头像上传接口在用户更新资料后触发)

  • 判定逻辑:资料完整度基于 UserProfile 模型字段计算,共 6 个维度,每填一项算 1 分:

    序号维度所在表字段判定为"已填"的条件
    1昵称usersnamename is not null(注意:null 时 getter 会 fallback 为 username,此处必须判断数据库原始值非 null)
    2头像usersavataravatar is not null
    3性别user_profilesgendergender != 0(0=保密即未选择)
    4生日user_profilesbirthdaybirthday is not null
    5签名user_profilesbiobio is not null and bio != ''
    6地区user_profilesprovince_id / city_id / district_id三个字段均 is not null(省/市/区三级联动,都选才算填了地区)

    完整度 = 已填项数 / 6(满分 100%)。当完整度达到任务 condition.completeness_threshold(默认 100%,即全部填完)时标记完成。阈值通过任务的 condition JSON 配置,方便运营灵活调整。

  • condition 字段:{ "completeness_threshold": 100 },单位为百分比

  • period_key 策略:all(一次性,完成后永久有效,不重置)

  • 奖励模式:auto

  • 重置周期:none

  • 注意:资料完整度计算逻辑封装在 TaskService::calculateProfileCompleteness($user) 方法中,监听器调用此方法获得完整度百分比,与任务阈值对比判定是否达成。

3.2.3 关注公众号任务(follow_official_account) ​

  • 触发事件:UserFollowedOfficialAccount(公众号关注/取关回调时触发,携带 user_id、openid、is_followed)
  • 判定逻辑:
    1. 公众号关注回调(is_followed = true)时,通过 openid 匹配本地用户(socials 表中 provider = wechat_official 的记录)
    2. 匹配成功则对用户的 follow_official_account 类型任务标记完成
    3. 取关回调(is_followed = false)不做回滚(已发放的奖励不回收,但可在前台标记任务为未完成)
  • 奖励模式:auto
  • 重置周期:none
  • 注意:依赖项目已有 WechatService 和公众号回调处理,关注事件由微信服务器推送,需在 WechatController 中处理 event 消息并触发事件。用户与公众号的关联通过 socials 表的 provider = wechat_official 记录建立。

3.2.4 邀请好友任务(invite_user) ​

  • 触发事件:App\Events\Invite\InviteRelationshipCreated(已有事件,邀请关系建立时触发,携带 InviteRecord 模型)
  • 判定逻辑:
    1. 从事件中获取 InviteRecord,通过 $inviteRecord->inviter 获取邀请人
    2. 查询邀请人的 invite_user 类型任务
    3. 对匹配任务的进度 current_value + 1
    4. 当 current_value >= target_count 时标记完成
  • 奖励模式:auto 或 manual
  • 重置周期:none
  • 注意:该事件由 InviteService::createRelationshipByCode() 在邀请关系写入 invite_records 表后触发,任务系统监听此事件即可接入奖励。已有 CoinType::TYPE_INVITE_REGISTER 和 PointType::TYPE_INVITE_REGISTER 枚举可用。

3.2.5 每日登录任务(daily_login) ​

  • 触发事件:UserLoggedIn
  • 判定逻辑:当日有登录记录即完成,每日一次
  • 奖励模式:auto
  • 重置周期:daily

3.2.6 自定义行为任务(custom_action) ​

  • 触发事件:TaskActionTriggered(携带 action 和 value)
  • 判定逻辑:
    1. 匹配 condition.action == event.action 的任务
    2. 进度值累加 value
    3. 达到 target_count 即完成
  • 奖励模式:manual 或 auto
  • 重置周期:依业务而定

4. 奖励体系 ​

4.1 奖励类型 ​

类型值发放方式说明
金币coinCoinHelper::incr()对应 users.available_coins
积分pointPointHelper::incr()对应 users.available_points
VIP 天数vip_daysUserHelper::extendVip()对应 users.vip_expires_at
无奖励none—纯成就/解锁性质

4.2 发放流程 ​

事件触发 → 监听器 → 更新 task_progress → 判定是否达成
                              ↓ 达成且 claim_mode=auto
                         创建 task_log(事务内)
                              ↓
                    调用对应奖励 Helper 的 incr 方法
                              ↓
                    回填 reward_trade_id 到 task_log

4.3 并发与幂等保障 ​

  1. 唯一索引防重:task_progress 和 task_logs 均有 (user_id, task_id, period_key) 唯一索引,并发下重复插入会抛唯一键冲突,捕获后直接返回成功。
  2. 事务包裹:进度更新 + 奖励发放 + 日志写入在同一个 DB 事务中。
  3. 先写日志后发奖励:在事务内先 insert task_log(获取行锁),再发奖励,确保奖励只发一次。

5. 事件系统设计 ​

5.1 事件列表 ​

事件类触发场景携带数据
Events\User\UserSignedIn用户签到user_id、serial_days
Events\User\UserLoggedIn用户登录user_id、ip
Events\User\UserProfileUpdated用户更新资料user_id、completeness
Events\User\UserFollowedOfficialAccount公众号关注回调user_id、openid、is_followed
Events\Invite\InviteRelationshipCreated邀请关系建立(已有事件)inviteRecord(含 inviter、invitee 关联)
Events\Task\TaskActionTriggered通用任务行为触发user_id、action、value

5.2 监听器列表 ​

监听器监听事件职责
Listeners\Task\SignInTaskListenerUserSignedIn处理签到类任务进度
Listeners\Task\LoginTaskListenerUserLoggedIn处理每日登录任务
Listeners\Task\ProfileTaskListenerUserProfileUpdated处理完善资料任务
Listeners\Task\FollowOfficialAccountTaskListenerUserFollowedOfficialAccount处理关注公众号任务
Listeners\Task\InviteTaskListenerInviteRelationshipCreated处理邀请任务
Listeners\Task\CustomActionTaskListenerTaskActionTriggered处理自定义行为任务

每个监听器内部按以下通用流程处理:

php
// 伪代码
public function handle($event): void
{
    // 1. 找出该类型下所有启用的任务
    $tasks = Task::where('type', $this->taskType)->where('status', 1)->get();

    foreach ($tasks as $task) {
        // 2. 计算 period_key
        $periodKey = $task->group->getPeriodKey();

        // 3. upsert 进度(靠唯一索引防并发)
        $progress = TaskProgress::updateOrCreate(
            ['user_id' => $event->userId, 'task_id' => $task->id, 'period_key' => $periodKey],
            [...增量更新 current_value...]
        );

        // 4. 判定是否达成
        if ($progress->isCompleted() && !$progress->is_claimed) {
            // 5. 发放奖励(事务内)
            $task->grantReward($event->userId, $periodKey);
        }
    }
}

5.2.1 特殊监听器:SignInTaskListener ​

签到任务不走通用的 current_value 累加判定流程,因为连续签到天数由签到系统维护,任务系统只负责"匹配 + 发放"。

php
// SignInTaskListener 伪代码
public function handle(UserSignedIn $event): void
{
    $serialDays = $event->serial_days;

    // 1. 查询所有启用的签到任务
    $tasks = Task::where('type', TaskType::SIGN_IN)
        ->where('status', 1)
        ->with('group')
        ->get();

    foreach ($tasks as $task) {
        $conditionSerialDays = (int) ($task->condition['serial_days'] ?? 0);

        // 2. serial_days = 0 表示每日签到基础奖励,每天都匹配
        if ($conditionSerialDays === 0) {
            $periodKey = date('Y-m-d');
            $this->tryGrantReward($event->user_id, $task, $periodKey);
            continue;
        }

        // 3. serial_days > 0 表示连续签到阶梯奖励,精确匹配
        if ($conditionSerialDays === $serialDays) {
            $periodKey = "sign_in_day_{$conditionSerialDays}";
            $this->tryGrantReward($event->user_id, $task, $periodKey);
        }
    }
}

签到任务同样写入 task_progress 和 task_logs,保持数据一致性,方便前台列表统一展示。


6. API 设计 ​

6.1 前台用户端 API(/api/v1) ​

方法路径说明权限状态
GET/api/v1/tasks任务中心列表(按分组返回,含用户进度)已登录用户✅ 已完成
POST/api/v1/tasks/{task_id}/claim手动领取任务奖励已登录用户✅ 已完成
POST/api/v1/tasks/sign-in签到(同时触发签到事件)已登录用户⏳ 待实现

6.2 后台管理 API(/admin) ​

方法路径说明权限状态
GET/admin/task-groups任务组分页列表task-groups.index✅ 已完成
GET/admin/task-groups/options任务分组选项(不分页,下拉选择用)task-groups.index✅ 已完成
POST/admin/task-groups创建任务分组task-groups.create✅ 已完成
GET/admin/task-groups/{id}任务分组详情(含分组下任务)task-groups.index✅ 已完成
PUT/admin/task-groups/{id}更新任务分组task-groups.edit✅ 已完成
PUT/admin/task-groups/{id}/toggle-status切换任务分组启用状态task-groups.edit✅ 已完成
DELETE/admin/task-groups/{id}删除任务分组(有任务时不允许删除)task-groups.delete✅ 已完成
GET/admin/tasks任务分页列表(支持 group_id/type/status/keyword 筛选)tasks.index✅ 已完成
POST/admin/tasks创建任务tasks.create✅ 已完成
GET/admin/tasks/{id}任务详情tasks.index✅ 已完成
PUT/admin/tasks/{id}更新任务tasks.edit✅ 已完成
PUT/admin/tasks/{id}/toggle-status切换任务启用状态tasks.edit✅ 已完成
DELETE/admin/tasks/{id}删除任务tasks.delete✅ 已完成
GET/admin/task-logs任务完成日志列表task-logs.index⏳ 待实现
POST/admin/tasks/{id}/reward补发/调整奖励(高权限)tasks.reward⏳ 待实现

7. 核心代码结构 ​

app/
├── Enums/
│   ├── PointType.php                        # 新增 TYPE_TASK 枚举值
│   └── Task/
│       ├── TaskType.php                     # 任务类型枚举(sign_in/complete_profile/...)
│       ├── TaskGroupType.php                # 任务组类型(NEWBIE/DAILY/WEEKLY/SIGN_IN/CUSTOM)
│       ├── TaskResetType.php                # 重置周期枚举(NONE/DAILY/WEEKLY/MONTHLY)
│       ├── TaskRewardType.php               # 奖励类型枚举(coin/point/vip_days)
│       ├── TaskClaimMode.php                # 领取方式枚举(AUTO/MANUAL)
│       └── TaskProgressStatus.php           # 进度状态枚举
├── Models/
│   └── Task/
│       ├── TaskGroup.php                    # 任务组模型(type/reset_type 为 int Enum)
│       ├── Task.php                         # 任务模型(含 activeTasks 作用域)
│       ├── TaskProgress.php                 # 用户任务进度(status 为 Enum Cast)
│       └── TaskLog.php                      # 完成日志
├── Casts/
│   └── AsTaskCondition.php                  # 任务条件 JSON Cast,默认值 {serial_days:0,target_value:0,completeness_threshold:100}
├── Events/
│   └── User/
│       └── UserProfileUpdated.php           # ✅ 用户资料更新事件(modifyProfile 后触发)
├── Listeners/
│   └── Task/
│       └── ProfileTaskListener.php          # ✅ 完善资料监听器(ShouldQueue)
│       # SignInTaskListener / LoginTaskListener / FollowOfficialAccountTaskListener
│       # InviteTaskListener / CustomActionTaskListener ⏳ 待实现
├── Services/
│   └── TaskService.php                      # 任务进度判定 + 奖励发放编排
├── Support/
│   ├── UserHelper.php                       # 新增 extendVip($user, $days) 方法
│   └── Traits/
│       └── HasTaskProgress.php              # 用户模型的任务关联 trait
├── Http/
│   ├── Controllers/
│   │   ├── Api/V1/
│   │   │   └── TaskController.php           # ✅ 前台任务接口(index / claim)
│   │   └── Admin/
│   │       ├── TaskGroupController.php      # ✅ 任务分组 CRUD + options + toggle-status
│   │       └── TaskController.php           # ✅ 任务 CRUD + toggle-status
│   │       # TaskLogController.php        ⏳ 待实现
│   ├── Requests/
│   │   └── Admin/
│   │       ├── TaskGroup/
│   │       │   ├── TaskGroupIndexRequest.php
│   │       │   └── TaskGroupSaveRequest.php
│   │       └── Task/
│   │           ├── TaskIndexRequest.php
│   │           └── TaskSaveRequest.php
│   └── Resources/
│       ├── Admin/
│       │   ├── TaskGroupResource.php
│       │   └── TaskResource.php
│       └── Api/V1/
│           ├── TaskGroupResource.php
│           └── TaskResource.php
config/
└── morph_maps.php                           # 注册 'task' => Task::class 多态别名
database/
└── migrations/
    ├── 2026_09_10_150858_create_task_tables.php         # 4 张表(task_groups/tasks/task_progress/task_logs)
    ├── 2026_09_10_154135_seed_initial_task_data.php     # 初始任务组和任务种子(迁移方式,不用 Seeder)
    └── 2026_10_31_111934_seed_initial_admin_data.php    # 追加任务分组/任务管理菜单与按钮权限
routes/
├── api_v1.php                               # 新增 /tasks 路由组
└── admin.php                                # 新增 task-groups 和 tasks 资源路由
tests/
├── Feature/
│   ├── Api/V1/TaskControllerTest.php        # ✅ 前台任务接口测试(5 个用例)
│   └── Admin/                               # ⏳ 待补充后台 CRUD 测试
└── Unit/Services/
    └── TaskServiceTest.php                  # ✅ TaskService 单元测试(12 个用例)

注:实际表结构相比设计文档有所调整——task_groups 使用 int Enum(type/reset_type),不使用 visibility/deleted_at/task_count;tasks 不使用 target_count/icon/log_count/deleted_at,条件通过统一的 condition JSON 存储;task_progress 使用 status Enum(IN_PROGRESS/COMPLETED/CLAIMED)替代 is_completed+claimed_at 组合判断。

7.1 TaskService 核心方法 ​

方法说明
getUserTaskGroups($user)获取用户视角的任务组列表(含进度)
getUserProgress($user, $taskId)获取用户某任务的进度
incrementProgress($userId, $task, $value, $periodKey)增加任务进度,返回是否新达成
grantReward($userId, $task, $periodKey, $claimedBy)发放奖励并写日志,幂等
claimReward($userId, $taskId)手动领取奖励(manual 模式用)

7.2 User 模型集成 ​

通过 HasTaskProgress trait 给 User 模型追加:

php
// 关联
$this->taskProgresses(): HasMany
$this->taskLogs(): HasMany

// 快捷方法
$this->hasCompletedTask($taskCode): bool
$this->getTaskProgress($taskCode): ?TaskProgress

8. 初始种子数据 ​

初始数据通过 2026_09_10_154135_seed_initial_task_data.php 迁移写入(使用 updateOrCreate,幂等执行):

8.1 新手任务组(newbie,不重置,type=0, reset_type=0) ​

任务类型condition奖励奖励数量claim_mode
完善个人资料complete_profilecoin50auto
关注公众号follow_official_accountcoin30auto

8.2 签到任务组(sign_in,type=3, reset_type=1)⏳ 待补充 ​

任务typecondition.serial_daysreward_typereward_amountperiod_key 策略
每日签到sign_in0coin10Y-m-d,每日重置
连续签到第2天~第30天阶梯sign_in2/3/7/30coin15/20/100/500sign_in_day_N,永久唯一

8.3 每日任务组(daily,type=1, reset_type=1)⏳ 待补充 ​

任务类型奖励奖励数量
每日登录daily_loginpoint5

9. 权限与菜单 ​

9.1 新增权限 ​

权限标识说明状态
task-groups.index查看任务组列表✅ 已实现
task-groups.create创建任务组✅ 已实现
task-groups.edit编辑任务组✅ 已实现
task-groups.delete删除任务组✅ 已实现
tasks.index查看任务列表✅ 已实现
tasks.create创建任务✅ 已实现
tasks.edit编辑任务✅ 已实现
tasks.delete删除任务✅ 已实现
tasks.reward补发/调整任务奖励(高权限)⏳ 待实现
task-logs.index查看任务完成日志⏳ 待实现

9.2 新增菜单 ​

已追加到 database/migrations/2026_10_31_111934_seed_initial_admin_data.php,挂在「运营管理」目录下,原运营菜单 sort 值已顺延:

菜单 keypathcomponenttitleiconpermission按钮权限
operation.task-grouptask-group/operation/task-group任务分组ri:folder-3-linetask-groups.indexcreate/edit/delete
operation.tasktask/operation/task任务管理ri:task-linetasks.indexcreate/edit/delete
operation.task-logtask-log/operation/task-log任务记录—task-logs.index⏳ 待添加

10. 与现有系统的集成点 ​

10.1 金币 / 积分 / VIP ​

  • 金币奖励:调用 CoinHelper::incr($user, $amount, $taskLog, 'task', '完成任务奖励'),签到类用 CoinType::TYPE_SIGN_IN,邀请类用 CoinType::TYPE_INVITE_REGISTER
  • 积分奖励:调用 PointHelper::incr(...),同上区分类型
  • VIP 奖励:调用 UserHelper::extendVip($user, $days)
  • 所有奖励操作均通过已有 Helper,不直接修改 users 表字段

10.2 事件系统 ​

  • 复用现有 UserLoggedIn、UserProfileUpdated 等事件
  • 复用已有 InviteRelationshipCreated 事件接入邀请任务奖励
  • 新增 UserSignedIn、TaskActionTriggered 等事件
  • 项目使用 Laravel 11+ 自动事件发现机制,监听器放在 app/Listeners/ 下自动注册,无需手动绑定

10.3 多态映射 ​

在 config/morph_maps.php 中追加:

php
'task_group'   => \App\Models\Task\TaskGroup::class,
'task'         => \App\Models\Task\Task::class,
'task_log'     => \App\Models\Task\TaskLog::class,

11. 实施阶段 ​

阶段内容交付物状态
Phase 1 数据模型4 张表迁移 + 4 个模型 + 6 个枚举 + Cast;HasTaskProgress trait;UserHelper::extendVip;PointType::TYPE_TASK;morph map 注册;初始任务种子迁移可查询、可配置的任务数据层✅ 已完成
Phase 2 任务引擎(基础)TaskService 核心逻辑(周期计算、进度更新、完整度判定、签到处理、领取奖励、发奖事务、列表查询);UserProfileUpdated 事件 + ProfileTaskListener 队列监听器;UserController::modifyProfile 触发事件;单元测试覆盖(12 个用例)完善资料类型任务奖励引擎可用✅ 部分完成
Phase 3 前台 APIGET /api/v1/tasks(任务中心列表,按分组返回含进度);POST /api/v1/tasks/{taskId}/claim(手动领奖);TaskResource + TaskGroupResource;功能测试 5 个用例;Apifox Api 模块发布前台用户端基础接口可用✅ 已完成
Phase 4 后台管理(分组+任务)TaskGroupController(7 个接口:列表/options/创建/详情/更新/状态切换/删除);TaskController(6 个接口:列表/创建/详情/更新/状态切换/删除);Form Request(Index+Save);Admin Resource;菜单权限迁移;Apifox Admin 模块发布运营可配置任务分组和任务✅ 已完成
Phase 5 前端落地(分组+任务)Ai Forge Admin 中 src/views/operation/task-group/(列表+搜索+编辑弹窗)和 src/views/operation/task/(列表+搜索+编辑弹窗,条件字段按任务类型动态显示);API 函数 + TS 类型;pnpm build 通过分组和任务管理 UI 可用✅ 已完成
Phase 2+ 剩余任务类型引擎剩余 5 个事件(UserSignedIn/UserLoggedIn/UserFollowedOfficialAccount/InviteRelationshipCreated/TaskActionTriggered)+ 5 个监听器(SignIn/Login/Follow/Invite/CustomAction);签到特殊 period_key 策略;邀请任务累计;自定义行为通用触发;补齐单元测试全部任务类型奖励引擎可用⏳ 待实现
Phase 3+ 前台补充接口POST /api/v1/tasks/sign-in 签到专用接口(触发签到事件并返回签到结果);签到相关的连续签到天数返回前台签到功能可用⏳ 待实现
Phase 4+ 后台日志与补偿TaskLogController(日志分页列表/详情);补发奖励接口(POST /admin/tasks/{id}/reward);后台 CRUD 功能测试任务审计与运营补偿能力⏳ 待实现
Phase 5+ 前端日志页面Ai Forge Admin 中 src/views/operation/task-log/ 任务记录页面后台任务记录 UI⏳ 待实现
Phase 6 文档与收尾补齐测试覆盖率至 70%+;前端开发环境联调验证;README 更新;最终 Apifox 质量检查生产可用⏳ 待实现

当前可用功能(截至本次实现) ​

  • 后台可创建/编辑/启用/禁用任务分组(新手/每日/每周/签到/自定义 5 种类型)
  • 后台可创建/编辑/启用/禁用任务(6 种类型 + 3 种奖励 + 自动/手动领取模式 + 生效时间范围)
  • 前台用户调用 GET /api/v1/tasks 可查看按分组组织的任务列表及个人进度
  • 用户更新个人资料后自动触发完善资料任务判定,达到阈值(默认100%即6项全填)自动发放金币奖励
  • 前台用户对 manual 领取模式的任务可调用 POST /api/v1/tasks/{taskId}/claim 领取奖励
  • 所有写操作幂等安全(唯一索引 + 事务 + UniqueConstraintViolationException 捕获)

12. 风险与注意事项 ​

风险说明应对策略
重复发放奖励并发下同一用户同一任务可能被多个请求同时触发唯一索引 + 事务 + 捕获唯一键冲突返回成功
事件丢失事件监听器抛异常可能导致任务进度未更新关键事件启用 ShouldQueue + 失败重试 + failed_jobs 监控
重置周期边界跨日/跨周时旧周期数据残留所有查询均带 period_key 过滤,后台可定期归档历史数据
奖励发放失败金币/积分 Helper 抛异常导致部分成功全部操作在同一事务内,任何一步失败整体回滚
配置错误导致异常奖励运营配置 reward_amount 过大后台创建任务时增加最大奖励数量校验