从云盘存储到 WebRTC 在线传输:一体化文件平台的功能与技术架构设计

前言

在开发文件管理类产品时,最初的需求往往很简单:

用户上传一个文件,然后在其他设备上下载。

但随着使用场景增加,系统很快就会面临更多问题:

  • 有些文件需要长期保存;

  • 有些文件只需要临时发送一次;

  • 有些用户希望限制下载次数;

  • 有些文件需要自动过期;

  • 有些场景需要让别人向自己提交文件;

  • 有些用户希望电脑和手机之间直接传输;

  • 开发者还可能需要分发 APK 或 IPA 安装包;

  • 涉及证件、合同等文件时,还需要更严格的隐私保护。

如果每个需求都使用一个独立工具,文件会分散在多个平台中,用户也需要反复注册、上传、下载和整理。

基于这些实际场景,我们设计了一套一体化文件平台,将云盘存储、文件快传、在线传输、文件收集、笔记、便签、隐私空间和应用分发整合在同一个系统中。

本文主要从产品设计和技术实现两个角度,介绍一套综合型文件平台应当如何划分业务模块,以及不同传输方式之间应该如何配合。


一、为什么云盘不能只解决“存文件”

传统云盘的核心流程通常是:

选择文件
  ↓
上传到服务器
  ↓
保存到对象存储
  ↓
生成下载地址
  ↓
接收方下载

这套流程适合需要长期保存的文件,但并不适合所有场景。

例如,用户只是想把一个视频从电脑传到手机。如果仍然要求用户:

  1. 登录云盘;

  2. 上传文件;

  3. 等待上传完成;

  4. 在手机登录;

  5. 下载文件;

  6. 再删除云端文件;

整个过程就比较繁琐。

因此,在文件平台设计中,应该根据文件的生命周期和使用目的,将业务拆分为多个模式。


二、文件业务的四种典型模式

一套完整的文件平台,通常至少需要覆盖以下四种模式。

1. 长期存储

适用于:

  • 个人资料备份;

  • 图片和视频保存;

  • 工作文档归档;

  • 多设备同步访问;

  • 项目文件长期管理。

这类文件应进入正式云盘目录,并支持:

  • 文件夹分类;

  • 重命名;

  • 移动;

  • 收藏;

  • 分享;

  • 回收站;

  • 权限管理。

2. 临时快传

适用于:

  • 临时发送压缩包;

  • 给客户发送一次性文件;

  • 分享短期有效资料;

  • 限制文件下载次数。

这类文件不一定需要长期进入用户云盘,而是应该支持自动过期。

3. 实时在线传输

适用于:

  • 电脑向手机传文件;

  • 两台电脑之间互传文件;

  • 双方同时在线;

  • 不希望文件长期存储在服务器。

这种场景可以优先使用 WebRTC 建立点对点连接。

4. 文件收集

适用于:

  • 教师收作业;

  • 企业收员工资料;

  • 项目负责人收集协作文件;

  • 摄影师收集客户素材;

  • 活动方收集报名附件。

这种模式的方向和普通分享相反。

普通分享是:

我把文件发给别人

文件收集则是:

别人把文件提交给我

这两种模式在权限设计、目录管理和页面逻辑上完全不同。


三、整体系统架构

一体化文件平台可以拆分为以下几层:

用户端
├── Web 浏览器
├── 手机浏览器
└── 管理控制台

业务层
├── 云盘存储
├── 文件分享
├── 文件快传
├── 在线传输
├── 文件收集
├── 云笔记
├── 云便签
├── 隐私空间
└── 应用内测分发

服务层
├── 用户服务
├── 文件服务
├── 上传服务
├── 下载服务
├── 分享服务
├── WebSocket 信令服务
├── 权限验证服务
└── 任务调度服务

基础设施层
├── 对象存储
├── CDN
├── 数据库
├── Redis
├── STUN/TURN
└── 日志与监控

不同业务模块可以共用底层文件服务,但在权限、生命周期和访问方式上保持独立。


四、云盘存储模块设计

云盘存储是整个系统的基础。

1. 文件与目录数据结构

