Add media proxy feature for resource downloading
- Introduced a new API endpoint `GET /api/v1/video/proxy` to facilitate media resource downloads, allowing users to bypass domain restrictions imposed by WeChat. - Updated configuration to include proxy settings such as `SHORT_VIDEO_PROXY_ENABLED`, `SHORT_VIDEO_PROXY_ALLOWED_DOMAINS`, `SHORT_VIDEO_PROXY_MAX_SIZE_MB`, and `SHORT_VIDEO_PROXY_TIMEOUT_SECONDS`. - Enhanced the `ShortVideoConfig` struct to accommodate new proxy-related fields. - Improved error handling for proxy requests, including checks for allowed domains and file size limits. - Updated documentation to reflect the new proxy functionality and its configuration options, ensuring clarity for users and developers.
This commit is contained in:
@@ -0,0 +1,700 @@
|
||||
# 炸弹传递小游戏 - 产品需求文档 (PRD)
|
||||
|
||||
## 1. 产品概述
|
||||
|
||||
### 1.1 产品定位
|
||||
一款基于微信小程序的多人实时互动小游戏。玩家通过邀请好友进入房间,在倒计时内通过摇晃手机传递"炸弹",炸弹爆炸时持有者即为输家。玩法简单刺激,适合朋友间聚会娱乐。
|
||||
|
||||
### 1.2 核心价值
|
||||
- **社交互动**:通过微信邀请好友,增强社交黏性
|
||||
- **紧张刺激**:倒计时 + 随机爆炸机制带来紧张感
|
||||
- **操作简单**:摇一摇即可参与,零学习成本
|
||||
- **即时反馈**:所有人实时看到炸弹传递与爆炸结果
|
||||
|
||||
### 1.3 目标用户
|
||||
- 微信用户群体(朋友聚会、线上社交)
|
||||
- 喜欢轻量级派对游戏的玩家
|
||||
- 2~8 人小团体
|
||||
|
||||
---
|
||||
|
||||
## 2. 游戏规则
|
||||
|
||||
### 2.1 基本规则
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 玩家人数 | 2 ~ 8 人 |
|
||||
| 游戏时长 | 每轮倒计时 **30 秒**(可配置) |
|
||||
| 传递方式 | 持有炸弹的玩家 **摇晃手机达到指定次数** 后,炸弹传递给下一位玩家 |
|
||||
| 摇晃次数 | 每次传递随机生成 **3 ~ 8 次**(可配置),玩家需完成对应摇晃次数 |
|
||||
| 爆炸机制 | 倒计时结束时,持有炸弹的玩家被炸中 |
|
||||
| 传递顺序 | 按房间内玩家座位顺序(顺时针)传递 |
|
||||
| 胜负判定 | 炸弹爆炸时持有者为本轮输家,其余玩家获胜 |
|
||||
|
||||
### 2.2 特殊规则
|
||||
|
||||
| 规则 | 说明 |
|
||||
|------|------|
|
||||
| 掉线处理 | 玩家掉线 **10 秒** 内未重连,视为自动弃权,炸弹跳过该玩家传给下一位 |
|
||||
| 最少人数 | 房间内至少 **2 人** 才能开始游戏 |
|
||||
| 房主特权 | 房主可以 **开始游戏**、**踢出玩家**、**调整设置** |
|
||||
| 多轮机制 | 一轮结束后,房主可发起下一轮,输家作为下一轮炸弹初始持有者 |
|
||||
|
||||
### 2.3 计分规则(可选扩展)
|
||||
|
||||
| 事件 | 积分 |
|
||||
|------|------|
|
||||
| 存活(未被炸中) | +10 分 |
|
||||
| 被炸中 | +0 分 |
|
||||
| 连续存活 3 轮 | 额外 +5 分 |
|
||||
| 多轮游戏结束后积分最高者为总冠军 | - |
|
||||
|
||||
---
|
||||
|
||||
## 3. 游戏流程
|
||||
|
||||
### 3.1 房间阶段
|
||||
|
||||
```
|
||||
玩家 A 打开小程序
|
||||
↓
|
||||
创建房间 → 获得房间号 / 邀请链接
|
||||
↓
|
||||
分享邀请链接给微信好友
|
||||
↓
|
||||
好友点击链接 → 进入房间等候区
|
||||
↓
|
||||
等候区实时显示:已加入玩家列表、头像、准备状态
|
||||
↓
|
||||
房主点击「开始游戏」(需 ≥ 2 人)
|
||||
↓
|
||||
进入游戏阶段
|
||||
```
|
||||
|
||||
### 3.2 游戏阶段
|
||||
|
||||
```
|
||||
服务端初始化本轮游戏:
|
||||
├── 随机选定炸弹初始持有者(首轮随机,后续轮为上轮输家)
|
||||
├── 生成本轮倒计时时长(默认 30s)
|
||||
├── 生成首次传递需要的摇晃次数(3~8 次)
|
||||
└── 广播「游戏开始」消息给所有玩家
|
||||
↓
|
||||
所有玩家屏幕显示:
|
||||
├── 倒计时(全局同步)
|
||||
├── 玩家列表(圆形排列)
|
||||
├── 炸弹当前位置(高亮 + 动画)
|
||||
└── 持有者显示:需要摇晃的次数
|
||||
↓
|
||||
[炸弹持有者操作]
|
||||
├── 摇晃手机 → 客户端检测摇晃 → 上报摇晃事件
|
||||
├── 服务端计数 → 达到目标次数
|
||||
├── 服务端判定传递 → 炸弹转移给下一位
|
||||
├── 广播「炸弹传递」消息(包含新持有者、新摇晃目标次数)
|
||||
└── 循环直到倒计时结束
|
||||
↓
|
||||
[倒计时归零]
|
||||
├── 服务端判定当前持有者为输家
|
||||
├── 广播「炸弹爆炸」消息(包含输家信息)
|
||||
├── 所有人屏幕播放爆炸动画 + 显示 "XXX 被炸飞了!"
|
||||
└── 3 秒后进入结算界面
|
||||
↓
|
||||
[结算界面]
|
||||
├── 显示本轮结果(输家、存活者)
|
||||
├── 显示累计积分排行(如开启计分)
|
||||
├── 房主可点击「再来一轮」
|
||||
└── 玩家可点击「退出房间」
|
||||
```
|
||||
|
||||
### 3.3 异常流程
|
||||
|
||||
```
|
||||
[玩家掉线]
|
||||
├── 服务端检测 WebSocket 断开
|
||||
├── 标记玩家为「掉线」状态
|
||||
├── 等待 10 秒重连
|
||||
├── 重连成功 → 恢复游戏状态
|
||||
└── 重连失败 → 踢出房间,炸弹跳过该玩家
|
||||
|
||||
[房主掉线]
|
||||
├── 等待 10 秒重连
|
||||
├── 重连失败 → 自动转让房主给下一位玩家
|
||||
└── 如果房间内只剩 1 人 → 游戏结束,解散房间
|
||||
|
||||
[游戏中玩家不足]
|
||||
├── 游戏进行中玩家数 < 2
|
||||
└── 游戏自动结束,剩余玩家获胜
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 页面设计
|
||||
|
||||
### 4.1 首页(游戏大厅)
|
||||
|
||||
| 元素 | 说明 |
|
||||
|------|------|
|
||||
| 用户头像 + 昵称 | 微信授权信息 |
|
||||
| 「创建房间」按钮 | 创建新游戏房间 |
|
||||
| 「加入房间」按钮 | 输入房间号加入 |
|
||||
| 游戏规则说明 | 简要规则介绍 |
|
||||
| 历史战绩 | 总局数、胜率、最长连胜(扩展) |
|
||||
|
||||
### 4.2 房间等候页
|
||||
|
||||
| 元素 | 说明 |
|
||||
|------|------|
|
||||
| 房间号 | 6 位数字,可复制 |
|
||||
| 邀请按钮 | 微信分享邀请链接 |
|
||||
| 玩家列表 | 头像 + 昵称 + 准备状态,圆形排列 |
|
||||
| 房间设置 | 倒计时时长、摇晃次数范围(房主可调) |
|
||||
| 开始游戏按钮 | 仅房主可见,≥ 2 人时可点击 |
|
||||
| 退出房间按钮 | 所有人可见 |
|
||||
|
||||
### 4.3 游戏进行页
|
||||
|
||||
| 元素 | 说明 |
|
||||
|------|------|
|
||||
| 倒计时 | 顶部居中,大字体,< 5 秒时变红闪烁 |
|
||||
| 玩家环形排列 | 头像圆形排列,当前持有者高亮 + 炸弹图标 |
|
||||
| 炸弹动画 | 炸弹在玩家之间传递的动画效果 |
|
||||
| 摇晃提示 | 持有者屏幕显示「摇一摇!还需 X 次」 |
|
||||
| 摇晃进度 | 进度条或数字展示已摇次数 / 目标次数 |
|
||||
| 振动反馈 | 每次有效摇晃触发手机振动 |
|
||||
|
||||
### 4.4 爆炸结算页
|
||||
|
||||
| 元素 | 说明 |
|
||||
|------|------|
|
||||
| 爆炸动画 | 全屏爆炸特效 |
|
||||
| 输家展示 | 大头像 + "XXX 被炸飞了!" |
|
||||
| 本轮结果 | 所有玩家结果列表(存活 / 被炸) |
|
||||
| 积分排行 | 多轮游戏时的累计积分 |
|
||||
| 「再来一轮」 | 房主按钮 |
|
||||
| 「退出房间」 | 所有人可点 |
|
||||
| 「分享战绩」 | 生成分享卡片 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 技术架构
|
||||
|
||||
### 5.1 通信方案
|
||||
|
||||
游戏需要 **实时双向通信**,采用 **WebSocket** 作为核心通信协议:
|
||||
|
||||
```
|
||||
┌──────────────┐ WebSocket ┌──────────────────┐
|
||||
│ 微信小程序 │ ◄──────────────► │ Go 后端服务 │
|
||||
│ (前端客户端) │ │ (Gin + gorilla) │
|
||||
└──────────────┘ └────────┬─────────┘
|
||||
│
|
||||
┌────────┴─────────┐
|
||||
│ Redis │
|
||||
│ (房间状态/Pub-Sub)│
|
||||
└────────┬─────────┘
|
||||
│
|
||||
┌────────┴─────────┐
|
||||
│ MySQL │
|
||||
│ (用户/战绩持久化) │
|
||||
└──────────────────┘
|
||||
```
|
||||
|
||||
**为什么选择 WebSocket:**
|
||||
- 游戏需要服务端主动推送(倒计时同步、炸弹传递、爆炸通知)
|
||||
- HTTP 轮询延迟高、开销大,不适合实时游戏
|
||||
- 微信小程序原生支持 `wx.connectSocket` WebSocket API
|
||||
|
||||
### 5.2 系统架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Go 后端服务 │
|
||||
│ │
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
|
||||
│ │ REST API │ │ WebSocket │ │ Game Engine │ │
|
||||
│ │ (Gin) │ │ Hub │ │ (房间/游戏逻辑) │ │
|
||||
│ │ │ │ │ │ │ │
|
||||
│ │ - 创建房间 │ │ - 连接管理 │ │ - 房间状态机 │ │
|
||||
│ │ - 加入房间 │ │ - 消息路由 │ │ - 倒计时管理 │ │
|
||||
│ │ - 用户信息 │ │ - 心跳检测 │ │ - 摇晃计数 │ │
|
||||
│ │ - 战绩查询 │ │ - 断线重连 │ │ - 炸弹传递逻辑 │ │
|
||||
│ └─────────────┘ └─────────────┘ │ - 爆炸判定 │ │
|
||||
│ └─────────────────────┘ │
|
||||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||
│ │ 数据层 │ │
|
||||
│ │ Redis: 房间实时状态、玩家会话、Pub/Sub 消息广播 │ │
|
||||
│ │ MySQL: 用户信息、游戏记录、战绩统计 │ │
|
||||
│ └─────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 5.3 房间状态机
|
||||
|
||||
```
|
||||
创建房间
|
||||
│
|
||||
▼
|
||||
┌───────────────┐
|
||||
│ WAITING │ ←── 玩家加入 / 离开
|
||||
│ (等待中) │
|
||||
└───────┬───────┘
|
||||
│ 房主点击开始(≥ 2 人)
|
||||
▼
|
||||
┌───────────────┐
|
||||
│ COUNTDOWN │
|
||||
│ (倒计时准备) │ 3, 2, 1...
|
||||
└───────┬───────┘
|
||||
│
|
||||
▼
|
||||
┌───────────────┐
|
||||
│ PLAYING │ ←── 摇晃 / 传递 / 倒计时
|
||||
│ (游戏中) │
|
||||
└───────┬───────┘
|
||||
│ 倒计时归零 / 玩家不足
|
||||
▼
|
||||
┌───────────────┐
|
||||
│ EXPLODED │
|
||||
│ (已爆炸) │ 展示结果
|
||||
└───────┬───────┘
|
||||
│ 3 秒后
|
||||
▼
|
||||
┌───────────────┐ 房主再来一轮
|
||||
│ FINISHED │ ──────────────────► WAITING
|
||||
│ (已结束) │
|
||||
└───────┬───────┘
|
||||
│ 所有人退出
|
||||
▼
|
||||
┌───────────────┐
|
||||
│ DISSOLVED │
|
||||
│ (已解散) │
|
||||
└───────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 数据模型
|
||||
|
||||
### 6.1 数据库表结构
|
||||
|
||||
#### bomb_game_rooms(游戏房间)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | BIGINT, PK | 主键 |
|
||||
| room_code | VARCHAR(6), UNIQUE | 房间号(6 位数字) |
|
||||
| owner_id | BIGINT, FK → users.id | 房主用户 ID |
|
||||
| status | ENUM | 房间状态:waiting / playing / finished / dissolved |
|
||||
| max_players | TINYINT | 最大玩家数,默认 8 |
|
||||
| round_duration | INT | 每轮倒计时秒数,默认 30 |
|
||||
| shake_min | INT | 最少摇晃次数,默认 3 |
|
||||
| shake_max | INT | 最多摇晃次数,默认 8 |
|
||||
| current_round | INT | 当前轮数 |
|
||||
| mini_program_id | BIGINT, FK | 所属小程序 ID |
|
||||
| created_at | DATETIME | 创建时间 |
|
||||
| updated_at | DATETIME | 更新时间 |
|
||||
|
||||
#### bomb_game_players(房间玩家)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | BIGINT, PK | 主键 |
|
||||
| room_id | BIGINT, FK → bomb_game_rooms.id | 房间 ID |
|
||||
| user_id | BIGINT, FK → users.id | 用户 ID |
|
||||
| seat_index | TINYINT | 座位序号(0 开始,决定传递顺序) |
|
||||
| is_alive | BOOL | 本轮是否存活 |
|
||||
| is_online | BOOL | 是否在线 |
|
||||
| score | INT | 累计积分 |
|
||||
| joined_at | DATETIME | 加入时间 |
|
||||
|
||||
#### bomb_game_rounds(游戏轮次记录)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | BIGINT, PK | 主键 |
|
||||
| room_id | BIGINT, FK → bomb_game_rooms.id | 房间 ID |
|
||||
| round_number | INT | 第几轮 |
|
||||
| loser_id | BIGINT, FK → users.id | 输家用户 ID |
|
||||
| bomb_start_player_id | BIGINT | 炸弹初始持有者 |
|
||||
| total_passes | INT | 本轮总传递次数 |
|
||||
| duration | INT | 实际持续秒数 |
|
||||
| started_at | DATETIME | 开始时间 |
|
||||
| ended_at | DATETIME | 结束时间 |
|
||||
|
||||
#### bomb_game_stats(玩家战绩统计)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | BIGINT, PK | 主键 |
|
||||
| user_id | BIGINT, FK → users.id | 用户 ID |
|
||||
| mini_program_id | BIGINT, FK | 所属小程序 ID |
|
||||
| total_games | INT | 总局数 |
|
||||
| total_wins | INT | 获胜局数 |
|
||||
| total_losses | INT | 失败局数 |
|
||||
| win_streak | INT | 当前连胜 |
|
||||
| max_win_streak | INT | 最长连胜 |
|
||||
| total_score | INT | 累计积分 |
|
||||
| updated_at | DATETIME | 最后更新时间 |
|
||||
|
||||
### 6.2 Redis 数据结构(实时游戏状态)
|
||||
|
||||
游戏进行中的状态存储在 Redis 中,保证实时性能:
|
||||
|
||||
```
|
||||
# 房间实时状态 (Hash)
|
||||
bomb:room:{room_code} = {
|
||||
"status": "playing",
|
||||
"current_holder": "user_id_123", // 当前炸弹持有者
|
||||
"next_player": "user_id_456", // 下一个接收者
|
||||
"shake_target": 5, // 当前需要摇晃的次数
|
||||
"shake_current": 2, // 已摇晃次数
|
||||
"round": 1, // 当前轮数
|
||||
"timer_end": 1706000030, // 倒计时结束时间戳
|
||||
"exploded": false // 是否已爆炸
|
||||
}
|
||||
|
||||
# 房间玩家有序列表 (Sorted Set, score = seat_index)
|
||||
bomb:room:{room_code}:players = {
|
||||
"user_id_1": 0,
|
||||
"user_id_2": 1,
|
||||
"user_id_3": 2,
|
||||
...
|
||||
}
|
||||
|
||||
# 玩家连接映射 (Hash) - 用于断线重连
|
||||
bomb:player:{user_id} = {
|
||||
"room_code": "123456",
|
||||
"conn_id": "ws_conn_xxx"
|
||||
}
|
||||
|
||||
# 房间 TTL: 房间创建后 2 小时未活动自动过期清理
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. API 设计
|
||||
|
||||
### 7.1 REST API(房间管理)
|
||||
|
||||
基础路径:`/api/v1/bomb-game`
|
||||
|
||||
#### 创建房间
|
||||
|
||||
```
|
||||
POST /api/v1/bomb-game/rooms
|
||||
Authorization: Bearer <token>
|
||||
|
||||
Request Body:
|
||||
{
|
||||
"max_players": 8, // 可选,默认 8
|
||||
"round_duration": 30, // 可选,默认 30 秒
|
||||
"shake_min": 3, // 可选,默认 3
|
||||
"shake_max": 8 // 可选,默认 8
|
||||
}
|
||||
|
||||
Response 200:
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"room_code": "582916",
|
||||
"room_id": 1,
|
||||
"owner_id": 123,
|
||||
"max_players": 8,
|
||||
"round_duration": 30,
|
||||
"share_ticket": "xxx" // 用于微信分享
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 加入房间
|
||||
|
||||
```
|
||||
POST /api/v1/bomb-game/rooms/:room_code/join
|
||||
Authorization: Bearer <token>
|
||||
|
||||
Response 200:
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"room_code": "582916",
|
||||
"seat_index": 2,
|
||||
"players": [
|
||||
{
|
||||
"user_id": 123,
|
||||
"nickname": "张三",
|
||||
"avatar_url": "https://...",
|
||||
"seat_index": 0,
|
||||
"is_owner": true
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Error 400 (房间已满):
|
||||
{
|
||||
"code": 40001,
|
||||
"message": "房间已满"
|
||||
}
|
||||
|
||||
Error 400 (游戏已开始):
|
||||
{
|
||||
"code": 40002,
|
||||
"message": "游戏已开始,无法加入"
|
||||
}
|
||||
```
|
||||
|
||||
#### 获取房间信息
|
||||
|
||||
```
|
||||
GET /api/v1/bomb-game/rooms/:room_code
|
||||
Authorization: Bearer <token>
|
||||
|
||||
Response 200:
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"room_code": "582916",
|
||||
"status": "waiting",
|
||||
"owner_id": 123,
|
||||
"max_players": 8,
|
||||
"round_duration": 30,
|
||||
"current_round": 0,
|
||||
"players": [ ... ]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 离开房间
|
||||
|
||||
```
|
||||
POST /api/v1/bomb-game/rooms/:room_code/leave
|
||||
Authorization: Bearer <token>
|
||||
|
||||
Response 200:
|
||||
{
|
||||
"code": 0,
|
||||
"message": "已离开房间"
|
||||
}
|
||||
```
|
||||
|
||||
#### 获取个人战绩
|
||||
|
||||
```
|
||||
GET /api/v1/bomb-game/stats
|
||||
Authorization: Bearer <token>
|
||||
|
||||
Response 200:
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"total_games": 42,
|
||||
"total_wins": 30,
|
||||
"total_losses": 12,
|
||||
"win_rate": 71.4,
|
||||
"win_streak": 3,
|
||||
"max_win_streak": 7,
|
||||
"total_score": 380
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7.2 WebSocket 连接
|
||||
|
||||
#### 连接建立
|
||||
|
||||
```
|
||||
ws://<host>/api/v1/bomb-game/ws?room_code=582916&token=<jwt_token>
|
||||
```
|
||||
|
||||
连接建立后,服务端进行身份验证并将玩家加入房间的 WebSocket 广播组。
|
||||
|
||||
#### 心跳机制
|
||||
|
||||
```
|
||||
客户端每 5 秒发送: { "type": "ping" }
|
||||
服务端响应: { "type": "pong" }
|
||||
|
||||
超过 15 秒未收到心跳 → 标记为掉线
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. WebSocket 消息协议
|
||||
|
||||
所有消息均为 JSON 格式,基本结构:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "message_type",
|
||||
"data": { ... },
|
||||
"timestamp": 1706000000
|
||||
}
|
||||
```
|
||||
|
||||
### 8.1 客户端 → 服务端 消息
|
||||
|
||||
| type | 说明 | data 字段 |
|
||||
|------|------|-----------|
|
||||
| `ping` | 心跳 | 无 |
|
||||
| `shake` | 上报一次摇晃 | `{}` |
|
||||
| `start_game` | 房主开始游戏 | `{}` |
|
||||
| `next_round` | 房主发起下一轮 | `{}` |
|
||||
| `kick_player` | 房主踢人 | `{ "user_id": 456 }` |
|
||||
| `emoji` | 发送表情互动 | `{ "emoji_id": "laugh" }` |
|
||||
|
||||
### 8.2 服务端 → 客户端 消息
|
||||
|
||||
| type | 说明 | data 字段 |
|
||||
|------|------|-----------|
|
||||
| `pong` | 心跳响应 | 无 |
|
||||
| `room_update` | 房间信息更新(玩家加入/离开) | `{ "players": [...], "owner_id": 123 }` |
|
||||
| `game_countdown` | 游戏即将开始倒计时 | `{ "countdown": 3 }` |
|
||||
| `game_start` | 游戏正式开始 | `{ "round": 1, "bomb_holder": "user_id", "shake_target": 5, "timer_end": 1706000030 }` |
|
||||
| `shake_update` | 摇晃进度更新 | `{ "user_id": "xxx", "current": 3, "target": 5 }` |
|
||||
| `bomb_pass` | 炸弹传递 | `{ "from": "user_id_1", "to": "user_id_2", "shake_target": 4 }` |
|
||||
| `bomb_explode` | 炸弹爆炸 | `{ "loser_id": "user_id", "loser_nickname": "张三" }` |
|
||||
| `round_result` | 轮次结算 | `{ "round": 1, "loser": {...}, "scores": [...] }` |
|
||||
| `player_offline` | 玩家掉线 | `{ "user_id": "xxx" }` |
|
||||
| `player_reconnect` | 玩家重连 | `{ "user_id": "xxx" }` |
|
||||
| `game_state_sync` | 完整状态同步(重连用) | `{ "room": {...}, "game": {...} }` |
|
||||
| `emoji_broadcast` | 表情广播 | `{ "user_id": "xxx", "emoji_id": "laugh" }` |
|
||||
| `error` | 错误消息 | `{ "code": 50001, "message": "..." }` |
|
||||
| `owner_changed` | 房主变更 | `{ "new_owner_id": 456 }` |
|
||||
| `room_dissolved` | 房间解散 | `{ "reason": "房主退出" }` |
|
||||
|
||||
### 8.3 消息流转时序
|
||||
|
||||
#### 正常游戏流程时序:
|
||||
|
||||
```
|
||||
玩家A(房主) 服务端 玩家B 玩家C
|
||||
│ │ │ │
|
||||
│ start_game │ │ │
|
||||
│─────────────►│ │ │
|
||||
│ │ game_countdown(3) │
|
||||
│◄─────────────│─────────────►│─────────────►│
|
||||
│ │ game_countdown(2) │
|
||||
│◄─────────────│─────────────►│─────────────►│
|
||||
│ │ game_countdown(1) │
|
||||
│◄─────────────│─────────────►│─────────────►│
|
||||
│ │ │ │
|
||||
│ │ game_start(holder=B, shake=5)│
|
||||
│◄─────────────│─────────────►│─────────────►│
|
||||
│ │ │ │
|
||||
│ │ shake │ │
|
||||
│ │◄─────────────│ │
|
||||
│ │ shake_update(current=1) │
|
||||
│◄─────────────│─────────────►│─────────────►│
|
||||
│ │ ... │ │
|
||||
│ │ shake(第5次) │
|
||||
│ │◄─────────────│ │
|
||||
│ │ │ │
|
||||
│ │ bomb_pass(B→C, shake=4) │
|
||||
│◄─────────────│─────────────►│─────────────►│
|
||||
│ │ │ │
|
||||
│ │ [倒计时归零] │
|
||||
│ │ │ │
|
||||
│ │ bomb_explode(loser=C) │
|
||||
│◄─────────────│─────────────►│─────────────►│
|
||||
│ │ │ │
|
||||
│ │ round_result(...) │
|
||||
│◄─────────────│─────────────►│─────────────►│
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 防作弊设计
|
||||
|
||||
### 9.1 服务端权威原则
|
||||
|
||||
| 策略 | 说明 |
|
||||
|------|------|
|
||||
| 摇晃验证 | 客户端上报摇晃事件,**服务端计数**,不信任客户端计数 |
|
||||
| 频率限制 | 单次摇晃事件最小间隔 **200ms**,防止脚本快速刷摇晃 |
|
||||
| 倒计时服务端控制 | 客户端倒计时仅为展示,**爆炸判定由服务端触发** |
|
||||
| 传递判定 | 炸弹传递逻辑在服务端执行,客户端无法伪造传递 |
|
||||
|
||||
### 9.2 其他安全措施
|
||||
|
||||
| 措施 | 说明 |
|
||||
|------|------|
|
||||
| WebSocket 鉴权 | 连接时验证 JWT token |
|
||||
| 房间权限 | 只有房间内玩家可收发该房间消息 |
|
||||
| 操作校验 | 只有当前持有者可上报摇晃事件 |
|
||||
| 房主操作校验 | 开始游戏、踢人等操作验证房主身份 |
|
||||
|
||||
---
|
||||
|
||||
## 10. 性能与扩展性考量
|
||||
|
||||
### 10.1 并发处理
|
||||
|
||||
| 方面 | 方案 |
|
||||
|------|------|
|
||||
| 房间隔离 | 每个房间独立 goroutine 处理游戏逻辑,避免跨房间锁竞争 |
|
||||
| 消息广播 | 使用 channel 实现房间内广播,每个房间维护独立的客户端列表 |
|
||||
| 定时器 | 使用 `time.AfterFunc` 管理倒计时,避免全局 ticker 开销 |
|
||||
|
||||
### 10.2 扩展性
|
||||
|
||||
| 方面 | 方案 |
|
||||
|------|------|
|
||||
| 多实例部署 | 通过 Redis Pub/Sub 实现跨实例房间消息广播 |
|
||||
| 房间分配 | 使用 Redis 存储房间 ↔ 实例映射,支持负载均衡 |
|
||||
| 状态恢复 | 服务重启后从 Redis 恢复房间状态 |
|
||||
|
||||
### 10.3 资源清理
|
||||
|
||||
| 触发条件 | 清理动作 |
|
||||
|----------|----------|
|
||||
| 房间 2 小时无活动 | Redis key 自动过期,MySQL 标记 dissolved |
|
||||
| 所有玩家退出 | 立即清理 Redis 数据 |
|
||||
| 服务端定时任务 | 每 30 分钟扫描僵尸房间并清理 |
|
||||
|
||||
---
|
||||
|
||||
## 11. 项目文件结构(建议)
|
||||
|
||||
```
|
||||
internal/
|
||||
└── bomb_game/
|
||||
├── handler/
|
||||
│ ├── room_handler.go # REST API: 创建/加入/离开房间、查询战绩
|
||||
│ └── ws_handler.go # WebSocket: 连接建立、消息路由
|
||||
├── service/
|
||||
│ ├── room_service.go # 房间管理业务逻辑
|
||||
│ ├── game_service.go # 游戏核心逻辑(状态机、倒计时、爆炸判定)
|
||||
│ └── stats_service.go # 战绩统计
|
||||
├── model/
|
||||
│ ├── room.go # Room, Player 数据库模型
|
||||
│ ├── round.go # Round 数据库模型
|
||||
│ └── stats.go # Stats 数据库模型
|
||||
├── hub/
|
||||
│ ├── hub.go # WebSocket Hub: 连接管理、消息广播
|
||||
│ ├── client.go # WebSocket Client: 单个连接封装
|
||||
│ └── message.go # 消息类型定义
|
||||
└── engine/
|
||||
├── game_engine.go # 游戏引擎: 房间级游戏循环
|
||||
└── timer.go # 倒计时管理器
|
||||
```
|
||||
|
||||
路由注册文件:
|
||||
|
||||
```
|
||||
internal/routes/
|
||||
└── bomb_game_routes.go # 路由注册
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 12. 待扩展功能
|
||||
|
||||
- [ ] 观战模式:允许旁观者进入房间观看
|
||||
- [ ] 道具系统:缩短/延长倒计时、跳过本次传递、反转传递方向
|
||||
- [ ] 排行榜:全局积分排行、好友排行
|
||||
- [ ] 房间密码:私密房间需要密码才能加入
|
||||
- [ ] 语音互动:游戏中实时语音(微信实时通话组件)
|
||||
- [ ] 惩罚机制:输家需完成随机挑战(真心话/大冒险)
|
||||
- [ ] 自定义皮肤:炸弹皮肤、爆炸特效自定义
|
||||
- [ ] 匹配模式:随机匹配陌生人游戏
|
||||
Reference in New Issue
Block a user