6.9 KiB
name, overview, todos, isProject
| name | overview | todos | isProject | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Support Invite 离线补推 | 用户提出的「离线写 Redis + WS 连接后补推」方案可行;项目已有 `user_websocket_connect` 事件与在线 Redis Set,可在 `deliverInviteeSupportInvitePopup` 与连接回调中实现,无需回退 HomeEvent。 |
|
false |
Support Invite 离线 Redis 补推方案
结论:方案可行
你的思路与现网架构匹配,比把 Support Invite 塞回 HomeEvent 更贴切(仍只弹一次,且针对「注册时 WS 尚未连上」的竞态)。
现网已有能力:
| 能力 | 位置 |
|---|---|
| WS 连接 MQ | slot_game gateway → console_bus TYPE_USER_WEBSOCKET_CONNECT |
| 连接处理入口 | EventBus::websocketConnectEvent → WebsocketConnectEventService::run |
| 在线判定 Redis Set | RedisKeyManagerService::getUserOnlineKey(),connect 时 sAdd、disconnect 时 sRem |
| 即时弹窗 | WsService::notifyClientPOP → hub MQ(离线会丢) |
DelayTipPopService 是「延时 N 秒再推 tipPop」的 ZSet,不适合本场景;本需求是「等 WS 连上再推」,应走 connect 时 flush,不是 cron 扫延时队列。
sequenceDiagram
participant Bind as invite_bind_success
participant Logic as deliverInviteeSupportInvitePopup
participant Redis as Redis_pending
participant Hub as WsService
participant WS as websocketConnectEvent
Bind->>Logic: openBox + buildPopup
alt user_online
Logic->>Hub: notifyClientPOP
else user_offline
Logic->>Redis: SET payload EX TTL
end
WS->>Redis: GET pending
alt has_pending
WS->>Hub: notifyClientPOP
WS->>Redis: DEL
end
推荐实现(在 Redis 方案上略作增强)
1. 新增 LuckyRewardPendingPopupService(或 Logic 内 private + 小 Service)
职责单一:被邀请人 Support Invite 的 pending 投递。
- Redis Key:
lucky_reward:support_invite_popup:{uid}:{cycle_id}(幂等,同一轮次只存一条) - Value:
json_encode后的 popList 单项(与现buildSupportInvitePopup结构一致) - TTL:建议 30~60 分钟(覆盖注册加载 + WS 握手;过短会丢弹,过长无必要)
- 写入:
SET key value EX ttl NX(NX 防止 bind 重试覆盖/重复)
2. 改 deliverInviteeSupportInvitePopup
顺序不变:先 ensureInviteeAutoOpenBox,再 buildSupportInvitePopup;推送逻辑改为:
if ($this->isUserOnline($inviteeUid)) {
WsService::notifyClientPOP($inviteeUid, [$supportInvitePopup]);
return;
}
$this->pendingPopupService->enqueueSupportInvite($inviteeUid, $cycleId, $supportInvitePopup);
在线判定:Redis::sIsMember(RedisKeyManagerService::getUserOnlineKey(), $inviteeUid)(与现网 disconnect 维护的 Set 一致)。
注意:存在 竞态(bind 比
user_websocket_connect早几十~几百 ms)。可选增强(推荐):在线则即时推 + 仍 SET NX pending;connect flush 时 GETDEL,若已推过则 payload 相同、客户端需幂等忽略重复 type。更简单做法:仅离线写 Redis;若偶发丢弹可接受则不做双写。
3. WS 连接时 flush
在 WebsocketConnectEventService::run($uid) 末尾(或 EventBus::websocketConnectEvent 调 Logic 一行):
- 查当前 active
cycle_id(与buildSupportInvitePopup同源) GETDEL lucky_reward:support_invite_popup:{uid}:{cycle_id}- 有值 →
json_decode→WsService::notifyClientPOP($uid, [$popup])
只弹一次:发送成功后 key 已 DEL;重连不会重复(除非 bind 再次 success 且 NX 允许新 key——同一 cycle 不会)。
4. 可选:DB 兜底(比纯 Redis 更稳)
若担心 TTL 内仍未连 WS:connect 时除读 Redis 外,可 fallback:
helper存在 +box_opened_at > 0+ Redis 无 key + 从未发送标记
→ 再buildSupportInvitePopup推一次
发送标记可用 Redis lucky_reward:support_invite_sent:{uid}:{cycle_id} SET NX(永久或 7d TTL)。与「只弹一次」产品一致,且 bind 时 openBox 已落库,不依赖 Redis 里缓存 payload。
建议:Redis 存 payload 为主,connect 时 GETDEL;TTL 过期则 fallback 从 DB 重建一次(实现成本低、容错更好)。
不建议的做法
- 不要再挂 HomeEvent(与「只弹一次、注册时触发」冲突,且会每次回大厅重复弹)
- 不要复用
user_pending_popup表(项目已废弃,见 deploy 文档) - 不要只用
DelayTipPopService固定延时 N 秒(N 难估,且 WS 未连上仍会丢)
改动文件(实施范围)
| 文件 | 改动 |
|---|---|
新建 app/service/luckyReward/LuckyRewardSupportInvitePendingService.php |
enqueue / flush / isUserOnline 封装 |
LuckyRewardInviteLogic.php |
deliverInviteeSupportInvitePopup 分支在线/离线 |
WebsocketConnectEventService.php |
connect 时 flush pending |
RedisKeyManagerService.php |
新增 key 常量 + 中文注释 |
| 单测/集成测 | mock Redis 或集成测:离线 enqueue → connect flush |
不在本次范围(除非你要求):YApi/05 文档、邀请人 Claim Now、helper 未落库排查。
风险与对策
| 风险 | 对策 |
|---|---|
| bind 早于 connect,误判离线 | 可接受则只写 Redis;要严格则 connect 必 flush + DB fallback |
| hub 已连但 console 在线 Set 未更新 | connect 事件顺序:flush 放在 websocketConnectEvent sAdd 之后 |
| 重复弹 | key 含 cycle_id + GETDEL + 客户端按 type 去重 |
| TTL 过期未连 WS | fallback 从 DB rebuild(helper + opened box) |
结论
可行,且是正确补洞方向;实现上建议:离线 Redis pending + WS connect flush,并加 DB fallback / sent 标记 防止 TTL 丢弹。在线判定复用现有 USER_ONLINE_SET,接入点放在已存在的 WebsocketConnectEventService。