Files
file-transfer-go/ARCHITECTURE.md

37 KiB
Raw Blame History

文件快传Chuan— 系统架构与技术设计文档

最后更新2025-08-02


一、项目总览

文件快传Chuan 是一个基于 WebRTC P2P 的文件传输应用。核心设计理念:所有实际数据(文件、文字、桌面画面)均通过 WebRTC 点对点直传,服务器仅承担信令中继和房间管理职责

技术栈

层级 技术选型
后端 Go 1.21 · chi/v5 路由 · gorilla/websocket
前端 Next.js 15 · React 19 · TypeScript · Tailwind CSS 4
状态管理 Zustand 5 + React useState/useRef
UI 组件 shadcn/ui (Radix UI) · Lucide Icons
P2P 通信 WebRTC DataChannel文件/文字)· MediaStream桌面共享
部署 双模式Next.js 开发模式 / Go 嵌入前端静态文件

核心特性

功能 实现方式
文件传输 WebRTC DataChannel · 256KB 分块 · CRC32 校验 · ACK 确认
文本消息 WebRTC DataChannel · 实时双向同步 · 打字状态
桌面共享 WebRTC MediaStream · getDisplayMedia · renegotiation
ICE 配置 默认 5 个 STUN · 支持自定义 STUN/TURN · localStorage 持久化

二、系统架构

2.1 整体架构图