数据库中通常不直接保存文件内容,而是保存文件元数据。

例如:

CREATE TABLE files (
    id BIGINT PRIMARY KEY,
    user_id BIGINT NOT NULL,
    parent_id BIGINT DEFAULT 0,
    file_name VARCHAR(255) NOT NULL,
    storage_key VARCHAR(512),
    file_size BIGINT DEFAULT 0,
    mime_type VARCHAR(128),
    file_hash VARCHAR(128),
    is_folder TINYINT DEFAULT 0,
    status TINYINT DEFAULT 1,
    created_at DATETIME,
    updated_at DATETIME
);

其中:

  • parent_id 用于实现多级目录;

  • storage_key 对应对象存储中的真实路径;

  • file_hash 可以用于秒传和重复文件检测;

  • status 可以区分正常、回收站和彻底删除状态;

  • 文件夹本身不需要对应对象存储文件。

2. 文件内容与元数据分离

文件内容保存在对象存储中,数据库只保存索引信息。

这样可以避免:

  • 数据库体积过大;

  • 数据库备份困难;

  • 大文件读写阻塞;

  • 应用服务器磁盘不足。

典型流程如下:

浏览器申请上传凭证
        ↓
服务端验证用户权限和空间
        ↓
浏览器直接上传对象存储
        ↓
对象存储返回结果
        ↓
服务端写入文件元数据

相比文件先经过业务服务器,再上传到对象存储,客户端直传可以减少应用服务器的带宽压力。


五、分片上传与大文件处理

大文件不能始终使用普通单请求上传。

如果上传一个数GB文件时网络发生短暂中断,用户不应该从头重新上传。

因此需要实现分片上传。

1. 分片上传流程

创建上传任务
    ↓
文件切片
    ↓
并发上传多个分片
    ↓
记录成功分片
    ↓
失败分片重试
    ↓
服务端或对象存储合并
    ↓
写入完整文件记录

前端切片示例:

function createChunks(file, chunkSize = 10 * 1024 * 1024) {
  const chunks = [];
  let start = 0;
  let index = 0;

  while (start < file.size) {
    const end = Math.min(start + chunkSize, file.size);

    chunks.push({
      index,
      blob: file.slice(start, end),
      start,
      end
    });

    start = end;
    index += 1;
  }

  return chunks;
}

2. 断点续传

服务端应该保存已经上传成功的分片编号。

用户重新打开页面后,可以先查询任务状态:

{
  "uploadId": "upload_123456",
  "uploadedParts": [1, 2, 3, 5, 6]
}

客户端只上传缺少的第4片以及后续分片。

3. 文件哈希

可以通过文件哈希实现:

  • 文件完整性校验;

  • 重复文件识别;

  • 快速上传;

  • 分片校验。

但浏览器对超大文件计算完整哈希可能比较耗时,因此可以采用:

  • Web Worker计算;

  • 抽样哈希;

  • 首尾分片加文件大小;

  • 后台异步完整校验。


六、文件快传与生命周期控制

文件快传和普通云盘上传最大的区别,是文件具有明确的失效条件。

例如:

  • 保存24小时;

  • 保存12小时;

  • 保存6小时;

  • 保存60分钟;

  • 下载100次后失效;

  • 下载10次后失效;

  • 下载1次后失效;

  • 接收后立即删除。

1. 快传任务表

CREATE TABLE transfer_tasks (
    id BIGINT PRIMARY KEY,
    user_id BIGINT,
    transfer_code VARCHAR(64) UNIQUE,
    storage_key VARCHAR(512) NOT NULL,
    file_name VARCHAR(255),
    file_size BIGINT,
    expire_at DATETIME,
    max_downloads INT DEFAULT 1,
    download_count INT DEFAULT 0,
    status TINYINT DEFAULT 1,
    created_at DATETIME
);

下载前需要同时判断:

当前时间是否超过 expire_at
下载次数是否达到 max_downloads
任务状态是否有效
文件对象是否仍然存在

验证示例:

function canDownload(task) {
  if (task.status !== 1) {
    return false;
  }

  if (new Date() >= new Date(task.expireAt)) {
    return false;
  }

  if (task.downloadCount >= task.maxDownloads) {
    return false;
  }

  return true;
}

2. 自动清理机制

文件过期后不能只在数据库中标记失效,还应定期删除对象存储中的实际文件。

可以使用定时任务:

每5分钟扫描过期任务
       ↓
更新任务状态
       ↓
删除对象存储文件
       ↓
记录清理日志

任务量较大时,可以将过期时间写入延迟队列,而不是频繁全表扫描。


七、基于 WebRTC 的在线传输

对于双方同时在线的传输场景,可以使用 WebRTC DataChannel。

其基本架构包括:

发送端浏览器
      │
      │ WebSocket 信令
      ▼
信令服务器
      ▲
      │ WebSocket 信令
      │
接收端浏览器

发送端 ←── WebRTC DataChannel ──→ 接收端

信令服务器只交换:

  • Offer;

  • Answer;

  • ICE Candidate;

  • 房间状态;

  • 连接状态。

文件数据尽量不经过信令服务器。

1. 创建连接

const peerConnection = new RTCPeerConnection({
  iceServers: [
    {
      urls: "stun:stun.example.com:3478"
    },
    {
      urls: [
        "turn:turn.example.com:3478?transport=udp",
        "turn:turn.example.com:3478?transport=tcp"
      ],
      username: "temporary-user",
      credential: "temporary-password"
    }
  ]
});

2. 创建 DataChannel

const channel = peerConnection.createDataChannel("file-transfer", {
  ordered: true
});

文件传输一般使用可靠、有序模式。

3. 文件分片发送

async function sendFile(file, channel) {
  const chunkSize = 64 * 1024;
  let offset = 0;

  while (offset < file.size) {
    if (channel.readyState !== "open") {
      throw new Error("DataChannel 已关闭");
    }

    while (channel.bufferedAmount > 1024 * 1024) {
      await new Promise(resolve => setTimeout(resolve, 50));
    }

    const chunk = file.slice(offset, offset + chunkSize);
    const buffer = await chunk.arrayBuffer();

    channel.send(buffer);
    offset += buffer.byteLength;
  }
}

八、为什么 WebRTC 仍然需要 TURN

不少开发者第一次实现 WebRTC 时,只配置了 STUN。

在局域网或部分家庭宽带环境中可以正常连接,但换成:

  • 移动网络;

  • 企业网络;

  • 校园网;

  • 对称型 NAT;

  • 严格防火墙;

连接就可能失败。

STUN只能帮助客户端发现公网映射地址,并不能解决所有NAT穿透问题。

因此,生产环境通常还需要部署TURN服务器。

当P2P直连失败时,文件数据会通过TURN中继:

发送端 → TURN服务器 → 接收端

此时连接成功率会更高,但服务器带宽成本也会增加。

因此系统可以采用以下策略:

  1. 优先尝试P2P直连;

  2. 直连失败后切换TURN;

  3. 界面提示当前传输模式;

  4. 对TURN任务设置合理带宽和频率限制。


九、文件收集模块的权限设计

文件收集不能直接开放用户的云盘目录权限。

合理的设计应该是:

创建者建立收集任务
        ↓
指定接收目录
        ↓
生成独立收集链接
        ↓
提交者打开上传页面
        ↓
提交者只能上传
        ↓
无法查看其他文件
        ↓
文件自动归档到指定目录

收集任务表可以包含:

CREATE TABLE collection_tasks (
    id BIGINT PRIMARY KEY,
    user_id BIGINT NOT NULL,
    title VARCHAR(255),
    target_folder_id BIGINT NOT NULL,
    collection_code VARCHAR(64) UNIQUE,
    require_name TINYINT DEFAULT 1,
    expire_at DATETIME,
    status TINYINT DEFAULT 1,
    created_at DATETIME
);

上传记录单独保存:

CREATE TABLE collection_submissions (
    id BIGINT PRIMARY KEY,
    collection_id BIGINT NOT NULL,
    submitter_name VARCHAR(128),
    file_id BIGINT NOT NULL,
    ip_address VARCHAR(64),
    created_at DATETIME
);

