This commit is contained in:
ray zhou
2026-05-29 11:21:40 +08:00
parent 5d6d482efe
commit f71a5c59af
447 changed files with 32245 additions and 116 deletions

View File

@@ -0,0 +1,408 @@
---
name: 每日返水需求文档
overview: 在现有 user_profit_daily 日下注统计基础上,新增「每日返水」活动:累进档位计费、仅充值用户参与、次日 00:00 起可领且 24 小时内有效;配套用户端 API、后台档位配置与综合统计页。
todos:
- id: schema
content: 设计并评审 daily_rebate_record、daily_rebate_tier_config 表结构与索引
status: in_progress
- id: calc-service
content: 实现累进分段 RebateCalcService + tier_snapshot + 单元测试(含 $2000→$50 等用例)
status: pending
- id: cron
content: slot_consoledailyRebateSettle / dailyRebateExpire 定时任务
status: pending
- id: user-api
content: slot_console 用户端 info/claim API + slot_wallet 入账与新 transaction type
status: pending
- id: admin-tier
content: slot_admin 每日返水档位 CRUD 页
status: pending
- id: admin-stats
content: slot_admin 综合统计/每日返水统计(列表+筛选+排序+汇总,参考 FreeCreditsStats
status: pending
- id: pwa-ui
content: 前端三态 UI、7 日表、倒计时、领取按钮
status: pending
isProject: false
---
# 每日返水Daily Rebate 3%)需求规格书
## 1. 背景与目标
- **业务目标**:对已充值用户,按自然日累计有效下注给予累进比例返水,提升留存与投注激励。
- **数据基础**`[slot_console](slot_console)` 已通过 `[LoseReturnDeposit](slot_console/app/command/LoseReturnDeposit.php)` + Redis `[UserProfitService](slot_pwa/app/service/user/UserProfitService.php)` 维护 `[s_statistics.user_profit_daily](slot_console/app/model/statistics/UserProfitDaily.php)`(字段 `bet`/`win`/`profit`/`create_date`/`source`)。
- **与 VIP 亏损返利的区别**VIP 返利基于「当日净亏损」且需 VIP 等级;本活动基于「当日总下注额」、仅需历史充值、档位为累进分段,产品独立。
---
## 2. 术语
| 术语 | 定义 |
| ---- | ------------------------------------------------------------------------------------------------------------- |
| 自然日 | 服务器时区 `Asia/Shanghai`(与 `[slot_center/config/app.php](slot_center/config/app.php)` 一致)的 `00:00:00``23:59:59` |
| 有效下注 | **等于** `user_profit_daily.bet`(厘),与 PWA 下注时 `incBet` 累计值一致;无单独过滤规则 |
| 返水金额 | 按档位配置对当日有效下注做**累进分段**计算后的金额(厘) |
| 统计日期 | 发生下注的自然日 `create_date`,非领取日 |
| 充值用户 | 结算时刻钱包 `total_deposit > 0``[WalletStatModel](slot_wallet/app/model/multi/WalletStatModel.php)` |
---
## 3. 核心业务规则
### 3.1 参与资格
- **未充值**不参与返水前端展示「锁定」态Unlock Cashback不可领、列表可置灰或仅展示引导文案。
- **已充值**:自充值成功当日起,当日及之后有有效下注的自然日可产生返水记录;历史未充值日的下注不补发。
### 3.2 返水计算(累进分段,非整笔单一比例)
**已确认**:采用分段累进,与原型及后台示例一致($2000 → $50
默认档位(首版种子数据,**后台可配置**
| 分段序号 | 下注区间(含边界,美元展示) | 比例 |
| ---- | --------------- | ---- |
| 1 | $0 $1,000 | 3% |
| 2 | $1,001 $3,000 | 2% |
| 3 | $3,001 $5,000 | 1% |
| 4 | $5,001 及以上 | 0.5% |
**计算公式**`B` = 当日有效下注,美元;存储与计算用厘,`1 USD = 1000` 厘,与 `[share_config.moneyFormat](slot_center/config/share_config.php)` 一致):
```
rebate_li = Σ segment_i( min(B, max_i) - min(B, min_i) + ε_i ) × rate_i
```
- 各档 `min_i`/`max_i` 以后台配置为准(最后一档 `max` 为空表示无上限)。
- 分段左闭右闭:第 1 档覆盖 `[0, 1000]`,第 2 档覆盖 `(1000, 3000]`,依此类推(实现时用「上一档上限 + 1」作为下一档起点避免重复计费
- **舍入**:返水金额入库前 `floor` 到厘;前端展示保留 2 位小数美元。
**计算示例**
| 有效下注 | 计算过程 | 返水 |
| ------- | --------------------------------------- | ------ |
| $125.56 | 125.56 × 3% | $3.77 |
| $200 | 200 × 3% | $6.00 |
| $2,000 | 1000×3% + 1000×2% | $50.00 |
| $6,000 | 1000×3% + 2000×2% + 2000×1% + 1000×0.5% | $95.00 |
**待定态(当天)**:用 Redis 当日 `bet` 实时重算返水预览;不入库终态金额,或与 DB 行 `status=pending` 同步更新。
### 3.3 结算与领取时间轴
```mermaid
sequenceDiagram
participant User
participant PWA as slot_pwa
participant Redis
participant Cron as slot_console_cron
participant DB as daily_rebate_record
Note over User,Redis: D日 00:00-23:59
User->>PWA: 下注
PWA->>Redis: incBet
Note over DB: status=pending 实时 bet/rebate
Note over Cron,DB: D+1日 00:00后 cron
Cron->>Redis: 读取 D日 bet
Cron->>DB: 写入/更新 settle: claimable, dead_time=D+2 00:00
Note over User,DB: D+1日 仅可领 D日 返水
User->>PWA: claim(stat_date=D)
PWA->>DB: status=claimed
Note over DB: 超过 dead_time 未领
Cron->>DB: status=expired
```
| 时点 | 行为 |
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| D 日进行中 | 状态 **待定**;有效下注、返水金额随 Redis 刷新 |
| D+1 日 00:00 | 定时任务结算 D 日:有 `bet>0` 且已充值 → 生成/更新记录,状态 **待领取**`claimable_at = D+1 00:00:00``expire_at = D+2 00:00:00`(领取窗口 **24 小时**,对齐 VIP `[vip_rebate_lose_time](slot_center/config/share_config.php)` |
| D+1 日全天 | 用户**只能领取 stat_date=D** 的返水(「前一天」) |
| D+2 日 00:00 前未领 | 状态 **过期**`rebate_amount>0` 且未领 |
| 领取成功 | 状态 **已领**;记 `claimed_at`;钱包入账 |
**无有效下注**:不生成记录(后台列表、前端 7 日表均不展示该日)。
**返水为 0**:若档位计算为 0`bet=0` 已排除),`bet>0` 但舍入为 0 时可不落库或落库且不可领——建议 **不落库**,与「未有有效下注不显示」一致。
### 3.4 状态机
| 状态码 | 中文 | 条件 |
| ----------- | --- | --------------------------------------------------------------------- |
| `pending` | 待定 | `stat_date = 今天`;实时统计未结束 |
| `claimable` | 待领取 | `stat_date < 今天``rebate_amount > 0``claimed_at` 空;`now < expire_at` |
| `claimed` | 已领 | `claimed_at` 非空 |
| `expired` | 过期 | `rebate_amount > 0`;未领;`now >= expire_at` |
状态迁移:
- `pending``claimable`:日切结算任务(仅昨日及更早批量处理;今日保持 pending
- `claimable``claimed`:用户领取接口(幂等)
- `claimable``expired`:过期扫描任务或领取时校验
前端映射(英文 UI
| 状态 | 展示 | 行样式 |
| --------- | --------- | -------------------- |
| pending | Pending | 绿色标签(进行中) |
| claimable | Claimable | 绿色可点 |
| claimed | Claimed | 白色/灰色 |
| expired | Expired | 红色/灰色 |
| 无记录 | No Bets | 灰字(仅前端 7 日占位,无 DB 行) |
### 3.5 领取规则
- 每次领取**一条**指定 `stat_date`(通常为昨日)。
- 并发DB 行级锁 / `UPDATE ... WHERE status=claimable` 防重复领。
- 入账:新增钱包流水类型(建议 `TRANSACTION_TYPE_DAILY_REBATE = 65`,在 `[Consts.php](slot_lib/src/common/const/Consts.php)` 登记);更新 `total_cashback`
- 失败:事务回滚,状态不变。
---
## 4. 数据设计
### 4.1 沿用表
- `**s_statistics.user_profit_daily`**:只读来源,提供 `bet``source``create_date`**不扩展**状态/返水字段。
### 4.2 新建表 `daily_rebate_record`(建议库:`s_common`
| 字段 | 类型 | 说明 |
| ----------------------- | ----------------- | ----------------------------------------- |
| id | bigint PK | |
| uid | bigint | 用户 ID |
| source | varchar | 渠道号(结算时快照) |
| stat_date | date | 统计日期(下注日) |
| bet_amount | bigint | 有效下注(厘) |
| rebate_amount | bigint | 返水金额(厘) |
| status | tinyint | 1 pending 2 claimable 3 claimed 4 expired |
| tier_snapshot | json | 结算时档位快照(审计) |
| claimable_at | datetime | 可领取开始 |
| expire_at | datetime | 过期时间 |
| claimed_at | datetime nullable | 领取时间 |
| created_at / updated_at | datetime | |
**唯一索引**`(uid, stat_date)`
**索引**`(stat_date, status)``(source, stat_date)``(uid)`
### 4.3 新建表 `daily_rebate_tier_config`(后台可配置)
| 字段 | 说明 |
| ----------------------- | ------------------ |
| id | PK |
| sort | 排序(从小到大) |
| min_bet | 区间下限(厘,含) |
| max_bet | 区间上限NULL=无上限) |
| rate_percent | 比例,如 3.00 表示 3% |
| status | 启用/停用 |
| updated_by / updated_at | 审计 |
校验:档位连续无空洞、无重叠;至少一档;最后一档可无 `max_bet`
---
## 5. 定时任务slot_console
| 任务 | 触发 | 职责 |
| ----------------------------------------------------------------------------- | ------------- | ------------------------------------------------------------------------------- |
| `dailyRebateSettle` | 每日 00:05可配置 | 结算 **昨日** `user_profit_daily` + 充值校验 → upsert `daily_rebate_record` 为 claimable |
| `dailyRebateExpire` | 每小时或 00:10 | 将 `claimable``now>=expire_at` 置为 expired |
| (可选)与现有 `[loseReturnDeposit](slot_console/app/command/LoseReturnDeposit.php)` | 同批读 Redis | 避免重复扫 Redis**推荐独立命令**以免耦合 VIP 逻辑 |
**待定实时**用户打开活动页时Logic 读 Redis `user:profit:{Ymd}` + 当前档位配置计算预览,不写 claimable。
---
## 6. 用户端PWA / Gateway
### 6.1 页面态(对齐原型)
| 态 | 条件 | 主按钮 | 表格 |
| --- | ------------------------------- | --------------- | ------------- |
| 锁定 | `total_deposit==0` | Unlock Cashback | 模糊/遮罩 |
| 已解锁 | 已充值,无可领 | Play Now | 近 7 日明细 |
| 可领取 | 存在 `claimable``stat_date=昨天` | Claim($X.XX) | 昨日行 Claimable |
**倒计时**「THIS ROUND」= 距今日自然日结束的秒数(与 Rates reset 00:00 文案一致)。
### 6.2 API建议落在 slot_console `/api/daily-rebate/*`,经 gateway 转发)
**GET `/api/daily-rebate/info`**
响应示例字段:
```json
{
"unlocked": true,
"countdown_seconds": 86399,
"claimable": { "stat_date": "2026-05-25", "rebate_amount": 3000, "display": "3.00" },
"tiers": [{ "min": 0, "max": 1000000, "rate": 3 }],
"records": [
{ "stat_date": "05/26", "bet_amount": 125560, "rebate_amount": 3770, "status": "pending" }
]
}
```
- `records`:最近 7 个自然日,**有 bet 或 DB 行**的日期;无下注日可不返回或前端填 No Bets。
- 打开弹窗时 **强制刷新** 当日 pending 数据。
**POST `/api/daily-rebate/claim`**
- 入参:`stat_date`(可选,默认昨天)
- 校验已充值、status=claimable、未过期、rebate>0
- 出参:领取后余额/流水号
**GET `/api/daily-rebate/tiers`**可选info 已含则省略)
---
## 7. 管理后台slot_admin + slot_admin_vue
### 7.1 菜单
- **活动配置 / 每日返水档位**新建CRUD [`daily_rebate_tier_config`],校验区间连续。
- **综合统计 / 每日返水统计**(新建):列表 + 汇总。
实现参考:列表+汇总 `[FreeCreditsStats](backend/slot_admin/app/game/logic/FreeCreditsStatsLogic.php)` + 前端 `[freeCreditsStats/index.vue](backend/slot_admin_vue/src/views/game/freeCreditsStats/index.vue)`
### 7.2 统计列表
**列**ID(uid) | 渠道号 | 有效下注额⇅ | 返水金额⇅ | 状态 | 统计日期
**筛选**
- UID精确
- 渠道(`source`,可搜索下拉,复用 `commonStore.allSourcesOptionsNoAll`
- 状态:待定 / 待领取 / 已领 / 过期(多选)
- 统计日期范围(`stat_date[]`
**排序白名单**`bet_amount``rebate_amount`(默认 `stat_date desc, id desc`
**数据范围**:仅 `total_deposit>0` 用户在结算时已入库的记录;**待定**行可对「今天」合并 Redis 实时 bet与 Free Credits「打开刷新」一致
### 7.3 顶部汇总(随筛选变化)
| 指标 | 计算 |
| ---- | ----------------------------------------------- |
| 总领取 | `status=claimed``sum(rebate_amount)` |
| 总待领取 | `status=claimable` 的 sum |
| 总过期 | `status=expired` 的 sum |
| 待定 | `status=pending` 的 sum**若筛选日期范围不含今天则为 0** |
| 领取率 | `总领取 / (总领取 + 总过期 + 总待领取)`;分母为 0 时显示 `0%``-` |
展示格式:`$888.88``rebate_amount / 1000`2 位小数)。
---
## 8. 非功能需求
- **幂等**:结算任务对 `(uid, stat_date)` upsert领取 CAS 更新状态。
- **性能**:日活结算批量按日期分页;后台列表分页默认 100最大 100。
- **审计**`tier_snapshot` 保留结算时档位;领取写 wallet log `biz_extra: {activity:"daily_rebate", stat_date}`
- **监控**:结算失败/领取失败打日志 + 指标;过期数量日报。
---
## 9. 边界与异常
| 场景 | 处理 |
| ------------- | ----------------------------------------------------------- |
| 结算日用户刚充值、昨日下注 | 昨日结算时已按当时 `total_deposit` 判断;若需「充值后立即对历史补发」→ **不做**,以结算时刻为准 |
| 结算任务重复跑 | upsert不重复入账 |
| 时区变更 | 禁止随意改;变更需重算规则文档化 |
| bet>0 未充值 | 不生成记录 |
| 领取时档位已改 | 以 `tier_snapshot` 为准,不受后续配置变更影响 |
| 跨天未关弹窗 | 前端倒计时结束刷新 infopending→claimable 由后端状态驱动 |
---
## 10. 实施拆分(供研发排期)
```mermaid
flowchart LR
subgraph phase1 [Phase1 数据与配置]
T1[daily_rebate_tier_config]
T2[daily_rebate_record]
T3[admin 档位 CRUD]
end
subgraph phase2 [Phase2 结算与过期]
C1[dailyRebateSettle]
C2[dailyRebateExpire]
L1[rebate calc service]
end
subgraph phase3 [Phase3 用户端]
A1[info API]
A2[claim API + wallet]
F1[PWA 三态 UI]
end
subgraph phase4 [Phase4 后台统计]
B1[DailyRebateStats Logic]
B2[admin vue 列表汇总]
end
phase1 --> phase2 --> phase3
phase2 --> phase4
```
| 仓库 | 改动要点 |
| ---------------------- | ---------------------------------------------------------- |
| slot_console | Model、结算/过期 Command、RebateCalcService、用户 API、Redis 读当日 bet |
| slot_wallet | 新 transaction type、领取入账 |
| slot_lib | Consts 新类型 |
| backend/slot_admin | Controller/Logic/Validate、菜单 SQL |
| backend/slot_admin_vue | 档位页 + 统计页 |
| slot_sdk | 若 gateway 经 sdk 调 console补 Client 方法 |
**不建议**复用 `vip_rebate_record` 表:业务语义、状态机、计费基础均不同。
---
## 11. 验收标准(摘要)
1. 充值用户 D 日下注 $2000D+1 00:00 后显示待领取 $5024h 内领取成功,流水类型正确。
2. 未充值用户仅见锁定态,无领取接口成功路径。
3. 超 24h 未领变过期,不可再领。
4. 后台筛选/status/排序/汇总与文档公式一致;改档位仅影响新结算日。
5. 前端 7 日表、Claim 按钮金额与后台一致;当日为 Pending 且随下注刷新。
---
## 12. 已确认决策
- 计费方式:**累进分段**(非整笔落档单一比例)。
- 有效下注:**等同 `user_profit_daily.bet`**。
- 档位:**后台可配置**`daily_rebate_tier_config`)。