PHP 压缩文件夹生成zip(解决中文文件名问题,可压缩带子文件夹的文件夹)
2017-09-14 16:33
501 查看
(前面我写个只压缩文件夹内文件,不带子文件夹的方法。后面我补充了个加强版,可以压缩文件夹里面的文件和子文件夹,可以耐心看下去)
↓↓↓这个链接是整理版↓↓↓,只贴出了最新改进后的方法和特点http://blog.csdn.net/qq_29238009/article/details/79063894
这里下面的都是一些写的时候的思路,需要直接用的看↑这个链接,想知道每次修改的解题思路的看↓的文章
前言:
一般来说PHP压缩文件,如果没有额外置入其他插件的话,普遍是使用ZipArchive的。在网上一般来说搜索是能搜索得到各种压缩的算法,但是我发现了他们都不能压缩进中文名称的文件,即使是各种修改字符编码都没有,然后我在多次试验后,突然发现了ZipArchive还有个小“漏洞”能利用~于是就成功压缩进中文名称的文件啦。
需求:
现在一个文件夹内有一堆格式文件,然后需要将目录下的文件压缩进一个zip里面,然后返回下载。已知小“漏洞”:
ZipArchive的所有方法,不支持输入中文(各种字符编码),但是能够在成功addFile后使用renameName,这个重命名方法是支持输入中文的。意思就是我们能在文件已经在zip的情况后,在里面更改文件的名字,将英文数字文件名改成中文文件名。解题思路:
(1)将文件夹内的所有文件名(例如 中文.txt),改名成为其他文件名(除中文外,例如1.txt),并且保存好对应关系,并且保存好对应关系,并且保存好对应关系(重要事情说三遍)。(2)将改名后的文件添加进ZipArchive中。
(3)利用(1)中保留的名称对应关系,将ZipArchive中的文件名更改回正确的中文名,然后$zip->close()。
(4)将文件夹中的所有文件名根据(1)中的对应关系更改回来。(3、4步骤不能调换!zip close之前,目录下的文件名要和add的时候一致,不然就找不到文件了)
源码:
function zipDir($basePath,$zipName){ $zip = new ZipArchive(); $fileArr = []; $fileNum = 0; if (is_dir($basePath)){ if ($dh = opendir($basePath)){ $zip->open($zipName,ZipArchive::CREATE); while (($file = readdir($dh)) !== false){ if(in_array($file,['.','..',])) continue; //无效文件,重来 $file = iconv('gbk','utf-8',$file); $extension = strchr($file,'.'); rename(iconv('UTF-8','GBK',$basePath.'\\'.$file), iconv('UTF-8','GBK',$basePath.'\\'.$fileNum.$extension)); $zip->addFile($basePath.'\\'.$fileNum.$extension,$fileNum.$extension); $zip->renameName($fileNum.$extension,$file); $fileArr[$fileNum.$extension] = $file; $fileNum++; } $zip->close(); closedir($dh); foreach($fileArr as $k=>$v){ rename(iconv('UTF-8','GBK',$basePath.'\\'.$k), iconv('UTF-8','GBK',$basePath.'\\'.$v)); } } } }
使用:
$basePath = storage_path('excel'); $zipName = storage_path('test.zip'); zipDir($basePath,$zipName);
注:
1.我是在wamp+laravel下做的,其他环境和框架请自行调整下吧,反正php都是一样的
2.这里我保存文件名对应关系是用array数组。你也可以先跑一遍目录,将对应关系先写进一个文件中;再跑一遍,将文件名都改了;再执行上面的代码(当然需要微调下代码)。这样安全性就高点,不怕中途各种原因(断电?服务器突然崩了?)导致你不知道哪个文件原来的名字是什么,还能找到对应关系的文件手工改回来
3.上面压缩的是一个目录下全部都是文件,没有子目录。我暂时先写到这里,过会有时间我再把递归子目录的方法加上来,反正解决了这个中文问题其他的就好办了,这个递归子目录的方法网上也挺多的,先mark一下(todo)。
**
——————————2018.1.2补充递归压缩——————————
**解题思路:
注意:ZipArchive在新增文件的时候,不允许输入中文,但是生成文件夹的时候,可以输入中文。
1. 用modifiyFileName将整个文件夹的文件名换成我写的编码,然后写进关系数组relationArr
2. 根据关系数组relationArr,用zipDir写进压缩文件
3. 根据关系数组relationArr,用restoreFileName将原来的名字还原回来
这里我用zip()将这个逻辑包装起来了,你们需要的话可以直接用。
先写上两个需要提前准备的处理的函数
1.modifiyFileName。将文件夹路径path里面的所有文件名,文件夹名都转成其他编号,然后将关系记录在relationArr数组中,备用。(备用两个字,总让我感觉写语法像做菜一样= =。)
function modifiyFileName($path,&$relationArr){ if(!is_dir($path) || !is_array($relationArr)){ return false; } if($dh = opendir($path)){ $count = 0; while (($file = readdir($dh)) !== false){ if(in_array($file,['.','..',null])) continue; //无效文件,重来 if(is_dir($path.'\\'.$file)){ $relationArr['dir'.$count] = [ 'originName' => iconv('GBK','UTF-8',$file), 'is_dir' => true, 'children' => [] ]; rename($path.'\\'.$file, $path.'\\'.'dir'.$count); modifiyFileName($path.'\\'.'dir'.$count,$relationArr['dir'.$count]['children']); $count++; } else{ $extension = strchr($file,'.'); $relationArr['file'.$count.$extension] = [ 'originName' => iconv('GBK','UTF-8',$file), 'is_dir' => false, 'children' => [] ]; rename($path.'\\'.$file, $path.'\\'.'file'.$count.$extension); $count++; } } } }
2.restoreFileName。根据从modifiyFileName保存的关系数组,将编号的文件名和文件夹名还原成原来的中文名。
function restoreFileName($path,$relationArr){ foreach($relationArr as $k=>$v){ if(!empty($v['children'])){ restoreFileName($path.'\\'.$k,$v['children']); rename($path.'\\'.$k,iconv('UTF-8','GBK',$path.'\\'.$v['originName'])); }else{ rename($path.'\\'.$k,iconv('UTF-8','GBK',$path.'\\'.$v['originName'])); } } }
先测试一下这两个函数能不能用:
$path = storage_path('excel'); $relationArr = [storage_path('excel')=>[ 'originName'=>storage_path('excel'), 'is_dir' => true, 'children'=>[] ]]; modifiyFileName($path,$relationArr[storage_path('excel')]['children']); restoreFileName(array_keys($relationArr)[0],array_values($relationArr)[0]['children']); dd($relationArr);
上面的文件夹是excel,里面有两个子文件夹,叫“中文1”和“中文2”,还有个txt文件叫“根目录文件1.txt”。“中文1”里面有个txt叫“中文1的文件.txt”,“中文2”里面有个txt叫“中文2的文件.txt”。
这就是文件夹的结构了。
然后上面关系数组里面的结构就是 数组的1个键名和3个键值。除开最高一层的键名,其他的键名就是被改后的编码,“中文1”被改成“dir0”,“中文2”被改成“dir1”。三个键值,originName是这个文件原本的名字,is_dir是表明这个文件是否为文件夹,true就是文件夹,false就不是文件夹而是文件,children里面写着这个文件下面还有没文件,如果是文件就是为空的,如果是文件夹就可能不为空。is_dir和children结合起来才能判断这个文件是否为文件夹,这在生成zip的时候很重要。
改良后的压缩函数叫zip()好了。
我写的这个zip多的不说了,大概就跟你右键文件夹压缩出来的一样(微笑),而且压缩包名字也能写中文,总的说我觉得很强势~为自己点赞~:
function zip($dir_path,$zipName){ $relationArr = [$dir_path=>[ 'originName'=>$dir_path, 'is_dir' => true, 'children'=>[] ]]; modifiyFileName($dir_path,$relationArr[$dir_path]['children']); $zip = new ZipArchive(); $zip->open($zipName,ZipArchive::CREATE); zipDir(array_keys($relationArr)[0],'',$zip,array_values($relationArr)[0]['children']); $zip->close(); restoreFileName(array_keys($relationArr)[0],array_values($relationArr)[0]['children']); } function zipDir($real_path,$zip_path,&$zip,$relationArr){ $sub_zip_path = empty($zip_path)?'':$zip_path.'\\'; if (is_dir($real_path)){ foreach($relationArr as $k=>$v){ if($v['is_dir']){ //是文件夹 $zip->addEmptyDir($sub_zip_path.$v['originName']); zipDir($real_path.'\\'.$k,$sub_zip_path.$v['originName'],$zip,$v['children']); }else{ //不是文件夹 $zip->addFile($real_path.'\\'.$k,$sub_zip_path.$k); $zip->renameName($sub_zip_path.$k,$sub_zip_path.$v['originName']); } } } }
zip() 里面调用了 modifiyFileName()、zipDir()和restoreFileName()。注意,这里的zipDir和最前面的那个不一样的!
使用方法:
//这里写你要压缩的文件夹名的绝对地址 $dir_path = storage_path('excel'); //这里写你要压缩的压缩文件名的绝对地址,不需要创建这个压缩文件,代码里面会新建 $zipName = storage_path('中文.zip'); zip($dir_path,$zipName);
然后打开你的压缩文件的地址就可以看到生成的压缩文件啦~
测试过后,基本各种类型文件和文件夹都能压缩,而且压缩速度和压缩后的文件大小和右键文件夹用好压压缩出来的一样~
2018.1.3
发现一个小bug,如果你连续执行两次,或者已经有一个zip里面文件和你现在要压缩的目录文件有交集的同名zip,那么第二步里面添加文件进去后再改名的操作将会失效。
例如已经有个test.zip,里面有个base.txt,然后再执行一遍代码,base.txt更名为编号file0.txt写进压缩包,但是却改不回原来的名字(base.txt)了,因为已经存在这个文件名了,那么它将会以file0.txt这个名字继续存在在压缩包里面,那么这个压缩包里面既有base.txt,也有file0.txt,这两个文件可能是相同的。除了会有个被改成编码名字的同样的文件之外也没什么其他的问题了。
因为ZipArchive不能判断zip里面的文件名,也不能删除已经加进去的文件,所以这里暂时做不到整合压缩包的功能。所以你有两个额外的操作可以预防这种小bug。
操作1:在执行代码之前,判断zipName是否存在,如果存在,就unlink(zipName),然后执行zip()。
操作2:在执行代码之前,判断zipName是否存在,如果存在,把zipName加个后缀,例如test_1.zip之类的,这个你自己操作啦。记得改了test_1.zip之后也要再判断一下test_1.zip是否存在哦~一直改后缀到当前目录下不存在为止再执行zip()。
当然你如果是百分百确定不会重复执行代码,不会有出现同名压缩包的情况,或者你根本不care这个小问题的话,可以当我上面的话没说(づ ̄3 ̄)づ╭❤~
———————————————————————2018.01.15改良版—————————————————————————
啪啪啪啪打脸,能够删除zip里面的文件的,上面的问题已经解决了,同一个zip多次压缩也不会有问题了,会直接覆盖掉,有交集的会合并起来。然后这次我把命名改进了下,防止命名冲突。现在做到的效果就真的和右键压缩一样了。
function zip($dir_path,$zipName){
$relationArr = [$dir_path=>[
'originName'=>$dir_path,
'is_dir' => true,
'children'=>[]
]];
modifiyFileName($dir_path,$relationArr[$dir_path]['children']);
$zip = new ZipArchive();
$zip->open($zipName,ZipArchive::CREATE);
zipDir(array_keys($relationArr)[0],'',$zip,array_values($relationArr)[0]['children']);
$zip->close();
restoreFileName(array_keys($relationArr)[0],array_values($relationArr)[0]['children']);
}
function zipDir($real_path,$zip_path,&$zip,$relationArr){
$sub_zip_path = empty($zip_path)?'':$zip_path.'\\';
if (is_dir($real_path)){
foreach($relationArr as $k=>$v){
if($v['is_dir']){ //是文件夹
$zip->addEmptyDir($sub_zip_path.$v['originName']);
zipDir($real_path.'\\'.$k,$sub_zip_path.$v['originName'],$zip,$v['children']);
}else{ //不是文件夹
$zip->addFile($real_path.'\\'.$k,$sub_zip_path.$k);
$zip->deleteName($sub_zip_path.$v['originName']);
$zip->renameName($sub_zip_path.$k,$sub_zip_path.$v['originName']);
}
}
}
}
function modifiyFileName($path,&$relationArr){
if(!is_dir($path) || !is_array($relationArr)){
return false;
}
if($dh = opendir($path)){
$count = 0;
while (($file = readdir($dh)) !== false){
if(in_array($file,['.','..',null])) continue; //无效文件,重来
if(is_dir($path.'\\'.$file)){
$newName = md5(rand(0,99999).rand(0,99999).rand(0,99999).microtime().'dir'.$count);
$relationArr[$newName] = [
'originName' => iconv('GBK','UTF-8',$file),
'is_dir' => true,
'children' => []
];
rename($path.'\\'.$file, $path.'\\'.$newName);
modifiyFileName($path.'\\'.$newName,$relationArr[$newName]['children']);
$count++;
}
else{
$extension = strchr($file,'.');
$newName = md5(rand(0,99999).rand(0,99999).rand(0,99999).microtime().'file'.$count);
$relationArr[$newName.$extension] = [
'originName' => iconv('GBK','UTF-8',$file),
'is_dir' => false,
'children' => []
];
rename($path.'\\'.$file, $path.'\\'.$newName.$extension);
$count++;
}
}
}
}
function restoreFileName($path,$relationArr){ foreach($relationArr as $k=>$v){ if(!empty($v['children'])){ restoreFileName($path.'\\'.$k,$v['children']); rename($path.'\\'.$k,iconv('UTF-8','GBK',$path.'\\'.$v['originName'])); }else{ rename($path.'\\'.$k,iconv('UTF-8','GBK',$path.'\\'.$v['originName'])); } } }
使用方法没变。这个应该算比较完整的版本了。
相关文章推荐
- PHP生成zip压缩包 解决中文不能压缩或者乱码问题
- (Java控制台程序版)递归打包整个父文件夹下的文件和子文件夹成压缩文件(*.ZIP)以及打包时常见的文件乱码和文件名乱码以及丢失文件或文件损坏问题解决办法
- Java ZIP压缩和解压缩文件(解决中文文件名乱码问题)
- 关于ZIP压缩问题 解决中文文件名乱码
- PHP——上传中文文件名乱码问题解决
- Java实现Zip压缩与解压(解决中文乱码问题)
- 生成txt文件并且压缩,其中有解决中文问题
- Java中ZIP压缩与解压--中文文件名乱码解决办法
- 解决PHP Header强制下载IE文件名中文乱码问题
- 【成功解决】ubuntu 12.04 解压 zip 7z rar jar 文件,文件名中文乱码的问题
- 【成功解决】ubuntu 12.04 解压 zip 7z rar jar 文件,文件名中文乱码的问题
- ios中Mini zip开源工程ZipArchive,压缩中文文件名乱码问题【解决】
- 使用ant自带的org.apache.tools.zip包来压缩zip文件,重点:中文路径文件名问题
- 压缩解压缩文件夹文件——解决了中文乱码问题
- Java实现Zip压缩与解压(解决中文乱码问题)
- 解决PHP在IE浏览旗下载文件,中文文件名乱码问题
- org.apache.tools.zip在Linux下压缩文件中文乱码问题解决
- PHP 从数据库Mysql中读取数据生成excel(解决乱码问题,解决中文变问号问题)
- Ubuntu下解决zip 文件中中文文件名乱码的问题
- Linux 学习解决归档管理器打开rar和zip中文文件名乱码问题