这样可以实现:

  • 查看谁提交了文件;

  • 统计提交数量;

  • 限制单次文件数量;

  • 限制文件类型;

  • 阻止匿名滥用;

  • 对恶意上传进行追踪。


十、云笔记与文件系统的关联

很多文件平台只存储文件,但用户实际还需要记录文件相关信息。

例如:

  • 项目需求;

  • 文件说明;

  • 修改记录;

  • 客户反馈;

  • 待办事项。

因此,笔记模块可以和云盘文件建立关联关系。

CREATE TABLE note_attachments (
    id BIGINT PRIMARY KEY,
    note_id BIGINT NOT NULL,
    file_id BIGINT NOT NULL,
    created_at DATETIME
);

用户在笔记中插入附件时,不需要重复上传,而是直接关联已有云盘文件。

这样可以避免:

  • 同一文件保存多份;

  • 占用重复空间;

  • 修改后多个副本不一致。

云便签则更偏向短文本和待办事项,可以采用更轻量的数据结构。


十一、隐私空间的设计思路

隐私空间不能只理解为一个普通文件夹。

至少应该在以下方面与普通文件区分:

  • 独立访问验证;

  • 独立权限判断;

  • 文件加密;

  • 操作日志;

  • 敏感操作二次验证;

  • 分享能力限制。

一种实现方式是对文件内容进行客户端加密,再将密文上传到对象存储。

基本流程:

用户输入隐私空间密码
        ↓
本地派生加密密钥
        ↓
浏览器加密文件
        ↓
上传密文
        ↓
下载后本地解密

这种方式可以降低服务端直接读取明文的可能性。

但需要特别注意:

  • 用户忘记密钥后可能无法恢复;

  • 大文件加解密性能;

  • 浏览器内存限制;

  • 密钥不能明文存储在服务端;

  • 密钥派生需要使用安全算法;

  • 不应自行设计未经验证的加密协议。

实际项目中可以采用 Web Crypto API,并选择成熟的加密模式。


十二、应用内测分发模块

应用分发的本质是特殊类型的文件托管和下载页面。

相较普通文件分享,还需要额外处理:

  • APK信息解析;

  • IPA信息解析;

  • 应用名称;

  • 应用图标;

  • 版本号;

  • 包名;

  • 更新说明;

  • 下载次数;

  • 历史版本;

  • 分发状态。

解析后可以生成应用页面:

应用图标
应用名称
当前版本
文件大小
更新时间
更新说明
下载安装按钮

对于Android APK,可以在服务端解析:

  • packageName

  • versionName

  • versionCode

  • 应用图标

  • 最低系统版本

对于IPA,由于签名和安装机制更复杂,还需要根据实际分发方式处理描述文件、签名状态和安装链接。

应用分发不能简单等同于普通文件下载,还需要做好:

  • 文件合法性验证;

  • 恶意文件检测;

  • 上传者身份验证;

  • 下载频率限制;

  • 内容审核;

  • 投诉举报机制。


十三、多节点与CDN设计

当用户分布在不同地区时,单一存储节点可能导致访问延迟较高。

可以根据业务情况部署多个区域节点,例如:

  • 中国大陆;

  • 亚洲;

  • 北美;

  • 欧洲。

上传时可以让用户选择目标区域,或者根据用户IP自动推荐。

节点选择策略可以综合考虑:

用户所在地区
文件主要下载地区
节点实时负载
节点存储容量
跨区域流量成本
合规要求

对于公开分享和应用分发,可以使用CDN缓存下载内容。

但对于以下文件,应谨慎缓存:

  • 隐私空间文件;

  • 一次性下载文件;

  • 权限敏感文件;

  • 带动态鉴权的文件。

下载地址应使用短期签名URL,而不是长期公开对象地址。


十四、下载鉴权与防盗链

如果对象存储地址直接暴露,用户可能绕过业务系统访问文件。

常见解决方案包括:

  • 签名URL;

  • 下载Token;

  • Referer防盗链;

  • IP限制;

  • 下载次数验证;

  • 有效时间限制。

下载流程可以设计为:

用户请求下载
      ↓
服务端验证登录状态
      ↓
