Files
cursor/plans/转盘分享周期绑定_d7eeeacf.plan.md
ray zhou 2dd9f17da9 ok
2026-06-29 14:51:55 +08:00

8.1 KiB
Raw Permalink Blame History

name, overview, todos, isProject
name overview todos isProject
转盘分享周期绑定 推荐在 `share_url` 增加通用 `biz_type` + `biz_id`(转盘绑定 `lucky_reward_cycle.id`),按当前轮次复用/新建短码;过期校验以 `lucky_reward_cycle.end_at` 为真源invite_bind 侧对过期轮次返回 skipped 而不影响注册。
id content status
ddl-share-url-biz share_url 增加 biz_type/biz_id + 唯一键;同步 slot_console/slot_user Model PHPDoc completed
id content status
share-service-wheel-reuse ShareService wheel 分支:按 active cycle_id 复用或新建短码 completed
id content status
invite-bind-expired-check LuckyRewardInviteLogic invite_bind 校验 share 绑定 cycle 未过期且匹配 active cycle返回 skipped_expired_cycle completed
id content status
docs-tests 更新 05 文档;单测/集成测覆盖同轮复用、跨轮新建、过期跳过 completed
false

转盘分享链接与活动轮次绑定方案

结论(直接回答你的二选一)

推荐:biz_type + biz_id(转盘 biz_id = cycle_id),不推荐单独加 expire_at 作为唯一方案。

方案 优点 缺点
expire_at 查询简单(expire_at > now lucky_reward_cycle.end_at 双份真源,易漂移;user_agent/red_packet 等 origin 语义不同,全局 expire_at 难统一
biz_type + biz_id(推荐) 与业务轮次一一对应;有效期以 lucky_reward_cycle.end_at 为准;同轮次内复用、新轮次新建;可扩展其它活动 需改 ShareService 查询与唯一键invite_bind 需多一步校验

可选:可冗余 expire_at = cycle.end_at 仅作索引加速,但判定仍应 join/查 lucky_reward_cycle,不以 share_url.expire_at 为 SSOT。


现状问题

ShareService::createUrl 通过 ShareUrlModel::isPlayerCreated($uid, $from)(creator_id, origin) 永久复用一条记录:

$model = ShareUrlModel::isPlayerCreated($this->userEntity->uid, $from);
if ($model === false) { /* 新建 */ }

LuckyRewardInviteLogic::buildInviteLink 每次调 createUrl(SHARE_FROM_WHEEL)新 cycle 开始后仍返回旧短码

注册侧 slot_user AbstractRegisterService 只校验 short_code 存在,不校验轮次console handleInviteBind 直接用当前 active cycle 发奖,导致旧链接可能计入新轮次(与「每轮转盘有有效期」不符)。

你已确认过期策略:注册/agent 关系照常Lucky Rewards 不计助力


推荐数据模型

s_common.share_url 增加:

ALTER TABLE share_url
  ADD COLUMN biz_type varchar(32) NOT NULL DEFAULT '' COMMENT '业务类型wheel填lucky_reward_cycle',
  ADD COLUMN biz_id bigint NOT NULL DEFAULT 0 COMMENT '业务IDwheel填lucky_reward_cycle.id';

-- wheel同一用户同一轮次唯一其它 origin 保持 biz 为空
ADD UNIQUE KEY uk_creator_origin_biz (creator_id, creator_type, origin, biz_type, biz_id);

语义约定:

origin biz_type biz_id 行为
user_agent / red_packet / user_self '' 0 现网永久短码,不变
wheel lucky_reward_cycle cycle.id 每轮一条,同轮复用

code 加密 JSON 可冗余写入 cycle_id 便于排查,但复用查询走 DB 列,不走解密。


核心流程

sequenceDiagram
    participant Client as ActivityPage
    participant Console as ShareService
    participant Cycle as lucky_reward_cycle
    participant Share as share_url
    participant User as slot_user_register
    participant Logic as LuckyRewardInviteLogic

    Client->>Console: invite-link / createShareUrl from=wheel
    Console->>Cycle: resolveActiveCycle
    Console->>Share: find uid+wheel+biz(cycle_id)
    alt 当前轮次已有短码
        Share-->>Console: 复用 short_code
    else 新轮次或无记录
        Console->>Share: insert 新 short_code + biz_id
    end
    Console-->>Client: code + land_url

    User->>Share: invite_code 查 short_code
    User->>Logic: invite_bind share_origin=wheel
    Logic->>Share: invite_code -> biz_id(cycle_id)
    Logic->>Cycle: 校验 cycle 仍进行中且 end_at>now
    alt cycle 已结束或不匹配 active
        Logic-->>User: skipped_expired_cycle
    else
        Logic-->>User: 正常助力流程
    end

实现要点(按服务)

1. slot_console — 分享创建

改动 ShareService::createUrl

  • from !== wheel:保持 isPlayerCreated(uid, from)biz 为空)。
  • from === wheel
    1. LuckyRewardConfigService::requireEnabledConfig + resolveActiveCycle
    2. ShareUrlModel::findByPlayerBiz($uid, 'wheel', 'lucky_reward_cycle', $cycleId)
    3. 命中则复用;否则新建并写入 biz_type/biz_id
    4. (可选)冗余 expire_at = cycle.end_at 仅索引

