天天看點

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);

PHP 壓縮檔案夾生成zip(解決中文檔案名問題,可壓縮帶子檔案夾的檔案夾)

上面的檔案夾是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