Files
cursor/plans/provider_callback_log_308e4907.plan.md
ray zhou ad623aad91 ok
2026-05-29 18:16:34 +08:00

245 lines
11 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.

---
name: Provider Callback Log
overview: 在 slot-pwa 按分库分表(对齐 GameResultLogModel落地 game_provider_callback_log_{NN},并实现 POP 回调全链路(收包落库 → 验签/解析/处理状态)。其余厂商后续 PR 扩展。
todos:
- id: shard-ddl-model
content: CreateSharedTable 增加 callback_log 分表 DDLshard/GameProviderCallbackLogModel + Params + IdGenerator
status: completed
- id: callback-service
content: ProviderCallbackLogService按 shardUid 路由)+ config/game_gateway.php
status: completed
- id: pop-middleware
content: ProviderCallbackLogMiddleware + pop 中间件链 + POPAuthMiddleware 验签回写
status: completed
- id: pop-logic
content: CashLogic/CashController parse/process 状态(含重复 tid、BALANCE
status: completed
- id: tests
content: Feature 测试shard 写入/更新)+ docker phpunitverify-slot-backend.sh
status: completed
isProject: false
---
# Provider 原始回调日志02实现计划
## 范围(已确认)
- **本期**:分库分表 DDL + Shard Model/Service + POP 两条路由接入 + PHPUnit
- **后续 PR**gasea / quick / jsgame / bbgt / jdb
**明确不做**(属 [03_provider_tx.md](docs/requirements/game_gateway/03_provider_tx.md)`game_provider_tx_01`、wallet 改造、Round 聚合。
**文档与 DDL**:已与 [02 §4.1](docs/requirements/game_gateway/02_provider_callback_log.md)、[`slot-pwa/db/game_provider_callback_log_shard.sql`](slot-pwa/db/game_provider_callback_log_shard.sql) 对齐(无 `gateway_version`;含分库分表说明)。
---
## 现状与约束
| 项 | 现状 |
|---|---|
| POP 路由 | [`config/route.php`](slot-pwa/config/route.php) `/pop/Cash/Get``/pop/Cash/TransferInOut` |
| POP 验签 | [`POPAuthMiddleware`](slot-pwa/app/middleware/POPAuthMiddleware.php) 验签在 Controller 前 |
| **分表参考(本期主参考)** | [`app/model/shard/GameResultLogModel.php`](slot-pwa/app/model/shard/GameResultLogModel.php) + [`CreateSharedTable`](slot-pwa/app/command/CreateSharedTable.php) |
| 非分表参考 | [`GameLaunchSessionModel`](slot-pwa/app/model/game/GameLaunchSessionModel.php) 仅用于 Launch Session**不**用于 callback_log |
| ID 生成 | [`IdGenerator::nextGameResultId()`](slot-pwa/app/service/IdGenerator.php) 同款 Redis 序号 + 毫秒时间戳 |
需求文档表名 `game_provider_callback_log_01` 表示**逻辑表前缀**;物理表为 `user_transaction_{dbNN}.game_provider_callback_log_{tableNN}`(与 `game_result_log_{NN}` 同库族)。
---
## 分库分表设计
### 路由规则(对齐 GameResultLog
```php
// GameResultLogModel
getPreDatabase() => 'user_transaction'
getPreTable() => 'game_provider_callback_log' // 物理表 game_provider_callback_log_01..NN
getShardKey() => 'uid'
```
- 构造:`new GameProviderCallbackLogModel($shardUid)`,由 [`ShardService`](slot-pwa/app/service/ShardService.php) `crc32($shardUid) % (库数×表数)` 定位库表。
- 写入:[`insertData()`](slot-pwa/app/model/shard/SelfBaseModel.php)(同 `TransactionLog::insertGameResult`)。
- 更新:[`updateByWhere()`](slot-pwa/app/model/shard/SelfBaseModel.php),条件必须含 `id` + `uid`(分片键),例如 `['id' => $id, 'uid' => $shardUid]`
### 分片键 `uid` 的选取(收包时)
| 场景 | shardUid |
|------|----------|
| POP 已带 `userid` | `(int) post.userid` |
| 尚未解析 / 验签失败但 body 无 uid | `0`(需求允许 `uid=0`,仍落同一分片桶,保留审计) |
| 解析成功后 uid 与收包时不一致 | **不迁移分片**;仅 `UPDATE` 修正行内 `uid` 等业务字段(行已在收包分片,避免跨库搬家) |
**Request 上下文**Middleware 写入Logic/Middleware 读取):
- `providerCallbackLogId` — 雪花主键
- `providerCallbackShardUid` — 分片键,后续所有 UPDATE 必带
### 与 `GameResultLog` 的差异
| 项 | GameResultLog | callback_log |
|----|---------------|----------------|
| 汇总库 `s_all.*` | 有 [`app/model/all/GameResultLogModel`](slot-pwa/app/model/all/GameResultLogModel.php) + MQ 同步 | **本期不做**(无跨分片报表需求;后续若要全局检索再加 DBSync |
| 字段 | 精简结果字段 | 需求全文raw_body/headers、验签/解析/处理状态等 |
---
## 处理顺序
```mermaid
sequenceDiagram
participant Provider
participant LogMW as ProviderCallbackLogMiddleware
participant PopAuth as POPAuthMiddleware
participant Logic as CashLogic
participant Shard as user_transaction_XX.game_provider_callback_log_YY
Provider->>LogMW: POST
LogMW->>LogMW: resolveShardUid from post
LogMW->>Shard: INSERT id=nextProviderCallbackLogId
LogMW->>PopAuth: request + logId + shardUid
PopAuth->>Shard: UPDATE verify_status
PopAuth->>Logic: handler
Logic->>Shard: UPDATE parse/process
```
---
## 1. 数据库(分表 DDL
### 修改 [`CreateSharedTable`](slot-pwa/app/command/CreateSharedTable.php)
`createShareTable()` 循环内、`createGameResultTable()` 之后增加 `createGameProviderCallbackLogTable($db, $num)`
- 库:`user_transaction_01``user_transaction_{share_database_num}`
- 表:`game_provider_callback_log_01``game_provider_callback_log_{share_table_num}`
- 字段/索引以 [02 §4.1](docs/requirements/game_gateway/02_provider_callback_log.md) 为准,**除外**:不建 `gateway_version`、不建 `idx_gateway_version`;其余 KEY`idx_provider_tx``idx_round``idx_uid_received``idx_body_hash``idx_process_status`)保留
- 主键 `id` **非自增**,应用写入 `IdGenerator::nextProviderCallbackLogId()`
### 参考 SQL文档用非单库执行
新增 [`slot-pwa/db/game_provider_callback_log_shard.sql`](slot-pwa/db/game_provider_callback_log_shard.sql):单分片模板 + 注释说明需通过 `php webman createSharedTable`(或项目既有命令)批量建表。
部署:`docker exec ... php webman createSharedTable`(与现有 `user_transaction_log` / `game_result_log` 相同流程)。
---
## 2. 配置
[`config/game_gateway.php`](slot-pwa/config/game_gateway.php)
| 配置项 | 用途 |
|--------|------|
| `callback_request_id_prefix` | 如 `creq_` |
| `route_callback_map` | 路径 → 默认 `callback_type` |
| `provider_shard_uid_field` | 厂商 → POST 分片字段POP`userid` |
[`RedisKeyManagerService`](slot-pwa/app/service/RedisKeyManagerService.php)`PROVIDER_CALLBACK_LOG_NEXT_ID`
[`IdGenerator`](slot-pwa/app/service/IdGenerator.php)`nextProviderCallbackLogId()`(对齐 `nextGameResultId`
---
## 3. 领域层
### Shard Model — [`app/model/shard/GameProviderCallbackLogModel.php`](slot-pwa/app/model/shard/GameProviderCallbackLogModel.php)
继承 [`BaseShardModel`](slot-pwa/app/model/shard/BaseShardModel.php)
```php
protected function getPreDatabase(): string { return 'user_transaction'; }
protected function getPreTable(): string { return 'game_provider_callback_log'; }
protected function getShardKey(): string { return 'uid'; }
```
- 状态常量:`VERIFY_*` / `PARSE_*` / `PROCESS_*`
- 数据访问(均需构造时传入 `$shardUid`
- `insertReceived(GameProviderCallbackLogReceiveParams $params): void`(内部 `insertData`
- `updateVerifyStatus(int $id, ...)`
- `updateParseResult(int $id, GameProviderCallbackLogParseParams $params)`
- `updateProcessStatus(int $id, ...)`
- `findById(int $id): ?array``findOne(['id' => $id, 'uid' => $this->_shardVal])`
### Value Objects可放 `app/model/game/` 或 `app/entity/game/`
- `GameProviderCallbackLogReceiveParams` — 含 `shardUid`、落库字段
- `GameProviderCallbackLogParseParams` — 解析后业务字段
### Service — [`app/service/game/ProviderCallbackLogService.php`](slot-pwa/app/service/game/ProviderCallbackLogService.php)
- `recordReceivedFromRequest(Request $request): void` — 解析 `shardUid`、生成 `id`/`request_id`、**同步** `new GameProviderCallbackLogModel($shardUid)->insertReceived(...)`,设置 `$request->providerCallbackLogId` / `providerCallbackShardUid`
- `markVerify*(int $shardUid, int $id, ...)`
- `markParse*(int $shardUid, int $id, ...)`
- `markProcess*(int $shardUid, int $id, ...)`
所有 mark 方法内部统一:`new GameProviderCallbackLogModel($shardUid)->updateByWhere(['id' => $id, 'uid' => $shardUid], ...)`
---
## 4. MiddlewarePOP 本期)
- 新增 [`ProviderCallbackLogMiddleware`](slot-pwa/app/middleware/ProviderCallbackLogMiddleware.php) — **最先**执行
- [`config/middleware.php`](slot-pwa/config/middleware.php) `'pop' => [ProviderCallbackLogMiddleware, POPAuthMiddleware]`
- [`POPAuthMiddleware`](slot-pwa/app/middleware/POPAuthMiddleware.php):验签结果写回 callback_log失败 `VERIFY_FAILED` + `PROCESS_REJECTED`
- `/pop/admin/` 跳过落库
---
## 5. POP Logic
[`CashLogic`](slot-pwa/app/pop/logic/CashLogic.php) / [`CashController`](slot-pwa/app/pop/controller/CashController.php)
-`$request` 读取 `providerCallbackLogId` + `providerCallbackShardUid`
- `balance` / `modifyTransferInOut`parse/process 状态;重复 tid → `PROCESS_DUPLICATE`
- Validate 失败:`PARSE_FAILED` + `PROCESS_REJECTED`(保留已落库的 raw 证据)
---
## 6. 测试
参照分表写入方式(非 LaunchSession 单表):
- [`tests/Support/ProviderCallbackLogTestCase.php`](slot-pwa/tests/Support/ProviderCallbackLogTestCase.php) — 固定测试 `shardUid``insertReceived` + teardown `delete`
- [`tests/Feature/GameProviderCallbackLogShardModelTest.php`](slot-pwa/tests/Feature/GameProviderCallbackLogShardModelTest.php)
- 初始 `verify_status=0``process_status=0`
- `updateByWhere` 后终态、`processed_at` 写入
-`body_hash`、同/不同 `shardUid` 可各插一条
`docker exec -w /app/www/slot/slot-pwa php82 ./vendor/bin/phpunit`
---
## 7. 验收对照POP + 分表)
| 验收项 | 实现 |
|--------|------|
| 原始请求可追溯 | `raw_body` / `raw_headers` 写入对应分片表 |
| 验签/解析失败仍有行 | Middleware 先 INSERTshardUid 可为 0 |
| BALANCE 仅 callback_log | 无 provider_tx |
| 重复回调多条 log | 每次 INSERTtid 重复 → `process_status=3` |
| 分库分表 | 与 GameResultLog 相同 `user_transaction_*` 族 |
---
## 8. 后续 PR
- 其它厂商 Middleware + `provider_shard_uid_field`
- 可选:`app/model/all/GameProviderCallbackLogModel` + DBSync仅当需要 `s_all` 汇总检索时)
---
## 关键文件
| 操作 | 路径 |
|------|------|
| 修改 | `app/command/CreateSharedTable.php` |
| 新增 | `db/game_provider_callback_log_shard.sql`(模板) |
| 新增 | `app/model/shard/GameProviderCallbackLogModel.php` |
| 新增 | `app/model/game/GameProviderCallbackLog*Params.php` |
| 新增 | `app/service/game/ProviderCallbackLogService.php` |
| 新增 | `app/middleware/ProviderCallbackLogMiddleware.php` |
| 修改 | `IdGenerator.php`, `RedisKeyManagerService.php`, `middleware.php`, `POPAuthMiddleware.php` |
| 修改 | `app/pop/logic/CashLogic.php`, `app/pop/controller/CashController.php` |
| 新增 | `tests/Feature/GameProviderCallbackLogShardModelTest.php` |
完成编码后执行 `~/.cursor/hooks/verify-slot-backend.sh` 并粘贴输出。