Files
wx_service/docs/bomb_game/PRD.md
T
nepiedg 1b8ff310eb 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.
2026-02-06 11:28:02 +00:00

25 KiB
Raw Blame History

炸弹传递小游戏 - 产品需求文档 (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 格式,基本结构:

{
    "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. 待扩展功能

  • 观战模式:允许旁观者进入房间观看
  • 道具系统:缩短/延长倒计时、跳过本次传递、反转传递方向
  • 排行榜:全局积分排行、好友排行
  • 房间密码:私密房间需要密码才能加入
  • 语音互动:游戏中实时语音(微信实时通话组件)
  • 惩罚机制:输家需完成随机挑战(真心话/大冒险)
  • 自定义皮肤:炸弹皮肤、爆炸特效自定义
  • 匹配模式:随机匹配陌生人游戏