必须用分片上传+断点续传双机制兜底:前端按10mb切片、sha-256生成fileid实现重复文件复用;后端用$request->getcontent()接收二进制流,redis记录已传分片并校验数量与哈希,合并时流式写入不占内存。

当用户上传500MB视频或2GB工程文件时,单次HTTP请求必然超时失败,PHP内存爆满,Nginx直接切断连接——必须用分片上传+断点续传双机制兜底。
前端切片与元数据构造
使用Blob.slice()按10MB固定大小切割,避免动态分片导致后端校验逻辑复杂化。
第一步:读取文件并生成唯一fileId,采用SHA-256全文件哈希值前16位,而非仅文件名拼接时间戳——【重复文件将复用已存分片,节省70%以上带宽】。
第二步:为每个分片附加三个必传参数——chunkIndex(从0开始)、totalChunks、fileId,全部通过URL Query传递,不塞进FormData。
第三步:发起并发上传请求,每片单独携带X-Chunk-Hash请求头,值为该分片内容的SHA-256——服务端据此做内容级去重,不是只看序号。
服务端接收分片:绕过$_FILES陷阱
WebUploader或自研前端发送的分片是纯二进制流,$request->file('chunk')永远为null,必须用$request->getContent()读取原始字节。
方法一:基础接收
检查$request->has(['chunkIndex', 'totalChunks', 'fileId'])成立后,立即用file_put_contents()写入storage_path("app/chunks/{$fileId}/{$chunkIndex}"),路径中不包含任何用户输入字段。
方法二:防覆盖加固
在写入前先执行is_file($targetPath) && filesize($targetPath) === $request->headers->get('Content-Length')双重判断——若文件已存在且大小一致,直接跳过写入,返回200;否则才落盘。
这一步漏掉会引发并发写入错乱,尤其在Chrome多线程上传场景下,同一分片可能被发两次。
Redis状态管理与断点查询接口
GET /api/upload/progress/{fileId} 必须秒级响应,不能扫磁盘目录。
上传成功后执行:Redis::sAdd("uploaded_chunks:{$fileId}", $chunkIndex),同时设置过期时间Redis::expire("uploaded_chunks:{$fileId}", 86400)。
查询接口直接返回Redis::smembers("uploaded_chunks:{$fileId}")结果,转为数字数组后JSON输出——不要做任何排序或过滤,前端自己处理索引缺口。
注意:Redis key命名必须统一用冒号分隔,避免在集群模式下因key分布不均导致查询倾斜。
分片合并与完整性校验
合并前必须完成两道硬性校验,缺一不可。
第一道:数量校验。用Redis::scard("uploaded_chunks:{$fileId}")获取已上传分片数,严格等于$totalChunks才继续。
第二道:内容校验。遍历0到$totalChunks - 1,对每个分片文件重新计算SHA-256,比对Redis中存储的"chunk_hash:{$fileId}:{$i}"值——【任意一片哈希不匹配即终止合并,删除全部临时分片】。
校验通过后,用fopen('php://output', 'wb')打开目标文件句柄,循环fread(fopen($chunkPath, 'rb'), 8192)逐块写入,全程不加载整片进内存。
最后执行Redis::del("uploaded_chunks:{$fileId}")和Filesystem::deleteDirectory(storage_path("app/chunks/{$fileId}"))清理痕迹。











