Yii中需禁用multipart解析,用php://input流式读取二进制数据并写入磁盘或Flysystem;关键步骤包括清空parsers、分块读写、关闭句柄、返回安全URL。

需要在Yii框架中接收前端传来的原始二进制文件(如PDF、视频、固件包),不经过multipart解析,直接流式写入磁盘或远程存储,避免内存溢出和临时文件干扰。
禁用默认 multipart 解析器
Yii 默认会自动解析 multipart/form-data 请求体,将文件存为临时路径并封装成 CUploadedFile 对象。但大文件或非表单上传(如 fetch + body: arrayBuffer)时,这会导致内存暴涨甚至解析失败。
在对应控制器的配置中,显式清空 parsers 数组:【必须移除或置空 'parsers' => [],否则 php://input 将被提前消费】
若使用 RESTful ActiveController,需在 behaviors() 中覆盖 parser 配置;若为普通 Controller,可在 action 开头手动调用 Yii::$app->request->enableCsrfValidation = false; 并确保未触发 multipart 解析流程。
用 php://input 流式读取并写入本地磁盘
第一步:确认请求方法为 PUT 或 POST,且 Content-Type 为 application/octet-stream、audio/wav 等二进制类型,而非 multipart/form-data。
第二步:打开只读输入流:$input = fopen('php://input', 'rb');。注意:该流只能读一次,且不可 rewind。
第三步:生成安全目标路径,例如:$targetPath = Yii::getAlias('@webroot/uploads/') . uniqid('bin_') . '.dat';。确保 @webroot/uploads/ 目录存在且 Web 服务器有写权限。
第四步:以二进制写模式打开输出文件:$output = fopen($targetPath, 'wb');,然后循环分块读写:while (!feof($input)) { fwrite($output, fread($input, 8192)); }。每次读 8KB 是平衡性能与内存占用的合理值。
第五步:关闭两个句柄:fclose($input); fclose($output);。遗漏 fclose 会导致文件句柄泄漏,在高并发下迅速耗尽系统资源。
对接 Flysystem 统一存储后端
方法一:使用 creocoder/yii2-flysystem 扩展,先通过 Composer 安装:composer require creocoder/yii2-flysystem league/flysystem-aws-s3-v3(按需添加适配器)。
方法二:在 config/web.php 的 components 中注册 fs 组件,例如对接 S3:
'fs' => [ 'class' => 'creocoder\flysystem\FlysystemComponent', 'filesystems' => [ 's3' => [ 'class' => 'League\Flysystem\Filesystem', 'adapter' => [ 'class' => 'League\Flysystem\AwsS3V3\AwsS3Adapter', 'constructor' => [/* AWS 凭据与 bucket 配置 */], ], ], ], ]
方法三:在 action 中获取 Flysystem 实例并写入流:$stream = fopen('php://input', 'rb'); $result = Yii::$app->fs->writeStream('uploads/' . uniqid() . '.bin', $stream); fclose($stream);。注意:Flysystem 的 writeStream 会自动消耗流,无需手动 fread。
【关键前提:$stream 必须是真实 PHP stream resource,不能是字符串或 file_get_contents 结果】
响应返回文件访问路径
写入成功后,不要返回绝对路径(如 @webroot/xxx),而应返回可公开访问的 URL 路径,例如:return ['url' => '/uploads/' . basename($targetPath)];。
若使用 Flysystem 存储到 S3,需调用 Yii::$app->fs->getAdapter()->getUrl($path) 获取预签名 URL 或公开 endpoint,不能硬拼域名。
这一步操作起来很简单,直接把生成的 URL 包进数组 return 就行。


















