ThinkPHP无法通过$_FILES接收Blob流,因Blob默认以application/octet-stream发送,不触发PHP文件上传解析;必须用file_get_contents('php://input')手动读取原始流,并校验长度、补扩展名、设安全权限。

ThinkPHP 无法直接用 $_FILES 接收前端传来的 Blob 流,必须手动读取原始请求体并保存为文件。
为什么 $_FILES 拿不到 Blob 上传的数据
浏览器用 fetch 或 axios 发送 Blob(如 new Blob([data]))时,默认是 Content-Type: application/octet-stream,不走 multipart/form-data 编码。PHP 不会解析这种原始二进制流到 $_FILES,$_FILES 只响应表单编码的文件字段。
- 常见错误现象:
$_FILES为空,$request->file()返回null - 使用场景:Canvas 导出图片、录音文件分片上传、PDF 生成后直传
- 关键判断点:检查请求头
Content-Type是否为application/octet-stream或无明确boundary
如何用 file_get_contents('php://input') 正确读取流
这是最稳定的方式,但要注意几个硬性限制:
-
php://input只能读一次,且不能和$_POST/$_GET同时使用(除非enable_post_data_reading = Off,不推荐) - 必须在控制器入口尽早读取,避免被框架中间件或 Request 类提前消耗
- ThinkPHP 6+ 中,
$request->input()会触发php://input读取,但返回的是字符串而非原始二进制,可能损坏数据 —— 必须用原生方式 - 示例操作:
$rawData = file_get_contents('php://input'); if ($rawData === false || $rawData === '') { throw new \Exception('Empty binary stream received'); } file_put_contents('/tmp/upload.bin', $rawData); // 先落地验证
保存为文件前必须做的三件事
光写入还不够,漏掉任一环节都可能导致文件损坏或安全风险:
- 校验
Content-Length头是否与实际读取长度一致,防止截断或注入:$expectedLen = (int) $_SERVER['CONTENT_LENGTH'] ?? 0; if (strlen($rawData) !== $expectedLen) { throw new \Exception('Content-Length mismatch'); } - 根据业务需要补全文件扩展名(前端不会传),不能仅靠 MIME 猜测;例如录音用
.wav,Canvas 图片建议统一转.png - 设置合理权限,避免上传目录可执行:
chmod($filePath, 0644),且路径不能落在 Web 可访问根目录下(如不要放在public/uploads/)
ThinkPHP 6+ 的 Request 类陷阱
很多人试图用 $request->getRawBody() 或 $request->getContent(),但它们在某些 SAPI(如 Apache mod_php)下不可靠,且 ThinkPHP 内部可能已调用过 php://input。
- 真实兼容写法只有
file_get_contents('php://input'),且必须放在__construct()或 action 第一行 - 如果用了路由中间件(如鉴权中间件),确保它没调用过
$request->param()或$request->post() - CLI 或 Swoole 环境下,
php://input可能不可用,需改用STDIN—— 这类环境本身就不适合处理上传,应规避
真正麻烦的不是读取,而是后续的 MIME 校验、病毒扫描、分片合并、断点续传这些——但第一步没走对,后面全白搭。别信“自动识别”,也别依赖前端传的 filename 字段,它根本不可信。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!










