Files
cursor/plans/support_invite_离线补推_847de6ef.plan.md
ray zhou 2dd9f17da9 ok
2026-06-29 14:51:55 +08:00

6.9 KiB
Raw Permalink Blame History

name, overview, todos, isProject
name overview todos isProject
Support Invite 离线补推 用户提出的「离线写 Redis + WS 连接后补推」方案可行;项目已有 `user_websocket_connect` 事件与在线 Redis Set可在 `deliverInviteeSupportInvitePopup` 与连接回调中实现,无需回退 HomeEvent。
id content status
pending-service 新增 LuckyRewardSupportInvitePendingServiceRedis key/TTL、enqueue、flush、可选 sent 标记 completed
id content status
deliver-branch deliverInviteeSupportInvitePopup在线即时推离线 enqueue completed
id content status
ws-connect-flush WebsocketConnectEventService::run 连接后 flush pendingsAdd 之后) completed
id content status
tests 集成/单测:离线 enqueue + connect flush在线直推不写 Redis completed
false

Support Invite 离线 Redis 补推方案

结论:方案可行

你的思路与现网架构匹配,比把 Support Invite 塞回 HomeEvent 更贴切(仍只弹一次,且针对「注册时 WS 尚未连上」的竞态)。

现网已有能力:

能力 位置
WS 连接 MQ slot_game gateway → console_bus TYPE_USER_WEBSOCKET_CONNECT
连接处理入口 EventBus::websocketConnectEventWebsocketConnectEventService::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 Keylucky_reward:support_invite_popup:{uid}:{cycle_id}(幂等,同一轮次只存一条)
  • Valuejson_encode 后的 popList 单项(与现 buildSupportInvitePopup 结构一致)
  • TTL:建议 3060 分钟(覆盖注册加载 + WS 握手;过短会丢弹,过长无必要)
  • 写入SET key value EX ttl NXNX 防止 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 pendingconnect flush 时 GETDEL,若已推过则 payload 相同、客户端需幂等忽略重复 type。更简单做法仅离线写 Redis;若偶发丢弹可接受则不做双写。

3. WS 连接时 flush

WebsocketConnectEventService::run($uid) 末尾(或 EventBus::websocketConnectEvent 调 Logic 一行):

  1. 查当前 active cycle_id(与 buildSupportInvitePopup 同源)
  2. GETDEL lucky_reward:support_invite_popup:{uid}:{cycle_id}
  3. 有值 → json_decodeWsService::notifyClientPOP($uid, [$popup])

只弹一次:发送成功后 key 已 DEL重连不会重复除非 bind 再次 success 且 NX 允许新 key——同一 cycle 不会)。

4. 可选DB 兜底(比纯 Redis 更稳)

若担心 TTL 内仍未连 WSconnect 时除读 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 时 GETDELTTL 过期则 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 rebuildhelper + opened box

结论

可行,且是正确补洞方向;实现上建议:离线 Redis pending + WS connect flush,并加 DB fallback / sent 标记 防止 TTL 丢弹。在线判定复用现有 USER_ONLINE_SET,接入点放在已存在的 WebsocketConnectEventService