┌──────────────────────────────────────────────────────────────────────┐
│                        Go 后端 (:8080)                               │
│  ┌──────────────┐  ┌───────────────────┐  ┌─────────────────────┐  │
│  │  REST API     │  │  WebSocket 信令    │  │  静态文件服务        │  │
│  │  /api/*       │  │  /api/ws/webrtc   │  │  go:embed frontend  │  │
│  │               │  │  /ws/webrtc       │  │                     │  │
│  │ - create-room │  │                   │  │  SPA 回退 index.html │  │
│  │ - room-info   │  │  纯中继 转发消息   │  │                     │  │
│  └──────────────┘  └───────────────────┘  └─────────────────────┘  │
│                              │                                       │
│              ┌───────────────┼───────────────┐                       │
│              │   内存房间管理  (sync.RWMutex)  │                       │
│              │   map[string]*WebRTCRoom       │                       │
│              │   1小时过期 · 5分钟定时清理     │                       │
│              └───────────────────────────────┘                       │
└──────────────────────────────────────────────────────────────────────┘
        │ WebSocket                                    │ WebSocket
        │ (信令: offer/answer/ice-candidate)            │
        ▼                                              ▼
┌───────────────┐          WebRTC P2P          ┌───────────────┐
│  发送方浏览器   │ ◄══════════════════════════► │  接收方浏览器   │
│               │   DataChannel (文件/文字)      │               │
│  Next.js App  │   MediaStream (桌面共享)       │  Next.js App  │
└───────────────┘                              └───────────────┘

2.2 数据流向

1. 控制平面 (HTTP):  浏览器 ──── REST API ────→ Go 后端    (房间创建/查询)
2. 信令平面 (WS):    浏览器 ←──── WebSocket ──→ Go 后端    (SDP/ICE 交换)
3. 数据平面 (P2P):   浏览器 ◄═══ DataChannel ══► 浏览器     (文件/文字直传)
4. 媒体平面 (P2P):   浏览器 ◄═══ MediaStream ══► 浏览器     (桌面画面直传)

关键设计:数据平面完全绕过服务器,文件和文字内容不经服务器中转。


三、后端设计

3.1 目录结构

cmd/
├── main.go          # 入口:参数解析 → 配置 → 路由 → 启动服务器
├── config.go        # 配置管理:命令行 > 环境变量 > .chuan.env > 默认值
├── router.go        # chi 路由注册 + 中间件 + 前端静态服务
└── server.go        # HTTP Server 封装 + 优雅关闭 (SIGINT/SIGTERM)

internal/
├── handlers/
│   └── handlers.go  # HTTP/WebSocket 请求处理(薄层,委托给 service
├── models/
│   └── models.go    # 数据模型WebRTCRoom、WebRTCClient、RoomStatus
├── services/
│   └── webrtc_service.go  # 核心信令服务:房间管理 + WebSocket 消息转发
└── web/
    └── frontend.go  # go:embed 嵌入前端 + SPA 回退

3.2 API 端点

方法 路径 功能 请求/响应
POST /api/create-room 创建房间 {}{success, code, message}
GET /api/room-info?code=XXX 查询房间状态 {success, status: RoomStatus}
GET /api/webrtc-room-status?code=XXX 同上(别名) 同上
WS /api/ws/webrtc?code=&role= WebRTC 信令 WebSocket 双向
WS /ws/webrtc?code=&role= 同上(兼容路径) 同上
GET /* 前端静态文件 SPA 回退

3.3 房间管理

  • 房间代码6 位,字符集 123456789ABCDEFGHIJKLMNPQRSTUVWXYZ(排除 0 和 O避免混淆
  • 房间容量:最多 2 人1 sender + 1 receiver
  • 过期策略:创建后 1 小时过期,每 5 分钟后台清理
  • 存储方式:纯内存 map[string]*WebRTCRoom + sync.RWMutex,无数据库

3.4 WebSocket 信令协议

连接 URL

ws[s]://host/api/ws/webrtc?code=ROOM_CODE&role=sender|receiver&channel=shared

服务端 → 客户端

type payload 触发时机
peer-joined { role } 对方加入房间
disconnection { role, message } 对方断开连接
error { message } 房间不存在 / 已满 / 参数无效

客户端 ↔ 客户端(经服务端纯转发)

type payload 说明
offer RTCSessionDescription SDP Offer
answer RTCSessionDescription SDP Answer
ice-candidate RTCIceCandidate ICE 候选地址

服务端对 offer/answer/ice-candidate 消息不做任何解析,仅中继转发给房间内另一方。


四、前端设计(重点)

4.1 目录结构

chuan-next/src/
├── app/                            # Next.js App Router
│   ├── page.tsx                    # 入口页SSG
│   ├── layout.tsx                  # 根布局字体、Toast、Umami 统计)
│   ├── globals.css                 # 全局样式 + 动画定义
│   ├── HomePage.tsx                # 主页面组件Tab 管理 + WebRTC 状态)
│   ├── HomePageWrapper.tsx         # Suspense 包裹SSR 兼容)
│   └── api/                        # Next.js API Routes开发模式代理
│       ├── create-room/route.ts    # → GO_BACKEND_URL/api/create-room
│       ├── room-info/route.ts      # → GO_BACKEND_URL/api/room-info
│       ├── room-status/route.ts    # → GO_BACKEND_URL/api/room-status
│       ├── create-text-room/route.ts
│       ├── get-text-content/route.ts
│       └── update-files/route.ts
│
├── components/                     # UI 组件
│   ├── WebRTCFileTransfer.tsx      # 文件传输整合组件659行
│   ├── WebRTCTextImageTransfer.tsx # 文字+图片传输整合组件
│   ├── DesktopShare.tsx            # 桌面共享整合组件
│   ├── Hero.tsx                    # 页头标题、GitHub 链接)
│   ├── webrtc/                     # WebRTC 功能子组件
│   │   ├── WebRTCFileUpload.tsx    # 文件发送 UI
│   │   ├── WebRTCFileReceive.tsx   # 文件接收 UI
│   │   ├── WebRTCTextSender.tsx    # 文字发送 UI
│   │   ├── WebRTCTextReceiver.tsx  # 文字接收 UI
│   │   ├── WebRTCDesktopSender.tsx # 桌面共享发送 UI
│   │   ├── WebRTCDesktopReceiver.tsx # 桌面共享接收 UI
│   │   └── WebRTCSettings.tsx      # ICE 服务器配置 UI
│   └── ui/                         # 基础 UI 组件 (shadcn/ui)
│       ├── button.tsx              # 按钮6种 variant
│       ├── tabs.tsx                # 标签页Radix Tabs
│       ├── input.tsx               # 输入框
│       ├── card.tsx                # 卡片
│       ├── dialog.tsx              # 对话框
│       ├── progress.tsx            # 进度条
│       ├── textarea.tsx            # 文本域
│       ├── toast.tsx               # Toast (Radix)
│       ├── toast-simple.tsx        # 轻量 Toast (自定义)
│       ├── toaster.tsx             # Toast 渲染
│       └── confirm-dialog.tsx      # 确认对话框
│
├── hooks/                          # 自定义 Hooks核心逻辑层
│   ├── index.ts                    # 统一导出
│   ├── connection/                 # WebRTC 连接管理
│   │   ├── useSharedWebRTCManager.ts      # 整合入口
│   │   ├── useWebRTCConnectionCore.ts     # 核心连接WS + PeerConnection
│   │   ├── useWebRTCDataChannelManager.ts # DataChannel 管理 + 消息路由
│   │   ├── useWebRTCTrackManager.ts       # MediaStream 轨道管理
│   │   ├── useWebRTCStateManager.ts       # Zustand store 封装
│   │   ├── useRoomConnection.ts           # 加入房间逻辑
│   │   ├── useConnectionState.ts          # 连接状态变化处理
│   │   └── useWebRTCSupport.ts            # 浏览器 WebRTC 检测
│   ├── file-transfer/              # 文件传输业务
│   │   ├── useFileTransferBusiness.ts     # 核心:分块传输 + CRC32 + ACK
│   │   ├── useFileListSync.ts             # 文件列表实时同步
│   │   └── useFileStateManager.ts         # 文件状态(选中/下载/进度)
│   ├── text-transfer/              # 文字传输业务
│   │   └── useTextTransferBusiness.ts     # 实时文字同步 + 打字状态
│   ├── desktop-share/              # 桌面共享业务
│   │   └── useDesktopShareBusiness.ts     # getDisplayMedia + 轨道管理
│   ├── settings/                   # 设置
│   │   ├── useIceServersConfig.ts         # ICE 服务器配置 + localStorage
│   │   └── useWebRTCConfigSync.ts         # 配置变更监听
│   └── ui/                         # UI 相关
│       ├── webRTCStore.ts                 # Zustand 全局状态
│       ├── useURLHandler.ts               # URL 参数管理
│       ├── useTabNavigation.ts            # Tab 切换管理
│       └── useConfirmDialog.ts            # 确认对话框 Hook
│
├── lib/                            # 工具库
│   ├── config.ts                   # 环境配置API URL / WS URL 动态计算)
│   ├── utils.ts                    # cn() 样式合并
│   ├── client-api.ts               # ClientAPI 封装
│   ├── api-utils.ts                # apiFetch() 统一请求
│   └── webrtc-support.ts           # WebRTC 支持检测
│
└── types/
    └── index.ts                    # 全局类型定义

4.2 组件层次结构

RootLayout (layout.tsx)
  └─ ToastProvider (toast-simple.tsx)
       └─ HomePageWrapper (Suspense)
            └─ HomePage
                 ├─ Hero                           # 页头
                 ├─ WebRTCUnsupportedModal          # WebRTC 不支持提示
                 ├─ ConfirmDialog                   # Tab 切换确认
                 └─ Tabs (5个标签)
                      │
                      ├─ [Tab: webrtc] WebRTCFileTransfer
                      │    ├─ 模式切换 (发送/接收)
                      │    ├─ [发送模式] WebRTCFileUpload
                      │    │    ├─ 拖拽上传区域 / 文件列表
                      │    │    ├─ RoomInfoDisplay (取件码 + 二维码 + 复制)
                      │    │    ├─ ConnectionStatus (连接状态)
                      │    │    └─ 传输进度条
                      │    └─ [接收模式] WebRTCFileReceive
                      │         ├─ 取件码输入 (6位)
                      │         ├─ ConnectionStatus (连接状态)
                      │         └─ 文件列表 + 下载按钮
                      │
                      ├─ [Tab: message] WebRTCTextImageTransfer
                      │    ├─ 模式切换 (发送/接收)
                      │    ├─ [发送模式] WebRTCTextSender
                      │    │    ├─ Textarea 编辑器
                      │    │    ├─ 图片发送按钮
                      │    │    └─ RoomInfoDisplay
                      │    └─ [接收模式] WebRTCTextReceiver
                      │         ├─ 取件码输入
                      │         └─ 实时文字显示 + 图片接收
                      │
                      ├─ [Tab: desktop] DesktopShare
                      │    ├─ 模式切换 (共享/查看)
                      │    ├─ [共享模式] WebRTCDesktopSender
                      │    │    └─ 选择屏幕 → 开始共享
                      │    └─ [查看模式] WebRTCDesktopReceiver
                      │         └─ DesktopViewer (video 标签)
                      │
                      ├─ [Tab: wechat] WeChatGroup
                      │    └─ 微信群二维码
                      │
                      └─ [Tab: settings] WebRTCSettings
                           └─ ICE 服务器配置表单

4.3 Hooks 架构(核心设计)

前端逻辑层采用 分层 Hooks 架构,自底向上组合:

                    ┌─────────────────────────────┐
                    │    Zustand Store             │  ← 最底层:全局状态
                    │    (webRTCStore.ts)           │
                    │    isConnected, isConnecting  │
                    │    currentRoom, error         │
                    └──────────────┬──────────────┘
                                   │
            ┌──────────────────────┼──────────────────────┐
            │                      │                      │
   ┌────────▼────────┐  ┌─────────▼──────────┐  ┌───────▼─────────┐
   │ StateManager     │  │ DataChannelManager  │  │ TrackManager     │
   │ (Zustand 封装)    │  │ (消息路由 + 收发)    │  │ (媒体轨道管理)    │
   └────────┬────────┘  └─────────┬──────────┘  └───────┬─────────┘
            │                      │                      │
            └──────────────────────┼──────────────────────┘
                                   │
                    ┌──────────────▼──────────────┐
                    │ ConnectionCore               │  ← WebSocket + PeerConnection
                    │ (useWebRTCConnectionCore)     │     信令处理 + ICE 交换
                    └──────────────┬──────────────┘
                                   │
                    ┌──────────────▼──────────────┐
                    │ SharedWebRTCManager          │  ← 整合入口4合1
                    │ (useSharedWebRTCManager)      │     返回统一 WebRTCConnection
                    └──────────────┬──────────────┘
                                   │
          ┌────────────────────────┼────────────────────────┐
          │                        │                        │
 ┌────────▼────────┐   ┌──────────▼──────────┐   ┌────────▼────────┐
 │ FileTransfer     │   │ TextTransfer         │   │ DesktopShare     │
 │ Business         │   │ Business             │   │ Business         │
 │ (文件分块+校验)    │   │ (实时同步文字)         │   │ (屏幕采集+推流)    │
 └────────┬────────┘   └──────────┬──────────┘   └────────┬────────┘
          │                        │                        │
          └────────────────────────┼────────────────────────┘
                                   │
                    ┌──────────────▼──────────────┐
                    │ 业务级组件 (TSX)              │
                    │ WebRTCFileTransfer.tsx 等     │
                    └─────────────────────────────┘

4.3.1 连接层 — useWebRTCConnectionCore

职责:管理 WebSocket 信令连接 + RTCPeerConnection 生命周期。

connect(code, role)
    │
    ├─ 1. WebSocket 连接到 ws://host/api/ws/webrtc?code=XXX&role=YYY
    │
    ├─ 2. 收到 "peer-joined" → 创建 RTCPeerConnection
    │      └─ ICE 服务器从 getIceServersConfig() 获取
    │
    ├─ 3. Sender 创建 DataChannel → 创建 Offer
    │      Receiver 等待 ondatachannel
    │
    ├─ 4. offer/answer/ice-candidate 通过 WebSocket 交换
    │
    ├─ 5. ICE 连接建立 → DataChannel open → 连接完成
    │
    └─ 6. 断开时:发送 disconnection → 清理 PeerConnection

4.3.2 数据通道层 — useWebRTCDataChannelManager

职责:管理 DataChannel 消息的路由分发。

核心设计:多 channel 路由

所有 JSON 消息通过 channel 字段路由到不同的业务处理器:

// 消息格式
{
  channel: "file-transfer" | "text-transfer" | "desktop-share",
  type: "具体消息类型",
  payload: { ... }
}
DataChannel.onmessage
    │
    ├─ typeof data === 'string' → JSON.parse
    │   ├─ channel === 'file-transfer' → fileTransferHandler
    │   ├─ channel === 'text-transfer' → textTransferHandler
    │   └─ channel === 'desktop-share' → desktopShareHandler
    │
    └─ typeof data === ArrayBuffer → 二进制数据
        └─ 优先路由到 'file-transfer' handler文件块数据

注意:三个功能(文件/文字/桌面)共享同一个 DataChannelshared-channel),而非各自创建独立通道。

4.3.3 轨道管理层 — useWebRTCTrackManager

职责:管理 MediaStream 轨道(仅桌面共享使用)。

  • addTrack(track, stream) — 添加视频/音频轨道
  • removeTrack(sender) — 移除轨道
  • createOfferNow() — 添加轨道后触发重协商
  • onTrack 回调 — 接收远程媒体流

4.4 状态管理设计

采用 三层状态模型

层级 技术 用途 示例
全局共享状态 Zustand WebRTC 连接状态 isConnected, currentRoom, error
组件级状态 React useState UI 交互状态 selectedFiles, mode, progress
引用状态 React useRef 回调/缓冲区/定时器 receiveBufferRef, messageHandlers, 防抖 timer

Zustand Store 结构

interface WebRTCState {
  // 连接状态
  isConnected: boolean;       // 总体连接状态
  isConnecting: boolean;      // 正在连接中
  isWebSocketConnected: boolean;  // WebSocket 层连接
  isPeerConnected: boolean;   // PeerConnection 层连接

  // 错误状态
  error: string | null;
  canRetry: boolean;

  // 房间信息
  currentRoom: { code: string; role: 'sender' | 'receiver' } | null;

  // Actions
  updateState(partial: Partial<WebRTCState>): void;
  setCurrentRoom(room: { code: string; role: string } | null): void;
  resetToInitial(): void;
}

4.5 文件传输详细设计

4.5.1 DataChannel 消息协议

file-transfer channel 消息类型:

方向 type payload 说明
S→R file-list FileInfo[] 发送方文件列表(实时同步)
R→S file-request { fileId, fileName } 接收方请求下载
S→R file-metadata { id, name, size, type } 文件元信息
S→R file-chunk-info { fileId, chunkIndex, totalChunks, checksum } 块元信息(含 CRC32
S→R (二进制) ArrayBuffer (≤256KB) 文件块数据
R→S file-chunk-ack { fileId, chunkIndex, success, checksum } 块确认(含校验结果)
S→R file-complete { fileId } 文件传输完成

4.5.2 传输参数

参数 说明
块大小 256 KB 每个 chunk 的最大字节数
最大重试 5 次 单个 chunk 校验失败后的重试上限
退避策略 指数退避 重试间隔指数增长
校验算法 CRC32 每个 chunk 独立校验
流控 自适应 根据平均传输速度动态调整发送间隔
DataChannel 配置 ordered: true, maxRetransmits: 3 有序可靠传输

4.5.3 完整传输时序

发送方                              信令服务器                           接收方
  │                                     │                                 │
  │  1. POST /api/create-room           │                                 │
  │ ──────────────────────────────────► │                                 │
  │ ◄── {success: true, code: "AB12CD"} │                                 │
  │                                     │                                 │
  │  2. WS connect (role=sender)        │                                 │
  │ ═══════════════════════════════════► │                                 │
  │                                     │                                 │
  │                                     │  3. GET /api/room-info?code=... │
  │                                     │ ◄────────────────────────────── │
  │                                     │ ── {success, status} ─────────► │
  │                                     │                                 │
  │                                     │  4. WS connect (role=receiver)  │
  │                                     │ ◄═══════════════════════════════ │
  │                                     │                                 │
  │  5. ◄── peer-joined ──             │  ── peer-joined ──►             │
  │                                     │                                 │
  │  6. 创建 PeerConnection + DataChannel                                 │
  │  7. 创建 SDP Offer                                                    │
  │  ═══ offer ═══════════════════════► │ ═══ offer ═══════════════════► │
  │                                     │                                 │
  │                                     │  8. 创建 PeerConnection         │
  │                                     │  9. 设置 Remote/Local SDP       │
  │  ◄═══ answer ══════════════════════ │ ◄═══ answer ═════════════════ │
  │                                     │                                 │
  │  10. ICE Candidate 交换 ◄═══════════╋══════════════════════════════► │
  │                                     │                                 │
  │  ══════════ DataChannel OPEN ═══════╪══════════════════════════════ │
  │                                     │                                 │
  │  11. file-list ─────────────────────┼────────────────────────────────► │
  │      [{id, name, size, type}, ...]  │                                 │
  │                                     │                                 │
  │  ◄─────────────────────────────────┼──── 12. file-request ────────── │
  │      {fileId, fileName}             │                                 │
  │                                     │                                 │
  │  13. file-metadata ────────────────┼────────────────────────────────► │
  │      {id, name, size, type}         │                                 │
  │                                     │                                 │
  │  ╔══ 循环每个 chunk ═══════════════╪════════════════════════════════╗ │
  │  ║  14. file-chunk-info ───────────┼──────────────────────────────► ║ │
  │  ║      {fileId, chunkIndex,        │                              ║ │
  │  ║       totalChunks, checksum}     │                              ║ │
  │  ║                                  │                              ║ │
  │  ║  15. [ArrayBuffer 256KB] ───────┼──────────────────────────────► ║ │
  │  ║                                  │                              ║ │
  │  ║  ◄───────────────────────────────┼── 16. file-chunk-ack ─────── ║ │
  │  ║      {fileId, chunkIndex,        │      success, checksum}      ║ │
  │  ║                                  │                              ║ │
  │  ║  [如果 success=false, 指数退避重试最多5次]                      ║ │
  │  ╚══════════════════════════════════╪══════════════════════════════╝ │
  │                                     │                                 │
  │  17. file-complete ────────────────┼────────────────────────────────► │
  │      {fileId}                       │                                 │
  │                                     │                   18. 组装 Blob  │
  │                                     │                       → File   │
  │                                     │                       → 下载    │

4.5.4 文件列表同步机制

发送方选择文件后,文件列表会 实时同步 到接收方:

发送方文件变更 → useFileListSync hook
    │
    ├─ 150ms 防抖debounce
    │
    ├─ 比对新旧文件列表(避免无变更的冗余同步)
    │
    └─ 通过 DataChannel 发送 file-list 消息
        │
        └─ 接收方更新 UI 展示文件列表

4.6 文字传输详细设计

4.6.1 DataChannel 消息协议

text-transfer channel 消息类型:

方向 type payload 说明
S→R text-sync { text: string } 实时文本内容同步
双向 text-typing { typing: boolean } 打字状态指示

4.6.2 实时同步机制

发送方 Textarea 输入
    │
    ├─ onChange → handleTextInputChange()
    │
    ├─ 调用 textTransfer.sendTextSync(text)
    │   └─ DataChannel 发送 { channel: "text-transfer", type: "text-sync", payload: { text } }
    │
    ├─ 同时发送 text-typing: { typing: true }
    │   └─ 1秒后自动发送 text-typing: { typing: false }
    │
    └─ 接收方
        ├─ onTextSync 回调 → 更新显示文本
        └─ onTyping 回调 → 显示 "对方正在输入..." 动画

图片传输:复用文件传输的 file-transfer channel图片作为文件发送。

4.7 桌面共享详细设计

发送方
    │
    ├─ navigator.mediaDevices.getDisplayMedia({ video: true, audio: true })
    │
    ├─ 获取 MediaStream → 提取 video/audio Track
    │
    ├─ trackManager.addTrack(track, stream)
    │   └─ peerConnection.addTrack()
    │
    ├─ trackManager.createOfferNow()  ← 触发 SDP 重协商
    │   └─ 新 Offer → WebSocket → 对方 → Answer → WebSocket → 本方
    │
    └─ 接收方
        ├─ ontrack 事件 → 获取远程 MediaStream
        └─ <video> 元素播放 → DesktopViewer 组件

4.8 ICE 配置设计

默认 STUN 服务器

const DEFAULT_ICE_SERVERS = [
  { urls: 'stun:stun.easyvoip.com:3478' },
  { urls: 'stun:stun.miwifi.com:3478' },
  { urls: 'stun:stun.l.google.com:19302' },
  { urls: 'stun:stun1.l.google.com:19302' },
  { urls: 'stun:global.stun.twilio.com:3478' },
];

用户自定义

  • 通过 WebRTCSettings 界面添加 STUN/TURN 服务器
  • 配置持久化到 localStoragekey: webrtc-ice-config
  • getIceServersConfig() 导出给非 React 代码使用
  • 配置变更后提示用户断开重连

4.9 环境适配设计

                    ┌─────────────────────────────────────────┐
                    │           config.ts 环境判断              │
                    │                                         │
                    │  isStaticMode = Next.js 构建输出判断      │
                    │  isDev = NODE_ENV === 'development'      │
                    └────────────────┬────────────────────────┘
                                     │
                   ┌─────────────────┼─────────────────┐
                   │                 │                 │
          ┌────────▼────────┐ ┌─────▼──────┐ ┌───────▼───────┐
          │ 开发模式          │ │ SSG 静态模式 │ │ 嵌入 Go 模式   │
          │ (yarn dev)       │ │ (build:ssg) │ │ (go run)      │
          │                  │ │             │ │               │
          │ API:             │ │ API:        │ │ API:          │
          │ /api/* → Next.js │ │ 直连 Go     │ │ 同源 /api/*   │
          │ API Route 代理   │ │ 后端地址     │ │               │
          │                  │ │             │ │               │
          │ WS:              │ │ WS:         │ │ WS:           │
          │ ws://localhost   │ │ 当前域名     │ │ 当前域名       │
          │ :8080/...        │ │ ws[s]://    │ │ ws[s]://      │
          └──────────────────┘ └─────────────┘ └───────────────┘

URL 动态计算逻辑

// API URL
getApiUrl()  
  开发模式 ? '/api'  (通过 Next.js API Route 代理到 GO_BACKEND_URL)
  静态模式 ? getDirectBackendUrl() + '/api'  (直连 Go 后端)

// WebSocket URL
getWsUrl() 
  开发模式 ? 'ws://localhost:8080'  (直连 Go 后端 WS)
  静态模式 ? 'ws[s]://' + window.location.host  (同源)

4.10 URL 路由设计

前端通过 URL 参数控制功能和模式:

/?type=webrtc&mode=send            → 文件传输-发送模式
/?type=webrtc&mode=receive         → 文件传输-接收模式
/?type=webrtc&mode=receive&code=ABC123  → 自动填入取件码
/?type=message&mode=send           → 文字消息-发送模式
/?type=desktop&mode=send           → 桌面共享-共享模式

URL 参数映射

  • type: webrtc | message | desktop | wechat | settings → 对应 Tab
  • mode: send | receive → 发送/接收子模式
  • code: 6 位取件码 → 自动填入并可自动加入房间

五、关键设计决策

5.1 为什么用纯 P2P

  • 隐私:文件内容不经服务器,用户数据零留存
  • 成本:服务器仅做信令,带宽成本极低
  • 性能:局域网环境下可接近网卡极限速度

5.2 为什么共享单个 DataChannel

三个功能(文件/文字/桌面控制)共享同一个 shared-channel,通过 JSON channel 字段路由:

  • 简化连接管理:只需建立一次 P2P 连接
  • 减少信令开销:无需为每个功能单独协商
  • 统一状态管理:连接/断开状态全局一致

5.3 为什么用 CRC32 + ACK

WebRTC DataChannel 本身基于 SCTP配置为 ordered: true, maxRetransmits: 3),已有一定可靠性。额外的 CRC32 + ACK 是 应用层二次保障

  • 防止极端情况下 SCTP 重传仍失败后的数据损坏
  • 提供块级粒度的错误恢复(只重传失败的块,无需从头开始)
  • 接收方独立验证数据完整性

5.4 为什么 256KB 块大小?

  • WebRTC DataChannel 最大消息大小约 256KB不同浏览器有差异
  • 小块有利于进度反馈的精度
  • 小块降低单次重传的代价

5.5 为什么关闭 React Strict Mode

// next.config.ts
reactStrictMode: false

React 18+ Strict Mode 会在开发环境 双重调用 effect导致

  • WebSocket 连接被创建两次
  • PeerConnection 生命周期混乱
  • DataChannel 状态不一致

六、部署模式

模式一:开发模式

# 终端 1Go 后端
go run cmd/main.go          # :8080

# 终端 2Next.js 前端
cd chuan-next && yarn dev   # :3000 (turbopack)

前端 API 调用链:浏览器 → localhost:3000/api/* → Next.js API Route → localhost:8080/api/*

模式二生产模式Go 嵌入前端)

cd chuan-next && yarn build:ssg    # 输出到 out/
cp -r out/ ../internal/web/frontend/  # 复制到 Go embed 目录
go build -o chuan cmd/*.go         # 编译
./chuan                            # :8080 同时提供 API + 前端

所有请求都由 Go 单进程处理,前端静态文件通过 go:embed 嵌入二进制。


七、安全与限制

维度 现状
加密 WebRTC 自带 DTLS 加密P2P 数据全程加密传输
认证 无用户认证,仅靠 6 位取件码
房间安全 取件码空间 34^6 ≈ 15 亿,暴力破解概率低
数据留存 服务器零数据留存,所有文件仅在浏览器内存/临时存储
并发限制 每房间最多 2 人,单进程内存管理
文件大小 受限于浏览器内存(大文件需接收方有足够内存组装 Blob
NAT 穿透 依赖 STUN 服务器,对称 NAT 需 TURN 服务器(默认未配置)