加入文档
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
# PrivyDrop - 系统架构总览
|
||||
|
||||
本文档旨在提供一个关于 PrivyDrop 项目整体架构的高层次概览,帮助开发者理解各个技术组件是如何协同工作的。
|
||||
|
||||
## 一、核心组件
|
||||
|
||||
PrivyDrop 系统主要由以下几个核心部分组成:
|
||||
|
||||
1. **前端 (Frontend)**: 一个使用 Next.js 构建的单页应用 (SPA)。它是用户直接交互的界面,负责处理文件选择、UI 展示、发起 WebRTC 连接等所有客户端逻辑。
|
||||
2. **后端 (Backend)**: 一个使用 Node.js 和 Express 构建的服务器。它不处理任何文件数据,其核心职责是:
|
||||
- **信令服务 (Signaling)**: 通过 Socket.IO 实现,为 WebRTC 连接建立前的“握手”过程传递信令消息 (如 SDP 和 ICE Candidates)。
|
||||
- **房间管理 (Room Management)**: 处理房间的创建、加入和状态检查。
|
||||
- **API 服务**: 提供一些辅助性的 HTTP 接口。
|
||||
3. **Redis**: 一个内存数据库,用于后端存储临时数据,如房间信息、房间内的用户列表等,并利用其 TTL 特性自动清理过期房间。
|
||||
4. **TURN/STUN 服务器【可选】**: 用于辅助 WebRTC 进行 NAT 穿透,确保在复杂网络环境下的 P2P 连接成功率。STUN 用于发现公网地址,TURN 则作为最后的备选方案,当中继服务器使用(目前没有使用这个特性)。
|
||||
|
||||
## 二、数据流与交互图
|
||||
|
||||
下图展示了用户建立连接和传输文件的主要流程:
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph "用户A 浏览器"
|
||||
ClientA[Frontend UI]
|
||||
end
|
||||
subgraph "用户B 浏览器"
|
||||
ClientB[Frontend UI]
|
||||
end
|
||||
|
||||
subgraph "服务器基础设施"
|
||||
Nginx[Nginx 反向代理]
|
||||
Backend[Backend API / Socket.IO]
|
||||
Redis[Redis 缓存]
|
||||
TURN[TURN/STUN 服务器]
|
||||
end
|
||||
|
||||
ClientA -- 1.创建/加入房间 (HTTP/Socket) --> Nginx
|
||||
Nginx --> Backend
|
||||
Backend -- 读/写房间状态 --> Redis
|
||||
|
||||
ClientB -- 2.加入同一房间 (HTTP/Socket) --> Nginx
|
||||
|
||||
Backend -- 3.广播用户加入事件 --> ClientA
|
||||
Backend -- 3.广播用户加入事件 --> ClientB
|
||||
|
||||
ClientA -- 4.发送信令 (Offer/ICE) --> Backend
|
||||
Backend -- 5.转发信令 --> ClientB
|
||||
ClientB -- 6.发送信令 (Answer/ICE) --> Backend
|
||||
Backend -- 7.转发信令 --> ClientA
|
||||
|
||||
ClientA <-.-> |8.STUN检查| TURN
|
||||
ClientB <-.-> |8.STUN检查| TURN
|
||||
|
||||
ClientA <-..- |9.P2P直连数据传输| ClientB
|
||||
ClientA <-.-> |9.TURN中继数据传输| TURN
|
||||
ClientB <-.-> |9.TURN中pey数据传输| TURN
|
||||
```
|
||||
|
||||
**流程说明:**
|
||||
|
||||
1. **房间创建/加入**: 用户 A(发送方)通过前端请求后端创建一个唯一的房间 ID。后端在 Redis 中记录该房间。
|
||||
2. **分享与加入**: 用户 A 通过链接或二维码将房间 ID 分享给用户 B。用户 B 使用此 ID 请求加入房间。
|
||||
3. **信令交换**:
|
||||
- 一旦房间内有两位或更多用户,他们便开始通过后端的 Socket.IO 服务交换 WebRTC 信令。
|
||||
- 这个过程包括交换网络信息 (ICE candidates) 和会话描述 (SDP offers/answers)。后端服务器仅作为这些信令消息的“邮差”,进行转发,不理解其内容。
|
||||
4. **NAT 穿透**: 浏览器利用从信令中获取的网络信息,并借助 STUN/TURN 服务器来尝试建立直接的 P2P 连接。
|
||||
5. **P2P 连接建立**: 一旦连接建立成功,所有文件和文本数据都将直接在用户 A 和用户 B 的浏览器之间传输,不再经过任何服务器。如果直连失败,数据将通过 TURN 服务器进行中继。
|
||||
|
||||
## 三、设计哲学
|
||||
|
||||
- **隐私优先**: 核心文件数据永不上传到服务器。服务器只承担“介绍人”的角色。
|
||||
- **前后端分离**: 前后端职责清晰。前端负责所有与用户交互和 WebRTC 的复杂逻辑;后端则提供轻量、高效的信令和房间管理服务。
|
||||
- **水平扩展**: 后端是无状态的(状态存储在 Redis 中),理论上可以通过增加 Node.js 实例来水平扩展,以应对大量并发信令请求。
|
||||
@@ -0,0 +1,135 @@
|
||||
# PrivyDrop - 后端架构详解
|
||||
|
||||
## 一、概述
|
||||
|
||||
### 1.1 核心职责
|
||||
|
||||
PrivyDrop 的后端是一个基于 Node.js 和 Express.js 的轻量级服务器。它的核心职责**并非直接传输文件**,而是作为 WebRTC 连接建立过程中的“**信令服务器**”和“**房间协调员**”。
|
||||
|
||||
主要功能包括:
|
||||
|
||||
- **HTTP API 服务**: 提供 RESTful 接口用于房间的创建、查询和状态检查。
|
||||
- **WebRTC 信令**: 通过 Socket.IO 实时转发客户端之间的信令消息(SDP Offers/Answers, ICE Candidates),以促成 P2P 连接。
|
||||
- **房间生命周期管理**: 使用 Redis 高效地管理房间和参与者的状态,并利用其 TTL 机制实现自动清理。
|
||||
- **基础的安全性**: 实现了基于 IP 的速率限制以防止服务被滥用。
|
||||
|
||||
### 1.2 设计原则
|
||||
|
||||
- **无状态 (Stateless)**: 后端服务本身不持有任何与房间或用户相关的状态。所有状态都被委托给外部的 Redis 服务进行管理,这使得后端应用可以轻松地进行水平扩展。
|
||||
- **轻量级信令**: 服务器仅作为信令消息的中转站,不解析也不存储信令内容,确保了端到端通信的隐私性。
|
||||
- **高效率与低延迟**: 采用 Redis 作为内存数据库来管理房间状态,并通过 Socket.IO 进行实时通信,最大限度地降低了信令交换的延迟。
|
||||
- **职责单一**: 每个模块(API、Socket 处理、Redis 服务)都有明确且单一的职责,易于理解、维护和测试。
|
||||
|
||||
## 二、项目结构
|
||||
|
||||
后端源代码遵循功能模块化的组织方式,主要位于 `src/` 目录中:
|
||||
|
||||
```
|
||||
backend/
|
||||
├── src/
|
||||
│ ├── config/ # 环境变量和服务器配置 (CORS)
|
||||
│ │ ├── env.ts
|
||||
│ │ └── server.ts
|
||||
│ ├── routes/ # API 路由定义 (Express 路由)
|
||||
│ │ └── api.ts
|
||||
│ ├── services/ # 核心业务逻辑 (房间, Redis, 速率限制)
|
||||
│ │ ├── rateLimit.ts
|
||||
│ │ ├── redis.ts
|
||||
│ │ └── room.ts
|
||||
│ ├── socket/ # Socket.IO 事件处理程序和信令逻辑
|
||||
│ │ └── handlers.ts
|
||||
│ ├── types/ # TypeScript 类型定义和接口
|
||||
│ │ ├── room.ts
|
||||
│ │ └── socket.ts
|
||||
│ └── server.ts # 主应用程序入口点: Express 和 Socket.IO 设置
|
||||
├── ecosystem.config.js # PM2 配置文件
|
||||
├── package.json
|
||||
└── tsconfig.json
|
||||
```
|
||||
|
||||
## 三、核心模块详解
|
||||
|
||||
### 3.1 应用入口 (`src/server.ts`)
|
||||
|
||||
这是应用的启动文件。它负责:
|
||||
|
||||
1. 加载环境变量。
|
||||
2. 初始化 Express 应用实例。
|
||||
3. 配置 CORS、JSON 解析等中间件。
|
||||
4. 挂载 `/api` 路由。
|
||||
5. 创建 HTTP 服务器并附加 Socket.IO 服务。
|
||||
6. 调用 `initializeSocketHandlers` 设置所有 Socket.IO 事件监听器。
|
||||
7. 启动服务器并监听指定端口。
|
||||
|
||||
### 3.2 API 路由 (`src/routes/api.ts`)
|
||||
|
||||
定义了所有供前端调用的 HTTP RESTful API。
|
||||
|
||||
- **`POST /api/create_room`**: 接收前端指定的 `roomId`,检查是否可用,如果可用则创建新房间。
|
||||
- **`GET /api/get_room`**: 生成一个唯一的、随机的房间 ID,创建房间后返回给前端。
|
||||
- **`POST /api/check_room`**: 检查给定的 `roomId` 是否已存在。
|
||||
- **`POST /api/set_track`**: 用于追踪流量来源。
|
||||
- **`POST /api/logs_debug`**: 一个简单的调试端点,用于接收前端日志并打印在后端控制台。
|
||||
|
||||
### 3.3 Socket.IO 事件处理 (`src/socket/handlers.ts`)
|
||||
|
||||
这是信令交换的核心。`initializeSocketHandlers` 函数为传入的 `socket` 连接绑定了一系列事件处理器。
|
||||
|
||||
- **连接与断开**:
|
||||
- `connection`: 当一个新客户端连接时,记录其 `socket.id`。
|
||||
- `disconnect`: 当客户端断开时,从其所在的房间中移除,并通知房间内其他对等方 (`peer-disconnected`)。
|
||||
- **房间逻辑**:
|
||||
- **`join`**: 处理客户端加入房间的请求。它会验证房间是否存在,并将该客户端的 `socket.id` 添加到房间的成员集合中,最后向请求方发送 `joinResponse`。
|
||||
- **`initiator-online`**: 由房间创建者(发起者)发出(当 web 被切到后台掉线时),用于通知接收者“我已经上线了,准备重新建立连接”。
|
||||
- **`recipient-ready`**: 由接收者发出,通知房间内的发起者“准备就绪,可以开始重连”,这通常是触发 WebRTC `offer` 流程的信号。
|
||||
- **WebRTC 信令转发**:
|
||||
- **`offer`**, **`answer`**, **`ice-candidate`**: 这三个事件是纯粹的信使,负责将一个对等方的 WebRTC 信令消息准确无误地转发给房间内的另一个对等方。
|
||||
|
||||
### 3.4 服务层 (`src/services/`)
|
||||
|
||||
封装了与外部依赖(如 Redis)和核心业务逻辑的交互。
|
||||
|
||||
- **`redis.ts`**: 提供了 Redis 客户端的单例实例。所有与 Redis 的交互都应通过此模块。
|
||||
- **`room.ts`**: 封装了所有与房间相关的 Redis 操作。例如 `createRoom`, `isRoomExist`, `bindSocketToRoom` 等。它将业务逻辑(如“将用户添加到房间”)与底层 Redis 命令(如 `SADD`, `HSET`)解耦。
|
||||
- **`rateLimit.ts`**: 实现了一个基于 IP 和 Redis Sorted Set 的速率限制器,用于限制用户在短时间内频繁创建或加入房间。
|
||||
|
||||
## 四、Redis 数据结构详解
|
||||
|
||||
Redis 是后端的关键组件,用于存储所有临时状态。我们巧妙地利用了不同的数据结构来满足业务需求,并为所有键设置了 TTL,以确保数据能自动清理。
|
||||
|
||||
- **1. 房间信息 (`Hash`)**:
|
||||
|
||||
- **键模式**: `room:<roomId>` (例如: `room:ABCD12`)
|
||||
- **用途**: 存储房间的元数据。
|
||||
- **字段**:
|
||||
- `created_at`: 房间创建时的时间戳。
|
||||
- **示例**: `HSET room:ABCD12 created_at 1705123456789`
|
||||
|
||||
- **2. 房间内的套接字 (`Set`)**:
|
||||
|
||||
- **键模式**: `room:<roomId>:sockets` (例如: `room:ABCD12:sockets`)
|
||||
- **用途**: 存储一个房间内所有客户端的 `socketId`。使用 Set 可以保证成员的唯一性,并方便地进行添加和删除。
|
||||
- **成员**: 客户端的 `socketId`。
|
||||
- **示例**: `SADD room:ABCD12:sockets "socketId_A" "socketId_B"`
|
||||
|
||||
- **3. 套接字到房间的映射 (`String`)**:
|
||||
|
||||
- **键模式**: `socket:<socketId>` (例如: `socket:xgACY6QcQCojsOQaAAAB`)
|
||||
- **用途**: 将一个 `socketId` 反向映射到它所属的 `roomId`。这在处理客户端断开连接时非常有用,我们仅需 `socketId` 即可快速找到其房间并执行清理。
|
||||
- **值**: `roomId`。
|
||||
- **示例**: `SET socket:xgACY6QcQCojsOQaAAAB ABCD12`
|
||||
|
||||
- **4. 速率限制 (`Sorted Set`)**:
|
||||
|
||||
- **键模式**: `ratelimit:join:<ipAddress>` (例如: `ratelimit:join:192.168.1.100`)
|
||||
- **用途**: 记录特定 IP 地址在指定时间窗口内的所有请求时间戳。
|
||||
- **成员**: `timestamp-randomNumber` (例如: `1678886400000-0.12345`)。使用随机数后缀确保同一毫秒内多个请求的唯一性。
|
||||
- **分数**: 请求的 Unix 时间戳(毫秒)。
|
||||
- **逻辑**: 通过 `ZREMRANGEBYSCORE` 移除时间窗口外的旧记录,再用 `ZCARD` 统计窗口内的请求数,从而判断是否超出限制。
|
||||
|
||||
- **5. 来源跟踪 (`Hash`)**:
|
||||
- **键模式**: `referrers:daily:<YYYY-MM-DD>` (例如: `referrers:daily:2023-03-15`)
|
||||
- **用途**: 按天统计不同来源(Referrer)的访问次数。
|
||||
- **字段**: 来源域名 (例如: `google.com`, `github.com`)。
|
||||
- **值**: 当天的累计访问次数。
|
||||
- **逻辑**: 使用 `HINCRBY` 命令原子性地增加指定来源的计数值。
|
||||
@@ -0,0 +1,248 @@
|
||||
# Privydrop 部署指南
|
||||
|
||||
本指南提供部署 Privydrop 全栈应用的全面说明,包括设置 Redis、TURN 服务器、后端服务、前端应用以及配置 Nginx 作为反向代理。
|
||||
|
||||
## 1. 引言
|
||||
|
||||
本文档将引导您完成准备服务器环境、配置依赖项和部署 Privydrop 的前后端。无论您是设置开发/测试环境还是完整的生产实例,本指南都旨在涵盖所有基本方面。
|
||||
|
||||
## 2. 先决条件
|
||||
|
||||
在开始之前,请确保您的服务器环境满足以下要求:
|
||||
|
||||
- **操作系统:** Linux 发行版(例如,推荐 Ubuntu 20.04 LTS 或更高版本)。
|
||||
- **Node.js:** v18.x 或更高版本。
|
||||
- **npm (或 yarn/pnpm):** Node.js 的包管理器。
|
||||
- **Root 或 Sudo 权限:** 安装软件包和配置服务所需。
|
||||
- **域名:** 生产环境部署需要一个域名。
|
||||
- **可选:基础环境与 Docker 镜像参考:** 如果您需要从一个非常纯净的系统环境开始搭建,或者希望了解用于 Docker 构建的基础依赖,可以参考 `backend/docker/Dockerfile` 文件(用于 Docker 镜像构建)和 `backend/docker/env_install.log` 文件(依赖安装记录)。
|
||||
|
||||
## 3. 依赖服务安装与配置
|
||||
|
||||
### 3.1. Redis 服务器
|
||||
|
||||
Redis 用于后端的房间管理、会话信息和缓存。
|
||||
|
||||
**安装 (Ubuntu 示例):**
|
||||
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install redis-server
|
||||
```
|
||||
|
||||
**配置:**
|
||||
|
||||
- 默认情况下,Redis 监听 `127.0.0.1:6379` 且无需密码。请确保后端的 `.env` 文件中包含 `REDIS_HOST` 和 `REDIS_PORT`。
|
||||
- 确保 Redis 正在运行:`sudo systemctl status redis-server`
|
||||
- 如果未运行,请启动:`sudo systemctl start redis-server`
|
||||
|
||||
### 3.2. TURN/STUN 服务器 (Coturn)
|
||||
|
||||
**重要提示:本节为可选配置。** Privydrop 默认仅使用公共 STUN 服务器,在多数网络环境下足以建立连接。只有当您对 NAT 穿透成功率有极高要求时,才需要搭建自己的 TURN 服务器。
|
||||
|
||||
TURN 服务器对于 WebRTC 穿透 NAT 和防火墙至关重要。Coturn 是一个流行的实现。
|
||||
|
||||
**安装 (Ubuntu 示例):**
|
||||
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install coturn
|
||||
```
|
||||
|
||||
**配置:**
|
||||
|
||||
1. **启用 Coturn 服务:** 编辑 `/etc/default/coturn` 并取消注释 `TURNSERVER_ENABLED=1`。
|
||||
2. **防火墙配置:** 在服务器的防火墙上打开必要的端口 (例如,使用 `ufw`):
|
||||
- TCP & UDP `3478`: 用于 STUN 和 TURN。
|
||||
- TCP & UDP `5349`: 用于 TURNS (TURN over TLS/DTLS) - **生产环境**。
|
||||
- UDP `49152-65535`: Coturn 的默认中继端口范围。
|
||||
```bash
|
||||
sudo ufw allow 3478
|
||||
sudo ufw allow 5349
|
||||
sudo ufw allow 49152:65535/udp
|
||||
sudo ufw enable
|
||||
```
|
||||
3. **生产环境的 SSL 证书 (用于 TURNS):**
|
||||
为你的 TURN 域名 (例如 `turn.yourdomain.com`) 获取 SSL 证书。
|
||||
```bash
|
||||
# 确保 DNS 'A' 记录将 turn.yourdomain.com 指向服务器 IP
|
||||
sudo apt install certbot
|
||||
sudo certbot certonly --standalone -d turn.yourdomain.com
|
||||
```
|
||||
4. **SSL 证书权限验证:**
|
||||
Coturn 进程(通常以用户 `turnserver` 运行)需要读取 SSL 证书和密钥的权限。
|
||||
|
||||
- 检查当前权限:
|
||||
```bash
|
||||
sudo ls -lh /etc/letsencrypt/live/turn.yourdomain.com/fullchain.pem
|
||||
sudo ls -ld /etc/letsencrypt/archive/
|
||||
```
|
||||
- 如果 Coturn 日志显示权限错误:
|
||||
创建一个组(例如 `ssl-cert`),将 `turnserver` 添加到该组,并调整权限:
|
||||
```bash
|
||||
sudo groupadd -f ssl-cert
|
||||
# 查找 coturn 运行的用户,通常是 'turnserver' 或 'coturn'
|
||||
# ps aux | grep turnserver
|
||||
sudo usermod -a -G ssl-cert turnserver # 如果不同,请替换 'turnserver'
|
||||
sudo chown -R root:ssl-cert /etc/letsencrypt/
|
||||
sudo chmod -R 750 /etc/letsencrypt/
|
||||
```
|
||||
验证 `/etc/letsencrypt/archive/` 和 `/etc/letsencrypt/live/` 上的新权限。
|
||||
|
||||
5. **配置并启动 Coturn:**
|
||||
|
||||
- 在后端的 `.env` 文件中配置 `TURN_*` 相关环境变量(如用户名、密码、证书路径等)。
|
||||
- 在测试环境下需要填入的变量为:
|
||||
```
|
||||
TURN_EXTERNAL_IP=YourServerPublicIP # 例如: 123.123.456.567
|
||||
TURN_REALM=YourServerPublicIP
|
||||
TURN_USERNAME=YourTurnUsername
|
||||
TURN_PASSWORD=YourTurnPassword
|
||||
```
|
||||
- 在生产部署环境下需要填入的变量为:
|
||||
```
|
||||
TURN_EXTERNAL_IP=YourServerPublicIP # 例如: 123.123.456.567
|
||||
TURN_REALM=turn.yourdomain
|
||||
TURN_USERNAME=YourTurnUsername
|
||||
TURN_PASSWORD=YourTurnPassword
|
||||
TURN_CERT_PATH=/etc/letsencrypt/live/turn.yourdomain/fullchain.pem
|
||||
TURN_KEY_PATH=/etc/letsencrypt/live/turn.yourdomain/privkey.pem
|
||||
```
|
||||
- 使用项目提供的脚本生成配置文件并启动服务:
|
||||
|
||||
```bash
|
||||
# 位于 backend/ 目录
|
||||
sudo bash ./docker/TURN/configure.sh path/to/your/.env.production.local
|
||||
# 开发环境使用 .env.development.local
|
||||
sudo systemctl status coturn
|
||||
```
|
||||
|
||||
- 检查日志 `/var/log/turnserver.log` 确认无误。
|
||||
|
||||
6. **在线测试(可选):**
|
||||
使用在线工具,如 Metered TURN Server Tester (https://www.metered.ca/turn-server-testing):
|
||||
|
||||
- **用于开发/测试 (非 TLS):**
|
||||
- TURN URL: `你的服务器公网IP`
|
||||
- TURN Port: `3478`
|
||||
- 用户名: `你的Turn用户名`
|
||||
- 密码: `你的Turn密码`
|
||||
- **用于生产 (TURNS):**
|
||||
- TURNS URL: `turn.yourdomain`
|
||||
- TURNS Port: `5349`
|
||||
- 用户名: `你的Turn用户名`
|
||||
- 密码: `你的Turn密码`
|
||||
|
||||
正常的话,能看到 "Reachable" 消息。
|
||||
|
||||
## 4. 应用部署 (生产环境)
|
||||
|
||||
本节介绍如何使用 Nginx 和 PM2 在生产环境部署 PrivyDrop。
|
||||
|
||||
### 4.1. 获取代码并安装依赖
|
||||
|
||||
```bash
|
||||
git clone <your-repository-url> privydrop
|
||||
cd privydrop
|
||||
|
||||
# 安装后端依赖
|
||||
cd backend && npm install && cd ..
|
||||
|
||||
# 安装前端依赖
|
||||
cd frontend && pnpm install && cd ..
|
||||
```
|
||||
|
||||
### 4.2. 配置环境变量
|
||||
|
||||
- **后端:**
|
||||
- 在 `backend/` 目录下创建 `.env.production.local` 或 `.env.development.local` 文件
|
||||
- 在 `.env.development.local` 文件中填入环境变量 (BACKEND_PORT, REDIS_HOST, REDIS_PORT, CORS_ORIGIN)。
|
||||
- 在 `.env.production.local` 文件中除了上述变量外,还要加入 (NGINX_SERVER_NAME, NGINX_SSL_CERT, NGINX_SSL_KEY, NGINX_FRONTEND_ROOT)。
|
||||
- **前端:** 在 `frontend/` 目录下创建 `.env.production` 或 `.env.development` 文件,并填入环境变量 (NEXT_PUBLIC_API_URL)。
|
||||
|
||||
### 4.3. 构建前端应用
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
pnpm build
|
||||
```
|
||||
|
||||
这将在 `frontend/.next` 目录生成优化后的生产版本。
|
||||
|
||||
### 4.4. 使用 PM2 运行应用
|
||||
|
||||
PM2 是一个强大的 Node.js 进程管理器,我们将用它来分别运行后端服务和前端服务。
|
||||
|
||||
1. **全局安装 PM2:**
|
||||
|
||||
```bash
|
||||
sudo npm install -g pm2
|
||||
```
|
||||
|
||||
2. **启动后端服务:**
|
||||
项目后端目录提供了一个 `ecosystem.config.js` 文件用于 PM2。
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
# 确保 .env.production.local 已配置完毕
|
||||
pm2 start ecosystem.config.js
|
||||
```
|
||||
|
||||
3. **启动前端服务:**
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
pm2 start npm --name "privydrop-frontend" -- run start
|
||||
```
|
||||
|
||||
`npm start` 会启动 Next.js 的生产服务器,默认监听 3000 端口。
|
||||
|
||||
4. **管理应用**
|
||||
- 查看状态: `pm2 list`
|
||||
- 查看日志: `pm2 logs <app_name>`
|
||||
- 设置开机自启: `pm2 startup` 然后 `pm2 save`
|
||||
|
||||
### 4.5. 配置 Nginx 作为反向代理
|
||||
|
||||
在生产中,Nginx 将作为所有流量的入口,负责 SSL 终止,并将请求路由到正确的前端或后端服务。
|
||||
|
||||
1. **安装 Nginx:** 推荐安装支持 HTTP/3 的较新版本。
|
||||
|
||||
2. **防火墙:** 确保 `TCP:80 (HTTP)` 和 `TCP/UDP:443 (HTTPS/HTTP3)` 端口已打开。
|
||||
|
||||
3. **主域名 SSL 证书:** 为你的主域名 (如 `yourdomain.com`) 获取证书。
|
||||
|
||||
```bash
|
||||
sudo apt install python3-certbot-nginx
|
||||
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
|
||||
```
|
||||
|
||||
4. **Nginx 配置文件:**
|
||||
后端项目 `backend/docker/Nginx/` 目录中提供了配置脚本和模板。
|
||||
|
||||
- 在后端的 `.env.production.local` 文件中添加 `NGINX_*` 相关变量,包括域名、证书路径和**前端构建产物的根目录**,示例为:
|
||||
|
||||
```
|
||||
NGINX_SERVER_NAME=yourdomain # 不带 www 前缀,yourdomain包含了后缀
|
||||
NGINX_SSL_CERT=/etc/letsencrypt/live/yourdomain/fullchain.pem
|
||||
NGINX_SSL_KEY=/etc/letsencrypt/live/yourdomain/privkey.pem
|
||||
NGINX_FRONTEND_ROOT=/path/to/your/frontend/build # 前端静态文件构建产物的路径
|
||||
```
|
||||
|
||||
5. **应用配置:** 创建软链接并重启 Nginx。
|
||||
```bash
|
||||
# 此脚本会使用 .env.production.local 中的 NGINX_* 变量来生成 Nginx 配置文件
|
||||
sudo bash docker/Nginx/configure.sh .env.production.local
|
||||
```
|
||||
|
||||
## 5. 故障排除
|
||||
|
||||
- **连接问题:** 检查防火墙、Nginx 代理设置、CORS_ORIGIN 配置,确保所有 PM2 进程都在运行。
|
||||
- **Nginx 错误:** `sudo nginx -t` 检查语法,查看 `/var/log/nginx/error.log`。
|
||||
- **PM2 问题:** `pm2 logs <app_name>` 查看应用日志。
|
||||
- **证书权限 (生产环境):** 如果 Coturn 或 Nginx 无法读取 SSL 证书,请仔细检查文件权限和用户/组设置。
|
||||
|
||||
## 7. 安全与维护
|
||||
|
||||
- **SSL 证书续订 (生产环境相关):** 可以参考 `backend/docker/Nginx/renew_ssl.sh` 脚本进行自动续订。
|
||||
- **防火墙:** 保持防火墙规则严格,仅允许必要的端口。
|
||||
@@ -0,0 +1,141 @@
|
||||
# Privydrop 前端架构文档
|
||||
|
||||
## 一、 架构总览
|
||||
|
||||
### 1.1 项目愿景
|
||||
|
||||
Privydrop 是一个基于 WebRTC 的 P2P 文件/文本分享工具,旨在提供一个安全、私密、高效的在线分享解决方案。前端架构的核心目标是构建一个高性能、易于维护、可扩展的现代化 Web 应用,并遵循 Next.js 的最佳实践。
|
||||
|
||||
### 1.2 设计理念
|
||||
|
||||
在最近的重构中,我们确立了以“**关注点分离**”和“**逻辑内聚**”为核心的设计理念:
|
||||
|
||||
- **UI 与逻辑分离**: 视图(Components)应尽可能保持“纯粹”,仅负责渲染 UI 和响应用户交互。所有复杂的业务逻辑、状态管理和副作用都应从组件中剥离。
|
||||
- **Hooks 作为业务逻辑核心**: 自定义 React Hooks 是我们组织业务逻辑和状态的第一公民。每个 Hook 封装一个独立的、高内聚的功能模块(如 WebRTC 连接、房间管理),使得逻辑单元可复用、可测试,并极大地简化了组件树。
|
||||
- **分层架构**: 代码库遵循清晰的分层结构,确保不同层次的职责单一,降低模块间的耦合度。
|
||||
|
||||
### 1.3 核心技术栈
|
||||
|
||||
- **框架**: Next.js 14 (App Router)
|
||||
- **语言**: TypeScript
|
||||
- **UI**: React 18, Tailwind CSS, shadcn/ui (基于 Radix UI)
|
||||
- **状态管理**: 以自定义 React Hooks 为核心的模块化状态管理
|
||||
- **WebRTC 信令**: Socket.IO Client
|
||||
- **数据获取**: React Server Components (RSC), Fetch API
|
||||
- **国际化**: `next/server` 中间件 + 动态 JSON 字典
|
||||
- **内容**: MDX (用于博客和静态内容页面)
|
||||
|
||||
### 1.4 高阶分层模型
|
||||
|
||||
应用的前端架构可大致分为四个层次:
|
||||
|
||||
```
|
||||
+---------------------------------------------------+
|
||||
| ① 应用与路由层 (App Router) | app/
|
||||
| (页面、布局、路由、国际化中间件、数据获取) |
|
||||
+---------------------------------------------------+
|
||||
| ② UI 与组件层 | components/
|
||||
| (协调器组件、UI 面板、通用组件、基础 UI 元素) |
|
||||
+---------------------------------------------------+
|
||||
| ③ 业务逻辑与状态层 (Hooks) | hooks/
|
||||
| (WebRTC 连接、房间管理、文件传输、剪贴板操作等) |
|
||||
+---------------------------------------------------+
|
||||
| ④ 核心库与工具层 | lib/
|
||||
| (底层 WebRTC 封装、文件处理、工具函数、API 客户端) |
|
||||
+---------------------------------------------------+
|
||||
```
|
||||
|
||||
- **① 应用与路由层**: 由 Next.js App Router 管理,负责页面渲染、路由控制、国际化和初始数据获取。
|
||||
- **② UI 与组件层**: 负责所有用户界面的展示。它消费来自下一层 (Hooks) 的状态和方法,并将用户交互事件向上传递。
|
||||
- **③ 业务逻辑与状态层**: **这是应用的“大脑”**。通过一系列自定义 Hooks,封装了所有核心功能的业务逻辑和状态。
|
||||
- **④ 核心库与工具层**: 提供最底层的、与框架无关的纯粹功能,如 WebRTC 的底层封装、API 请求等。
|
||||
|
||||
---
|
||||
|
||||
## 二、 核心功能实现:P2P 文件/文本传输
|
||||
|
||||
本节将详细阐述应用最核心的 P2P 传输功能是如何通过不同架构层次协同实现的。
|
||||
|
||||
### 2.1 整体流程
|
||||
|
||||
1. **用户操作 (`components`)**: 用户在 `SendTabPanel` 或 `RetrieveTabPanel` 中进行操作(如选择文件、输入房间号)。
|
||||
2. **逻辑处理 (`hooks`)**: `useFileTransferHandler` 和 `useRoomManager` 等 Hooks 捕捉这些操作,管理相关状态(如待发送文件列表),并调用 `useWebRTCConnection` Hook 提供的连接方法。
|
||||
3. **连接建立 (`hooks` -> `lib`)**: `useWebRTCConnection` 调用 `lib/webrtc_*.ts` 中的底层方法,通过 Socket.IO 信令服务器与对端协商,建立 `RTCPeerConnection`。
|
||||
4. **数据传输 (`lib`)**: 连接建立后,`lib/fileSender.ts` 和 `lib/fileReceiver.ts` 负责将文件分片、序列化,并通过 `RTCDataChannel` 进行传输。
|
||||
5. **状态更新与回调 (`lib` -> `hooks` -> `components`)**: 传输过程中,`lib` 层通过回调函数(如进度更新)通知 `hooks` 层更新状态,`hooks` 层的状态变化最终驱动 `components` 层的 UI 重新渲染。
|
||||
|
||||
### 2.2 模块详解
|
||||
|
||||
- **WebRTC 底层封装 (`lib/`)**:
|
||||
|
||||
- `webrtc_base.ts`: 封装了与 Socket.IO 信令服务器的交互、`RTCPeerConnection` 的通用管理(ICE、连接状态)和 `RTCDataChannel` 的创建,是所有 WebRTC 操作的基石。
|
||||
- `fileSender.ts` / `fileReceiver.ts`: 负责文件/文本的发送和接收逻辑,包括元数据交换、文件分块、进度计算、数据拼接和文件保存等。
|
||||
|
||||
- **WebRTC 业务逻辑封装 (`hooks/`)**:
|
||||
|
||||
- `useWebRTCConnection.ts`: **连接的“中枢管理员”**。它初始化和管理 `sender` 和 `receiver` 实例,处理连接生命周期,并向上层提供一个简洁的 API(如 `broadcastDataToAllPeers`)和状态(如 `peerCount`, `sendProgress`)。
|
||||
- `useRoomManager.ts`: **房间的“状态机”**。负责房间 ID 的创建、验证(带防抖)、加入逻辑,并管理房间相关的 UI 状态文本。
|
||||
- `useFileTransferHandler.ts`: **传输内容的“数据中心”**。管理待发送/已接收的文本和文件,处理文件添加/移除/下载等用户操作,并为 `useWebRTCConnection` 提供处理接收数据的回调函数。
|
||||
|
||||
- **UI 协调与展示 (`components/`)**:
|
||||
- `ClipboardApp.tsx`: **核心应用的“总协调员”**。它不包含任何业务逻辑,其唯一职责是集成上述所有核心 Hooks,然后将从 Hooks 中获取的状态和回调函数作为 props 分发给具体的 UI 子组件。
|
||||
- `SendTabPanel.tsx` / `RetrieveTabPanel.tsx`: 纯粹的 UI 展示组件,负责渲染发送和接收面板,并响应用户的输入。
|
||||
- `FileListDisplay.tsx`: 用于展示文件列表和传输状态。
|
||||
|
||||
## 三、 应用层详细架构
|
||||
|
||||
### 3.1 目录结构与职责
|
||||
|
||||
- **`frontend/app/`**: 应用核心路由和页面。
|
||||
|
||||
- `[lang]/`: 实现多语言动态路由。
|
||||
- `layout.tsx`: 全局布局,提供 Provider (Theme, i18n)。
|
||||
- `page.tsx`: 主页入口,渲染 `HomeClient`。
|
||||
- `HomeClient.tsx`: (客户端组件) 承载核心应用 `ClipboardApp` 和其他营销展示组件。
|
||||
- `config/`: 应用配置。
|
||||
- `api.ts`: 统一封装与后端 API 的交互。
|
||||
- `environment.ts`: 管理环境变量和运行时配置(如 ICE 服务器)。
|
||||
|
||||
- **`frontend/components/`**: UI 组件库。
|
||||
|
||||
- `ClipboardApp/`: `ClipboardApp` 拆分出的所有 UI 子组件。**实现了关注点分离**。
|
||||
- `common/`: 可在项目中多处复用的通用组件 (如 `YouTubePlayer`)。
|
||||
- `ui/`: (来自 shadcn/ui) 基础原子组件。
|
||||
- `web/`: 网站页面级别的大型静态组件 (如 `Header`, `Footer`)。
|
||||
- `Editor/`: 自定义富文本编辑器。
|
||||
|
||||
- **`frontend/hooks/`**: **业务逻辑和状态管理的核心**。上面已详述。
|
||||
|
||||
- **`frontend/lib/`**: 核心库与工具函数。
|
||||
|
||||
- `webrtc_*.ts`, `fileSender.ts`, `fileReceiver.ts`: WebRTC 核心。
|
||||
- `dictionary.ts`: 国际化字典加载。
|
||||
- `utils.ts`, `fileUtils.ts`: 通用工具函数。
|
||||
|
||||
- **`frontend/types/`**: 全局 TypeScript 类型定义。
|
||||
|
||||
- **`frontend/constants/`**: 应用范围内的常量,主要是 i18n 配置和消息文件。
|
||||
|
||||
### 3.2 状态管理策略
|
||||
|
||||
项目**以自定义 React Hooks 为核心进行模块化状态管理**。我们刻意避免了引入全局状态管理库(如 Redux, Zustand),理由如下:
|
||||
|
||||
- **降低复杂性**: 对于当前应用规模,全局状态会引入不必要的复杂性。
|
||||
- **促进内聚**: 将相关联的状态和逻辑封装在同一个 Hook 内,使得代码更易于理解和维护。
|
||||
- **利用 React 原生能力**: 通过 Context 和 Props 传递由 Hooks 管理的状态,足以满足当前所有需求。
|
||||
|
||||
### 3.3 国际化 (i18n)
|
||||
|
||||
- **路由驱动**: 通过 URL 路径 (`/[lang]/`) 实现语言切换。
|
||||
- **自动检测**: `middleware.ts` 拦截请求,根据 `Accept-Language` 头或 Cookie 自动重定向到合适的语言路径。
|
||||
- **动态加载**: `lib/dictionary.ts` 中的 `getDictionary` 函数根据 `lang` 参数异步加载对应的 `messages/*.json` 文件,实现了代码分割。
|
||||
|
||||
## 四、 总结与展望
|
||||
|
||||
当前的前端架构通过分层设计和以 Hooks 为中心的逻辑封装,成功地将一个复杂的 WebRTC 应用拆解为一系列清晰、可维护的模块。UI、业务逻辑和底层库之间的界限分明,为未来的功能扩展和维护奠定了坚实的基础。
|
||||
|
||||
未来可优化的方向包括:
|
||||
|
||||
- **增加单元/集成测试**: 为核心的 Hooks (`useWebRTCConnection` 等) 和 `lib` 中的工具类编写测试用例。
|
||||
- **Bundle 分析**: 定期使用 `@next/bundle-analyzer` 分析打包体积,寻找优化点。
|
||||
- **组件库完善**: 持续沉淀和打磨 `components/common` 中的通用组件。
|
||||
Reference in New Issue
Block a user