ZipArchive 解压中文文件乱码解决方案和疑惑
今天接到一个需求,用户上传压缩包,后台解压缩并根据解压后的文件名来新建数据。想想解压缩的功能在网上应该也有现成的轮子了,很快也找到了相应的轮子:Zipper
简单两部操作很快就发现了一个严重的问题,压缩包的内容是中文的,解压缩以后。得到了一堆乱码 :astonished: :astonished: :astonished:
踩坑过程(可跳过)
在扩展的 issue 里没有找到合适的解决方案(也可能是英语渣没找着)。不得已,又开始搜查各种资料,发现了如下解决方法:
在扩展源码的 ZipRepository.php
中
public function each($callback)
{
for ($i = 0; $i < $this->archive->numFiles; ++$i) {
//skip if folder
$stats = $this->archive->statIndex($i, ZipArchive::FL_ENC_RAW); // 添加这个参数 返回未修改的字符串内容
$filename = iconv('GBK//IGONRE', 'UTF-8', $stats['name']); // 再将文件名转码为中文
if ($stats['size'] === 0 && $stats['crc'] === 0) {
continue;
}
call_user_func_array($callback, [
'file' => $stats['name'],
]);
}
}
按照这个办法,发现解压出来的文件都是中文了~
然后再仔细看一下,又发现了一个大坑。
嗯!?解压出来的文件大小都是 0kb?原来问题出在 $this->archive->getFileStream($fileName)
,传入中文名以后 getFileStream()
会返回 false。
既然动了源码了,那就再动一点吧 :sweat_smile: :sweat_smile:
最终解决方案
最后把扩展源码的以下部分做了修改,同时传入自动转码的文件名以及手动由GBK转码为 UTF-8 的中文文件名,用自动转码的文件名获取资源,然后以中文名命名。
ZipRepository.php
public function each($callback)
{
for ($i = 0; $i < $this->archive->numFiles; ++$i) {
//skip if folder
$stats = $this->archive->statIndex($i);
if ($stats['size'] === 0 && $stats['crc'] === 0) {
continue;
}
// 这里做了修改
$rawName = $this->archive->getNameIndex($i, ZipArchive::FL_ENC_RAW);
$rawName = iconv('GBK//IGORNE', 'UTF-8', $rawName);
call_user_func_array($callback, [
$stats['name'], $rawName
]);
}
}
Zipper.php
private function extractFilesInternal($path, callable $matchingMethod)
{
$self = $this;
// 添加一个中文名参数
$this->repository->each(function ($fileName, $CNName) use ($path, $matchingMethod, $self) {
$currentPath = $self->getCurrentFolderWithTrailingSlash();
if (!empty($currentPath) && !starts_with($fileName, $currentPath)) {
return;
}
$filename = str_replace($self->getInternalPath(), '', $fileName);
if ($matchingMethod($filename)) {
$self->extractOneFileInternal($fileName, $CNName, $path);
}
});
}
private function extractOneFileInternal($fileName, $CNName, $path)
{
$tmpPath = str_replace($this->getInternalPath(), '', $CNName); // 用手动转码的中文文件名命名
// We need to create the directory first in case it doesn't exist
$dir = pathinfo($path.DIRECTORY_SEPARATOR.$tmpPath, PATHINFO_DIRNAME);
if (!$this->file->exists($dir) && !$this->file->makeDirectory($dir, 0755, true, true)) {
throw new \RuntimeException('Failed to create folders');
}
$toPath = $path.DIRECTORY_SEPARATOR.$tmpPath;
$fileStream = $this->getRepository()->getFileStream($fileName); // 用系统转码的文件名获取资源
$this->getFileHandler()->put($toPath, $fileStream);
}
疑惑
为什么 getStream()
传入 ZipArchive::FL_ENC_RAW
返回的 name 会返回 false 呢?
各位大佬有没有遇到类似的情况,有什么更好的解决办法呢?
推荐文章: