ok
This commit is contained in:
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`,最终回复粘贴完整输出。
|
||||
Reference in New Issue
Block a user