根本原因是CodeIgniter通过文件头(magic bytes)检测MIME类型,而非扩展名;若mimes.php未配置对应MIME(如xlsx缺少application/vnd.openxmlformats-officedocument.spreadsheetml.sheet),即使扩展名正确也会被拒。

为什么 $_FILES 上传后 upload->do_upload() 总是报 “The filetype you are attempting to upload is not allowed”
根本原因不是文件后缀名不对,而是 CodeIgniter 默认只认 mimes.php 配置里注册的 MIME 类型 —— 它压根不看扩展名,而是读取文件头(magic bytes)做判断。你传 .xlsx,但服务器实际检测到的是 application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,而这个字符串没在 application/config/mimes.php 里映射到 xlsx,就直接拒了。
实操建议:
- 打开
application/config/mimes.php,找到'xlsx' => array(...)这一项,确保数组里包含'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' - 如果用的是 PHP 7.4+ + Apache,注意某些环境会把
application/zip当作.xlsx的 MIME(因为 xlsx 本质是 zip),也得加进去 - 别改
$_FILES['xxx']['type']—— 这个值由浏览器提供,不可靠,CI 根本不用它验证
upload->set_allowed_types() 写 'jpg|png|gif' 为什么 PDF 还能上传成功
因为 set_allowed_types() 控制的是「扩展名白名单」,而 MIME 检查是独立开启的默认行为。两者不互斥:扩展名过了,MIME 又过了,才算通过;但如果你没配对的 MIME 映射,哪怕扩展名对了也会被拦下。
实操建议:
- 想只靠扩展名控制(比如内部系统、可信用户),显式关掉 MIME 检查:
$this->upload->set_detect_mime(false) - 想严格校验(推荐),保留
set_detect_mime(true)(默认就是 true),同时确保mimes.php里每个扩展名对应的所有可能 MIME 都列全 -
set_allowed_types('*')不等于“放行所有”,它只跳过扩展名检查,MIME 检查照常执行
上传大文件时 upload->do_upload() 返回 false 且无错误信息
大概率是 PHP 层面被截断了 —— upload_max_filesize 或 post_max_size 小于文件体积,导致 $_FILES 根本收不到完整数据,CI 的上传类连初始化都失败,自然不报具体错误。
实操建议:
- 上传前先检查:
var_dump($_FILES);,如果'error' => 1或'size' => 0,基本确定是 PHP 配置问题 - 确认
php.ini中upload_max_filesize和post_max_size都调大,且post_max_size >= upload_max_filesize - Apache 下还要留意
LimitRequestBody,Nginx 则要检查client_max_body_size
CI3 里怎么让上传支持 WebP 图片但不破坏原有 JPG/PNG 验证逻辑
WebP 在不同系统上报的 MIME 不稳定:Chrome 可能报 image/webp,Safari 旧版可能报 application/octet-stream,而 CI 默认的 mimes.php 里 'webp' 只写了 image/webp,漏掉了 fallback 场景。
实操建议:
- 编辑
mimes.php中'webp'对应的数组,补上'application/octet-stream'(谨慎使用,仅限已知可控来源) - 更稳妥的做法:在控制器里手动预检
$_FILES['xxx']['tmp_name']是否可被getimagesize()识别为 WebP:if (function_exists('imagewebp') && getimagesize($tmp)['mime'] === 'image/webp') { ... } - 别依赖
$_FILES['xxx']['type']判断 WebP —— 这个字段浏览器随便填,iOS Safari 就经常返回空或错值
真正麻烦的从来不是写几行验证代码,而是每次上线后发现某台 CentOS 服务器的 libmagic 版本太老,连 WebP 头都识别不出来 —— 这时候就得退回到扩展名+文件头字节硬匹配,而不是指望 set_detect_mime(true) 自动兜底。










