php 的 $_files 为空是因为前端未正确构造 formdata:漏掉 name 字段、未用 file 对象包装 blob、键名不匹配或未以 multipart/form-data 发送。

前端用 canvas 压缩图片后传给 PHP,为什么 PHP 里 $_FILES 里没文件?
因为前端压缩后生成的是 Blob 或 File 对象,但没正确构造 FormData —— 尤其容易漏掉 name 字段,导致 PHP 不识别为上传文件。PHP 的 $_FILES 只认 multipart/form-data 中带 name 的 file 字段,其他方式(比如直接传 base64 字符串)不会进 $_FILES。
实操建议:
- 前端压缩完图片,用
canvas.toBlob()转成Blob,再用new File([blob], filename, {type: 'image/jpeg'})包一层,确保有合法文件名和 MIME 类型 -
FormData.append('images[]', file)—— 注意键名要和 PHP 后端读取的$_FILES['images']一致,加[]表示多图 - 别用
json.stringify()发送,必须用原生fetch或XMLHttpRequest发FormData,且不能手动设Content-Type(让浏览器自动设multipart/form-data; boundary=...)
PHP 接收多图时 $_FILES['images']['Error'] 全是 UPLOAD_ERR_NO_FILE
这基本等于前端根本没发成功,或者发了但字段名对不上。PHP 不会报错,只是默默把对应字段置为空数组。
排查重点:
立即学习“PHP免费学习笔记(深入)”;
- 打印
var_dump($_FILES)看结构,确认键名是不是images,而不是image、files或带空格/大小写错误 - 检查前端是否用了
enctype="multipart/form-data"(如果是表单 submit)或是否在fetch中漏了body: formData - chrome DevTools → Network → 点开请求 → Headers → 查看
Form Data面板,确认里面真有文件内容,而不仅是空 key
PHP 用 imagejpeg() 压缩 JPEG 时画质崩了或变黑
常见原因是没正确处理 Alpha 通道(比如 PNG 转 JPEG),或者图像资源创建失败后还强行操作。
安全做法:
- 先用
getimagesize()检查文件类型和尺寸,过滤掉非图片或超大文件(如 >20MB) - 用
imagecreatefromjpeg()/imagecreatefrompng()创建资源前,加@抑制警告,并判断返回值是否为false - PNG 转 JPEG 必须手动填充背景色:
$bg = imagecolorallocate($img, 255, 255, 255); imagefill($img, 0, 0, $bg);,否则透明区域变黑 -
imagejpeg($img, NULL, 80)第三个参数控制质量,75–85 是画质/体积平衡点;低于 60 易出现明显色块
并发上传多图时 PHP 写文件失败或覆盖
多个请求同时写同一个临时路径、或用固定文件名(如 upload.jpg)会导致覆盖或权限冲突。
必须做:
- 用
uniqid('', true)+mt_rand()生成唯一文件名,例如:$filename = uniqid('img_') . '_' . mt_rand(1000,9999) . '.jpg'; - 目标目录要提前
mkdir($dir, 0755, true),并确认 PHP 进程有写权限(尤其 nginx+PHP-FPM 场景下,用户常是www-data) - 不要直接
move_uploaded_file($_FILES['images']['tmp_name'][$i], $target),要逐个循环处理,每个文件单独校验$_FILES['images']['error'][$i] === UPLOAD_ERR_OK - 上传完成后记得
imagedestroy($img)释放内存,尤其批量处理时防 OOM
真正麻烦的是前后端时间差:前端压缩耗时不可控,用户可能连点两次提交,后端没做重复请求拦截或幂等处理,就会收到两套同名但不同内容的图。这事得靠前端加 loading 锁 + 后端接收到后生成唯一任务 ID 记日志,不是光靠文件名能兜住的。