Create Your Own 文件传输 Service: Dev 教程
Build a 文件传输 service from scratch. Backend API design, storage integration, and user interface development tutorial.
《网络安全法》(CSL)第二十二条要求网络产品和服务提供者不得收集与其提供服务无关的用户数据。这一原则直接指导文件传输服务的设计——数据库里不放用户表、不放邮件地址、不放 IP 日志,只存储传输会话所必需的最小元数据。构建自己的文件传输服务需要五个组件:在浏览器中切片和可选加密文件的前端、生成上传会话和预签名存储 URL 的 REST API、S3 兼容对象存储(Cloudflare R2、Backblaze B2 或 MinIO)、轻量元数据数据库(SQLite 或 Postgres),以及 TLS 1.3 反向代理。
使用 Node 22、Fastify 和 Vite,约 600 行代码可交付 WeTransfer 风格的服务,中等流量下每月托管成本 5–15 美元。
首先确定存储层
存储经济学决定文件传输服务的成败。AWS S3 每 GB 出口收费 $0.09,意味着一个热门的 2 GB 文件被下载 1,000 次,仅带宽就要花 $180。Cloudflare R2 出口免费,存储每 GB $0.015。Backblaze B2 出口每 GB $0.01,存储每 GB $0.006。对于免费层传输服务,R2 是显而易见的选择。MinIO 允许在自有硬件上自托管 S3 语义,适合已有闲置带宽和磁盘的场景。
使用预签名 URL,让客户端直接上传和下载,永远不经过服务器。这使 API 成为 CPU 密集型而非带宽密集型。
设计 API 接口
保持 API 小巧。五个端点覆盖 95% 的需求:
POST /sessions— 创建上传会话,返回 ID 和预签名分片 URLPOST /sessions/:id/complete— 完成分片上传,返回分享 URLGET /shares/:id— 返回文件元数据(大小、过期时间、剩余下载次数)GET /shares/:id/download— 返回预签名下载 URLDELETE /shares/:id— 允许所有者提前撤销(需要撤销令牌)
对 POST /sessions 进行积极限流——每 IP 每小时 20 次已足够。所有端点返回 JSON,使用 Cache-Control: no-store,HTML 页面设置 Content-Security-Policy。
用 Fastify 搭建后端
Fastify 提供 JSON Schema 验证、合理的日志记录,每核每秒 30,000 个请求,无需费力。最简 POST /sessions:
fastify.post('/sessions', {
schema: {
body: {
type: 'object', required: ['size'],
properties: {
size: { type: 'integer', minimum: 1, maximum: 10737418240 },
contentType: { type: 'string', maxLength: 120 }
}
}
}
}, async (req) => {
const id = nanoid(16);
const key = `blobs/${id}`;
const upload = await s3.createMultipartUpload({
Bucket: 'transfers', Key: key, ContentType: req.body.contentType
});
const partCount = Math.ceil(req.body.size / (8 * 1024 * 1024));
const urls = await Promise.all(
Array.from({ length: partCount }, (_, i) =>
getSignedUrl(s3, new UploadPartCommand({
Bucket: 'transfers', Key: key,
UploadId: upload.UploadId, PartNumber: i + 1
}), { expiresIn: 3600 }))
);
db.prepare('INSERT INTO sessions VALUES (?, ?, ?, ?)')
.run(id, upload.UploadId, key, Date.now());
return { id, uploadId: upload.UploadId, partUrls: urls };
});
10 GB 上限,8 MB 分片,URL 有效期 1 小时。按流量情况调整。
构建上传 UI
Vite + React + 零 UI 库,约 150 行代码即可实现拖放上传。关键是上传 Worker:启动专用 Web Worker,传入 File 句柄和分片 URL,让它以 3 路并发 PUT 每个块。主线程保持响应,进度更新通过 postMessage 传递。
self.onmessage = async ({ data: { file, partUrls } }) => {
const partSize = 8 * 1024 * 1024;
const etags = [];
for (let i = 0; i < partUrls.length; i++) {
const blob = file.slice(i * partSize, (i + 1) * partSize);
const res = await fetch(partUrls[i], { method: 'PUT', body: blob });
etags[i] = res.headers.get('etag');
self.postMessage({ type: 'progress', done: i + 1, total: partUrls.length });
}
self.postMessage({ type: 'done', etags });
};
添加密码字段,通过 PBKDF2(600,000 次迭代,SHA-256)派生密钥,并在 PUT 前对每块进行 AES-256-GCM 加密,即可将服务从服务端加密升级为端对端加密。
设计数据库 Schema
SQLite 通过 better-sqlite3 处理数千万行数据毫无压力。保持 Schema 精简:
CREATE TABLE sessions (
id TEXT PRIMARY KEY, upload_id TEXT, object_key TEXT, created_at INTEGER
);
CREATE TABLE shares (
id TEXT PRIMARY KEY, object_key TEXT, size_bytes INTEGER,
content_type TEXT, expires_at INTEGER, max_downloads INTEGER,
download_count INTEGER DEFAULT 0, revocation_token TEXT
);
CREATE INDEX idx_shares_expires ON shares(expires_at);
无用户表,无邮件,无 IP 日志。这是刻意的选择——它使 PIPL(个人信息保护法)合规几乎变得微不足道。每夜定时任务删除过期行及其 S3 对象。
防御滥用
文件传输服务容易吸引滥用者。按影响大小排列的缓解措施:按 IP 限制上传频率,要求最小文件大小(10 KB 以下几乎都是垃圾),计算密文 SHA-256 并与已知恶意哈希黑名单比对,以及提供零摩擦的举报表单。ClamAV 扫描解密内容只在拥有密钥时有效,这与端到端加密相悖——正确答案是与内容无关的速率限制加上响应式下架机制。
低成本部署运行
单台 $10 的 Hetzner CX22(2 vCPU,4 GB RAM)运行 Fastify + SQLite + Caddy 反向代理,可处理数千个活跃用户。Caddy 通过 Let's Encrypt 自动提供 TLS 1.3。在前面放 Cloudflare 用于 DDoS 防护和免费 WAF。整体思路与 HexaTransfer 的构建方式一致——Fastify API,R2 存储,浏览器端 Web Crypto,SQLite 元数据,无账户。
立即体验:https://hexatransfer.com——免费,无需注册,最大支持 10 GB。
v1 阶段跳过什么
不要在 v1 中构建账户、团队或文件夹。不要添加邮件投递。不要实现视频预览或图片缩略图(两者都迫使服务器看到明文)。不要在 Web 版在移动端 Safari 上稳定之前发布移动应用。功能蔓延始终遵循同一模式:账户、存储配额、付款、团队套餐——之后你就在与 Dropbox 竞争,而不是交付一个快速、私密的传输工具。保持功能面小,加密负重前行。