必须用swoole协程解析multipart并分块写入,禁用$_files;启用enable_coroutine、合理设worker_num、手动提取boundary;中小文件用coroutine\file::write,大文件流式落盘,关键步骤需原子重命名。

要在高并发场景下稳定支撑成百上千用户同时上传文件,必须绕过PHP-FPM默认的$_FILES机制,用Swoole直接解析HTTP请求体并协程化写入磁盘,否则单次大文件上传就会吃光内存、阻塞整个worker进程。
启用协程与HTTP服务器基础配置
第一步:确认Swoole已启用协程支持,并启动HTTP服务器实例。这一步是后续所有异步操作的前提,【enable_coroutine 必须设为 true】,否则 file_put_contents 或 Swoole\Coroutine\File::write 将退化为同步阻塞行为。
第二步:设置 worker_num 为合理值(建议 ≥ CPU核心数),避免单个worker被长时上传任务独占。若上传请求中混杂大量小文件,可将 worker_num 设为 8~16;若以单个超大文件为主,4~8 更稳妥。
第三步:禁用 upload_tmp_dir 自动处理——Swoole不读取 php.ini 中的 upload 相关配置,【所有上传逻辑必须完全自主控制】,否则会因 $_FILES 为空而直接报错或静默失败。
手动解析 multipart/form-data 请求体
方法一:使用 $request->files 获取元信息 + $request->rawcontent() 提取原始数据
这一步不能跳过 boundary 提取。multipart 数据以类似 --AaB03x 开头的分隔符划分字段,需先从 Content-Type 头中提取 boundary 字符串,再用它切分 rawcontent 得到各段。漏掉这步会导致文件内容被当作文本字段丢弃。
方法二:依赖 Swoole 4.8+ 的 $request->getData()(仅限简单表单)
该方法自动解析普通字段,但对文件字段仍只返回元信息,不包含二进制流。它适合「带少量文本参数+单文件」的轻量场景,省去手动切分逻辑,但无法处理多文件或嵌套字段。
安全写入文件的三种路径
① 协程文件写入(推荐用于中小文件 ≤50MB)
调用 Swoole\Coroutine\File::write($targetPath, $binaryData),该操作在协程内非阻塞执行,不会拖慢同worker其他请求。注意:$binaryData 必须是从 multipart 解析出的真实文件流,不是 tmp_name 路径。
② 分块流式落盘(适用于 ≥50MB 大文件)
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
不要一次性加载整个文件到内存。应边解析 boundary 边读取每段数据,每次提取 2MB 左右的 chunk,用 Swoole\Coroutine\File::write 追加写入目标文件。这样内存占用恒定在几MB,避免OOM崩溃。
③ 临时文件中转 + 原子重命名(兼顾安全性与兼容性)
先写入 /tmp/upload_XXXXX.tmp,校验 MD5 或文件头后,再 rename() 到最终路径。rename 在同一文件系统下是原子操作,能防止上传中途被其他进程读到半成品文件。这一步 【不可省略,尤其在多worker共享存储时】。
生成缩略图与自动压缩
方法1:上传完成后触发 GD/ImageMagick 处理
在文件落盘成功后,用 exec() 或 Imagick::resizeImage() 生成缩略图。注意要限制宽高不超过 136×136,质量设为 100,避免二次失真。此方式简单,但会短暂占用CPU。
方法2:集成 upload 模块并预设参数
在 apps/configs/upload.php 中配置 base_dir → '/var/www/uploads/',再设置 $this->upload->thumb_width = 136 等参数。调用 $this->upload->save('file_key') 时自动完成缩略图生成。该模块内部已做协程适配,无需额外包裹 go()。
方法3:异步任务队列处理(适合高负载集群)
上传成功后,把文件路径推入 Redis 队列,由独立的 consumer 进程调用 swoole_process 启动子进程处理缩略图。主HTTP worker不等待,响应更快,但架构复杂度上升。
异步下载流式响应
当用户请求下载已上传文件时,不要用 readfile() 加载全文件到内存。直接调用 $response->createDownloadStream($filePath, 'export.zip'),Swoole 内部会分片读取、逐块发送,内存占用恒定在 KB 级别。该方法仅支持 Swoole ≥4.8 版本,旧版本需手动实现 chunked transfer。
若需添加访问权限校验,务必在 createDownloadStream() 之前完成——一旦调用该方法,响应头即锁定,后续 header() 或 setCookie() 全部失效。










