--- name: Free Credits 老用户隐藏评审 overview: EventBus 写库确定参加、status 只读展示;在此基础上用环境变量 FREE_CREDITS_ENROLL_REG_AFTER 做注册时间门槛——regTime 晚于该时间的用户才有资格参加,否则一律 status=-1 且 EventBus 跳过写池。 todos: - id: env-reg-cutoff content: 新增 FREE_CREDITS_ENROLL_REG_AFTER 环境变量与 FreeCreditsLogic::isEligibleByRegTime(uid) 统一门禁 status: completed - id: apply-gate-all-paths content: 在 status、syncHomeWithdrawUnlocked、handleFreeCreditInit、advanceAfterRecharge、claim、firstCashout 入口应用注册时间门禁 status: completed - id: fix-docs-lobby content: 修正 FreeCreditsController PHPDoc(含 status=-1 含老用户/未达注册门槛);LobbyController 清理无用 import status: completed - id: guard-claim-cashout content: 无资格或无 player 时 claim/firstCashout 返回明确业务错误 status: completed - id: verify-reg-cutoff content: 联调:regTime 早于门槛 status=-1 且 EventBus 不建池;晚于门槛走 win/首充完整流程 status: completed isProject: false --- # Free Credits 老用户隔离:注册时间门槛(计划) ## 目标 C 端全量更新后,**注册时间早于活动上线节点的用户视为老用户,不参加、不展示**;**注册时间晚于该节点的新用户**,在现有「EventBus 写库 = 参加」模型下正常走活动。 「默认参加」含义:**有资格参加**(EventBus 允许建池、status 可读),**不是**无需 EventBus 自动插入 `free_credits_player`。 --- ## 分层架构(保持不变) ```mermaid flowchart TB gate{regTime > FREE_CREDITS_ENROLL_REG_AFTER?} gate -->|否| block[不参加: status=-1 EventBus return] gate -->|是| eligible[有资格] eligible --> win[EventBus syncHomeWithdrawUnlocked] eligible --> init[EventBus free_credit_init] eligible --> api[status 只读 player 行] win --> pool[(free_credits_player)] init --> pool api --> pool ``` | 路径 | 行为 | | --- | --- | | **读** `status()` | 未达注册门槛 → `status=-1`;达门槛且无 player 行 → `-1`;有行 → `buildStatus` | | **写** EventBus | 未达注册门槛 → 各 Logic 方法开头直接 return,不建池、不定格、不推进 | --- ## 实现要点 ### 1. 环境变量 在 [`.env`](slot_console/.env) / 部署说明中增加(示例): ```env # 用户注册时间(Unix 或 Y-m-d H:i:s,建议与 TIMEZONE 一致)晚于此值才可参加 Free Credits FREE_CREDITS_ENROLL_REG_AFTER=2026-05-20 00:00:00 ``` - 在 [`config/app.php`](slot_console/config/app.php) 或新建 `config/free_credits.php` 读取:`getenv('FREE_CREDITS_ENROLL_REG_AFTER')`,`strtotime` 解析为 `enroll_reg_after_ts`(启动时或首次调用缓存)。 - **未配置时的默认策略(已确认)**:`FREE_CREDITS_ENROLL_REG_AFTER` 为空或未配置 → **全员不参加**(`isEligibleByRegTime` 恒 false,`status=-1`,EventBus 不写池)。 ### 2. 统一门禁方法(建议放在 [`FreeCreditsLogic`](slot_console/app/api/logic/FreeCreditsLogic.php)) ```php /** * 是否具备 Free Credits 参与资格(注册时间晚于环境变量门槛)。 */ protected function isEligibleByRegTime(int $uid): bool { $cutoff = self::enrollRegAfterTimestamp(); // 0 表示未配置 if ($cutoff <= 0) { return false; } $userInfo = \app\service\user\UserService::getUserInfoEntity($uid); if ($userInfo === null || $userInfo->create_at === '') { return false; } $regTime = strtotime($userInfo->create_at); return $regTime !== false && $regTime > $cutoff; // 严格「晚于」 } ``` - **数据来源**:[`app\service\user\UserService::getUserInfoEntity`](slot_console/app/service/user/UserService.php)(调 user 服 `/innerapi/user/info`),与 [`EventBus::registerEvent`](slot_console/app/command/EventBus.php) 等同用法。 - **注册时间字段**:[`app\entity\UserInfoEntity::$create_at`](slot_console/app/entity/UserInfoEntity.php)(`Y-m-d H:i:s`),比较前 `strtotime` 为 Unix 秒。 - **不用** `UserTagService::tagInfo->regTime`(标签缓存,与账号创建时间可能不一致)。 - 备选:若 Logic 内已大量使用 slotLib,可用 `\slotLib\services\UserService::getInstance()->setUid($uid)->getUserInfo()?->regTime`(构造函数内由 `create_at` 解析),但 console 侧优先统一 `app\service\user\UserService`。 ### 3. 调用点(读写对称) | 方法 | 未达门槛时 | | --- | --- | | `status()` | 直接 `return ['status' => -1]` | | `syncHomeWithdrawUnlocked()` | `return null` | | `handleFreeCreditInit()` | `return` | | `advanceAfterRecharge()` | `return` | | `claim()` / `firstCashout()` | `throw BusinessException('...')` 或统一文案 | **说明**:已达门槛、但库中无 player 行 → 仍为 `status=-1`(尚未被 EventBus 纳入);达门槛且 win 后 → EventBus 建池 → status 非 `-1`。 ### 4. 与「已首充」的关系 - 注册门槛解决:**老账号 / 老注册** 不进入活动。 - 钱包侧 [`maybeSendFreeCreditInit`](slot_wallet/app/api/logic/WalletLogic.php) 仍仅 **首充** 发定格,二者叠加,互不替代。 ### 5. 撤销项 - 不再单独做 `total_deposit == 0` 的 EventBus 门禁(由注册时间门槛覆盖「老用户」定义)。 - 不再要求 `frozen_amount > 0` 才展示(保留 HOME_UNLOCKED 阶段)。 ### 6. 文档与测试 - 更新 [`FreeCreditsController`](slot_console/app/api/controller/FreeCreditsController.php):`status=-1` = 未参加 / 活动关 / **注册时间早于门槛**。 - 单测:mock `regTime` 与 env cutoff,覆盖 eligible / ineligible 的 status 与 freeze 是否被调用。 - 联调矩阵见下。 --- ## 联调矩阵 | regTime vs 门槛 | win 达门槛 | 首充定格 | status | EventBus 建池 | | --- | --- | --- | --- | --- | | 早于 | - | - | `-1` | 否 | | 晚于 | 否 | 否 | `-1` | 否 | | 晚于 | 是 | 否 | `1` | sync 建池 | | 晚于 | - | 是 | `3/4` + packages | freeze | --- ## 新 C 端约定(不变) - **`status === -1`**:隐藏(含老用户、未达注册门槛、未参加)。 - **`status !== -1`**:已参加,按 `FreeCreditsPlayerModel` 状态渲染。