大文件分片上传不能直接用 $_FILES,因Hyperf协程环境下其依赖同步I/O会阻塞协程、降低吞吐,且需绕过Nginx client_max_body_size限制;必须分片,建议2–5MB/片,前端传upload_id、chunk_index、total_chunks;Hyperf中须禁用自动解析multipart/form-data,改用getRawContent()配合Co\Stream\Stdio流式读取落盘;合并时用Redis分布式锁防竞态,校验分片存在性与大小,流式追加合并并及时清理临时文件;断点续传需服务端返回已成功上传的chunk_index列表,且MD5校验须在落盘后立即执行,使用Co\File::md5_file确保协程安全。

大文件分片上传为什么不能直接用 $_FILES?
Hyperf 是协程环境,$_FILES 依赖 PHP 的传统同步 I/O,上传大文件时会阻塞整个协程,导致服务吞吐骤降甚至超时。更关键的是,Nginx 默认限制单次请求体大小(client_max_body_size),分片上传本质是绕过这个限制的主动拆解策略——不是“能不能”,而是“必须分片”。
- 分片粒度建议控制在 2–5 MB,太小增加 HTTP 开销,太大削弱并发优势
- 前端需生成唯一
upload_id标识整个文件,每个分片带chunk_index和total_chunks - Hyperf 中禁用
multipart/form-data自动解析(关掉HttpServer的parse_upload),否则协程会被底层fgets卡住
如何用协程流接收并落盘分片?
核心是用 Swoole\Http\Request::getUploadedFile() 拿到原始 Swoole\Coroutine\Server\FileInfo 对象,再通过 Co\Stream\Stdio 或 Co\Stream\Socket 流式读取,避免内存爆涨。
- 关闭自动解析后,分片数据在
$request->getBody()->__toString()里是原始二进制,但不推荐整块读——改用$request->getRawContent()+Co\Stream\Stdio分段读取更可控 - 示例关键逻辑:
$stream = new Co\Stream\Stdio($request->getRawContent()); $fp = fopen("/tmp/{$upload_id}_{$chunk_index}", "w"); while ($chunk = $stream->read(8192)) { fwrite($fp, $chunk); } fclose($fp); - 注意:
getRawContent()在协程中是惰性加载,不会立即读完 body,所以配合流操作是安全的
合并分片时如何避免竞态和磁盘满?
多个分片请求可能并发写入,合并动作必须串行化;同时大文件合并过程本身吃 I/O,得防止单次合并耗尽磁盘空间。
- 用
Redis::setnx("merge_lock:{$upload_id}", 1, 30)加分布式锁,超时设为 30 秒足够覆盖多数合并场景 - 合并前先校验所有分片是否存在且 size 匹配(防止前端传错或网络丢包),用
Co\File::stat()而非filesize() - 用
Co\File::open()+Co\File::write()流式追加,别用file_put_contents(..., FILE_APPEND)—— 后者在协程下可能触发同步 syscall - 合并完成后立即删分片临时文件,用
Co\File::unlink(),别等 GC
前端断点续传怎么和服务端对齐?
断点续传靠服务端返回已上传成功的 chunk_index 列表,前端跳过重传。难点在于“已成功”的定义必须严格——不是收到请求就算,而是分片完整落盘且校验通过才算。
- 服务端接口
/api/upload/chunk必须返回{"uploaded": [0,1,3]},而非仅状态码 - MD5 校验放在分片写入后立刻做:
Co\File::md5_file("/tmp/{$upload_id}_{$chunk_index}") === $client_md5,不一致则删临时文件并报错 - 注意:PHP 的
md5_file()在协程中是阻塞的,换成Co\File::md5_file()(Swoole 4.8+)或手动分块读取计算


















