因公司业务需要,需要将学生上传的附件进行归类, 再将学生归到班级, 将班级归到年级,最后整合到学校一层。我们的客户是老师,不可能让他们自己一个附件一个附件的下载查看,那估计用户得崩溃。
现在基本上都用云存储了, 我们用的是七牛云。七牛提供了多文件压缩,但是下载之后解压缩会导致中文文件名乱码,向七牛官方反应过,但没有下文。那只能自己实现了。
php自带ZipArchive 类,打包就靠这个了。最简单的就是打包单文件而非文件夹, 先从简单的来。先确保zip扩展已加载,确保打包的源文件(夹)存在, 否则退出
if (!extension_loaded('zip') || !file_exists($source)) {
return false;
}
开始实例化ZipArchive
$zip = new ZipArchive();
打开 zip file archive
if (!$zip->open($destination, ZipArchive::CREATE)) { // 如果不存在则创建一个zip压缩包。
return false;
}
注意open方法的flags参数
- ZIPARCHIVE::CREATE (integer) 如果不存在则创建一个zip压缩包。
- ZIPARCHIVE::OVERWRITE (integer) 总是以一个新的压缩包开始,此模式下如果已经存在则会被覆盖。
- ZIPARCHIVE::EXCL (integer) 如果压缩包已经存在,则出错。
- ZIPARCHIVE::CHECKCONS (integer) 对压缩包执行额外的一致性检查,如果失败则显示错误。
打包单文件
$zip->addFromString(basename($source), file_get_contents($source));
在打包文件夹之前, 我们需要遍历文件夹,并把文件夹添加到压缩包。在遍历这一步, 我发现很多中文的资料还是采用很古老的方式, 而php早就提供了内置类,这里需要用到两个类The RecursiveDirectoryIterator class及The RecursiveIteratorIterator class
RecursiveDirectoryIterator提供递归遍历文件系统目录的接口,RecursiveIteratorIterator可用于通过递归迭代器进行遍历,我们开始吧
$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($source), RecursiveIteratorIterator::SELF_FIRST);
注意RecursiveIteratorIterator构造函数的mode参数
- RecursiveIteratorIterator::LEAVES_ONLY - The default. Lists only leaves in iteration.
- RecursiveIteratorIterator::SELF_FIRST - Lists leaves and parents in iteration with parents coming first.
- RecursiveIteratorIterator::CHILD_FIRST - Lists leaves and parents in iteration with leaves coming first.
我们选择RecursiveIteratorIterator::SELF_FIRST,因为需要保留目录。目前为止,我们还缺少一个将目录添加到压缩包的函数,php官方自带ZipArchive::addEmptyDir,下面是打包文件夹的代码
foreach ($files as $file)
{
$file = str_replace('\', '/', $file);
// 忽略 "." 和 ".." 目录
if( in_array(substr($file, strrpos($file, '/')+1), array('.', '..')) )
continue;
$file = realpath($file);
if (is_dir($file) === true)
{
$zip->addEmptyDir(str_replace($source . '/', '', $file . '/')); // 新建目录
}
else if (is_file($file) === true)
{
$zip->addFromString(str_replace($source . '/', '', $file), file_get_contents($file)); // 新建文件
}
}
到这就基本完成了,下面是完整的打包函数
/** 递归打包文件夹
* @param $source
* @param $destination
* @return bool
*/
function Zip($source, $destination)
{
if (!extension_loaded('zip') || !file_exists($source)) {
return false;
}
$zip = new ZipArchive();
if (!$zip->open($destination, ZipArchive::CREATE)) {
return false;
}
$source = str_replace('\', '/', realpath($source));
if (is_dir($source) === true)
{
$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($source), RecursiveIteratorIterator::SELF_FIRST);
foreach ($files as $file)
{
$file = str_replace('\', '/', $file);
// Ignore "." and ".." folders
if( in_array(substr($file, strrpos($file, '/')+1), array('.', '..')) )
continue;
$file = realpath($file);
if (is_dir($file) === true)
{
$zip->addEmptyDir(str_replace($source . '/', '', $file . '/'));
}
else if (is_file($file) === true)
{
$zip->addFromString(str_replace($source . '/', '', $file), file_get_contents($file));
}
}
}
else if (is_file($source) === true)
{
$zip->addFromString(basename($source), file_get_contents($source));
}
return $zip->close();
}
再额外讲一些,我们将打包好的文件夹回传到七牛供用户下载, 或者放到自己服务器供用户下载。而在下载这里, 我发现很多文章还是用php读取的方式, 这样真的很浪费资源, 这里我们可以借助nginx的XSendfile, 有兴趣的同学可以试试,可以参考这篇文章使用X-Sendfile机制加快PHP下文件的下载。另外一个问题就是打包时间可能过长, 我们可以提前告知用户正在打包, 打包在后台进行, 大家可以参考fastcgi_finish_request。