Files
cursor/plans/RTP-ffd1b7b6.plan.md
ray zhou 2dd9f17da9 ok
2026-06-29 14:51:55 +08:00

139 lines
5.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!-- ffd1b7b6-16e7-4f41-9f8b-a22ab65c6a00 -->
---
todos:
- id: "ddl"
content: "新增 slot-pwa/db/game_user_recharge_rtp_stat.sql用户 DDL 原样)"
status: pending
- id: "service-model"
content: "GameUserRechargeRtpStatModel + GameUserRechargeRtpStatService + UserRechargeStatusService"
status: pending
- id: "wire-wallet-service"
content: "WalletService bet/win 成功后挂载累计与 RTP 重算"
status: pending
- id: "redis-key"
content: "RedisKeyManagerService 补充 recharge:exchange key"
status: pending
isProject: false
---
# game_user_recharge_rtp_statslot-pwa
## 变更范围
- **表**:按你提供的 DDL不做额外字段
- **服务**[`slot-pwa`](slot-pwa) 统一处理(游戏回调 → 调 wallet 的入口)
- **不做**wallet 侧改表、移除 `TrialValidBetNotifyService`、activity 同步(后续再定)
---
## 表结构(原样采用)
库:`s_common`(与 [`game_launch_session`](slot-pwa/db/game_launch_session.sql) 同库)
```sql
CREATE TABLE `game_user_recharge_rtp_stat` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`uid` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '用户ID',
`before_recharge_bet_amount` BIGINT NOT NULL DEFAULT 0 COMMENT '充值前累计下注金额金额扩大1000倍',
`before_recharge_payout_amount` BIGINT NOT NULL DEFAULT 0 COMMENT '充值前累计派奖金额金额扩大1000倍',
`before_recharge_rtp_rate` DECIMAL(10, 6) NOT NULL DEFAULT 0.000000 COMMENT '充值前RTPpayout / bet',
`after_recharge_bet_amount` BIGINT NOT NULL DEFAULT 0 COMMENT '充值后累计下注金额金额扩大1000倍',
`after_recharge_payout_amount` BIGINT NOT NULL DEFAULT 0 COMMENT '充值后累计派奖金额金额扩大1000倍',
`after_recharge_rtp_rate` DECIMAL(10, 6) NOT NULL DEFAULT 0.000000 COMMENT '充值后RTPpayout / bet',
`created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`updated_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (`id`),
UNIQUE KEY `uk_uid` (`uid`),
KEY `idx_before_rtp_bet` (`before_recharge_rtp_rate`, `before_recharge_bet_amount`),
KEY `idx_after_rtp_bet` (`after_recharge_rtp_rate`, `after_recharge_bet_amount`),
KEY `idx_before_bet` (`before_recharge_bet_amount`),
KEY `idx_after_bet` (`after_recharge_bet_amount`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='用户充值前后下注派奖RTP累计统计表';
```
DDL 文件:[`slot-pwa/db/game_user_recharge_rtp_stat.sql`](slot-pwa/db/game_user_recharge_rtp_stat.sql)
---
## 为什么在 slot-pwa
所有厂商下注/派奖最终都走 [`WalletService::bet()`](slot-pwa/app/service/WalletService.php) / `win()`pop / quick / jsgame / jdb / self / gasea / bbgt 等),与现有 [`UserProfitService`](slot-pwa/app/service/user/UserProfitService.php) 挂载点一致,**一处写入、全覆盖**。
```mermaid
flowchart LR
Provider[厂商回调] --> PwaCash[pop/quick/... CashLogic]
PwaCash --> WalletSvc[WalletService bet/win]
WalletSvc --> WalletApi[slot-wallet API]
WalletSvc --> RtpStat[GameUserRechargeRtpStatService]
RtpStat --> Db[(game_user_recharge_rtp_stat)]
```
---
## 阶段判定(充值前 / 充值后)
读 wallet 同款 Redis 充值标记([`RechargeExchangeService::isRecharge`](slot-wallet/app/service/wallet/RechargeExchangeService.php)
- Key`recharge:exchange:{uid % 10}`field = `uid``recharge > 0`**充值后**
- 否则 → **充值前**
在 slot-pwa 新增薄封装 `UserRechargeStatusService`(读 default Rediskey 规则与 wallet 一致),**不新增表字段**。
---
## 写入口径
挂载点:[`WalletService::bet()`](slot-pwa/app/service/WalletService.php) / `win()`,在 **`updateWallet()` 成功返回非 null 之后** 写统计(比现有 `UserProfitService` 更早调用更安全,避免 wallet 失败仍累加)。
| 动作 | 条件 | 更新字段 |
|------|------|----------|
| bet 成功 | 未充值 | `before_recharge_bet_amount += fee` |
| bet 成功 | 已充值 | `after_recharge_bet_amount += fee` |
| win 成功 | `is_end === 1` 且未充值 | `before_recharge_payout_amount += fee` |
| win 成功 | `is_end === 1` 且已充值 | `after_recharge_payout_amount += fee` |
**派奖仅计 `is_end=1`**:与 wallet 实际入账一致(中间派奖 `is_end=0` 只写 Redis pending不落库
**RTP 重算**(同事务内 SQL 更新):
```
before_recharge_rtp_rate = before_bet > 0
? ROUND(before_payout / before_bet, 6) : 0
after_recharge_rtp_rate = after_bet > 0
? ROUND(after_payout / after_bet, 6) : 0
```
**幂等**:依赖 slot-pwa 已有 `provider_tx` 幂等(同一 biz 不会重复调 wallet不在本表再存 biz_id。
**fee=0**跳过累加zero win 等场景)。
---
## 代码落点
| 文件 | 职责 |
|------|------|
| `slot-pwa/db/game_user_recharge_rtp_stat.sql` | 建表 |
| `slot-pwa/app/model/game/GameUserRechargeRtpStatModel.php` | ThinkORM Model + 完整 PHPDoc |
| `slot-pwa/app/service/game/GameUserRechargeRtpStatService.php` | `accumulateBet` / `accumulatePayout`、RTP 重算、upsert by uid |
| `slot-pwa/app/service/user/UserRechargeStatusService.php` | 读 Redis 判断是否已充值 |
| `slot-pwa/app/service/WalletService.php` | bet/win 成功后调用 Service |
| `slot-pwa/app/service/RedisKeyManagerService.php` | 补充 `getRechargeExchangeKey`(与 wallet 一致) |
---
## 与 wallet / activity 的关系(后续)
- 本表成为 **充值前后 bet/payout/RTP 权威数据源**
- activity 的 `valid_bet_amount` / 4→5 解锁、移除 `trial_valid_bet_accumulate` MQ → **下一阶段**,本计划不改动
---
## 验收
1. 未充值用户游戏:只涨 `before_*`RTP 随 bet/win 更新
2. 首充后:只涨 `after_*`
3. `is_end=0` 的中间 win 不计入 payout
4. wallet 调用失败时不写表
5. 风控可按 `idx_before_rtp_bet` / `idx_after_rtp_bet` 扫异常 RTP