网站上传的方法有
把文件拖进浏览器窗口,网站松手,上传进度条跑完,网站上传就完成了。上传这是网站现在最省事的做法,但背后发生的上传事比这个动作复杂得多。网站上传文件,网站本质上要解决三个问题:文件怎么送过去、上传送到哪里、网站服务器怎么接住。上传
最常见的网站方案是表单上传。HTML里一个 <input type="file"> 配合 <form enctype="multipart/form-data">,上传浏览器会把文件切成数据块,网站拼进HTTP请求体里发给后端。上传PHP项目里用 $_FILES 接收,网站Node.js用 multer 这类中间件解析。这种方式兼容性最好,连IE6都能跑,代价是页面会刷新,体验割裂。我见过不少老系统至今还在用隐藏iframe来假装“不刷新”,挺别扭的。
后来Ajax普及,FormData对象让JS能自己组装上传请求。再往后,fetch API 配合 FormData 成了标配。前端代码大概长这样:
const fd = new FormData(); fd.append('file', fileInput.files[0]); fetch('/upload', { method: 'POST', body: fd }); 这比表单优雅,但本质上还是把整个文件塞进一个请求。文件一大,超时、内存暴涨、断线重传全是问题。我传过一个2GB的视频,浏览器直接崩了,后端也没等到完整数据。
于是分片上传登场。把文件切成固定大小的块,比如5MB一片,逐片发送。每片带一个序号,服务端收齐后按序合并。好处很明显:断点续传有了基础,单片失败只重传那片,并发上传还能提速。B站、百度网盘的上传逻辑都走这条路。实现时要注意切片策略——按字节切还是按时间切,以及合并时的顺序校验。我见过有人用文件名当合并依据,结果用户传了两个同名文件,直接串数据。
比切片更底层的是直接传二进制流。用 XMLHttpRequest 的 send(blob) 或者 fetch 的 body: file,不包FormData,Content-Type设成文件本身的MIME。这种适合对接对象存储,比如阿里云OSS、AWS S3的预签名URL方案。流程是:前端先问后端要一个带签名的上传地址,然后直接把文件PUT过去,不经过业务服务器。省带宽,也省服务器磁盘IO。缺点是要处理跨域和签名过期,调试时容易卡在CORS上。
还有一种被低估的方式:WebSocket上传。适合实时性要求高的场景,比如在线协作工具里粘贴图片立刻同步。但WebSocket本身不是为传大文件设计的,帧大小和背压控制要自己处理,一般项目没必要折腾。
服务端接住文件后,事情还没完。临时目录权限、文件大小限制、MIME类型校验、病毒扫描、重命名策略——每一步都能踩坑。我见过有人直接拿用户上传的文件名拼路径,结果被 ../../etc/passwd 这种路径穿越攻击打穿。也有图省事把上传目录设成可执行,传个 .php 文件就能拿shell。
上传进度条的实现也值得说一句。XHR的 upload.onprogress 事件能拿到已发送字节数,除以文件总大小就是百分比。fetch目前没有原生进度事件,得用ReadableStream手动包一层,麻烦不少。所以很多项目明明用了fetch,上传部分还是退回XHR。
选哪种方法,看场景。小文件、表单提交,传统表单或FormData够用。大文件、弱网环境,分片加断点续传是底线。对接云存储,预签名URL最干净。至于WebSocket,除非有实时同步需求,否则别给自己找事。