name, overview, todos, isProject
name
overview
todos
isProject
Spin去spin_id加锁
手动 Spin 去掉前端必填 `spin_id`,改为 Redis 用户锁防并发 + 事务内 `spin_available` 校验;`spin_id` 仅服务端写库用;连点拿不到锁立即报错。
id
content
status
api-validator
spin API/Validator: 去掉 spin_id 入参; Controller 两参调用 Logic
completed
id
content
status
logic-lock
LuckyRewardLogic: SET NX 用户锁 fail-fast + 服务端生成 spin_id + 移除客户端幂等分支
completed
id
content
status
tests-docs
RedisKeyManagerService 锁 key + 单测/文档/YApi 更新
completed
false
Spin 去掉前端 spin_id( Redis 锁 + 次数校验)
产品约定(已确认)
防并发 : Redis 用户级锁,连点第二笔 拿不到锁立即报错 (不重试等待)。
防重复扣次 :事务内校验 spin_available > 0,成功则 -1;无次数则拒绝。
不做 客户端幂等 / 弱网重试幂等:超时后若已扣次,再次 spin 会报「无可用 Spin」→ 前端应改调 status / records 刷新 ,勿无脑重试 spin。
代码改动( slot_console)
1. API 层
LuckyRewardController::spin()
移除 LuckyRewardValidator::SCENE_SPIN 校验及对 spin_id 的读取。
调用 $this->logic->spin($uid, $source)(两参)。
LuckyRewardValidator
删除 SCENE_SPIN / spin_id 规则(或保留空 scene 备用) ; spin 接口不再走 Validate。
2. Logic 核心
LuckyRewardLogic::spin()
变更
说明
签名
spin(int $uid, string $source): SpinResultEntity
删除
入口 findByUidAndSpinId + buildSpinResultFromRecord 的客户端幂等 分支
新增
方法开头 acquireSpinLock($uid) / finally releaseSpinLock
新增
服务端生成 spinId: manual:{uid}:{cycleId}:{uniqid()}(或 random_bytes 短串),仅用于 lucky_reward_spin_record.spin_id 满足 uk_uid_spinid
保留
开宝箱校验、grantDailySpinIfNeeded、ensureUserCanSpin、事务内二次 spin_available 校验、抽奖与落库逻辑
响应
SpinResultEntity.spinId 仍返回(服务端生成,供 records 展示);isDuplicate 固定 false(或后续从 Entity 移除该字段需评估 FE 兼容性,建议先保留恒 false)
Redis 锁实现 (复用现有 RedLock) :
TTL 5000ms :覆盖一次抽奖+事务;RedLock::lock 默认 retryCount=3 会在内部短暂重试——若需严格「一次拿不到就失败」,在 Logic 侧用 单次 SET NX ( Redis::set($key, $token, 'EX', 5, 'NX'))替代 RedLock, 或给 RedLock 封装 tryLockOnce()。建议 :新增 private tryAcquireSpinLockOnce() 用 SET NX EX 5,符合 fail-fast 选型。
3. 辅助方法
RedisKeyManagerService :新增 luckyRewardSpinLockKey(int $uid): string。
可选: private generateManualSpinId(int $uid, int $cycleId): string 集中生成写库 id。
不改 / 兼容
表结构 : lucky_reward_spin_record.spin_id + uk_uid_spinid 保留;值改为服务端生成。
records API :仍返回 spinId 字段,无 breaking change。
开宝箱自动首次 Spin :仍用 auto_first:{uid}:{cycle_id},不受影响。
测试
项
做法
单元测
新增 LuckyRewardSpinLockUnitTest: mock Redis 或使用可注入 lock 的测试双(若难 mock, 至少测 generateManualSpinId + validator 无 spin_id)
Logic 单测
更新/新增:无 spin_id 参数签名;isDuplicate 恒 false
集成测
若有 DB spin 集成测则去掉 payload spin_id;并发锁集成测可选(需 Redis)
当前仓库 无 spin 集成测,以 Logic 单测 + 手动冒烟为主。
文档
前端契约(联调说明)
点击 Spin 后 UI 防抖 (禁用按钮至响应返回)。
请求失败/超时:不要 立即再次 spin; 先 status / records 看 spinAvailable、myAmount、最新 record。
若返回「Spin is in progress」: 稍后重试或等当前请求结束。
已知边界(产品已接受)
场景
行为
连点
第二笔拿不到锁 → 立即报错
弱网:已成功但响应丢失
再次 spin → spin_available=0 报错;靠 status/records 恢复 UI
弱网:仍有多次数且用户再点
会消耗下一次 Spin( 非重试上一把)
涉及文件