验证文件权限
      ↓
验证有效期和下载次数
      ↓
生成短期签名地址
      ↓
浏览器跳转下载

签名地址的有效期不宜过长。

例如:

5分钟
10分钟
30分钟

具体取决于文件大小和下载场景。


十五、文件平台的安全风险

文件上传类系统必须重点关注安全问题。

1. 任意文件上传

不能仅根据文件扩展名判断类型。

需要结合:

  • MIME类型;

  • 文件头;

  • 扩展名;

  • 内容扫描;

  • 解压检测。

2. XSS文件名

用户上传的文件名可能包含HTML或脚本内容。

展示时必须转义。

3. 恶意压缩包

压缩包可能存在:

  • 超高压缩比;

  • 嵌套压缩;

  • 路径穿越;

  • 恶意脚本;

  • 病毒文件。

4. 下载接口刷流量

攻击者可能反复请求大文件,造成高额带宽消耗。

需要设置:

  • 单IP限速;

  • 单账号限速;

  • 单链接下载次数;

  • 异常流量监控;

  • CDN防盗链。

5. WebRTC连接码枚举

在线传输的连接码不能过短。

建议:

  • 使用足够长度的随机字符串;

  • 设置短时间有效期;

  • 限制错误尝试次数;

  • 成功连接后立即失效;

  • 使用临时会话Token。


十六、为什么要把多种功能放在同一个平台

单独实现云盘、快传、P2P传输或文件收集并不困难。

真正有价值的是让不同模块形成连续的文件工作流。

例如,一个项目可以按照以下流程处理:

通过文件收集获取客户资料
           ↓
文件自动进入云盘目录
           ↓
使用云笔记记录项目需求
           ↓
使用云便签记录临时待办
           ↓
通过在线传输在设备间移动素材
           ↓
通过文件快传发送临时版本
           ↓
通过云盘分享交付正式文件
           ↓
敏感合同进入隐私空间
           ↓
如果是App项目,再创建内测分发

这类整合的价值不只是功能数量,而是减少文件在多个平台之间反复上传和迁移。


十七、实际项目中的取舍

在真实开发中,没有一种文件传输方式适合所有场景。

对象存储适合

  • 长期保存;

  • 一对多下载;

  • 接收方不在线;

  • CDN加速;

  • 历史文件管理。

文件快传适合

  • 短期保存;

  • 限制下载次数;

  • 自动过期;

  • 一次性交付。

WebRTC适合

  • 双方同时在线;

  • 设备之间互传;

  • 不需要长期保存;

  • 希望减少中心服务器流量。

TURN适合

  • P2P无法连接;

  • 企业网络或复杂NAT;

  • 需要提高连接成功率。

文件收集适合

  • 多人向一个用户提交文件;

  • 文件自动归档;

  • 避免聊天工具中资料分散。

因此,合理的系统不应该强制所有业务使用同一种传输方式,而应该根据文件生命周期和网络环境动态选择。


十八、总结

一个现代文件平台需要解决的问题,早已不只是“上传和下载”。

它还需要处理:

  • 长期存储;

  • 临时传输;

  • 点对点在线传输;

  • 文件收集;

  • 多设备访问;

  • 文件分享;

  • 隐私保护;

  • 应用内测分发;

  • 多区域节点;

  • 生命周期管理;

  • 下载鉴权;

  • 安全审核。

从技术角度看,对象存储、CDN、WebSocket、WebRTC、STUN、TURN、数据库和任务调度各自承担不同职责。

从产品角度看,最重要的是根据用户真实场景,将这些能力组合成一条完整的文件工作流。

本文所介绍的功能和架构,已经应用在实际文件平台项目中。感兴趣的开发者可以访问项目官网了解相关功能:

https://okpan.com

后续还可以继续深入讨论以下内容:

  • 基于Node.js搭建WebRTC信令服务器;

  • Coturn服务器部署与配置;

  • 大文件分片上传和断点续传;

  • 对象存储直传与签名URL;

  • 文件收集系统的数据库设计;

  • WebRTC大文件传输的内存优化。

Logo

邀请您加入社区

更多推荐