需求:将指定路径下的文件夹及文件筛选出符合条件的文件并打包下载。
注意:php的mvc结构可以通过控制器添加header头去下载,但是通过前端Ajax请求接口的话就不能用此方法,因为不管是json数据还是二进制文件都会被浏览器当成字符串处理。
并且,header下载完可以直接unlink删除压缩包,js的话没办法立即删除,只能回调成功下载文件后在调用删除接口,或者可以将压缩的文件名写到数据库,然后在写一个方法去执行数据库逻辑删除,或者crontab定时物理删除,或者干脆不管它,反正每次压缩前会先检查是否存在执行删除操作。等等。
public function download(Request $request){
$data = $request->json()->all();
$uuid = $data['uuid'];
$source_file_path = "./upload/".$uuid; //需要遍历的文件夹地址
if(!is_dir($source_file_path)){ //检查目录是否存在
return response()->json(['status' => '403']);
}
$des_file_path = storage_path("zip/".$uuid.".zip"); //目的路径
if(file_exists($des_file_path)){ //重新生成文件
unlink($des_file_path);
}
$zip=new \ZipArchive();
if($zip->open($des_file_path, \ZipArchive::OVERWRITE) === TRUE){
$this->addFileToZip($source_file_path, $zip); //调用方法,对要打包的根目录进行操作,并将ZipArchive的对象传递给方法
$zip->close(); //关闭处理的zip文件
if(!file_exists($des_file_path)){
return response()->json(['status' => '404','msg'=>'没有查询到符合条件的文件']);
//exit('无法找到文件'); //即使创建,仍有可能失败 比如没有符合条件的file,就没有addfile,如果你遍历文件夹没有添加过滤条件,则可以忽略此if判断
}
//如果不是Ajax请求,可以直接通过注释的header去下载
// header("Cache-Control: public");
// header("Content-Description: File Transfer");
// header("Content-Type: application/zip"); //zip格式的
// header("Content-Transfer-Encoding: Binary"); //告诉浏览器,这是二进制文件
// header('Content-Disposition: attachment; filename='.basename($des_file_path)); //文件名
// header('Content-Length:'.filesize($des_file_path)); //告诉浏览器,文件大小
// @readfile($des_file_path);
// unlink($des_file_path);删除压缩包
// exit;
}else{
exit('无法打开文件,或者文件创建失败');
}
$download_file_path = "/Project/storage/zip/".$uuid.".zip";//js回掉的文件路径
return response()->json(['url' => $download_file_path],200,[],JSON_UNESCAPED_SLASHES);
}
2、控制器用到的addFileToZip方法(遍历文件夹),可以写到助手函数里面去
public function addFileToZip($path,$zip){
//定义一个数组,可以打印调试,列出所有符合条件的文件
//$files = array();
$handler=opendir($path); //打开当前文件夹由$path指定。
while(($file = readdir($handler)) !== false){
if($file != "." && $file != ".."){ //文件夹文件名字为'.'和‘..',不要对他们进行操作
if(is_dir($path."/".$file)){ // 如果读取的某个对象是文件夹,则递归
$this->addFileToZip($path."/".$file, $zip);
}else{ //将文件加入zip对象
//此处else写你想要过滤文件的条件代码,比如根据时间戳做一些操作,filectime
// $files[] = $path . "/" . $file;
$zip->addFile($path."/".$file,$file); //将遍历的文件添加到要压缩的zip里,第二个参数指定文件所在的文件夹名local_name(最终生成的文件名,如果本身文件就是zip包,再压缩需要加zip后缀)
// $path_arr = explode("/",$path);
// $prefix_file = $path_arr[count($path_arr)-1];
// $zip->renameName($path."/".$filename,$prefix_file.'/'.$file);//$prefix_file是指定压缩文件所在的文件夹名,不要的话,表示所有的文件同级,renameName等同于addFile方法的第二个参数。如果你的路径文件夹是最后一级(即目录下全是文件,无目录),就忽略前缀$prefix_file
}
}
}
@closedir($path);
}
3、前端调用接口
$.ajax({
type:"POST",
url:"download",//你的路由地址
dataType:"json",
async: false,
data:JSON.stringify({"uuid":123456789}),
success:function (result) {
if("404"==result.status){
alert(result.msg);
else{
window.open("" +result.url);
// window.location.href=result.url
}
}
})
备注:为什么js不能用header去下载?看这儿 情况和他描述的并无二致。
有时候open()方法会报错,我在php5.5版本使用正常,php7.2反而报错,返回状态码 9,表示没有此文件。具体原因未知。
\ZipArchive::open($file_path, \ZipArchive::OVERWRITE)
查询php.net官方Link,提供两种办法,其实结果应该都一样,| 表示左 | 位运算符:
这样以后,就可以正常下载了,或者根据文件是否存在进行三元判断是使用create还是overwrite也可以。
if($zip->open($destination,$overwrite ? ZIPARCHIVE::OVERWRITE : ZIPARCHIVE::CREATE) !== true)
{
return false;
}