PHP 压缩文件夹生成zip(解决中文文件名问题,可压缩带子文件夹的文件夹)

(前面我写个只压缩文件夹内文件,不带子文件夹的方法。后面我补充了个加强版,可以压缩文件夹里面的文件和子文件夹,可以耐心看下去)

↓↓↓这个链接是整理版↓↓↓,只贴出了最新改进后的方法和特点
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']));
        }
    }
}

使用方法没变。这个应该算比较完整的版本了。

  • 11
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值