This commit is contained in:
ray zhou
2026-05-29 17:23:17 +08:00
parent f71a5c59af
commit 1bcb6120dd
139 changed files with 4229 additions and 523 deletions

View 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.sqlDDL 与需求一致)
status: completed
- id: model-launch-session
content: 新增 GameLaunchSessionModel + 状态常量与查询/更新方法
status: completed
- id: logic-launch-session
content: 新增 GameLaunchSessionLogicRedis 去重、创建/关闭 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 委托新 LogicIdGenerator/RedisKey 扩展;跑 verify 脚本
status: completed
isProject: false
---
# 实现子需求 01Launch Sessionslot-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
```
手工:连续两次 launch8s 内)应复用 session`POST /slot-game/session/close` 后 DB `status=2`
## 完成门禁
实现 PHP 后执行 `~/.cursor/hooks/verify-slot-backend.sh`,最终回复粘贴完整输出。