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:
nepiedg
2026-02-06 11:28:02 +00:00
parent 9200600b1c
commit 1b8ff310eb
7 changed files with 1049 additions and 3 deletions
+700
View File
@@ -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. 待扩展功能
- [ ] 观战模式:允许旁观者进入房间观看
- [ ] 道具系统:缩短/延长倒计时、跳过本次传递、反转传递方向
- [ ] 排行榜:全局积分排行、好友排行
- [ ] 房间密码:私密房间需要密码才能加入
- [ ] 语音互动:游戏中实时语音(微信实时通话组件)
- [ ] 惩罚机制:输家需完成随机挑战(真心话/大冒险)
- [ ] 自定义皮肤:炸弹皮肤、爆炸特效自定义
- [ ] 匹配模式:随机匹配陌生人游戏
+101
View File
@@ -121,3 +121,104 @@ curl -X POST 'http://127.0.0.1:8080/api/v1/video/remove_watermark/unlock' \
| `userAgent` | string,可选 | 报告端的 UA,默认取 HTTP 头 |
服务端会额外记录请求来源 IP`client_ip`),并存入 `video_download_failures` 表,便于后续排查白名单或 CDN 问题。
## 6. 媒体代理下载
`GET /api/v1/video/proxy`
该接口用于代理媒体资源(视频/图片等)的下载,使小程序可以通过当前服务域名下载资源,避免微信对第三方域名的限制。
| 项目 | 说明 |
| --- | --- |
| Header | 无需鉴权 |
| Query 参数 | `url` - 需要代理的原始媒体地址(必填,需 URL 编码) |
| 响应 | 流式返回原始媒体内容,Content-Type 与源文件一致 |
curl 示例:
```bash
# 代理视频下载
curl -L 'http://127.0.0.1:8080/api/v1/video/proxy?url=https%3A%2F%2Fexample.com%2Fvideo.mp4' \
-o video.mp4
# 代理图片下载
curl -L 'http://127.0.0.1:8080/api/v1/video/proxy?url=https%3A%2F%2Fexample.com%2Fcover.jpg' \
-o cover.jpg
```
小程序端使用示例:
```javascript
// 使用 wx.downloadFile 下载
const proxyUrl = 'https://your-domain.com/api/v1/video/proxy';
const originalUrl = 'https://cdn.example.com/video.mp4';
wx.downloadFile({
url: `${proxyUrl}?url=${encodeURIComponent(originalUrl)}`,
success(res) {
if (res.statusCode === 200) {
// 下载成功,res.tempFilePath 为临时文件路径
wx.saveVideoToPhotosAlbum({
filePath: res.tempFilePath,
success() {
wx.showToast({ title: '保存成功' });
}
});
}
},
fail(err) {
console.error('下载失败', err);
}
});
```
**成功响应**
- HTTP 状态码:200
- Content-Type:与源文件一致(如 `video/mp4``image/jpeg`
- Body:媒体文件二进制流
**错误返回**
| HTTP 码 | code | message | 说明 |
| --- | --- | --- | --- |
| 400 | 400 | `请求参数错误,缺少 url 参数` | 未传 `url` 参数 |
| 400 | 400 | `无效的代理地址` | URL 格式不正确或协议不支持 |
| 403 | 403 | `该域名不在允许列表中` | 目标域名未在白名单内 |
| 413 | 413 | `文件过大,超出限制` | 文件大小超过配置的最大值 |
| 502 | 502 | `上游服务返回错误` | 源服务器返回非 2xx 状态码 |
| 503 | 503 | `代理服务未启用` | 代理功能被禁用 |
| 500 | 500 | `代理请求失败` | 其他内部错误 |
**配置项**
可通过环境变量配置代理行为:
| 环境变量 | 默认值 | 说明 |
| --- | --- | --- |
| `SHORT_VIDEO_PROXY_ENABLED` | `true` | 是否启用代理功能 |
| `SHORT_VIDEO_PROXY_ALLOWED_DOMAINS` | 空(允许所有) | 允许代理的域名白名单,多个用逗号分隔 |
| `SHORT_VIDEO_PROXY_MAX_SIZE_MB` | `100` | 代理文件最大大小(MB |
| `SHORT_VIDEO_PROXY_TIMEOUT_SECONDS` | `60` | 代理请求超时时间(秒) |
配置示例(`.env`):
```bash
# 启用代理
SHORT_VIDEO_PROXY_ENABLED=true
# 限制只能代理特定域名(安全推荐)
SHORT_VIDEO_PROXY_ALLOWED_DOMAINS=cdn.example.com,video.example.com,aweme.snssdk.com
# 最大文件 200MB
SHORT_VIDEO_PROXY_MAX_SIZE_MB=200
# 超时 120 秒
SHORT_VIDEO_PROXY_TIMEOUT_SECONDS=120
```
**安全建议**
1. **配置域名白名单**:强烈建议配置 `SHORT_VIDEO_PROXY_ALLOWED_DOMAINS`,只允许代理已知的 CDN 域名,防止被滥用为开放代理。
2. **设置合理的文件大小限制**:根据实际需求设置 `SHORT_VIDEO_PROXY_MAX_SIZE_MB`,避免服务器带宽被大文件消耗。
3. **监控带宽使用**:代理功能会消耗服务器带宽,建议监控流量并设置告警。