--- name: Console Redis 在线推送 overview: 不建议在 slot_game 新增「用户是否在线」接口;邀请成功弹窗的在线判断应放在 slot_console,复用 EventBus 已维护的 Redis 在线集合,在 PendingPopupService 入队后「在线才 WS 即时推送、离线靠 HomeEvent 补弹」。 todos: - id: user-online-service content: 新增 UserOnlineService::isUserOnline(读 Redis getUserOnlineKey)+ 单测 status: completed - id: pending-popup-online-gate content: PendingPopupService::enqueue 在线才 notifyClientPOP,离线仅持久化 status: completed - id: docs-verify-online content: 更新 07 文档 enqueue 说明;跑 verify + 相关单测 status: in_progress isProject: false --- # 在线判断方案:不建议 slot_game,采用 slot_console Redis ## 结论(对你问题的直接回答) **不建议在 [`slot_game`](slot_game) 新增控制器做在线判断。** | 服务 | 职责 | 是否适合判断「大厅弹窗在线」 | |------|------|------------------------------| | **slot_game** | 游戏 WebSocket 网关(spin/bet/进游戏) | 否 — 用户在大厅连的是 **slot_hub**,不一定连 slot_game | | **slot_hub** | 大厅 WS + `Gateway::isUidOnline($uid)`(见 [`BaseLogic`](slot_hub/plugin/slot/hub/logic/BaseLogic.php)) | 最准,但需跨服务 HTTP/RPC,07 文档一期未接 | | **slot_console** | 弹窗入队、EventBus 维护 Redis 在线集合 | **合适** — 数据已有,无跨服务调用 | 当前邀请成功链路(已实现): ```mermaid sequenceDiagram participant Logic as LuckyRewardInviteLogic participant Popup as PendingPopupService participant Redis as Redis_online_set participant WS as WsService_MQ participant Home as HomeEvent Logic->>Popup: enqueue 邀请人弹窗 Popup->>Popup: 写 user_pending_popup Popup->>WS: notifyClientPOP(当前始终发送) Note over Home: 离线/游戏中靠 home flush 补弹 ``` 你已选择 **slot_console + Redis** 方案:与 [07 待弹窗方案](docs/requirements/lucky_rewards/07_用户定向弹窗通知机制方案.md) 一致 — **持久化必做,WS 仅 best-effort**。 --- ## 推荐实现(小改动,不改 slot_game) ### 1. 封装在线查询(slot_console) 在 [`RedisKeyManagerService`](slot_console/app/service/RedisKeyManagerService.php) 或新建 `UserOnlineService`(公共能力,非 Logic 型业务编排): ```php public static function isUserOnline(int $uid): bool { if ($uid <= 0) { return false; } return (bool) Redis::sIsMember(self::getUserOnlineKey(), (string) $uid); } ``` 数据来源:[`EventBus::websocketConnectEvent`](slot_console/app/command/EventBus.php) / `websocketUnconnectEvent` 已对 `getUserOnlineKey()` 做 `sAdd` / `sRem`。 **语义说明**:Redis 在线 = **有 WebSocket 连接**,不等于「一定在大厅」;游戏内是否弹仍由**客户端忽略 WS** + **HomeEvent 仅非 enter_game 时 flush** 保证(PRD 4.2)。 ### 2. 调整 [`PendingPopupService::enqueue`](slot_console/app/service/PendingPopupService.php) - **不变**:幂等写 `user_pending_popup`(离线也必须写,否则丢通知) - **变更**:仅当 `isUserOnline($uid)` 为 true 时调用 `WsService::notifyClientPOP` - 离线:跳过 WS,等 [`HomeEvent::appendLuckyRewardHomePopups`](slot_console/app/command/event/HomeEvent.php) 补弹 ```php if (UserOnlineService::isUserOnline($enqueueDto->uid)) { WsService::notifyClientPOP(...); } ``` 此改动作用于**所有**走 `PendingPopupService` 的定向弹窗(含邀请成功 `lucky_reward_invite_success`),符合 F 通用机制。 ### 3. 测试 - 单元测:`isUserOnline` 对 Redis mock 或集成测 sAdd/sIsMember - 可选:集成测 invite_bind 成功后 pending 记录存在;在线/离线 WS 行为(若难 mock MQ,至少单测 PendingPopupService 分支) ### 4. 文档 - [07 用户定向弹窗通知机制方案](docs/requirements/lucky_rewards/07_用户定向弹窗通知机制方案.md) §4 `enqueue` 说明补一句:**即时 WS 仅在 Redis 在线集合命中时发送** - [`LuckyRewardInviteLogic`](slot_console/app/api/logic/LuckyRewardInviteLogic.php) 的 `notifyInviterInviteSuccess` PHPDoc 注明依赖 PendingPopupService 在线 gate,Logic 内**不再**单独查在线 --- ## 若将来需要 HTTP「查在线」给其他服务 - 优先 **slot_console `innerapi`**(如 `GET /innerapi/user/is-online?uid=`),读同一 Redis 集合 - 或 **slot_hub innerapi** 调 `Gateway::isUidOnline`(需 GatewayClient 配置,跨进程) - **仍不建议 slot_game** — 除非明确要查「是否在游戏 WS 连接中」(与大厅弹窗无关) --- ## 不在本次范围 - slot_game 新 Controller - slot_hub Gateway 跨服务封装(除非 Redis 与 Gateway 长期不一致再评估) - 区分「大厅 vs 游戏中」的服务端精确状态(仍靠客户端 + HomeEvent `from`)