# game-session-service 游戏会话服务

## 服务定位

游戏会话服务负责玩家进入游戏后的 token、会话状态、过期时间和踢线控制，是商户 API 登录和游戏侧请求之间的桥。

第一阶段实现已具备可运行 HTTP 服务，使用内存 repository 模拟 Redis TTL 与幂等索引。该服务只负责玩家游戏在线状态，不扣款、不结算、不计算 RTP。

## 核心职责

- 创建玩家游戏 session。
- 校验 session 有效期和商户绑定关系。
- 管理断线重连和踢线状态。
- 为 game-gateway 提供会话校验能力。

## 明确边界

- 负责：`session_id`、`session_token`、`merchant_id`、`player_id`、`game_code`、`provider`、`currency`、`ttl_seconds`、`last_seen_at`、`expires_at`、`ACTIVE/EXPIRED/CLOSED` 状态、关闭原因。
- 不负责：钱包扣款、钱包派彩、注单结算、RTP 计算、游戏结果生成。
- 创建幂等：同一 `merchant_id + idempotency_key` 和相同会话作用域重复创建，返回原会话；同 key 不同玩家/游戏/币种视为冲突。
- TTL 规则：创建时 `expires_at = now + ttl_seconds`；heartbeat 时刷新 `last_seen_at`，并将 `expires_at` 延长为 `now + ttl_seconds`；查询/心跳发现超时会落为 `EXPIRED`。

## 禁止事项

- 不能记录资金流水。
- 不能生成游戏结果。
- 不能绕过商户签名创建 session。

## 本地命令

```bash
cd /Users/amumu/Desktop/beifen/golang新架构/游戏服务/game-session-service
go test ./...
go build ./cmd/game-session-service
PORT=8092 go run ./cmd/game-session-service
```

## 健康检查

```bash
curl http://127.0.0.1:8092/health
curl http://127.0.0.1:8092/ready
```

## HTTP 接口

### 创建会话

```bash
curl -X POST http://127.0.0.1:8092/sessions \
  -H 'Content-Type: application/json' \
  -H 'X-Request-Id: req_001' \
  -d '{
    "trace_id": "trace_001",
    "merchant_id": "DEMO",
    "player_id": "player_001",
    "game_code": "demo-slots",
    "provider": "demo-provider",
    "currency": "CNY",
    "ttl_seconds": 300,
    "idempotency_key": "login_20260606_001"
  }'
```

返回 `data.session_id`、`data.session_token`、`data.status=ACTIVE`、`data.expires_at`。重复请求命中幂等时，`data.code=IDEMPOTENT_HIT`，会话 ID 和 token 不变。

### 会话心跳

```bash
curl -X POST http://127.0.0.1:8092/sessions/{session_id}/heartbeat \
  -H 'X-Request-Id: req_heartbeat_001'
```

只允许 `ACTIVE` 会话续期。成功后刷新 `last_seen_at`，并按会话自己的 `ttl_seconds` 重算 `expires_at`。

### 关闭会话

```bash
curl -X POST http://127.0.0.1:8092/sessions/{session_id}/close \
  -H 'Content-Type: application/json' \
  -d '{"reason":"PLAYER_EXIT"}'
```

首次关闭将状态置为 `CLOSED`，写入 `close_reason` 与 `closed_at`。重复关闭返回同一终态，视为幂等命中。

### 查询会话

```bash
curl http://127.0.0.1:8092/sessions/{session_id}
```

查询时如果当前时间已超过 `expires_at`，服务会将 `ACTIVE` 会话落为 `EXPIRED`，`close_reason=TTL_EXPIRED`。

## Redis 数据结构建议

第一阶段代码使用 `internal/repository/redis.MemorySessionRepository` 模拟，后续接 Redis 时建议保持以下 key：

```text
game-session:session:{session_id}
  type: HASH / JSON
  ttl: ttl_seconds + grace_seconds
  fields: session_id, session_token, merchant_id, player_id, game_code, provider,
          currency, status, created_at, last_seen_at, expires_at, closed_at, close_reason

game-session:token:{session_token}
  type: STRING
  value: session_id
  ttl: 与 session key 对齐

game-session:idempotency:{merchant_id}:{idempotency_key}
  type: STRING
  value: session_id
  ttl: 与 session key 对齐，或按登录幂等保留窗口配置

game-session:player-active:{merchant_id}:{player_id}
  type: SET / ZSET
  value: session_id
  score: expires_at unix seconds
```

状态更新建议使用 Lua 或事务保证 `session`、`token`、`idempotency` 索引一致。heartbeat 只延长 `ACTIVE` 会话 TTL；`CLOSED/EXPIRED` 不允许重新激活。

## 与周边服务关系

- `game-gateway`：登录或游戏请求入口拿到 `session_token` 后，应调用本服务查询/校验会话，按 `merchant_id/player_id/game_code/provider/currency` 做上下文绑定。gateway 可以在玩家持续游戏时周期性调用 heartbeat。
- `game-router`：只关心游戏路由和 provider 选择，不直接修改 session 状态；创建会话时可把路由结果里的 `provider` 写入本服务。
- `presence-service`：负责更宽泛的用户在线/离线与多端状态；本服务只表达“玩家在某个游戏内的会话是否有效”。后续可由本服务在创建、heartbeat、close、expire 时发在线状态事件给 presence。
