8.9 KiB
8.9 KiB
活动大厅(任务中心)规则与计算逻辑梳理
1. 后端数据结构与配置来源
- 任务、档位、奖励主表:
Task、TaskTier、TaskReward、UserTaskProgress与TaskEventLog分别落在task_center_tasks、task_center_task_tiers、task_center_task_rewards、task_center_user_progress、task_center_event_logs表中,字段包含任务级/档位级限额、活动绑定、扩展参数 JSON 以及幂等键等核心信息(bindbox_game/internal/repository/mysql/task_center/models.go:10-95)。 - 限额与扩展字段迁移:
migrations/task_level_quota.sql:1-12在 2026-02-16 引入任务级quota/claimed_count,同时移除旧的档位限额。migrations/20260206_add_task_tier_quota.sql:1-6再次为档位恢复独立限额字段,用于任务总限量之外的档位限量。migrations/20251223_add_user_invites_effective_columns.sql:1-4添加extra_params、effective_invite_count以及邀请有效性相关列,为后续“新用户限制”等规则提供数据面支撑。
- 管理端 CRUD & 配置 API:
internal/api/task_center/admin.go提供任务创建、修改、删除、档位/奖励配置、模拟事件与奖励发放统计接口(admin.go:14-486)。所有接口最终落到task_center/service.go的CreateTask、UpsertTaskTiers、UpsertTaskRewards等方法(service.go:953-1162),确保配置变更会触发缓存失效。
2. 运行期进度计算流程
- 时间窗口规范化:
normalizeWindow/normalizeWindowStrict将非法或空窗口统一视为lifetime(service.go:200-217),computeTimeWindow根据daily/weekly/monthly/activity_period返回具体起止时间,缺省为不限(service.go:500-525)。 - 订单指标采集:
fetchOrderMetricRows联结订单、期次、活动表,并按活动聚合抽赏次数/票价或订单金额(service.go:223-247);calculateEffectiveAmount/aggregateOrderMetrics负责将抽赏次数 × 票价或订单实付换算成有效金额,支持去重订单总量与活动内统计(service.go:249-281)。 - 邀请指标采集:
countInvites、countInvitesForActivities分别处理单一活动窗口和多活动聚合,只有邀请人邀请的用户在目标活动内完成已支付订单后才计入特定活动任务(service.go:283-346)。 - 任务列表出参:
ListTasks默认过滤启用且可见的任务(App 端)并预加载档位/奖励,同时根据task_center_task_rewards关联优惠券、道具卡、称号名称(service.go:348-498)。 - 按窗口分组统计进度:
GetUserProgress会先查询任务与其所有档位,根据(window, activity_id)组合拉取订单/邀请数据,产出TierProgressMap(每个档位独立窗口内订单/金额/邀请)与SubProgress(按活动粒度的订单汇总),同时聚合全局订单/邀请指标以及已领取档位列表(service.go:528-668)。
3. 达成校验与领取路径
- 前置校验:
ClaimTier首先调用GetUserProgress获取窗口化进度,并加载目标档位/任务最新配置(service.go:670-686)。 - Redis 限流:对于绑定
ActivityID的档位,会抢占tc:claim_lock:{user_id}:{activity_id}避免并发重复领(service.go:688-700)。 - 指标达成判定:优先读取
TierProgressMap中窗口内数值,其次使用SubProgress→ 全局进度回退;支持>=/=操作符,对first_order直接使用布尔值(service.go:702-752)。 - 跨任务资源占用校验:
calculateCrossTaskConsumedThreshold会收集同活动+同指标的其他任务已领取档位,按任务窗口是否重叠、创建时间先后过滤后累积最大门槛值,若currentValue < consumed + 自身阈值则拒绝领取(service.go:754-939)。 - 限额 & 状态更新:领取前更新任务级别
claimed_count,若已达quota返回“奖励已领完”(service.go:788-799);奖励发放成功后,事务性地在task_center_user_progress中追加tier_id,幂等写入claimed_tiersJSON(service.go:801-851)。
4. 奖励发放与事件机制
- 奖励发放器:
grantTierRewards会根据task_id + tier_id查询奖励列表、构造idempotency_key并写入task_center_event_logs,支持积分、优惠券、道具卡、称号、游戏票、实物商品等类型,并对quantity与 payload 内数量做“优先取 r.Quantity”策略修复(service.go:1400-1558)。 - 异步事件:
OnOrderPaid/OnInviteSuccess先尝试投递到 Redis 队列,否则直接同步处理;processOrderPaid只在订单状态为已支付时并发写入邀请人累计金额,同时加 Redis 幂等锁防止 24 小时内重复处理(service.go:1165-1243)。StartWorker在 Redis 队列可用时启动 5 个 goroutine 消费order_paid与invite_success事件,最终调用processOrderPaid/processInviteSuccess(worker.go:12-80)。
- 触发入口(上游链路):
- 微信支付回调完成后,后台会异步执行
_ = h.task.OnOrderPaid(...),确保真实支付订单刷新任务进度(internal/api/pay/wechat_notify.go:294-305)。 - 0 元抽奖订单、虚拟玩法等也会在服务器端模拟支付后触发
OnOrderPaid(internal/api/activity/lottery_app.go:458-464、internal/api/activity/matching_game_app.go:203-206)。 - 新用户带邀请码登录成功后,若存在邀请人则调用
OnInviteSuccess记入邀请进度(internal/api/user/login_app.go:78-83)。 - 管理端提供
/admin/task_center/simulate/order_paid与/simulate/invite_success便于调试触发(internal/api/task_center/admin.go:315-369)。
- 微信支付回调完成后,后台会异步执行
5. 小程序端消费链路
- API 封装:
getTasks/getTaskProgress/claimTaskReward直接映射到 App 端任务中心列表、进度、领取接口(bindbox-mini/api/appUser.js:262-271)。 - 入口触达:个人中心“常用功能”列表包含「任务中心」入口,点击跳转至
pages-user/tasks/index.vue页面(bindbox-mini/pages/mine/index.vue:140-184)。 - UI 与状态处理:
- 页面顶部展示订单数、邀请数、首单状态等汇总统计,同时支持空态、骨架加载(
pages-user/tasks/index.vue:1-56)。 - 任务卡片内根据
task.quota/claimed_count、tier.remaining、userProgress.claimedTiers和tier_activity_id决定显示“已领完”“可领取”“进行中”等状态(pages-user/tasks/index.vue:57-150、220-379)。 taskProgress为每个任务缓存独立进度,normalizeSubProgress+getSubProgress*系列方法根据sub_progress(活动维度进度)渲染子进度条,确保与后端TierProgressMap字段语义一致(pages-user/tasks/index.vue:222-479、590-666)。fetchData并行拉取任务与进度,首个任务默认展开,领取成功后本地更新claimedTiers并 toast 提示(pages-user/tasks/index.vue:515-589、481-505)。
- 页面顶部展示订单数、邀请数、首单状态等汇总统计,同时支持空态、骨架加载(
- 交互约束:未登录/未缓存用户信息会弹出“请先登录”提示并跳转登录页,确保任务接口只在持有 token 的情况下访问(
pages-user/tasks/index.vue:187-209)。
6. 测试覆盖与验证建议
- 单元/集成测试:
TestListTasks_FilterByStatusAndVisibility验证 App 列表只返回启用且可见的任务(internal/service/task_center/list_tasks_filter_test.go:11-95)。service_test.go中的场景覆盖活动有效期窗口统计与跨任务占用判断,确保TierProgressMap与calculateCrossTaskConsumedThreshold行为正确(internal/service/task_center/service_test.go:350-514)。TestInviteLogicSymmetry通过 SQLite 集成测试验证全局/特定活动邀请数统计与订单联动逻辑(internal/service/task_center/invite_logic_test.go:10-80)。
- 建议的手工校验:
- 使用管理端创建带活动绑定与不同窗口的任务→调用
/api/app/task-center/tasks与/progress/{user}核对sub_progress、tier_progress_map是否如预期。 - 在沙箱环境支付一笔订单或通过模拟接口触发
OnOrderPaid,确认 Redis 幂等锁与任务限额的日志输出,并在task_center_event_logs中观察发放记录。 - 在小程序端登陆真实账号,完成任务条件后点击“领取”,应同步看到
task.quota/tier.remaining与“已领取”状态更新,且后台奖励统计接口/admin/task_center/tasks/{id}/reward-stats的计数随之增加。
- 使用管理端创建带活动绑定与不同窗口的任务→调用
- 假设与范围:本文将“活动大厅”视为任务中心在小程序端的 UI 展示,不涉及其他独立模块;仅梳理现有逻辑,不包含代码改动。
- 交付复核:若需要进一步变更规则,可从本文档列出的结构/流程入手定位受影响的函数与 API。