CodeIgniter 3.x Zip库不支持UTF-8文件名,因未处理编码转换,默认按CP437写入ZIP;需手动用iconv或mb_convert_encoding转为GBK/UTF-8,并确保系统locale支持对应编码。

CodeIgniter Zip库默认不支持UTF-8文件名
CodeIgniter 3.x 自带的 $this->zip 库底层用的是 PHP 的 ZipArchive(部分版本甚至绕过它直接拼接 ZIP 格式),但完全没处理文件名编码——它把传入的字符串原样写进 ZIP 中央目录,而 ZIP 规范对文件名编码无强制要求,默认按 CP437 解析。中文路径一进去就变成乱码,解压时工具按 UTF-8 或 GBK 去读,自然对不上。
关键点在于:$this->zip->add_dir() 和 $this->zip->add_file() 的第一个参数(路径/文件名)必须是「ZIP 工具能正确识别的编码」,不是 PHP 脚本当前的 UTF-8 字符串。
实操建议:
- 不要依赖
$this->zip->set_charset('UTF-8')—— 这个方法在 CI 3.x 中只是设置内部变量,不改变实际写入 ZIP 的字节流 - 压缩前手动将文件名转为 GBK(Windows 环境常用)或 UTF-8(Linux/macOS 推荐),取决于目标解压端环境
- 若服务端是 Linux + Nginx + PHP-FPM,且用户主要用 Windows 解压,优先转 GBK;若用户多用 macOS/Linux 或 7-Zip/unar,优先转 UTF-8
用 mb_convert_encoding + iconv 双保险转码文件名
PHP 的 mb_convert_encoding 能识别常见编码,但对某些生僻 GBK 编码(如含全角符号、旧版 Windows 内码)可能失败;iconv 更底层、更可控,但遇到非法字节会报错。两者配合最稳妥。
示例代码(假设原始文件名来自 UTF-8 表单提交):
// 假设 $filename = '测试文档/报告_张三.pdf'
$utf8_filename = $filename;
// 先尝试转 GBK(适配 Windows 默认解压行为)
$gbk_filename = @iconv('UTF-8', 'GBK//IGNORE', $utf8_filename);
if ($gbk_filename === false) {
$gbk_filename = mb_convert_encoding($utf8_filename, 'GBK', 'UTF-8');
}
// 再确保路径分隔符统一为 ZIP 标准的 /
$gbk_filename = str_replace('\', '/', $gbk_filename);
$this->zip->add_file($file_content, $gbk_filename);
注意://IGNORE 是 iconv 关键参数,跳过无法转换的字符;不用它,一个乱码字就会让整条 iconv 失败返回 false。
Linux 服务器上 iconv locale 必须启用 C.UTF-8 或 zh_CN.UTF-8
很多 Docker 镜像或最小化 CentOS/Alpine 系统默认只装了 C locale,iconv -l | grep -i gb 可能查不到 GBK 编码支持,导致 iconv('UTF-8', 'GBK', ...) 直接失败。
验证和修复步骤:
- 运行
locale -a | grep -E "(UTF-8|GBK|zh_CN)",确认有zh_CN.UTF-8或C.UTF-8 - 若没有,Debian/Ubuntu 执行
sudo dpkg-reconfigure locales,勾选zh_CN.UTF-8;CentOS 执行sudo localedef -c -i zh_CN -f UTF-8 zh_CN.UTF-8 - PHP 中需显式设置 locale:在转码前加
setlocale(LC_ALL, 'zh_CN.UTF-8');,否则iconv可能 fallback 到不支持 GBK 的 C locale
比 CI Zip 库更稳的替代方案:直接用 ZipArchive
CI 自带 Zip 库已多年未更新,对 Unicode 支持基本靠“碰运气”。PHP 原生 ZipArchive 类从 5.6+ 开始支持 setArchiveComment() 等扩展,更重要的是:它允许你控制 ZIP 文件头的通用位标志(General Purpose Bit Flag),其中第11位(0-indexed)决定文件名是否按 UTF-8 编码存储。
关键操作只有两步:
- 创建
ZipArchive实例后,调用$zip->open($file, ZipArchive::CREATE) - 添加文件前,设置
$zip->setEncryptionName($filename, ZipArchive::EM_AES_256)不是重点;真正起作用的是:在$zip->addFile()或$zip->addFromString()之后,立即执行$zip->setArchiveComment('UTF-8')并手动修改中央目录记录——但这太复杂;更简单的是:用ZipArchive::CREATE | ZipArchive::OVERWRITE并确保 PHP 版本 ≥ 7.4,然后依赖其自动 UTF-8 检测(仍不 100% 可靠) - 最稳妥做法:放弃自动检测,改用
file_put_contents()写入 ZIP 字节流前,先用iconv把文件名转成 UTF-8,再调用$zip->addFromString($utf8_filename, $content)—— 此时文件名已是 UTF-8 字节,只要解压端支持 ZIP UTF-8 标志位(7-Zip、unar、macOS 归档实用工具都支持),就不会乱码
真正容易被忽略的点:即使你用了 ZipArchive,如果 PHP 运行在 CGI/FastCGI 模式下,且系统 locale 是 C,iconv 对 GBK 的支持依然可能失效。务必验证 iconv -f UTF-8 -t GBK 在命令行是否成功输出乱码字节,再写 PHP 逻辑。

















