Files
cursor/plans/实现_launch_session_a28d3479.plan.md
ray zhou 1bcb6120dd ok
2026-05-29 17:23:17 +08:00

185 lines
8.3 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: 实现 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`,最终回复粘贴完整输出。