--- name: is_end派奖结算改造 overview: 基于现有 BetFunding/WinService 实现,新增 is_end 分支:中间派奖仅累计不入账,结束派奖统一按 Round Final Settlement 入账;并对中间派奖增加 biz_id 严格幂等。 todos: - id: dto-validator-is-end content: 扩展 DTO 与校验器,支持 is_end 并强制 win 场景 round_id 校验 status: pending - id: redis-win-keys content: 新增 pending/dedupe Redis key 生成方法与 TTL 约定 status: pending - id: logic-win-branching content: 在 WalletLogic::win 中实现 is_end=0 累计与 is_end=1 最终结算分支 status: pending - id: idempotency-regression content: 补充关键回归场景说明并验证与现有幂等不冲突 status: pending isProject: false --- # is_end 驱动的派奖结算改造计划 ## 目标 让 `win` 接口支持第三方 `is_end` 语义: - `is_end=0`:仅记录/累计本局派奖,不做钱包入账、不做 Lot 终态收敛 - `is_end=1`:将本次派奖 + 已累计派奖合并后,执行一次 Final Settlement(现有 `WinService::execute`) ## 现状结论(基于代码) - 入口路由在 [app/api/logic/WalletLogic.php](app/api/logic/WalletLogic.php) 的 `type=win -> win()`。 - Final Settlement 主流程在 [app/service/wallet/WinService.php](app/service/wallet/WinService.php)。 - 当前 `win` 默认即 Final Settlement,不区分中间派奖事件。 - 下注资金事实表在 [app/model/multi/WalletBetFundingModel.php](app/model/multi/WalletBetFundingModel.php)。 ## 实现方案(按你选择) ### 1) 入参扩展与校验 - 在 [app/api/dto/request/wallet/WalletUpdateRequestDTO.php](app/api/dto/request/wallet/WalletUpdateRequestDTO.php) 增加字段:`is_end`(默认 `1`,仅允许 `0|1`)。 - 在 [app/validator/Wallet2Validator.php](app/validator/Wallet2Validator.php): - 增加 `is_end` 校验规则(整数且取值 `0/1`)。 - 将 `win` 也纳入 `round_id` 必传校验(当前仅 bet 必传)。 ### 2) Redis Key 设计 - 在 [app/service/RedisKeyManagerService.php](app/service/RedisKeyManagerService.php) 新增两个 key 生成器: - `wallet:win:pending:{uid}:{currency}:{round_id}`:累计未结算派奖金额 - `wallet:win:dedupe:{uid}:{currency}:{round_id}:{biz_id}`:中间派奖幂等标记 - 过期策略建议:`pending` 48h,`dedupe` 72h(与重放窗口对齐)。 ### 3) win 主流程分支 - 修改 [app/api/logic/WalletLogic.php](app/api/logic/WalletLogic.php) 的 `win()`: - `is_end=0`: - 命中 dedupe key 则直接返回当前钱包(幂等) - 未命中则将 `fee` 累加到 pending key,写 dedupe key,返回当前钱包(不写 `BIZ_TYPE_WIN` 流水) - `is_end=1`: - 读取 pending 累计并与本次 `fee` 合并为 `finalWinAmount` - 用 `finalWinAmount` 调用现有 `WinService::execute` - 事务提交后清理 pending key(失败不清) ### 4) 幂等与一致性 - `is_end=1` 继续沿用现有 `wallet_log(uid,biz_id,biz_type=win)` 幂等。 - `is_end=0` 使用 Redis dedupe key 防重,避免重复累计。 - 若 `is_end=1` 无待分配 funding,保持当前行为(报错),便于暴露上游时序问题。 ### 5) 兼容与回归 - 默认 `is_end=1`,兼容未传该字段的旧调用。 - 回归场景: - 单次结算(只发 `is_end=1`) - 多次派奖(多条 `is_end=0` + 一条 `is_end=1`) - `is_end=0` 重放同 biz_id 不重复累计 - `is_end=1` 重放同 biz_id 不重复入账 - `is_end=1` 失败后 pending 不丢失 ## 关键改动文件 - [app/api/dto/request/wallet/WalletUpdateRequestDTO.php](app/api/dto/request/wallet/WalletUpdateRequestDTO.php) - [app/validator/Wallet2Validator.php](app/validator/Wallet2Validator.php) - [app/service/RedisKeyManagerService.php](app/service/RedisKeyManagerService.php) - [app/api/logic/WalletLogic.php](app/api/logic/WalletLogic.php) ## 风险与边界 - 该方案不新增 `wallet_game_round` / `wallet_game_round_event` 持久化表;中间派奖仅 Redis 暂存,审计粒度弱于 DB 事件流。 - 若某局长期无 `is_end=1`,pending 依赖 TTL 过期清理。后续可升级为 DB 事件表方案。