ThinkPHP控制器接收原始文件流需用file_get_contents('php://input'),因$_FILES和$request->file()仅支持multipart/form-data;TP5无getInput()方法,TP6的getInput()存在缓存与版本兼容问题,均应避免使用input()等字符串处理方法。

ThinkPHP控制器怎么接收原始文件流
ThinkPHP默认不直接支持接收原始HTTP请求体中的二进制流(比如前端用 fetch + ArrayBuffer 或 FormData 以外方式发送的裸字节),$_FILES 和 $request->file() 都依赖 multipart/form-data 的标准表单上传结构。如果前端发的是纯 POST 带 Content-Type: application/octet-stream 的流,控制器必须手动读取原始输入。
实操上,直接用 file_get_contents('php://input') 是最可靠的方式,但要注意:
-
php://input在Content-Type为multipart/form-data时为空,所以务必确认前端没混用表单上传逻辑 - ThinkPHP 的
$request->input()对二进制流不可靠,它默认做 UTF-8 解码和字符串截断,会损坏文件内容 - 若用
$request->getInput()(TP6.1+),它只是对php://input的封装,但内部做了缓存,多次调用可能返回空,建议只调一次并保存结果
示例:
// 控制器方法内
$rawData = file_get_contents('php://input');
if ($rawData === false || strlen($rawData) === 0) {
throw new \Exception('Empty input stream');
}
// 后续可写入临时文件或直接处理
file_put_contents('/tmp/upload.bin', $rawData);
为什么 $request->file() 拿不到文件
因为 $request->file() 只解析 $_FILES 超全局数组,而该数组仅在 PHP 收到符合 RFC 7578 的 multipart/form-data 请求时由 CGI/SAPI 层自动填充。如果你的请求是:
立即学习“PHP免费学习笔记(深入)”;
- Header 中
Content-Type: application/octet-stream - 或用
axios.post(url, blob, { headers: { 'Content-Type': '...' } })直接传 Blob - 或用
curl -X POST --data-binary @file.zip
——那么 $_FILES 就是空的,$request->file() 必然返回 null 或空数组,这不是 ThinkPHP 的 bug,是 PHP 运行机制决定的。
验证方法:在控制器开头加 var_dump($_FILES, get_headers());,看是否真有数据;再对比 file_get_contents('php://input') 是否有内容。
如何安全保存接收到的二进制流
拿到 file_get_contents('php://input') 后,不能直接用 file_put_contents 写死路径,尤其要避开可执行目录、Web 可访问路径,且需校验长度和基础格式。
- 限制大小:上传前前端应带
Content-Length,后端也要检查strlen($rawData)是否超出阈值(如 100MB),避免内存溢出 - 禁止直接写入 Web 根目录(如
public/uploads/),推荐写入runtime/upload/等非公开路径 - 不要信任扩展名:流本身不含文件名,
Content-Disposition头在非 multipart 场景下通常不存在,无法可靠提取原始名 - 若需生成唯一文件名,用
sha256($rawData) . '.bin'或uniqid() . bin2hex(random_bytes(8)),避免时间戳冲突
示例(带简单校验):
$rawData = file_get_contents('php://input');
$maxSize = 100 * 1024 * 1024; // 100MB
if (strlen($rawData) > $maxSize) {
throw new \Exception('File too large');
}
$savePath = RUNTIME_PATH . 'upload/' . uniqid() . '.bin';
file_put_contents($savePath, $rawData);
TP5 和 TP6 在文件流处理上的关键差异
TP5 没有 $request->getInput() 方法,必须手写 file_get_contents('php://input');TP6.0+ 提供了 $request->getInput(),但它本质仍是封装,且存在两个易忽略点:
- TP6.0.0 ~ 6.0.8 中,
getInput()默认会尝试 JSON 解析并失败后才返回原始流,导致二进制被破坏(已修复于 6.0.9+) - TP6 的
$request实例是单例,getInput()内部用stream_get_contents()读取一次后流就 EOF,重复调用返回空字符串 - TP5 中若用了
input()函数,默认走$_POST,对二进制完全无效;TP6 的input()同样不适用
所以无论 TP5 还是 TP6,只要不是标准表单上传,就老实用 file_get_contents('php://input'),别绕弯。
真正麻烦的从来不是“怎么读”,而是前端没按约定发、后端没校验长度、保存路径权限失控——这些细节比函数选型更容易出问题。



















