FilePizza WebRTC 点对点文件传输:从本地跑通到生产交付的完整指南
【免费下载链接】filepizza:pizza: Peer-to-peer file transfers in your browser项目地址: https://gitcode.com/GitHub_Trending/fi/filepizza
FilePizza 是基于 Next.js 15 + WebRTC 的浏览器点对点文件传输应用:文件不经过服务器中转,而是由上传方浏览器直连下载方浏览器完成。本文按部署流水线把它从"能跑起来"推进到"可交付",帮你独立完成一次可信的本地验证与生产前检查。
锁定 Node 18 与 pnpm 跑通本地开发服务
现象与风险:项目依赖 pnpm 的严格依赖解析与 workspace 能力,用 npm/yarn 安装容易得到与锁文件不一致的依赖树;而 Next.js 15 需要 Node 18 及以上,Node 版本过低会在启动阶段直接报出crypto、fetch相关的 API 缺失错误。开发期还有一个隐性陷阱:next.config.js 里把reactStrictMode设为false,因为上传/下载组件都用useEffect监听 PeerJS 事件,严格模式的双调用会让连接建立两次——如果你按习惯改回true,会出现"握手两次、进度跳变"的诡异现象。
仓库证据:CLAUDE.md 明确写有Node.js (v18+)与pnpm (preferred);package.json 的dev脚本就是next,dev:full则叠加 Redis 与 COTURN 容器用于完整 WebRTC 联调。
正确做法 + 验证:
git clone https://gitcode.com/GitHub_Trending/fi/filepizza cd filepizza pnpm install pnpm dev打开两个浏览器窗口(一个上传、一个用生成的短/长链接下载),能看到进度条与完成回调即视为本地跑通。若只验证信令链路,pnpm dev:full会拉起 docker-compose.yml 中的redis与coturn服务。
用环境变量接管存储与信令,避免默认值悄悄生效
现象与风险:FilePizza 的多个关键行为都由环境变量驱动,而默认值会"悄悄"回退到公共或本地服务。不配置REDIS_URL时会回退到进程内内存存储;不配COTURN_ENABLED时只走公共 STUN;PEERJS_HOST默认指向公共0.peerjs.com。上线后若沿用这些默认值,信令会打到公共 PeerJS 服务器、通道元数据也留在内存里,重启即丢。
仓库证据:默认值分散在 src/app/api/ice/route.ts(TURN_HOST、STUN_SERVER、PEERJS_HOST的||兜底)与 src/redisClient.ts(process.env.REDIS_URL ? new Redis(...) : new Redis())。README.md 列出了完整变量表:REDIS_URL、COTURN_ENABLED、TURN_HOST、TURN_REALM、STUN_SERVER、PEERJS_HOST、PEERJS_PATH。
正确做法 + 验证:在你本地副本中按环境显式注入这些变量,而不是依赖兜底。启动后观察日志:getOrCreateChannelRepo会打印[ChannelRepo] Using Redis storage或Using in-memory storage,据此确认存储后端是否正确切换。生产环境建议自托管 PeerJS 并把PEERJS_HOST指向自己的域名,避免对外部公共节点产生运行时依赖。
收紧通道状态持久化与访问密钥
现象与风险:这是最容易被低估的一环。默认内存存储(MemoryChannelRepo)把通道放在Map里,进程一重启、多实例一部署,所有未完成的分享链接全部失效——这正是"游客态脆弱存储"的典型隐患。另一面是访问控制:通道靠secret做鉴权,renewChannel/destroyChannel都要求secret精确匹配,否则返回false。若把带secret的通道对象直接回传给前端,等于把"续期/删除权限"泄露给任意持有链接的人。
仓库证据:src/channel.ts 中MemoryChannelRepo与RedisChannelRepo实现同一ChannelRepo接口,由getOrCreateChannelRepo()按REDIS_URL二选一;deserializeChannel(str, scrubSecret)在scrubSecret=true时会把secret置为undefined再返回,下载页取通道时应走这条脱敏路径。
正确做法 + 验证:
// 下载/鉴权取通道时务必脱敏 const channel = await repo.fetchChannel(slug, true)生产环境使用RedisChannelRepo,并在 docker-compose.production.yml 中把 Redis 端口绑定到127.0.0.1:6379(而非0.0.0.0),杜绝数据库对公网暴露。验证方式:拿到一个分享链接后,用一个错误的secret调用续期接口,应返回失败;用正确secret才能续期成功。
配好 coturn 与 TURN 凭据,让 NAT 后的设备能连通
现象与风险:纯 STUN 只能做打洞,双方都在严格 NAT 后会连接失败,必须依赖 TURN 中继。常见错误有两个:一是生产 compose 没放开 UDP 中继端口段,TURN 握手能过但媒体流建不起来;二是 TURN 凭据有效期与TURN_REALM不一致,导致coturn校验 HMAC 失败。
仓库证据:src/coturn.ts 的setTurnCredentials用md5(username:realm:password)生成 HMAC 并以setex写入 Redis;src/app/api/ice/route.ts 每次生成临时username/password(TTL 24 小时),并返回turn:host:3478与turns:host:5349两条 ICE 服务器。docker-compose.production.yml 额外打开了60000-60128/udp中继段,并设--min-port=60000 --max-port=60128,与COTURN_ENABLED=true、env_file: .env配套。
正确做法 + 验证:在你本地副本的.env中补齐COTURN_ENABLED=true、TURN_HOST、TURN_REALM,并确认防火墙放行 UDP3478、5349与中继端口段。验证方式:运行pnpm dev:full后用两台不同网络(如手机热点 + 有线)互传文件,能稳定建连即说明 TURN 链路打通。
对齐通道结构与消息协议,让上传下载两端不"各说各话"
现象与风险:上传方与下载方之间靠一套 JSON 消息协议通信,任何一端字段名、枚举值对不上都不会报"接口错误",只会表现为传输卡住或进度不动。通道本身也是一个被严格约束的结构:短链接固定 8 位、长链接固定 4 个词,两端解析时若长度或字符集不一致就会取不到对应通道。
仓库证据:src/channel.ts 用 zod 的ChannelSchema约束{ secret?, longSlug, shortSlug, uploaderPeerID };src/slugs.ts 的generateShortSlug(8 个0-9a-z字符)与generateLongSlug(4 个词)定义了两端必须一致的 slug 形态;src/messages.ts 的MessageType枚举(RequestInfo、Info、Start、Chunk、ChunkAck、Pause、Done、Error、PasswordRequired、UsePassword、Report)是上传/下载两端共享的"契约"。
正确做法 + 验证:修改消息字段时以ChannelSchema与MessageType为唯一事实来源,同步更新上传/下载两侧。验证方式:跑pnpm test:e2e(Playwright 的端到端用例会走真实消息协议),或在浏览器 DevTools 的 PeerJS 日志里确认RequestInfo → Info → Start → Chunk/ChunkAck → Done全序列出现。
用密码与密钥保护分享链接,替代"有链接即可下"
现象与风险:FilePizza 没有传统"管理员"概念,访问控制落在两件事上——可选的上传密码,以及通道secret。若未设密码,任何人拿到短/长链接即可下载;若secret被回传,陌生人就能续期或撤销链接。把密码校验当作"可选增强"而在生产里放任为空,会让敏感分享失去最后一道闸门。
仓库证据:src/messages.ts 定义了PasswordRequired/UsePassword两条消息,下载方据此提示并回填密码;src/channel.ts 中secret是crypto.randomUUID()生成、仅参与renew/destroy的鉴权,前端展示一律走scrubSecret脱敏。
正确做法 + 验证:在生产入口为需要保护的上传强制填写密码,并确认所有对外的通道返回都经过scrubSecret=true处理。验证方式:对一个设了密码的链接,在未输入密码前无法开始下载;再用脱敏后的通道对象(无secret)尝试续期,应被拒绝。
生产构建、流式下载验证与交付前检查清单
现象与风险:output: 'standalone'的产物不会自动带上public/与静态资源,必须手动拷贝;而流式下载依赖的 Service Worker(StreamSaver)只在浏览器实际注册后生效,开发态测不出真实下载体验。跳过这一步,上线后可能遇到静态资源 404、大文件下载无法边下边写盘的问题。
仓库证据:package.json 的build脚本是next build && cp -r public .next/standalone/ && cp -r .next/static .next/standalone/.next/;Dockerfile 用deps → builder → runner三阶段,仅把public、standalone、static拷入最终镜像并以USER node运行server.js;public/sw.js 即 StreamSaver 的 Service Worker,负责把 WebRTC 数据流接到下载请求上。
正确做法 + 验证:
pnpm build pnpm docker:build # 三阶段镜像 pnpm docker:up构建后用一个大于内存缓冲的文件实测下载,确认能在浏览器中边收边写盘且进度连续,即验证了 Service Worker 流式下载链路。
交付前检查清单:
| 阶段 | 检查项 | 仓库证据 | 通过标准 |
|---|---|---|---|
| 运行时 | Node 18+ 且用 pnpm 安装 | CLAUDE.md | pnpm dev无依赖/版本报错 |
| 配置 | 显式注入REDIS_URL/COTURN/PEERJS | src/app/api/ice/route.ts | 日志出现Using Redis storage |
| 状态安全 | 走 Redis 且 Redis 仅绑本机 | docker-compose.production.yml | 错误secret无法续期 |
| NAT 穿透 | 放行 TURN 与 UDP 中继段 | src/coturn.ts | 跨网互传稳定建连 |
| 协议一致 | slug 与消息结构两端对齐 | src/slugs.ts、src/messages.ts | test:e2e通过 |
| 访问控制 | 密码 + 脱敏secret | src/channel.ts | 无密码不可下载 |
| 交付 | 独立构建 + 流式下载验证 | Dockerfile、public/sw.js | 大文件边下边写盘 🚀 |
按这条流水线逐项过完,你就把 FilePizza 从一个本地 Demo 推进到了一个信令可控、状态持久、NAT 可穿透、链接受保护、下载可验证的可交付点对点文件传输服务。🔧
【免费下载链接】filepizza:pizza: Peer-to-peer file transfers in your browser项目地址: https://gitcode.com/GitHub_Trending/fi/filepizza
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考