--- name: 注册奖励随机游戏 overview: 在 slot_console 注册活动(type=7)领取成功响应中,从活动 ext_config.game_ids 配置的候选池里,按当前渠道 game_model_id 过滤已上架游戏后随机返回 game_id,供前端直接进游戏。 todos: - id: model-published-ids content: GameApiModel 新增 publishedGameIds(gameModelId, gameIds) 查询 status: completed - id: logic-pick-random content: 新增 RegisterRewardLogic::pickRandomGameId 解析 ext_config 并随机 status: completed - id: entity-receive content: ActivityConfigEntity ACTIVITY_TYPE_REG 成功分支接入 Logic 并扩展返回 status: completed - id: controller-phpdoc content: GiftController::receive PHPDoc 补充 game_id 字段说明 status: completed - id: unit-tests content: RegisterRewardLogicTest + php82 容器跑单测 status: completed isProject: false --- # 注册奖励领取后随机返回 game_id ## 背景与范围 - **入口**:[`POST /api/gift/receive`](slot_console/app/api/controller/GiftController.php) → [`ActivityConfigEntity::receive()`](slot_console/app/entity/activity/ActivityConfigEntity.php) 中 `ACTIVITY_TYPE_REG`(type=7)。 - **不在范围**:注册自动到账(`UserRegisterEventService`)、后台 Vue 配置页、前端进游戏逻辑。 - **配置来源**(已确认):活动表 `s_recharge_gift_config.ext_config.game_ids`。 当前领取成功仅返回金额: ```750:750:slot_console/app/entity/activity/ActivityConfigEntity.php return ['gift_coin' => CommonFn::getNumberFormat($giftAmount), 'gift_bonus' => CommonFn::getNumberFormat($giftBonus)]; ``` `game_id` 语义与大厅一致:[`s_game_api.game_id`](slot_console/app/model/GameApiModel.php) = `game.id`,前端用该 ID 调 `POST /api/game/login` 的 `gameId` 或 napi 搜索同款字段。 ## 配置契约 在对应注册活动(type=7)的 `ext_config` 中增加: ```json { "game_ids": [101, 205, 308] } ``` - `game_ids`:`int[]`,运营在 DB/后台 JSON 中维护;本任务不实现 admin UI。 - 未配置、空数组、或过滤后无有效游戏:**不阻断领取**,响应中 **省略 `game_id` 字段**(与 `withdraw_guide` 用 `0` 不同,避免前端误开游戏)。 ## 数据流 ```mermaid sequenceDiagram Client->>GiftController: POST /api/gift/receive id=activityId GiftController->>ActivityConfigEntity: receive() ActivityConfigEntity->>WalletService: gift() 注册赠送入账 ActivityConfigEntity->>RegisterRewardLogic: pickRandomGameId(ext_config, gameModelId) RegisterRewardLogic->>GameApiModel: 过滤 status=1 且已发布 RegisterRewardLogic-->>ActivityConfigEntity: game_id|null ActivityConfigEntity-->>Client: gift_coin, gift_bonus, game_id? ``` ## 实现要点 ### 1. 新增 Logic:随机选游戏 新建 [`slot_console/app/api/logic/RegisterRewardLogic.php`](slot_console/app/api/logic/RegisterRewardLogic.php)(参考 [`FreeCreditsLogic`](slot_console/app/api/logic/FreeCreditsLogic.php) 的独立 Logic 拆分方式): | 方法 | 职责 | | --- | --- | | `pickRandomGameId(array\|null $extConfig, int $gameModelId): ?int` | 解析 `game_ids` → 去重/转 int → 调 Model 过滤 → `array_rand` 返回一个 id | 解析规则: - 支持 `game_ids` 为 JSON 数组或逗号分隔字符串(防御性,与部分旧配置风格兼容)。 - 非法/非正整数丢弃。 ### 2. Model:候选池与渠道上架交集 在 [`GameApiModel`](slot_console/app/model/GameApiModel.php) 增加查询方法,例如: ```php public static function publishedGameIds(int $gameModelId, array $gameIds): array ``` 条件:`game_id IN (...)`、`game_model_id = $gameModelId`、`status = STATUS_ON`;返回可用 `game_id` 列表。 不在 Logic 里直接拼 SQL,符合分层规则。 ### 3. 改动领取分支 在 [`ActivityConfigEntity::receive()`](slot_console/app/entity/activity/ActivityConfigEntity.php) 的 `ACTIVITY_TYPE_REG` case: - 发奖逻辑保持不变(`WalletService::gift` + `sendRegisterGiftWagerTask` + `setActivityFinishById`)。 - **仅在 `$res` 非空(领取成功)后**: - 用 `$this->where('id', $activityId)->find()` 读取原始 `ext_config`(`getActivityByInfo()` 经 `getGiftItemsByActivity` 组装,**不含** ext_config,与兑换码分支读库方式一致)。 - `$gameModelId = $this->_modelId`(已由 `setSource` 设置)。 - 调用 `RegisterRewardLogic::pickRandomGameId()`。 - 在方法末尾统一组装返回:有值则追加 `'game_id' => $pickedId`。 [`GiftController::receive()`](slot_console/app/api/controller/GiftController.php) 补充 PHPDoc:`data.game_id`(int,可选,领取注册活动成功且配置了有效候选池时返回)。 ### 4. 响应示例 成功且命中游戏: ```json { "code": 0, "data": { "gift_coin": "10.00", "gift_bonus": "0.00", "game_id": 205 } } ``` 成功但无可用游戏:仅 `gift_coin` / `gift_bonus`(与现网兼容)。 ## 单测 新建 [`slot_console/tests/Unit/RegisterRewardLogicTest.php`](slot_console/tests/Unit/RegisterRewardLogicTest.php): - `game_ids` 为空 / 缺失 → `null` - 字符串 `"1,2,3"` 解析 - Model 层可用 stub 或 sqlite/内存 mock(若项目已有 GameApi 测试惯例则对齐) 在 docker 内执行: ```bash docker exec -w /app/www/slot/slot_console php82 ./vendor/bin/phpunit tests/Unit/RegisterRewardLogicTest.php ``` ## 运维配置说明(无 UI) 对目标渠道 type=7 活动,更新 `s_recharge_gift_config.ext_config`: ```sql -- 示例:在现有 ext_config 上合并 game_ids UPDATE s_recharge_gift_config SET ext_config = JSON_SET(COALESCE(ext_config, '{}'), '$.game_ids', JSON_ARRAY(101, 205)) WHERE id = AND type = 7; ``` `game_ids` 须为当前 `model_id` 下已在 `s_game_api` 上架的 `game.id`。 ## 风险与边界 | 场景 | 行为 | | --- | --- | | 配置了已下架/未发布游戏 | 从池中剔除;池空则不下发 `game_id` | | 重复领取 | 现有 `Is Got` 拦截,不会二次随机 | | 多注册活动同渠道 | 按请求的 `id` 读对应活动 `ext_config` | ## 后续可选(本任务不做) - slot_admin 活动编辑页 type=7 增加「推荐游戏」多选,写入 `ext_config.game_ids` - `getActivityList` 预展示候选游戏(非领取必需)