同步改 GiftController::createShareUrlLuckyRewardInviteLogic::buildInviteLink 路径(均走 ShareService一处改即可

ShareUrlModel + slot_user/ShareUrlModel@property 与新查询方法。

2. slot_console — invite_bind 过期校验

LuckyRewardInviteLogic::handleInviteBind 有效助力判定链中(skipped_not_wheel 之后)增加:

  1. invite_codeshare_urlconsole 可读 share_url 或经 slot_user innerapi优先 console 直读同库 s_common.share_url
  2. origin=wheelbiz_type=lucky_reward_cycle
    • lucky_reward_cycle by biz_id
    • status=已结束end_at <= now() → 返回新 status skipped_expired_cycle(注册/agent 不受影响,仅不发 Spin
    • biz_id != 当前 active cycle.id → 同上(旧轮短码不计新轮助力)

InviteBindResultEntity 增加常量 + 文档表更新(05 文档 §3 判定表)。

3. slot_user — 无需阻断注册

按你的选择:不在注册阶段 reject invite_codeAbstractRegisterService 可不改,或仅补日志(share_url.biz_id + cycle 状态)便于排障。

4. 文档

更新 05_slot_console_邀请助力与弹窗方案.md §2.1

  • wheel 短码 按 cycle 复用/新建,非永久一条
  • 过期短码:注册 OKLucky Rewards skipped_expired_cycle

测试

  • UnitShareService wheel 分支 — mock cyclefindByPlayerBiz 命中复用 / 新 cycle 新建
  • Integration:同一 uid 同 cycle 两次 invite-link 返回相同 code;模拟新 cycle 返回不同 code
  • Integration:旧 cycle 短码 + invite_bind → skipped_expired_cycle,无 helper/grant

为何不选「只加 expire_at」

  1. 真源重复cycle 结束时间已在 lucky_reward_cycle.end_at;单独 expire_at 需与轮转任务同步,易不一致。
  2. 无法表达「属于哪一轮」:仅有 expire_at 难以区分「同用户第 N 轮 vs 第 N+1 轮」历史短码;biz_id=cycle_id 可精确归因与审计。
  3. 影响面expire_at 若加在全局 share_url,需约定 user_agent 等为 0/NULLbiz_id 对非 wheel 恒为 0更清晰。

不在本次范围

  • 修改 user_agent 分享逻辑
  • 前端 Copy Link 缓存策略(后端保证同 cycle 幂等即可)
  • 删除历史 cycle 的 share_url 行(可保留作审计;过期靠 invite_bind 拦截)