This commit is contained in:
ray zhou
2026-06-29 14:51:55 +08:00
parent 225fb2bd28
commit 2dd9f17da9
319 changed files with 29461 additions and 9412 deletions

138
plans/RTP-ffd1b7b6.plan.md Normal file
View File

@@ -0,0 +1,138 @@
<!-- 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