--- name: Console innerapi 架构说明 overview: 说明 slot_console 在本项目中的双重角色(C 端 BFF + 活动域服务),slot_sdk ConsoleClient 仅封装 `/innerapi/*` 供后端互调;若要坚持「纯 BFF 不被后端调用」,需拆分活动域或改异步回调。 todos: [] isProject: false --- # slot_console 为何可被 slot_sdk 调用? ## 结论(先答你的疑问) **可以给别人调,但调的不是「给 App 用的 BFF 接口」,而是同一进程里的 `/innerapi/*` 内部契约。** 在本仓库里,`slot_console` **不是纯 BFF**,而是: | 路由前缀 | 中间件 | 谁调用 | 职责 | | --- | --- | --- | --- | | `/api/*` | `AuthMiddleware`(JWT) | **C 端 App/PWA** | BFF:聚合 user/wallet/agent,暴露 Lucky Rewards 等 C 端 API | | `/innerapi/*` | `InnerAuthMiddleware` | **其它后端**(agent、admin、pwa…) | 活动域/内部能力:统计、邮件、游戏历史、**邀请回调** | | `/napi/*` | `NAuthMiddleware` | 大厅等 | 非 C 端 JWT 的另一套入口 | [`slot_sdk` 的 `ConsoleClient`](slot_sdk/src/service/console/ConsoleClient.php) 注释已写明:**「slot_console 内部 API 客户端」**,封装的是 `innerapi/...`,不是 `api/lucky-reward/spin` 这类 C 端路径。 ```mermaid flowchart LR subgraph clients [调用方] App[C端App] Agent[slot_agent] Admin[slot_admin] end subgraph console [slot_console] ApiLayer["/api/* BFF"] InnerLayer["/innerapi/* 域服务"] Logic[LuckyRewardInviteLogic等] end App --> ApiLayer Agent -->|"ConsoleClient via slot_sdk"| InnerLayer Admin --> InnerLayer ApiLayer --> Logic InnerLayer --> Logic ``` --- ## slot_sdk 里 console 接口是干什么的? 当前 [`ConsoleService`](slot_sdk/src/service/console/ConsoleService.php) 只有三类 **innerapi**: 1. `innerapi/free-credits/statistics|list` — **slot_admin** 后台统计代理 2. `innerapi/lucky-reward/invite-bind-callback` — **slot_agent** 邀请绑定后回调转盘助力 **实际调用方**(仓库内): - [`LuckyRewardInviteCallbackGatewayService`](slot_agent/app/service/LuckyRewardInviteCallbackGatewayService.php) → agent 调 console - [`ConsoleAPIService`](backend/slot_admin/app/service/api/ConsoleAPIService.php) / Free Credits 统计 → admin 调 console - [`slot_pwa` ConsoleService](slot_pwa/app/service/console/ConsoleService.php) → `innerapi/game/update-history` C 端 **不会** 也 **不应该** 通过 slot_sdk 调 console;C 端直接打 `consoleApiHost` 的 `/api/*`。 --- ## 为什么 agent 要调 console,而不是反过来? 这是 Lucky Rewards 需求里**刻意**定的边界(见 [01 文档](docs/requirements/lucky_rewards/01_slot_agent邀请回调对接方案.md)、[00 §1](docs/requirements/lucky_rewards/00_整体技术方案.md)): - **agent**:只管邀请关系、代理树、绑码奖励(通用裂变) - **console**:拥有转盘业务状态(`lucky_reward_player`、Spin、helper、弹窗) - 邀请绑定成功后,agent 广播「关系已建立」→ console **裁决**是否算转盘有效助力并发 Spin 所以不是「BFF 被乱调」,而是 **活动域挂在 console 上**,agent 通过 innerapi 触发 console 内的 [`LuckyRewardInviteLogic::handleInviteBind()`](slot_console/app/api/logic/LuckyRewardInviteLogic.php)。 `/api` 与 `/innerapi` 可以共用同一套 Logic(invite 回调与 C 端 invite-link 都走 `LuckyRewardInviteLogic`),只是入口和鉴权不同。 --- ## 「console 是 BFF」和现状如何对齐? 更准确的说法: - **对 C 端**:console 的 `/api/*` 承担 BFF(薄 Controller + Logic 编排 + 调 user/wallet/agent SDK) - **对活动数据**:console 同时是 **活动域 owner**(表在 `s_common`,逻辑在 console),历史上 Free Credits、签到、红包等同构 - **innerapi** = 把「活动域能力」暴露给 **trusted backend**,不是给浏览器/App 因此 slot_sdk 出现 `ConsoleClient` 与「console 有 BFF 职责」**不矛盾**——SDK 封装的是 **服务间** 调用,符合 [cross-service-sdk 规则](.cursor/rules/cross-service-sdk.mdc)(调用方用 slot_sdk,被调方实现 innerapi Controller/Logic)。 --- ## 若你认为「BFF 绝不能被后端调」——可选演进(非现状) 只有当你要把 console **严格降级为纯 BFF** 时,才需要改架构;代价是拆服务或改通信方式: | 方案 | 做法 | 优缺点 | | --- | --- | --- | | **A. 拆 slot_activity** | 转盘域独立服务;console 只转发 C 端;agent 调 activity 而非 console | 边界最清晰;迁移成本高 | | **B. MQ 替代 HTTP 回调** | agent 发 `lucky_reward_invite_bind` MQ,console 消费 | 解耦、可重试;需 MQ 契约与消费幂等 | | **C. 维持现状** | console = BFF + 活动域;innerapi 仅 trusted 服务 | **当前实现**;与现网 Free Credits innerapi 一致 | **建议**:除非全站统一「活动全部拆独立服务」,否则 **C 方案是本项目既定模式**,不必因 slot_sdk 有 ConsoleClient 而视为设计错误。 --- ## 安全与边界(现状) - innerapi 走 [`InnerAuthMiddleware`](slot_console/app/middleware/InnerAuthMiddleware.php)(内网 + 日志;生产通常靠网络隔离 + `server-name` 头标识调用方) - agent 回调带 `server-name: agent`(见 [deploy 文档](slot_console/doc/lucky_reward_deploy.md)) - **禁止**:在 agent/user 等业务代码里手写 `consoleApiHost + path` 字符串;应走 slot_sdk(与 wallet/user 一致) --- ## 无需改代码时的认知对齐 1. **slot_sdk ConsoleClient ≠ C 端调 console** 2. **console `/api` = BFF;`/innerapi` = 活动域对后端开放** 3. **agent → console** 是转盘需求文档明确的服务边界,不是临时 hack 若你后续希望 **agent 不再 HTTP 调 console**,再单独立项做方案 B(MQ)或方案 A(拆服务);当前代码与文档是一致的。