大文件上传失败因PHP的post_max_size和upload_max_filesize限制,需分片上传:前端切片并发上传并校验,后端用getContent()存分片、Redis计数,合并交由队列异步完成。

为什么直接用 request()->file() 会失败
大文件(比如 >100MB)上传时,Laravel 默认的表单提交方式会卡在 PHP 层就失败:不是 413 Request Entity Too Large,就是 500 且日志里出现 PHP Warning: POST Content-Length of XXX bytes exceeds the limit。根本原因不是 Laravel 拦着你,而是 PHP 的 post_max_size 和 upload_max_filesize 限制了整个请求体大小。分片上传必须绕过这个限制——把一个大文件切成小块(如 5MB/片),每片走独立 HTTP 请求,服务端不拼接成完整文件,而是存为临时分片。
前端怎么切片并控制并发
用 File.prototype.slice()(现代浏览器)或 Blob.slice() 切片,配合 FormData 发送每一片。关键点不是“能发”,而是“怎么发得稳”:
- 每片带上唯一
identifier(如文件名 + hash)、filename、chunkNumber、totalChunks、chunkSize - 用 Promise.allSettled 控制并发数(比如最多 3 片同时上传),避免压垮 Nginx 或触发连接超时
- 上传前先发个
HEAD请求查服务端是否已有该分片(用于断点续传) - 后端响应必须返回明确状态,例如
{ "uploaded": true, "chunk": 5 },不能只返回 200
后端如何接收分片并校验完整性
Laravel 路由接收分片时,不要用 $request->file('file'),它会尝试解析整个 multipart body,容易爆内存。改用 $request->getContent() 原始流读取,并手动写入磁盘:
// 示例:存储分片到 storage/app/chunks/{identifier}/{chunkNumber}
$identifier = $request->input('identifier');
$chunkNumber = (int) $request->input('chunkNumber');
$filePath = storage_path("app/chunks/{$identifier}/{$chunkNumber}");
file_put_contents($filePath, $request->getContent());
// 同时记录已上传分片数(可用 Redis incr)
Redis::incr("upload:{$identifier}:uploaded_chunks");
合并前必须校验:检查 uploaded_chunks == totalChunks,再遍历所有分片文件按序 cat 进一个目标文件。别忘了用 sha256_file() 校验最终文件哈希是否和前端传来的 fileHash 一致。
断点续传依赖的两个隐藏细节
断点续传不是“前端记着传到哪”,而是前后端共同维护状态。最容易被忽略的是:
- Nginx 默认关闭
client_body_timeout和client_header_timeout,大文件上传中途断开后,客户端重试时可能因超时被直接拒绝,需调大(如设为 300s) - 分片文件名不能只靠
chunkNumber,否则多个用户上传同名文件会冲突;必须用md5(原始文件名 . 时间戳 . 随机字符串)生成identifier,且该值要从第一次上传就固定下来
合并操作本身不能放在 Web 请求里做——1GB 文件拼接可能耗时几十秒,Nginx 或 PHP-FPM 会杀掉长请求。要用 dispatch(new MergeUploadJob($identifier)) 丢进队列异步处理,完成后通知前端或写入数据库标记完成。










