用户任务系统技术方案
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)
按业务场景组织任务的分组容器,如"新手任务""每日任务""成就任务"。
| 字段 | 类型 | 说明 |
|---|---|---|
| id | BIGINT PK | 主键 |
| name | VARCHAR(64) | 任务组名称,如"每日任务" |
| code | VARCHAR(64) UNIQUE | 任务组唯一标识,如 daily / newbie / achievement |
| description | VARCHAR(255) | 描述 |
| reset_type | VARCHAR(20) | 重置周期:none(不重置) / daily(每日) / weekly(每周) / monthly(每月) |
| visibility | TINYINT | 前台是否可见:0 不可见 1 可见 |
| status | TINYINT | 状态:0 禁用 1 启用 |
| sort | INT | 排序 |
| task_count | INT | 任务数(冗余) |
| created_at / updated_at | TIMESTAMP | 时间戳 |
| deleted_at | TIMESTAMP | 软删除 |
索引:(status, sort)、code 唯一索引。
2.2.2 任务表 (tasks)
单个任务的定义,包含类型、条件、奖励。
| 字段 | 类型 | 说明 |
|---|---|---|
| id | BIGINT PK | 主键 |
| group_id | BIGINT | 所属任务组 ID |
| name | VARCHAR(64) | 任务名称,如"每日签到" |
| code | VARCHAR(64) | 任务唯一标识(同组内唯一),如 daily_sign_in |
| type | VARCHAR(32) | 任务类型,见 TaskType 枚举 |
| description | VARCHAR(255) | 任务描述 |
| condition | JSON | 任务条件,见下方"条件结构" |
| target_count | INT DEFAULT 1 | 目标次数/数量(累计型任务用,1 表示一次性任务) |
| reward_type | VARCHAR(32) | 奖励类型:coin / point / vip_days / none |
| reward_amount | INT DEFAULT 0 | 奖励数量(金币数/积分数/VIP天数) |
| claim_mode | VARCHAR(20) | 领取方式:auto(达成自动发放) / manual(用户手动领取) |
| icon | VARCHAR(255) | 任务图标 URL(可选) |
| status | TINYINT | 状态:0 禁用 1 启用 |
| sort | INT | 排序 |
| log_count | INT | 完成次数(冗余统计) |
| created_at / updated_at | TIMESTAMP | 时间戳 |
| deleted_at | TIMESTAMP | 软删除 |
索引:(group_id, status, sort)、(type, status)、code 唯一索引。
条件结构(condition JSON):
不同任务类型使用不同字段,通过 AsTaskCondition Cast 提供默认值:
{
"serial_days": 0, // 连续天数(签到类)
"target_value": 0, // 目标值(数值累计类,如邀请人数)
"completeness_threshold": 100 // 资料完整度阈值,百分比(完善资料类)
}2.2.3 用户任务进度表 (task_progress)
记录每个用户在每个任务上的当前进度,用于累计型任务的中间状态。一次性任务也写入一条记录表示已完成。
| 字段 | 类型 | 说明 |
|---|---|---|
| id | BIGINT PK | 主键 |
| user_id | BIGINT | 用户 ID |
| task_id | BIGINT | 任务 ID |
| group_id | BIGINT | 任务组 ID(冗余,便于按组查询) |
| current_value | INT DEFAULT 0 | 当前进度值 |
| is_completed | TINYINT DEFAULT 0 | 是否已完成(达成目标) |
| claimed_at | TIMESTAMP NULL | 领取时间(manual 模式用) |
| period_key | VARCHAR(32) | 周期标识,如 2026-09-09(日) / 2026-W37(周) / 2026-09(月) / all(永久) |
| completed_at | TIMESTAMP NULL | 完成时间 |
| created_at / updated_at | TIMESTAMP | 时间戳 |
唯一索引: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)
记录用户完成任务并领取奖励的流水,用于审计和去重。
| 字段 | 类型 | 说明 |
|---|---|---|
| id | BIGINT PK | 主键 |
| user_id | BIGINT | 用户 ID |
| task_id | BIGINT | 任务 ID |
| group_id | BIGINT | 任务组 ID(冗余) |
| reward_type | VARCHAR(32) | 奖励类型(冗余快照) |
| reward_amount | INT | 奖励数量(冗余快照) |
| reward_trade_id | BIGINT NULL | 奖励关联的交易记录 ID(coin_trade_id / point_trade_id 等) |
| period_key | VARCHAR(32) | 周期标识 |
| claimed_by | VARCHAR(20) | 领取触发方:system(自动) / user(手动) |
| created_at | TIMESTAMP | 完成时间 |
唯一索引: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_IN | sign_in | 签到任务 | daily |
COMPLETE_PROFILE | complete_profile | 完善资料 | none(一次性) |
FOLLOW_OFFICIAL_ACCOUNT | follow_official_account | 关注公众号 | none(一次性) |
INVITE_USER | invite_user | 邀请好友注册 | none(累计) |
DAILY_LOGIN | daily_login | 每日登录 | daily |
CUSTOM_ACTION | custom_action | 自定义行为事件 | 按业务而定 |
CUSTOM_ACTION为兜底类型,业务方可通过事件名 +target_count快速接入新任务,无需新增枚举值。
3.2 各类型任务判定逻辑
3.2.1 签到任务(sign_in)
触发事件:
UserSignedIn(携带user_id、serial_days连续签到天数)实现方式:多任务 + serial_days 条件匹配(方案A)— 每个连续签到天数对应一条独立任务,由运营在后台配置。
判定逻辑:
- 监听
UserSignedIn事件,获取当前用户的连续签到天数serial_days - 查询所有
type = sign_in且status = 1的任务 - 遍历任务,按
condition.serial_days == serial_days精确匹配 - 匹配成功则发放对应奖励(
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_days reward_type reward_amount 每日签到 0 coin 10 连续签到第2天 2 coin 15 连续签到第3天 3 coin 20 连续签到第7天 7 coin 100 连续签到第30天 30 coin 500 注意:签到任务不走通用的
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 昵称 users namename is not null(注意:null 时 getter 会 fallback 为 username,此处必须判断数据库原始值非 null)2 头像 users avataravatar is not null3 性别 user_profiles gendergender != 0(0=保密即未选择)4 生日 user_profiles birthdaybirthday is not null5 签名 user_profiles biobio is not null and bio != ''6 地区 user_profiles province_id/city_id/district_id三个字段均 is not null(省/市/区三级联动,都选才算填了地区)完整度 = 已填项数 / 6(满分 100%)。当完整度达到任务
condition.completeness_threshold(默认 100%,即全部填完)时标记完成。阈值通过任务的conditionJSON 配置,方便运营灵活调整。condition 字段:
{ "completeness_threshold": 100 },单位为百分比period_key 策略:
all(一次性,完成后永久有效,不重置)奖励模式:auto
重置周期:none
注意:资料完整度计算逻辑封装在
TaskService::calculateProfileCompleteness($user)方法中,监听器调用此方法获得完整度百分比,与任务阈值对比判定是否达成。
3.2.3 关注公众号任务(follow_official_account)
- 触发事件:
UserFollowedOfficialAccount(公众号关注/取关回调时触发,携带user_id、openid、is_followed) - 判定逻辑:
- 公众号关注回调(
is_followed = true)时,通过openid匹配本地用户(socials表中provider = wechat_official的记录) - 匹配成功则对用户的
follow_official_account类型任务标记完成 - 取关回调(
is_followed = false)不做回滚(已发放的奖励不回收,但可在前台标记任务为未完成)
- 公众号关注回调(
- 奖励模式:auto
- 重置周期:none
- 注意:依赖项目已有
WechatService和公众号回调处理,关注事件由微信服务器推送,需在WechatController中处理event消息并触发事件。用户与公众号的关联通过socials表的provider = wechat_official记录建立。
3.2.4 邀请好友任务(invite_user)
- 触发事件:
App\Events\Invite\InviteRelationshipCreated(已有事件,邀请关系建立时触发,携带InviteRecord模型) - 判定逻辑:
- 从事件中获取
InviteRecord,通过$inviteRecord->inviter获取邀请人 - 查询邀请人的
invite_user类型任务 - 对匹配任务的进度
current_value + 1 - 当
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) - 判定逻辑:
- 匹配
condition.action == event.action的任务 - 进度值累加
value - 达到
target_count即完成
- 匹配
- 奖励模式:manual 或 auto
- 重置周期:依业务而定
4. 奖励体系
4.1 奖励类型
| 类型 | 值 | 发放方式 | 说明 |
|---|---|---|---|
| 金币 | coin | CoinHelper::incr() | 对应 users.available_coins |
| 积分 | point | PointHelper::incr() | 对应 users.available_points |
| VIP 天数 | vip_days | UserHelper::extendVip() | 对应 users.vip_expires_at |
| 无奖励 | none | — | 纯成就/解锁性质 |
4.2 发放流程
事件触发 → 监听器 → 更新 task_progress → 判定是否达成
↓ 达成且 claim_mode=auto
创建 task_log(事务内)
↓
调用对应奖励 Helper 的 incr 方法
↓
回填 reward_trade_id 到 task_log4.3 并发与幂等保障
- 唯一索引防重:
task_progress和task_logs均有(user_id, task_id, period_key)唯一索引,并发下重复插入会抛唯一键冲突,捕获后直接返回成功。 - 事务包裹:进度更新 + 奖励发放 + 日志写入在同一个 DB 事务中。
- 先写日志后发奖励:在事务内先 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\SignInTaskListener | UserSignedIn | 处理签到类任务进度 |
Listeners\Task\LoginTaskListener | UserLoggedIn | 处理每日登录任务 |
Listeners\Task\ProfileTaskListener | UserProfileUpdated | 处理完善资料任务 |
Listeners\Task\FollowOfficialAccountTaskListener | UserFollowedOfficialAccount | 处理关注公众号任务 |
Listeners\Task\InviteTaskListener | InviteRelationshipCreated | 处理邀请任务 |
Listeners\Task\CustomActionTaskListener | TaskActionTriggered | 处理自定义行为任务 |
每个监听器内部按以下通用流程处理:
// 伪代码
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 累加判定流程,因为连续签到天数由签到系统维护,任务系统只负责"匹配 + 发放"。
// 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,条件通过统一的conditionJSON 存储;task_progress使用statusEnum(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 模型追加:
// 关联
$this->taskProgresses(): HasMany
$this->taskLogs(): HasMany
// 快捷方法
$this->hasCompletedTask($taskCode): bool
$this->getTaskProgress($taskCode): ?TaskProgress8. 初始种子数据
初始数据通过 2026_09_10_154135_seed_initial_task_data.php 迁移写入(使用 updateOrCreate,幂等执行):
8.1 新手任务组(newbie,不重置,type=0, reset_type=0)
| 任务 | 类型 | condition | 奖励 | 奖励数量 | claim_mode |
|---|---|---|---|---|---|
| 完善个人资料 | complete_profile | coin | 50 | auto | |
| 关注公众号 | follow_official_account | coin | 30 | auto |
8.2 签到任务组(sign_in,type=3, reset_type=1)⏳ 待补充
| 任务 | type | condition.serial_days | reward_type | reward_amount | period_key 策略 |
|---|---|---|---|---|---|
| 每日签到 | sign_in | 0 | coin | 10 | Y-m-d,每日重置 |
| 连续签到第2天~第30天阶梯 | sign_in | 2/3/7/30 | coin | 15/20/100/500 | sign_in_day_N,永久唯一 |
8.3 每日任务组(daily,type=1, reset_type=1)⏳ 待补充
| 任务 | 类型 | 奖励 | 奖励数量 |
|---|---|---|---|
| 每日登录 | daily_login | point | 5 |
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 值已顺延:
| 菜单 key | path | component | title | icon | permission | 按钮权限 |
|---|---|---|---|---|---|---|
| operation.task-group | task-group | /operation/task-group | 任务分组 | ri:folder-3-line | task-groups.index | create/edit/delete |
| operation.task | task | /operation/task | 任务管理 | ri:task-line | tasks.index | create/edit/delete |
| operation.task-log | task-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 中追加:
'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 前台 API | GET /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 过大 | 后台创建任务时增加最大奖励数量校验 |