ok
This commit is contained in:
138
plans/dailyrebate_max_rate_5541d0bb.plan.md
Normal file
138
plans/dailyrebate_max_rate_5541d0bb.plan.md
Normal file
@@ -0,0 +1,138 @@
|
||||
---
|
||||
name: DailyRebate max_rate
|
||||
overview: 将每日返水 C 端 `info` 接口的 `tiers` 数组改为返回档位中最高返水比例 `max_rate`(float),并清理不再使用的格式化方法。
|
||||
todos:
|
||||
- id: update-info-return
|
||||
content: 修改 DailyRebateLogic::info():移除 tiers 返回,新增 max_rate
|
||||
status: completed
|
||||
- id: add-resolve-max-rate
|
||||
content: 新增 resolveMaxRateFromTiers(),删除 formatTiersForClient()
|
||||
status: completed
|
||||
- id: sync-phpdoc
|
||||
content: 更新 info() PHPDoc 与 Controller 注释
|
||||
status: completed
|
||||
- id: verify
|
||||
content: 执行 verify-slot-backend.sh 验证
|
||||
status: completed
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# 每日返水 info 返回 max_rate
|
||||
|
||||
## 背景
|
||||
|
||||
当前 [`DailyRebateLogic::info()`](slot_console/app/api/logic/DailyRebateLogic.php) 会把全部档位格式化为 C 端展示数组:
|
||||
|
||||
```php
|
||||
$tierDisplay = $this->formatTiersForClient($tiers);
|
||||
// ...
|
||||
'tiers' => $tierDisplay,
|
||||
```
|
||||
|
||||
C 端不需要完整 `tiers`,只需要**所有档位里 `rate_percent` 最大的那个比例**,字段名定为 **`max_rate`**(float,如 `3.0` 表示 3%)。
|
||||
|
||||
## 改动范围
|
||||
|
||||
仅改 [`slot_console/app/api/logic/DailyRebateLogic.php`](slot_console/app/api/logic/DailyRebateLogic.php)(Controller 注释可选同步一行)。
|
||||
|
||||
`tiers` 仍保留在内部用于 `buildInfoViewContext()` 计算近 7 日返水,**结算/领取逻辑不动**。
|
||||
|
||||
## 实现步骤
|
||||
|
||||
### 1. 修改 `info()` 返回结构
|
||||
|
||||
在 `info()` 中:
|
||||
|
||||
- 删除 `$tierDisplay = $this->formatTiersForClient($tiers);`
|
||||
- 新增 `$maxRate = $this->resolveMaxRateFromTiers($tiers);`
|
||||
- 返回字段由 `'tiers' => $tierDisplay` 改为 `'max_rate' => $maxRate`
|
||||
|
||||
更新 `info()` PHPDoc 返回结构:
|
||||
|
||||
```php
|
||||
/**
|
||||
* @return array{
|
||||
* ...
|
||||
* max_rate: float,
|
||||
* records: list<...>
|
||||
* }
|
||||
*/
|
||||
```
|
||||
|
||||
### 2. 新增私有方法 `resolveMaxRateFromTiers()`
|
||||
|
||||
替换原 `formatTiersForClient()`,语义更清晰:
|
||||
|
||||
```php
|
||||
/**
|
||||
* 从档位配置中取最高返水比例(C 端展示用)。
|
||||
*
|
||||
* @param list<array{sort:int,min_bet:int,max_bet:?int,rate_percent:float}> $tiers
|
||||
*/
|
||||
protected function resolveMaxRateFromTiers(array $tiers): float
|
||||
{
|
||||
if ($tiers === []) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
$maxRatePercent = 0.0;
|
||||
foreach ($tiers as $tier) {
|
||||
$maxRatePercent = max($maxRatePercent, (float) $tier['rate_percent']);
|
||||
}
|
||||
|
||||
return $maxRatePercent;
|
||||
}
|
||||
```
|
||||
|
||||
边界处理:
|
||||
|
||||
- 无活动 / `$tiers === []` → 返回 `0.0`
|
||||
- 多档同比例 → 取该值即可(`max()` 自然处理)
|
||||
|
||||
### 3. 删除 `formatTiersForClient()`
|
||||
|
||||
该方法仅在 `info()` 使用,删除避免 dead code。
|
||||
|
||||
### 4. 同步 Controller 注释(可选)
|
||||
|
||||
[`DailyRebateController::info()`](slot_console/app/api/controller/DailyRebateController.php) 注释「档位、近 7 日记录…」改为「最高返水比例、近 7 日记录…」。
|
||||
|
||||
## 接口变更(Breaking Change)
|
||||
|
||||
| 变更前 | 变更后 |
|
||||
| --- | --- |
|
||||
| `tiers: [{min, max, rate}, ...]` | **移除** |
|
||||
| — | `max_rate: 3.0` |
|
||||
|
||||
示例(有 4 档配置,最高 3%):
|
||||
|
||||
```json
|
||||
{
|
||||
"unlocked": true,
|
||||
"title": "...",
|
||||
"max_rate": 3.0,
|
||||
"records": [...]
|
||||
}
|
||||
```
|
||||
|
||||
当前仓库内 **slot_pwa 尚未引用 `tiers`**,后端先改即可;C 端接入时用 `max_rate` 展示「最高 X% 返水」类文案。
|
||||
|
||||
## 数据流(不变部分)
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
config[DailyRebateConfigService.resolveForSource]
|
||||
tiers[tiers内部数组]
|
||||
info[DailyRebateLogic.info]
|
||||
maxRate[max_rate]
|
||||
records[records近7日]
|
||||
config --> tiers
|
||||
tiers --> info
|
||||
info --> maxRate
|
||||
info --> records
|
||||
```
|
||||
|
||||
## 验证
|
||||
|
||||
- 跑 `~/.cursor/hooks/verify-slot-backend.sh`
|
||||
- 手动调 `DailyRebate info`:有活动时应返回 `max_rate` 等于配置中最大 `rate_percent`;无活动时为 `0.0`
|
||||
93
plans/launch_logic_参数与规范_8e5808ea.plan.md
Normal file
93
plans/launch_logic_参数与规范_8e5808ea.plan.md
Normal file
@@ -0,0 +1,93 @@
|
||||
---
|
||||
name: Launch Logic 参数与规范
|
||||
overview: 重构 `GameLaunchSessionLogic` 中仅为日志传递的 `$gameCode` 参数,并在用户级 `php-clean-code.mdc` / `agent-completion-gate.mdc` 中增加「参数必须服务于业务」的硬性约束与完成前自查项。
|
||||
todos:
|
||||
- id: refactor-find-methods
|
||||
content: 重构 GameLaunchSessionLogic:去掉 find* 中仅用于日志的 gameCode 参数,日志改用本步真实字段
|
||||
status: completed
|
||||
- id: update-php-clean-code
|
||||
content: php-clean-code.mdc §3 增加「参数与日志」、§8 增加自查项
|
||||
status: pending
|
||||
- id: update-completion-gate
|
||||
content: agent-completion-gate.mdc 增加 Logic 自查与 SLOT_ROOT=www/ray 校验说明
|
||||
status: pending
|
||||
- id: verify
|
||||
content: docker php -l + SLOT_ROOT verify-slot-backend.sh,回复粘贴完整输出
|
||||
status: completed
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# Launch Logic 参数重构 + 规范加固
|
||||
|
||||
## 问题
|
||||
|
||||
[`GameLaunchSessionLogic.php`](slot-pwa/app/api/logic/GameLaunchSessionLogic.php) 中以下方法将 `$gameCode` 作为入参,但**不参与查询/判断**,仅用于 `Log::error`:
|
||||
|
||||
- `findActivePlatformMapping(GGameModel $gameModel, string $gameCode)`
|
||||
- `findPlatformById(int $platformId, string $gameCode)`
|
||||
- `findProviderGameService(GPlatformModel $platformModel, string $gameCode)`
|
||||
|
||||
违反 [`php-clean-code.mdc`](/Users/ray/.cursor/rules/php-clean-code.mdc) §3(参数应表达业务需要、禁止误导性签名)。
|
||||
|
||||
## 一、代码重构(企业做法:查询方法只收查询条件)
|
||||
|
||||
**改动文件:** 仅 [`slot-pwa/app/api/logic/GameLaunchSessionLogic.php`](slot-pwa/app/api/logic/GameLaunchSessionLogic.php)
|
||||
|
||||
| 方法 | 调整后签名 | 失败日志字段(用已有入参) |
|
||||
|------|------------|---------------------------|
|
||||
| `findActivePlatformMapping` | `(GGameModel $gameModel)` | `game_id`、`active_platform_id` |
|
||||
| `findPlatformById` | `(int $platformId)` | `platform_id` |
|
||||
| `findProviderGameService` | `(GPlatformModel $platformModel)` | `provider_code`(`$platformModel->code`) |
|
||||
|
||||
`resolveGameLaunchContext` 调用改为:
|
||||
|
||||
```php
|
||||
$gameModel = $this->findGameByCode($gameLaunchDto->gameCode);
|
||||
$platformMapping = $this->findActivePlatformMapping($gameModel);
|
||||
$platformModel = $this->findPlatformById((int) $gameModel->active_platform_id);
|
||||
$providerGameService = $this->findProviderGameService($platformModel);
|
||||
```
|
||||
|
||||
**不引入** `GameLaunchResolveContext`(当前仅 3 步解析,编排层已有 `gameLaunchDto->gameCode`,避免过度设计)。
|
||||
|
||||
**保留** `findGameByCode(string $gameCode)`:`gameCode` 即查询条件,合理。
|
||||
|
||||
**可选增强(本计划内做):** 在 `resolveGameLaunchContext` 最外层若需串联排障,可在 public 入口 `launchWithSession` 的 catch 中统一补 `game_code`(已有 `launch game session fail` 日志,解析阶段失败由各 `find*` 用自身字段即可)。
|
||||
|
||||
## 二、用户级规范更新
|
||||
|
||||
### 1. [`php-clean-code.mdc`](/Users/ray/.cursor/rules/php-clean-code.mdc)
|
||||
|
||||
在 **§3 方法规则** 末尾新增小节 **「参数与日志」**:
|
||||
|
||||
- **禁止**为打日志、排障单独增加与该方法业务无关的参数(反例:`findPlatformById($id, $gameCode)` 且 `$gameCode` 只出现在 `Log::error`)。
|
||||
- **查询/校验类方法**(`find*`、`ensure*`)的参数必须等于该步骤的查询条件或判断依据。
|
||||
- 跨多步共享的排障字段(如 `game_code`)应在 **用例编排方法**(public Logic 入口或 `resolveXxx` 编排 private)集中记录;子步骤日志只写本子步骤真实使用的字段(`platform_id`、`provider_code` 等)。
|
||||
- 若多步都需要同一追溯上下文且步骤 ≥4,再引入 readonly `XxxResolveContext`,**禁止**向每个 private 方法重复挂相同标量。
|
||||
|
||||
在 **§8 Agent 自查** 增加一项:
|
||||
|
||||
- 是否存在「仅用于日志」的多余参数?
|
||||
|
||||
### 2. [`agent-completion-gate.mdc`](/Users/ray/.cursor/rules/agent-completion-gate.mdc)
|
||||
|
||||
在 **必须** 列表增加:
|
||||
|
||||
5. 修改 `app/**/Logic/**/*.php` 时,完成前对照 `php-clean-code` **§3 参数与日志** 与 **§8 自查**(不仅依赖 verify 脚本)。
|
||||
6. 工作区在 `www/ray` 时,执行校验须:`SLOT_ROOT=/Users/ray/Documents/project/www/ray ~/.cursor/hooks/verify-slot-backend.sh`(避免 `PASS (no changed files)` 误判)。
|
||||
|
||||
### 3. 不改动 verify 脚本(本计划)
|
||||
|
||||
`verify-slot-backend.sh` 难以可靠检测「参数仅用于日志」;以 **规则 + Agent 自查** 为主。若后续误报多,再考虑启发式检查。
|
||||
|
||||
## 三、验收
|
||||
|
||||
1. `findPlatformById` / `findActivePlatformMapping` / `findProviderGameService` 签名中无仅日志用的 `$gameCode`。
|
||||
2. `docker exec -w /app/www/ray/slot-pwa php82 php -l app/api/logic/GameLaunchSessionLogic.php` 通过。
|
||||
3. `SLOT_ROOT=/Users/ray/Documents/project/www/ray ~/.cursor/hooks/verify-slot-backend.sh` 输出 `PASS`(有 diff 时)。
|
||||
4. 规范文件已更新,Agent 自查项可对照执行。
|
||||
|
||||
## 范围外
|
||||
|
||||
- 不重构 `GameController` 响应格式。
|
||||
- 不新增 `GameLaunchResolveContext` DTO(除非实现时发现步骤继续增加)。
|
||||
132
plans/phpdoc_规范收紧_f84cb482.plan.md
Normal file
132
plans/phpdoc_规范收紧_f84cb482.plan.md
Normal file
@@ -0,0 +1,132 @@
|
||||
---
|
||||
name: PHPDoc 规范收紧
|
||||
overview: 调整 [`php-clean-code.mdc`](/Users/ray/.cursor/rules/php-clean-code.mdc) 第 5 节 PHPDoc 规则:除纯 getter/setter 外,Logic/Service/Model 的 public 方法一律必须写中文 PHPDoc;删除「方法名够清晰可不写」的豁免,并同步 Agent 自查清单。
|
||||
todos:
|
||||
- id: revise-section-5
|
||||
content: 重写 php-clean-code.mdc §5:默认必须、getter/setter 豁免、中文首行、删除旧豁免句
|
||||
status: completed
|
||||
- id: tighten-must-list
|
||||
content: 将原「以下情况必须写」改为「附加要求」,避免被理解为可选项
|
||||
status: completed
|
||||
- id: update-agent-checklist
|
||||
content: 更新 §8 Agent 自查:中文 PHPDoc 全覆盖检查项
|
||||
status: completed
|
||||
- id: verify-consistency
|
||||
content: 通读 Model @property 小节与 §5,确认无冲突表述
|
||||
status: completed
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# PHPDoc 规范收紧计划
|
||||
|
||||
## 背景
|
||||
|
||||
当前 [`php-clean-code.mdc`](/Users/ray/.cursor/rules/php-clean-code.mdc) 存在两条互相削弱的规则:
|
||||
|
||||
- 第 165 行:`Logic、Service、Model 的 public 方法**建议**写 PHPDoc`
|
||||
- 第 167–174 行:仅列举部分场景「**必须**写」
|
||||
- 第 203 行:`简单 getter/setter、方法名和类型已足够清晰时,**不强制** PHPDoc`
|
||||
|
||||
团队诉求:**除纯 getter/setter 外,其余方法都要写 PHPDoc,且以中文说明业务含义**(很多人看不懂英文方法名)。这与已存在的 [Model 类 `@property` 规范](/Users/ray/.cursor/rules/php-clean-code.mdc)(约 123–159 行)方向一致,需统一到同一套原则。
|
||||
|
||||
## 修改范围
|
||||
|
||||
**仅改规则文件**(不批量改历史代码):
|
||||
|
||||
- [`/Users/ray/.cursor/rules/php-clean-code.mdc`](/Users/ray/.cursor/rules/php-clean-code.mdc) — §5 PHPDoc 与注释、§8 Agent 自查
|
||||
|
||||
[`agent-completion-gate.mdc`](/Users/ray/.cursor/rules/agent-completion-gate.mdc) 仍引用「php-code PHPDoc 章节」,无需改路径;完成门禁时 Agent 按更新后的 §5 执行即可。
|
||||
|
||||
## §5 改写要点
|
||||
|
||||
### 1. 默认规则:从「建议」改为「必须」
|
||||
|
||||
将第 165 行改为明确默认值:
|
||||
|
||||
> **Logic / Service / Model 的 `public` 方法必须写 PHPDoc**(首行中文说明业务动作);`protected` 方法若承载业务步骤,同样必须。
|
||||
|
||||
### 2. 唯一豁免:纯 getter / setter
|
||||
|
||||
用白名单定义豁免,替代原第 203 行「方法名够清晰可不写」:
|
||||
|
||||
| 可豁免 | 不可豁免 |
|
||||
|--------|----------|
|
||||
| 无业务分支、无事务、无外部调用的 `getXxx()` / `setXxx()` | `findActiveBySessionId`、`createActiveSession`、`launchWithSession` 等 |
|
||||
| 只读/写入单个属性或 DTO 字段 | 名称像 getter 但含查询、状态判断、写入库表 |
|
||||
| | `isXxx()` / `hasXxx()` / `ensureXxx()` / `markXxx()` |
|
||||
|
||||
示例(写入规范正文):
|
||||
|
||||
```php
|
||||
// 可豁免
|
||||
public function getUid(): int { return $this->uid; }
|
||||
|
||||
// 不可豁免 — 必须中文 PHPDoc
|
||||
public static function findActiveBySessionId(string $sessionId): ?self
|
||||
```
|
||||
|
||||
### 3. 方法 PHPDoc 格式要求(中文优先)
|
||||
|
||||
规定最小合格格式(与现有 Model `@property` 风格一致):
|
||||
|
||||
```php
|
||||
/**
|
||||
* 按对外 session_id 查询未过期且有效的 Launch Session。
|
||||
*
|
||||
* @throws BusinessException 当 ...
|
||||
*/
|
||||
public static function findActiveBySessionId(string $sessionId): ?self
|
||||
```
|
||||
|
||||
- **首行必须是中文**,说明「做什么 / 业务结果」,不能只重复英文方法名。
|
||||
- 保留现有硬性要求:`array` 结构、`@throws`、状态流转等(作为**附加**要求,不是唯一触发条件)。
|
||||
- **禁止废话**:禁止只写 `/** get user */`、`/** @param int $uid */` 而无中文业务说明;禁止与签名完全重复的英文复述。
|
||||
|
||||
### 4. 删除 / 替换第 203 行
|
||||
|
||||
删除:
|
||||
|
||||
> 不要写废话 PHPDoc。简单 getter/setter、方法名和类型已足够清晰时,不强制 PHPDoc。
|
||||
|
||||
替换为两条并列原则:
|
||||
|
||||
- **禁止废话**:无信息增量、纯英文复述签名 → 不合格。
|
||||
- **除纯 getter/setter 外一律必须**:英文命名不能替代中文 PHPDoc。
|
||||
|
||||
保留第 205 行「注释解释为什么,不解释做什么」——仅适用于**行内注释**;方法 PHPDoc 首行仍要写「做什么」(中文),复杂处再用行内注释写「为什么」。
|
||||
|
||||
### 5. 收紧原「以下情况必须写」列表
|
||||
|
||||
原列表(返回 array、状态流转等)改为 **「以下情况除满足通用要求外,还须额外写明…」**,避免读者误以为「不在列表里就可以不写」。
|
||||
|
||||
结构示意:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
method[public方法] --> isGetter{纯getter/setter?}
|
||||
isGetter -->|是| exempt[可省略PHPDoc]
|
||||
isGetter -->|否| required[必须中文PHPDoc首行]
|
||||
required --> extra{array或throws等?}
|
||||
extra -->|是| addTags[补充结构或throws]
|
||||
```
|
||||
|
||||
## §8 Agent 自查增补
|
||||
|
||||
在 §8 增加 / 调整检查项(约 245 行后):
|
||||
|
||||
- Logic / Service / Model 的 `public` 方法是否均有**中文** PHPDoc(除纯 getter/setter)?
|
||||
- 是否存在「只有 `@param`/`@return` 类型、无中文业务说明」的 PHPDoc?
|
||||
|
||||
可将原「返回 array / 复杂数组 / throws」三条合并表述为「在通用要求之上是否满足附加结构」,避免清单过长。
|
||||
|
||||
## 不纳入本次范围
|
||||
|
||||
- **不**批量给存量 PHP 文件补 PHPDoc(体量大,另开重构任务)。
|
||||
- **不**强制 Controller / Validate / DTO 全量 PHPDoc(当前 §5 范围是 Logic/Service/Model;若需扩大可后续单独立项)。
|
||||
- **不**新增 CI 自动检测脚本(可选后续:`scripts/check-phpdoc.sh`)。
|
||||
|
||||
## 验收
|
||||
|
||||
- `php-clean-code.mdc` §5 不再出现「方法名够清晰可不写 PHPDoc」。
|
||||
- 新规则与 Model `@property` 小节无矛盾。
|
||||
- Agent 自查清单覆盖「中文 PHPDoc + getter/setter 豁免」。
|
||||
137
plans/startdto_去重与规范_edd8c0a8.plan.md
Normal file
137
plans/startdto_去重与规范_edd8c0a8.plan.md
Normal file
@@ -0,0 +1,137 @@
|
||||
---
|
||||
name: StartDTO 去重与规范
|
||||
overview: 收紧 `GameLaunchSessionStartDTO` 为仅组合两个子 DTO + accessor 派生字段,简化 `launchWithSession` 编排;并在 `php-clean-code.mdc` 增加「禁止编排层双份拷贝 / DTO 重复标量」规则。
|
||||
todos:
|
||||
- id: refactor-start-dto
|
||||
content: GameLaunchSessionStartDTO:仅 2 子 DTO + uid/providerCode/gameCode accessor
|
||||
status: completed
|
||||
- id: refactor-launch-logic
|
||||
content: 简化 launchWithSession;findReusableLaunch 收 StartDTO;createLaunchSession 用 accessor
|
||||
status: completed
|
||||
- id: update-rules-dto-dup
|
||||
content: php-clean-code §3 增加「编排上下文与聚合 DTO」;§8 与 agent-completion-gate 补充自查
|
||||
status: completed
|
||||
- id: verify-start-dto
|
||||
content: docker php -l + SLOT_ROOT verify-slot-backend.sh
|
||||
status: completed
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# StartDTO 去重重构 + 规范加固
|
||||
|
||||
## 问题
|
||||
|
||||
[`launchWithSession`](slot-pwa/app/api/logic/GameLaunchSessionLogic.php) 中:
|
||||
|
||||
```php
|
||||
$uid = (int) $gameLaunchDto->uid;
|
||||
$providerCode = $gameLaunchContext->providerCode;
|
||||
$gameCode = $gameLaunchDto->gameCode;
|
||||
// ...
|
||||
new GameLaunchSessionStartDTO($gameLaunchDto, $gameLaunchContext, $uid, $providerCode, $gameCode);
|
||||
```
|
||||
|
||||
[`GameLaunchSessionStartDTO`](slot-pwa/app/api/dto/GameLaunchSessionStartDTO.php) 同时持有 `gameLaunchDto`、`gameLaunchContext` **以及** 可从二者推导的 `uid` / `providerCode` / `gameCode`,属于 **编排层双份拷贝**,违反 DRY 与 [`php-clean-code.mdc`](/Users/ray/.cursor/rules/php-clean-code.mdc)「参数应表达业务需要、聚合 DTO 不重复字段」精神。
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph before [当前]
|
||||
DTO[GameLaunchDTO]
|
||||
CTX[GameLaunchContextDTO]
|
||||
locals[uid providerCode gameCode]
|
||||
Start[GameLaunchSessionStartDTO 5 fields]
|
||||
DTO --> locals
|
||||
CTX --> locals
|
||||
DTO --> Start
|
||||
CTX --> Start
|
||||
locals --> Start
|
||||
end
|
||||
```
|
||||
|
||||
## 一、代码重构
|
||||
|
||||
### 1. 收紧 `GameLaunchSessionStartDTO`
|
||||
|
||||
**文件:** [`slot-pwa/app/api/dto/GameLaunchSessionStartDTO.php`](slot-pwa/app/api/dto/GameLaunchSessionStartDTO.php)
|
||||
|
||||
- 构造函数仅保留:
|
||||
- `GameLaunchDTO $gameLaunchDto`
|
||||
- `GameLaunchContextDTO $gameLaunchContext`
|
||||
- 派生字段通过 **accessor** 提供(单一数据源):
|
||||
- `uid(): int` ← `(int) $gameLaunchDto->uid`
|
||||
- `providerCode(): string` ← `$gameLaunchContext->providerCode`
|
||||
- `gameCode(): string` ← `$gameLaunchDto->gameCode`
|
||||
|
||||
### 2. 简化 `launchWithSession`
|
||||
|
||||
**文件:** [`slot-pwa/app/api/logic/GameLaunchSessionLogic.php`](slot-pwa/app/api/logic/GameLaunchSessionLogic.php)
|
||||
|
||||
目标形态(可读性优先):
|
||||
|
||||
```php
|
||||
public function launchWithSession(GameLaunchDTO $gameLaunchDto): GameLaunchSessionResultDTO
|
||||
{
|
||||
$launchStart = new GameLaunchSessionStartDTO(
|
||||
$gameLaunchDto,
|
||||
$this->resolveGameLaunchContext($gameLaunchDto)
|
||||
);
|
||||
|
||||
$reusedLaunch = $this->findReusableLaunch($launchStart);
|
||||
if ($reusedLaunch !== null) {
|
||||
return $reusedLaunch;
|
||||
}
|
||||
|
||||
return $this->createLaunchSessionAndFetchUrl($launchStart);
|
||||
}
|
||||
```
|
||||
|
||||
- 删除 `$gameLaunchContext` / `$uid` / `$providerCode` / `$gameCode` 四个中间局部变量。
|
||||
|
||||
### 3. `findReusableLaunch` 收参为 StartDTO
|
||||
|
||||
将签名由 `(int $uid, string $providerCode, string $gameCode)` 改为:
|
||||
|
||||
```php
|
||||
private function findReusableLaunch(GameLaunchSessionStartDTO $launchStart): ?GameLaunchSessionResultDTO
|
||||
```
|
||||
|
||||
方法内使用 `$launchStart->uid()`、`providerCode()`、`gameCode()`,避免再次拆三个标量传递。
|
||||
|
||||
### 4. `createLaunchSessionAndFetchUrl` 统一 accessor
|
||||
|
||||
- `$launchStart->uid` 等改为 `$launchStart->uid()`(及 `providerCode()`、`gameCode()`)
|
||||
- Provider `launch()` 调用统一为 `$launchStart->uid()` + `$launchStart->gameLaunchContext->externalGameCode`(不再混用 `$launchStart->gameLaunchDto->uid`)
|
||||
|
||||
**改动范围:** 仅上述 2 个 PHP 文件,无 API / 路由变更。
|
||||
|
||||
## 二、用户级规范更新
|
||||
|
||||
### [`php-clean-code.mdc`](/Users/ray/.cursor/rules/php-clean-code.mdc)
|
||||
|
||||
在 **§3「参数与日志」** 后增加小节 **「编排上下文与聚合 DTO」**:
|
||||
|
||||
- **禁止**编排方法先把子 DTO/Context 字段拆成局部变量,再原样传入聚合 DTO(双份拷贝)。反例:`$uid = $dto->uid` 后 `new StartDTO($dto, $ctx, $uid, ...)`。
|
||||
- **禁止**聚合 DTO 构造函数同时接收「可从已有只读字段推导」的重复标量。反例:同时存 `gameLaunchDto` 与 `gameCode`。
|
||||
- 聚合上下文 DTO 应 **只组合** 子对象;派生值用 `uid()` / `providerCode()` 等 accessor 从子对象读取,或在用例方法内直接使用子 DTO 字段(二选一,不并存)。
|
||||
- 子步骤 private 方法若需多字段,优先传 **一个** 聚合 DTO,而不是再拆 3 个标量参数。
|
||||
|
||||
**§8 Agent 自查** 增加:
|
||||
|
||||
- 是否存在「局部变量 + 聚合 DTO」双份承载同一业务字段?
|
||||
- 聚合 DTO 是否包含可推导的重复标量?
|
||||
|
||||
### [`agent-completion-gate.mdc`](/Users/ray/.cursor/rules/agent-completion-gate.mdc)
|
||||
|
||||
在 Logic 自查条(现有第 5 条)中补充:**§3 编排上下文与聚合 DTO**。
|
||||
|
||||
## 三、验收
|
||||
|
||||
1. `launchWithSession` 无仅用于组 DTO 的 `$uid` / `$providerCode` / `$gameCode` 局部变量。
|
||||
2. `GameLaunchSessionStartDTO` 构造函数仅 2 个参数。
|
||||
3. `docker exec -w /app/www/ray/slot-pwa php82 php -l` 对相关文件通过。
|
||||
4. `SLOT_ROOT=/Users/ray/Documents/project/www/ray ~/.cursor/hooks/verify-slot-backend.sh` → `PASS`。
|
||||
|
||||
## 范围外
|
||||
|
||||
- 不改动 `GameLaunchContextDTO` / `GameLaunchSessionCreateParams` 结构。
|
||||
- 不修改 verify 脚本(仍依赖规则 + 自查)。
|
||||
184
plans/实现_launch_session_a28d3479.plan.md
Normal file
184
plans/实现_launch_session_a28d3479.plan.md
Normal file
@@ -0,0 +1,184 @@
|
||||
---
|
||||
name: 实现 Launch Session
|
||||
overview: 在 slot-pwa 中实现子需求 01:基于现有 GameLogic.launch 链路增加 game_launch_session 落库、Redis 短窗口防重复、Session 关闭接口与过期定时任务;本期跳过 game_gateway_control / 风险画像检查。
|
||||
todos:
|
||||
- id: ddl-launch-session
|
||||
content: 新增 slot-pwa/db/game_launch_session.sql(DDL 与需求一致)
|
||||
status: completed
|
||||
- id: model-launch-session
|
||||
content: 新增 GameLaunchSessionModel + 状态常量与查询/更新方法
|
||||
status: completed
|
||||
- id: logic-launch-session
|
||||
content: 新增 GameLaunchSessionLogic:Redis 去重、创建/关闭 Session、编排 Provider launch
|
||||
status: completed
|
||||
- id: api-routes
|
||||
content: 改造 GameController.launch 响应;新增 close 接口与 route/Validate/DTO
|
||||
status: completed
|
||||
- id: expire-command
|
||||
content: 新增 game:launch-session:expire 命令 + game.php TTL 配置
|
||||
status: completed
|
||||
- id: refactor-game-logic
|
||||
content: GameLogic 委托新 Logic;IdGenerator/RedisKey 扩展;跑 verify 脚本
|
||||
status: completed
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# 实现子需求 01:Launch Session(slot-pwa)
|
||||
|
||||
## 现状
|
||||
|
||||
- 需求文档:[01_launch_session.md](docs/requirements/game_gateway/01_launch_session.md)
|
||||
- 服务仓库:`slot-pwa`(PRD 中的 slot-pwa)
|
||||
- **已有**:[`GameController::launch`](slot-pwa/app/api/controller/GameController.php) → [`GameLogic::launch`](slot-pwa/app/api/logic/GameLogic.php) → 查 `g_game` / `g_platform` → 调 Provider `launch()` 返回 URL
|
||||
- **缺失**:`game_launch_session` 表、Model、Session 创建/关闭/过期、Redis 去重、`POST /slot-game/session/close` 路由
|
||||
- **本期不做**(你已确认):`game_gateway_control`、`game_user_risk_profile_01` 准入检查
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant GameController
|
||||
participant GameLaunchSessionLogic
|
||||
participant Redis
|
||||
participant DB as game_launch_session
|
||||
participant Provider
|
||||
|
||||
Client->>GameController: launch(gameCode)
|
||||
GameController->>GameLaunchSessionLogic: launchWithSession
|
||||
GameLaunchSessionLogic->>Redis: 短窗口去重
|
||||
alt 命中有效 Session
|
||||
GameLaunchSessionLogic-->>Client: 复用 launch_url 或重新拉 URL 策略见下
|
||||
else 新启动
|
||||
GameLaunchSessionLogic->>DB: INSERT status=1
|
||||
GameLaunchSessionLogic->>Provider: launch
|
||||
GameLaunchSessionLogic->>DB: 更新 launch_url_hash
|
||||
GameLaunchSessionLogic-->>Client: url + session_id
|
||||
end
|
||||
|
||||
Client->>GameController: session/close(session_id)
|
||||
GameController->>GameLaunchSessionLogic: closeSession
|
||||
GameLaunchSessionLogic->>DB: status=2, closed_at
|
||||
```
|
||||
|
||||
## 实现策略
|
||||
|
||||
### 1. 数据库迁移
|
||||
|
||||
新增 SQL:[slot-pwa/db/game_launch_session.sql](slot-pwa/db/game_launch_session.sql)
|
||||
|
||||
- DDL 与需求文档 **逐字一致**(`game_launch_session` 全字段 + 索引)
|
||||
- 部署:在 Docker MySQL 中执行(`goMysql` / 项目 `mysql` 连接)
|
||||
|
||||
### 2. Model 层
|
||||
|
||||
新建 [`slot-pwa/app/model/game/GameLaunchSessionModel.php`](slot-pwa/app/model/game/GameLaunchSessionModel.php)
|
||||
|
||||
- `connection = mysql`,`table = game_launch_session`
|
||||
- 状态常量:`STATUS_ACTIVE=1` … `STATUS_RISK_BLOCKED=6`(与 PRD 一致)
|
||||
- 查询方法(业务语义命名):
|
||||
- `findActiveBySessionId(string $sessionId)`
|
||||
- `findLatestActiveByUidProviderGame(int $uid, string $providerCode, string $gameCode)`(Redis 未命中时兜底)
|
||||
- `markClosed(int $id): void` / `markExpiredBatch(): int`(供定时任务)
|
||||
|
||||
### 3. Logic 层(核心)
|
||||
|
||||
新建 [`slot-pwa/app/api/logic/GameLaunchSessionLogic.php`](slot-pwa/app/api/logic/GameLaunchSessionLogic.php),从 [`GameLogic`](slot-pwa/app/api/logic/GameLogic.php) 抽离并编排:
|
||||
|
||||
| 步骤 | 说明 |
|
||||
|------|------|
|
||||
| 解析游戏 | 复用现有 `GGameModel` / `GGamePlatformMappingModel` / `GPlatformModel` 校验;`provider_code` = `GPlatformModel.code` |
|
||||
| Redis 去重 | Key:`game:launch:dedup:{uid}:{provider_code}:{game_code}`,TTL **8s**(需求 5~10s 取中值);Value:`session_id` |
|
||||
| 命中去重 | 查 DB 中 `status=1` 且未过期的 Session;**若存在且 Provider 允许**,直接返回已有 `session_id` + 不再请求 Provider(避免重复 LaunchURL);若 Session 无效则走新启动 |
|
||||
| 创建 Session | 生成 `session_id`、`launch_request_id`(`bin2hex(random_bytes(16))` 或类似 UUID 风格);`id` 复用 [`IdGenerator`](slot-pwa/app/service/IdGenerator.php) 模式新增 `nextLaunchSessionId()` |
|
||||
| 调 Provider | 调用现有 `BaseGameService::getGameService()->launch()` |
|
||||
| 写回 | `launch_url_hash = hash('sha256', $url)`;失败时 `status=4` + `remark` |
|
||||
| 关闭 | `closeSession(uid, session_id)`:校验归属 → `status=2`,`closed_at=now`;**不**调 wallet / round / 盈利统计 |
|
||||
|
||||
重构 [`GameLogic::launch`](slot-pwa/app/api/logic/GameLogic.php):
|
||||
|
||||
- 薄封装:委托 `GameLaunchSessionLogic::launchWithSession(GameLaunchDTO)`,保持 `game:launch` 命令兼容
|
||||
|
||||
### 4. API 层
|
||||
|
||||
**改造 Launch 响应**([`GameController::launch`](slot-pwa/app/api/controller/GameController.php)):
|
||||
|
||||
```php
|
||||
// 返回扩展示例
|
||||
['url' => $url, 'session_id' => $sessionId, 'launch_request_id' => $launchRequestId]
|
||||
```
|
||||
|
||||
**新增关闭接口**:
|
||||
|
||||
| 项 | 内容 |
|
||||
|----|------|
|
||||
| 路由 | `POST /slot-game/session/close`(写入 [`config/route.php`](slot-pwa/config/route.php)) |
|
||||
| Controller | 新建 `GameLaunchSessionController` 或扩展现有 `GameController` |
|
||||
| Validate | `session_id` require;`uid` 从 Auth 中间件 / 请求体(与现有 launch 一致用 body `uid`) |
|
||||
| DTO | `GameLaunchSessionCloseDTO` |
|
||||
|
||||
错误处理:将 `RuntimeException` 逐步改为 `Webman\Exception\BusinessException` + 明确错误码(与项目 exception handler 对齐);关键日志带 `uid`、`game_code`、`session_id`。
|
||||
|
||||
### 5. Redis Key 管理
|
||||
|
||||
在 [`ShareRedisKeyManagerService`](slot-pwa/app/service/ShareRedisKeyManagerService.php) 或 [`RedisKeyManagerService`](slot-pwa/app/service/RedisKeyManagerService.php) 增加:
|
||||
|
||||
```php
|
||||
getGameLaunchDedupKey(int $uid, string $providerCode, string $gameCode): string
|
||||
```
|
||||
|
||||
### 6. Session 过期任务
|
||||
|
||||
新建 Console 命令 [`slot-pwa/app/command/GameLaunchSessionExpire.php`](slot-pwa/app/command/GameLaunchSessionExpire.php):
|
||||
|
||||
- 名称:`game:launch-session:expire`
|
||||
- SQL 逻辑同需求文档:`status=1 AND expired_at < NOW(3)` → `status=3`
|
||||
- 可由 crontab / 运维定时 `docker exec ... php webman game:launch-session:expire`
|
||||
|
||||
配置项([`config/game.php`](slot-pwa/config/game.php) 或 `params.php`):
|
||||
|
||||
- `launch_session_ttl_seconds`(默认如 7200,写入 `expired_at`)
|
||||
- `launch_dedup_ttl_seconds`(默认 8)
|
||||
|
||||
### 7. 验收对照(子需求 §6)
|
||||
|
||||
| 验收项 | 实现方式 |
|
||||
|--------|----------|
|
||||
| session_id / launch_request_id 唯一 | DB UNIQUE + 生成逻辑 |
|
||||
| Redis 短窗口不重复 LaunchURL | dedup key + 复用 active session |
|
||||
| 退出 status=2,无资金副作用 | close 仅 UPDATE session |
|
||||
| 过期 status=3 | 定时命令 |
|
||||
|
||||
## 文件清单(新增/修改)
|
||||
|
||||
| 操作 | 路径 |
|
||||
|------|------|
|
||||
| 新增 | `slot-pwa/db/game_launch_session.sql` |
|
||||
| 新增 | `slot-pwa/app/model/game/GameLaunchSessionModel.php` |
|
||||
| 新增 | `slot-pwa/app/api/logic/GameLaunchSessionLogic.php` |
|
||||
| 新增 | `slot-pwa/app/api/controller/GameLaunchSessionController.php`(或扩展 GameController) |
|
||||
| 新增 | `slot-pwa/app/api/dto/GameLaunchSessionCloseDTO.php` |
|
||||
| 新增 | `slot-pwa/app/api/validate/GameLaunchSessionValidate.php` |
|
||||
| 新增 | `slot-pwa/app/command/GameLaunchSessionExpire.php` |
|
||||
| 修改 | `slot-pwa/app/api/logic/GameLogic.php` |
|
||||
| 修改 | `slot-pwa/app/api/controller/GameController.php` |
|
||||
| 修改 | `slot-pwa/config/route.php` |
|
||||
| 修改 | `slot-pwa/app/service/IdGenerator.php`(+ RedisKey 常量) |
|
||||
| 修改 | `slot-pwa/config/game.php`(TTL 配置) |
|
||||
|
||||
## 测试建议(实现后)
|
||||
|
||||
```bash
|
||||
# 建表
|
||||
docker exec -i goMysql mysql -uroot -p<pwd> <db> < slot-pwa/db/game_launch_session.sql
|
||||
|
||||
# Launch
|
||||
docker exec -w /app/www/slot/slot-pwa php82 php webman game:launch <uid> -c <gameCode>
|
||||
|
||||
# 过期
|
||||
docker exec -w /app/www/slot/slot-pwa php82 php webman game:launch-session:expire
|
||||
```
|
||||
|
||||
手工:连续两次 launch(8s 内)应复用 session;`POST /slot-game/session/close` 后 DB `status=2`。
|
||||
|
||||
## 完成门禁
|
||||
|
||||
实现 PHP 后执行 `~/.cursor/hooks/verify-slot-backend.sh`,最终回复粘贴完整输出。
|
||||
184
plans/拆分风控基础需求_85a83d2b.plan.md
Normal file
184
plans/拆分风控基础需求_85a83d2b.plan.md
Normal file
@@ -0,0 +1,184 @@
|
||||
---
|
||||
name: 拆分风控基础需求
|
||||
overview: 将 [game_gateway_risk_prd_v_2.md](docs/requirements/game_gateway_risk_prd_v_2.md) 中 3.1 第 1–4 项拆成 4 份可独立评审/开发的子需求文档,放在 `docs/requirements/game_gateway/` 下,并精简主文档为索引 + 交叉引用。
|
||||
todos:
|
||||
- id: create-subdir-readme
|
||||
content: 新建 docs/requirements/game_gateway/ 与 README.md(索引 + 依赖图 + 实施顺序)
|
||||
status: completed
|
||||
- id: doc-01-launch
|
||||
content: 编写 01_launch_session.md:迁移 §4.1/4.2、§8.1、3.1-12、相关验收
|
||||
status: completed
|
||||
- id: doc-02-callback
|
||||
content: 编写 02_provider_callback_log.md:迁移 §8.2、§5.1 相关行、callback 主链路步骤、§15.4-1
|
||||
status: completed
|
||||
- id: doc-03-tx
|
||||
content: 编写 03_provider_tx.md:迁移 §8.4、§7.2、§10、tx 流程片段、§15.1
|
||||
status: completed
|
||||
- id: doc-04-round
|
||||
content: 编写 04_game_round.md:迁移 §2.3、§4.4.1、§7.1、§8.3、round 流程、§15.2
|
||||
status: completed
|
||||
- id: slim-master-prd
|
||||
content: 精简 game_gateway_risk_prd_v_2.md:§3.1 改链接、删重复 DDL/章节、更新 §14/§15/§16
|
||||
status: completed
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# 拆分 Game Gateway 风控 PRD(3.1 项 1–4)
|
||||
|
||||
## 背景
|
||||
|
||||
主文档 [`docs/requirements/game_gateway_risk_prd_v_2.md`](docs/requirements/game_gateway_risk_prd_v_2.md) 第 3.1 节「第一期必须实现」前 4 项为**基础数据与链路能力**,彼此有依赖但可分期交付:
|
||||
|
||||
| 3.1 序号 | 能力 | 核心表 | 主文档现有章节 |
|
||||
|---------|------|--------|----------------|
|
||||
| 1 | 游戏启动 Session 记录 | `game_launch_session` | 4.1、4.2、8.1;3.1-12 |
|
||||
| 2 | Provider 原始回调日志 | `game_provider_callback_log_01` | 5.1(原始回调落库)、8.2 |
|
||||
| 3 | Provider Tx 幂等交易表 | `game_provider_tx_01` | 4.3/4.4(tx 部分)、7.2、8.4、10、15.1 |
|
||||
| 4 | Round 聚合状态表 | `game_round_01` | 2.3、4.3/4.4(round 部分)、4.4.1、7.1、8.3、15.2 |
|
||||
|
||||
当前 `docs/requirements/` 仅有这一份 PRD,需新建子目录承载拆分结果。
|
||||
|
||||
## 目标目录结构
|
||||
|
||||
```text
|
||||
docs/requirements/
|
||||
├── game_gateway_risk_prd_v_2.md # 总览 PRD(保留全局原则、5–16 章)
|
||||
└── game_gateway/
|
||||
├── README.md # 子需求索引与依赖关系
|
||||
├── 01_launch_session.md
|
||||
├── 02_provider_callback_log.md
|
||||
├── 03_provider_tx.md
|
||||
└── 04_game_round.md
|
||||
```
|
||||
|
||||
命名约定:`{序号}_{英文表名/能力}.md`,便于与建表、代码模块对齐。
|
||||
|
||||
## 子文档统一模板(每份独立可交付)
|
||||
|
||||
每份子需求包含以下固定章节(从主文档**迁移**而非重写业务含义):
|
||||
|
||||
1. **文档信息**:标题、版本、父文档链接、依赖子文档
|
||||
2. **目标与范围**:对应 3.1 单项 + In/Out of scope
|
||||
3. **依赖与边界**:slot-pwa / wallet / risk 职责(摘录主文档 §2 相关原则)
|
||||
4. **业务流程**:该能力专属流程图/步骤
|
||||
5. **状态定义**:仅该表相关状态(如 Round §7.1、Provider Tx §7.2)
|
||||
6. **数据表设计**:完整 DDL + 字段说明 + 索引/幂等约束
|
||||
7. **与主链路衔接**:在 BET/WIN 中的写入时机(引用主文档 §4,不重复全文)
|
||||
8. **验收标准**:从 §15 拆出与本能力相关的条目
|
||||
9. **第一期 Out of scope**:明确不属于本子需求的表/能力(交叉引用主文档 §3.2)
|
||||
|
||||
## 各子文档内容映射
|
||||
|
||||
### [`01_launch_session.md`](docs/requirements/game_gateway/01_launch_session.md)
|
||||
|
||||
**迁入内容:**
|
||||
|
||||
- §4.1 游戏启动 Launch 流程(含 `game_gateway_control` / `game_user_risk_profile_01` 检查表)
|
||||
- §4.2 用户退出流程 + 过期 SQL
|
||||
- §8.1 `game_launch_session` 完整 DDL
|
||||
- 3.1 第 12 条:退出只关 Session、不触发资金结算
|
||||
- 验收:§15.2 第 4 条(退出不触发 Round Final)
|
||||
|
||||
**接口提示(文档级,非实现):**
|
||||
|
||||
- Launch 创建 Session、`POST /slot-game/session/close`
|
||||
- Redis 短窗口防重复 Launch(`uid + provider_code + game_code + 5~10s`)
|
||||
|
||||
**依赖说明:** 不依赖 callback_log / provider_tx / round;可被后续回调通过 `session_id` 关联(若主链路需要,在子文档中写「可选关联字段」占位,不扩表)。
|
||||
|
||||
---
|
||||
|
||||
### [`02_provider_callback_log.md`](docs/requirements/game_gateway/02_provider_callback_log.md)
|
||||
|
||||
**迁入内容:**
|
||||
|
||||
- §8.2 `game_provider_callback_log_01` 完整 DDL + 「不加唯一键、幂等由 provider_tx 负责」说明
|
||||
- §5.1 表中「原始回调落库」「验签 / IP 校验」「参数解析」三行及原因
|
||||
- 主链路第一步:`Provider * callback → 同步写 callback_log`(从 §4.3、§4.4 摘取,各类型回调通用)
|
||||
- BALANCE 只记 callback_log、不进 provider_tx(§8.4 说明)
|
||||
- 验收:§15.4 第 1 条(资金交易可追溯到原始回调)
|
||||
|
||||
**依赖:** 被 `03_provider_tx` 通过 `callback_log_id` 引用。
|
||||
|
||||
---
|
||||
|
||||
### [`03_provider_tx.md`](docs/requirements/game_gateway/03_provider_tx.md)
|
||||
|
||||
**迁入内容:**
|
||||
|
||||
- §8.4 `game_provider_tx_01` 完整 DDL + 幂等键 `provider_code + provider_tx_id + tx_type`
|
||||
- §7.2 Provider Tx 状态
|
||||
- §4.3 / §4.4 中与 `game_provider_tx_01` 相关的步骤(创建/查询、状态更新、BET 成功后 status)
|
||||
- §10 wallet 超时与补偿(全文;与 tx 强绑定)
|
||||
- §3.2 不建 `game_wallet_call_log_01` 的原因(wallet 结果记在 provider_tx)
|
||||
- 验收:§15.1 全部 + §15.4 第 2 条
|
||||
|
||||
**依赖:** 必须先有 `02`(`callback_log_id`);与 `04` 并行更新 round。
|
||||
|
||||
---
|
||||
|
||||
### [`04_game_round.md`](docs/requirements/game_gateway/04_game_round.md)
|
||||
|
||||
**迁入内容:**
|
||||
|
||||
- §2.3 Round Final 才能做最终任务结算(原则)
|
||||
- §4.4.1 Round 结算模式(`AGGREGATE_FINAL` / `TX_REALTIME_FINALIZE` + wallet 调用类型)
|
||||
- §7.1 Round 状态
|
||||
- §8.3 `game_round_01` 完整 DDL
|
||||
- §4.3 BET 成功后 round 字段更新;§4.4 WIN/FINAL 后 round 更新(摘取)
|
||||
- §9 中 `game_round_final_settled_event` 事件说明(仅 round final 相关段落)
|
||||
- 验收:§15.2 全部
|
||||
|
||||
**依赖:** 与 `03` 在回调主链路中协同;不重复写 wallet 补偿细节(链接 `03`)。
|
||||
|
||||
---
|
||||
|
||||
### [`README.md`](docs/requirements/game_gateway/README.md)
|
||||
|
||||
简短索引 + 依赖图:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
launch[01_launch_session]
|
||||
callback[02_provider_callback_log]
|
||||
tx[03_provider_tx]
|
||||
round[04_game_round]
|
||||
callback --> tx
|
||||
tx --> round
|
||||
launch -.->|Launch准入| callback
|
||||
```
|
||||
|
||||
建议实施顺序:`02 → 03 → 04` 可同迭代;`01` 可与 `02` 并行。
|
||||
|
||||
## 主文档 [`game_gateway_risk_prd_v_2.md`](docs/requirements/game_gateway_risk_prd_v_2.md) 调整
|
||||
|
||||
1. **§3.1 第 1–4 条**改为链接列表,例如:
|
||||
- `1. [游戏启动 Session](game_gateway/01_launch_session.md)`
|
||||
- …
|
||||
2. **删除或收缩**已迁入子文档的重复正文:
|
||||
- §4.1、§4.2 → 保留 1 段摘要 + 链接
|
||||
- §8.1–§8.4 → 改为表名清单 + 链接(DDL 只在子文档保留一份)
|
||||
- §7.1、§7.2 → 移至子文档;主文档 §7 仅保留 §7.3 熔断状态(属 3.1-5)
|
||||
- §10 → 移至 `03`;主文档保留一句「见 provider_tx 子需求」
|
||||
- §15.1、§15.2 部分条目 → 子文档各持一份;主文档 §15 保留汇总 checklist 并链接子文档
|
||||
3. **§4.3、§4.4** 保留端到端主链路(便于理解全局),但表字段/SQL/状态机细节改为「详见子需求」引用,避免双份维护。
|
||||
4. **§14 建表清单**:前 4 张表标注对应子文档路径。
|
||||
5. **§16 总结**:保留一句话架构;补充子需求目录说明。
|
||||
|
||||
**不拆分范围(仍留主文档):** 3.1 第 5–12 项及 §8.5 以后、§11–§13、熔断/RTP/风险画像/MQ 等。
|
||||
|
||||
## 交叉引用规范
|
||||
|
||||
- 子文档文首:`> 父文档:[Game Gateway 游戏风控需求 V1.0](../game_gateway_risk_prd_v_2.md)`
|
||||
- 主文档与子文档互相使用相对路径 Markdown 链接
|
||||
- 涉及 wallet/risk 的段落写「行为以父文档 §2 为准」,不在 4 份子文档重复整章原则
|
||||
|
||||
## 质量检查
|
||||
|
||||
- 4 份子文档均含完整 DDL,与当前主文档 §8 SQL **逐字一致**(仅拆位置,不改语义)
|
||||
- 主文档通读无断链;§3.1 1–4 在子文档有对应验收标准
|
||||
- `README.md` 列出推荐开发顺序与表依赖
|
||||
|
||||
## 工作量说明
|
||||
|
||||
纯文档重构,不涉及代码与迁移脚本;预计 4 份子文档 + 1 索引 + 主文档瘦身,约 1 次集中编辑即可完成。
|
||||
101
plans/首充定格已解锁列_3c8b7cb9.plan.md
Normal file
101
plans/首充定格已解锁列_3c8b7cb9.plan.md
Normal file
@@ -0,0 +1,101 @@
|
||||
---
|
||||
name: 首充定格已解锁列
|
||||
overview: 在 Free Credits 统计(首充定格)列表页增加「已解锁金额」列;后端 `claimed_amount_qf` 已按第二档及以后 release 档已完成金额聚合,主要改 slot_admin_vue 展示即可。
|
||||
todos:
|
||||
- id: vue-column
|
||||
content: 在 freeCreditsStats/index.vue 增加 claimed_amount_qf 列(标题:已解锁金额)及金额展示模板
|
||||
status: completed
|
||||
- id: verify-ui
|
||||
content: 本地打开页面核对列值与 remaining_amount_qf 公式;跑 verify-slot-backend.sh
|
||||
status: completed
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# 首充定格列表增加「已解锁金额」列
|
||||
|
||||
## 背景
|
||||
|
||||
目标页面为 **Free Credits 统计**(菜单:活动管理 → Free Credits 统计):
|
||||
|
||||
- 前端:[slot_admin_vue/src/views/game/freeCreditsStats/index.vue](backend/slot_admin_vue/src/views/game/freeCreditsStats/index.vue)
|
||||
- 后端:[slot_admin/app/game/controller/FreeCreditsStatsController.php](backend/slot_admin/app/game/controller/FreeCreditsStatsController.php) → [FreeCreditsStatsLogic](backend/slot_admin/app/game/logic/FreeCreditsStatsLogic.php)
|
||||
|
||||
需求文档 §18.2 中对应字段为 **「已领取金额」**(后续已 Claim 金额);产品侧列名为 **「已解锁金额」**,语义一致:**第二档(`package_type=release`)起用户已成功领取的金额**,不含第一档免打码提现。
|
||||
|
||||
## 现状(无需重复造数)
|
||||
|
||||
后端 `list()` 已在每行返回 `claimed_amount_qf`,聚合逻辑在 `aggregatePackages()`:
|
||||
|
||||
```231:232:backend/slot_admin/app/game/logic/FreeCreditsStatsLogic.php
|
||||
SUM(CASE WHEN package_type = {$typeFirstCash} AND status = {$statusCompleted} THEN amount_qf ELSE 0 END) AS first_cashout_amount_qf,
|
||||
SUM(CASE WHEN package_type = {$typeRelease} AND status = {$statusCompleted} THEN amount_qf ELSE 0 END) AS claimed_amount_qf,
|
||||
```
|
||||
|
||||
- `TYPE_FIRST_CASH = 1`:第一档提现
|
||||
- `TYPE_RELEASE = 2`:第二档及以后 Claim
|
||||
- 仅 `status = STATUS_COMPLETED` 计入
|
||||
|
||||
`remaining_amount_qf` 已用该字段参与计算:`frozen - first_cashout - claimed`。
|
||||
|
||||
顶部汇总区已有「已领取金额」展示 `stats.claimed_amount_qf`,与行字段同源。
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph api [GET /game/freeCreditsStats/index]
|
||||
list[list.data]
|
||||
stats[otherData.statistics]
|
||||
end
|
||||
subgraph agg [aggregatePackages]
|
||||
release["SUM release completed amount_qf"]
|
||||
end
|
||||
release --> claimed["claimed_amount_qf per row"]
|
||||
claimed --> list
|
||||
release --> stats
|
||||
```
|
||||
|
||||
## 实现方案
|
||||
|
||||
### 1. slot_admin_vue(主要改动)
|
||||
|
||||
文件:[index.vue](backend/slot_admin_vue/src/views/game/freeCreditsStats/index.vue)
|
||||
|
||||
在 `columns` 中 **「完成进度」与「剩余未释放金额」之间** 插入一列:
|
||||
|
||||
| 列标题 | dataIndex | 说明 |
|
||||
|--------|-----------|------|
|
||||
| 已解锁金额 | `claimed_amount_qf` | 千分位转美元,与定格金额等列一致 |
|
||||
|
||||
新增 slot 模板(复用现有 `qfToDollar`):
|
||||
|
||||
```vue
|
||||
<template #claimed_amount_qf="{ record }">
|
||||
${{ qfToDollar(record.claimed_amount_qf) }}
|
||||
</template>
|
||||
```
|
||||
|
||||
无需改 [freeCreditsStats.js](backend/slot_admin_vue/src/api/game/freeCreditsStats.js) API 封装。
|
||||
|
||||
### 2. slot_admin 后端(可选 / 建议不改)
|
||||
|
||||
[FreeCreditsStatsLogic.php](backend/slot_admin/app/game/logic/FreeCreditsStatsLogic.php) **已满足需求**,无需新增接口或 SQL。
|
||||
|
||||
若希望 API 字段名与列标题一致,可额外返回别名 `unlocked_amount_qf`(与 `claimed_amount_qf` 同值),但会增加前后端两套字段,**不建议**,前端直接用 `claimed_amount_qf` 即可。
|
||||
|
||||
### 3. 命名对齐(可选)
|
||||
|
||||
- 列表列名:**已解锁金额**(按产品要求)
|
||||
- 顶部汇总仍为「已领取金额」;与需求文档 §18.1 一致。若产品要求顶部也改名,可一并改为「已解锁金额」,本次可只做列表列。
|
||||
|
||||
## 验证
|
||||
|
||||
1. 打开 Free Credits 统计页,确认新列有值;未领取第二档的用户为 `$0.00`。
|
||||
2. 抽查:某用户第二档及以后 `free_credits_package` 中 `package_type=2` 且 `status=3` 的 `amount_qf` 之和 = 列表「已解锁金额」× 1000。
|
||||
3. 确认「剩余未释放金额」= 定格金额 − 第一档已提现 − 已解锁金额(与现有公式一致)。
|
||||
4. 执行 `~/.cursor/hooks/verify-slot-backend.sh`(若仅改 Vue,预期 `PASS (no changed files)` 或仅前端相关检查)。
|
||||
|
||||
## 改动范围小结
|
||||
|
||||
| 仓库 | 文件 | 改动 |
|
||||
|------|------|------|
|
||||
| slot_admin_vue | `src/views/game/freeCreditsStats/index.vue` | 新增列 + 金额模板 |
|
||||
| slot_admin | — | 无(数据已返回) |
|
||||
Reference in New Issue
Block a user