# 活动大厅(任务中心)规则与计算逻辑梳理 ## 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_tiers` JSON(`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`)。 - **建议的手工校验**: 1. 使用管理端创建带活动绑定与不同窗口的任务→调用 `/api/app/task-center/tasks` 与 `/progress/{user}` 核对 `sub_progress`、`tier_progress_map` 是否如预期。 2. 在沙箱环境支付一笔订单或通过模拟接口触发 `OnOrderPaid`,确认 Redis 幂等锁与任务限额的日志输出,并在 `task_center_event_logs` 中观察发放记录。 3. 在小程序端登陆真实账号,完成任务条件后点击“领取”,应同步看到 `task.quota` / `tier.remaining` 与“已领取”状态更新,且后台奖励统计接口 `/admin/task_center/tasks/{id}/reward-stats` 的计数随之增加。 --- - **假设与范围**:本文将“活动大厅”视为任务中心在小程序端的 UI 展示,不涉及其他独立模块;仅梳理现有逻辑,不包含代码改动。 - **交付复核**:若需要进一步变更规则,可从本文档列出的结构/流程入手定位受影响的函数与 API。