安装扩展
压缩使用的是 PHP扩展 ZipArchive,确保当前的PHP版本已经安装了这个拓展类;
<?php
// 使用phpinfo()查看有没有zip扩展
echo phpinfo();
?>
有了这个则是已经有这个扩展了
- 没有的下载对应版本的扩展包,
- 下载地址
- 我的版本是7.4.3的所以我下载的是7.4
- 把解压的php_zip.dll文件放到php-5.6.27-nts/ext目录
- 在php.ini添加以下配置:extension=php_zip.dll ; 打开zlib.output_compression=On off改成On
- 重启Apache
实现原理
-
创建临时目录:
- 使用
mkdir()
创建一个临时目录,并设置权限为777。 - 使用
uniqid()
生成一个唯一的ID,将其与临时目录路径拼接形成唯一的目录名。
- 使用
-
压缩文件操作:
- 使用
ZipArchive
类创建一个压缩文件对象。 - 使用
open
方法打开压缩文件,并指定文件名和创建模式为ZipArchive::CREATE
。- 使用open方法的时候,第二个参数是可选的,用来指定对打开的zip文件的处理方式,共有四种情况
- ZIPARCHIVE::OVERWRITE总是创建一个新的文件,如果指定的zip文件存在,则会覆盖掉
- ZIPARCHIVE::CREATE 如果指定的zip文件不存在,则新建一个
- ZIPARCHIVE::EXCL 如果指定的zip文件存在,则会报错
- ZIPARCHIVE::CHECKCONS
- 使用open方法的时候,第二个参数是可选的,用来指定对打开的zip文件的处理方式,共有四种情况
- 使用
addFile
方法将文件添加到压缩文件中,使用basename($file_path)
作为文件在压缩文件中的名称。
- 使用
-
文件分块传输:
- 定义了文件分块传输的相关参数,其中
$chunkSize
表示每个文件块的大小。 - 通过
$_SERVER['HTTP_RANGE']
获取请求头中的HTTP_RANGE
值,判断是否支持文件分块传输。 - 使用正则表达式匹配请求头中的
bytes=(\d+)
格式,提取起始位置$startPos
- 定义了文件分块传输的相关参数,其中
-
读取文件内容:
- 使用
fopen
打开文件,模式为二进制读取。 - 使用
fseek
设置文件指针位置为起始位置$startPos
。 - 使用
fread
从文件中读取指定大小的内容,将文件内容读入内存。
- 使用
-
压缩文件下载:
-
使用
header
函数设置响应头信息,包括文件类型、文件名和文件大小。 -
使用
filesize
获取压缩文件的大小,并设置响应头中的文件大小。 -
如果请求头中存在
HTTP_RANGE
值,则进行分块传输:- 对请求头中的
HTTP_RANGE
进行处理,获取起始位置和结束位置。 - 使用
fopen
打开压缩文件,并设置文件指针位置为起始位置。 - 使用
fpassthru
将文件内容直接发送给浏览器进行下载或输出。
- 对请求头中的
-
如果请求头中不存在
HTTP_RANGE
值,则直接使用readfile
函数将压缩文件内容输出给浏览器。
-
-
删除临时文件和目录:
- 使用
glob
函数获取临时目录下的所有文件路径,使用通配符*
匹配所有文件。 - 使用
array_map
函数将unlink
函数应用到每个文件路径上,即删除对应的文件。 - 使用
rmdir
函数删除临时目录。
- 使用
源码
<?php
class File extends Backend
{
/**
* @description: 压缩下载
* @param {*} $params['uid']:用户ID,用于查询数据库中的文件列表。
* @param {*} $temp_dir:临时目录路径。
* @param {*} $username:用户名。
* @param {*} $zip_name:压缩文件名。
* @param {*} $chunkSize:每次读取文件的大小。
* @param {*} $range:请求头中的 HTTP_RANGE 值。
* @param {*} $startPos:传输文件的起始位置。
* @param {*} $files:用户的文件列表。
* @param {*} $file_path:文件路径。
* @param {*} $fp:文件资源。
* @param {*} $chunk:每次读取的文件内容的缓冲区。
* @param {*} $contentLength:压缩文件
* @return {*}
*/
public function export()
{
// TODO 1.创建一个临时目录,并检查是否已存在。如果不存在,则创建该目录
$temp_dir = './temp/' . '-' . uniqid();
if (!is_dir($temp_dir)) mkdir($temp_dir, 0777, true);
// TODO 2.创建一个 ZipArchive 对象,并指定压缩文件名。
$zip = new \ZipArchive();
$zip_name = $temp_dir . '/' . $username . '.zip';
if ($zip->open($zip_name, \ZipArchive::CREATE) !== true) exit("文件创建失败!");
// TODO 3.定义文件分块传输的相关参数
$chunkSize = 1024 * 1024 * 1; // 定义文件分块传输的相关参数。
$range = isset($_SERVER['HTTP_RANGE']) ? $_SERVER['HTTP_RANGE'] : ''; // 获取请求头中的 HTTP_RANGE 值,如果请求头中不存在 HTTP_RANGE 值,则将其设为空字符串
// TODO 4.定义传输文件的起始位置
$startPos = 0;
// preg_match(),用于执行正则表达式的匹配操作。它接受一个正则表达式模式和一个要搜索的字符串作为参数,并尝试在字符串中找到与模式匹配的部分。如果匹配成功,则返回 1,否则返回 0。
if (preg_match('/bytes=(\d+)/', $range, $matches)) $startPos = intval($matches[1]); // 如果请求头中的 HTTP_RANGE 值匹配了 bytes=(\d+) 的正则表达式,则将该值的整数部分赋值给 $startPos 变量。
// TODO 5.遍历文件列表
foreach ($files as $file) {
$file_path = '.' . $file['file_url']; //构建文件路径,添加文件路径前缀 '.' 并取出 $file['file_url'] 值。
$fp = fopen($file_path, 'rb'); // 打开文件,并将资源赋值给 $fp 变量。
if ($fp === false) exit("文件打开失败: $file_path");
fseek($fp, $startPos); //设置文件指针位置为起始位置 $startPos(0)
// TODO 6.通过文件路径打开文件,读取文件内容,并将每次读取的数据添加到压缩文件中。
while (!feof($fp)) { // 判断文件是否读完
$chunk = fread($fp, $chunkSize); // 从文件中读取指定大小的内容,成功读取内容时返回读取的数据,将文件读入内存
if ($chunk === false) exit("文件读取失败: $file_path");
$zip->addFile($file_path, basename($file_path)); // 将文件内容添加到压缩文件中,使用 basename($file_path) 作为文件在压缩文件中的名称。成功添加文件时返回 true
// $zip->addFile($file_path, basename($file_path)); // addFile 函数会一次性将整个文件加载到内存中,然后添加到 ZIP 压缩文件中。
// $zip->addFromString(basename($file_path), $chunk); // 将文件的每个数据块逐个添加到 ZIP 压缩文件中。
}
fclose($fp); // 关闭文件
}
// TODO 7.关闭压缩文件,并设置响应头信息,包括文件类型、文件名和文件大小。
$zip->close();
header('Content-Type: application/zip'); //设置响应头信息为 ZIP 文件类型
header('Content-Disposition: attachment; filename="' . $username . '_all.zip"'); // 这里对客户端的弹出对话框, 设置响应头中的文件名为 $XXX_all.zip
header('Content-Length: ' . filesize($zip_name)); // 设置响应头中的文件大小为压缩文件的大小
$contentLength = filesize($zip_name); //获取压缩文件的大小,返回值是文件的大小(字节数)
// TODO 8.如果请求头中存在 HTTP_RANGE 值,则进行分块传输
if ($range != '') {
// TODO 9.首先对请求头中的 HTTP_RANGE 进行处理,获取起始位置和结束位置
$range = preg_replace('/[\s|,].*/', '', $range); //preg_replace() 用于执行正则表达式的替换操作。它接受一个正则表达式模式、一个替换字符串和一个要进行替换的字符串作为参数,并尝试在字符串中找到与模式匹配的部分并进行替换
$ranges = explode('-', $range);
$startPos = intval($ranges[0]);
$endPos = $contentLength - 1;
if (isset($ranges[1]) && is_numeric($ranges[1]) && $ranges[1] < $endPos) {
$endPos = intval($ranges[1]);
}
header('HTTP/1.1 206 Partial Content'); //设置响应头状态为 206 Partial Content,表示只返回部分内容
header("Content-Range: bytes $startPos-$endPos/$contentLength"); // 设置响应头中的 Content-Range,指定返回的内容范围
$fp = fopen($zip_name, 'rb'); //打开压缩文件
fseek($fp, $startPos); //设置文件指针位置为起始位置
fpassthru($fp); // 直接将文件内容发送给浏览器进行下载或输出
ob_flush(); // 刷新输出缓冲区,将内容发送到浏览器
fclose($fp); //关闭文件
} else {
readfile($zip_name); //直接输出响应
}
// TODO 10.删除临时文件和目录
// array_map('unlink', glob($temp_dir . '/*'))的作用是先使用glob函数获取指定目录下的所有文件路径(使用通配符*获取目录下所有文件的路径)并返回一个数组,然后使用array_map函数将unlink函数应用到该数组的每个元素上,即删除对应的文件。
// array_map('unlink', glob($temp_dir . '/*'));
unlink($zip_name);
rmdir($temp_dir);
exit;
}
PHP 操作方法
fopen( file,'rb')
:
- 用于打开文件或 URL,并返回一个文件资源。它接受两个参数:文件名或 URL,以及打开文件的模式(读取、写入、追加等)。
fclose(要关闭的文件资源)
- 用于关闭先前由
fopen()
打开的文件资源。
feof(要检测的文件资源)
- 用于检测文件指针是否已到达文件末尾。
- 在循环中使用
feof()
可以判断文件是否已被完全读取。
fread(文件资源,要读取的字节数)
- 用于从文件中读取指定数量的字节。
- 使用
fread()
可以将文件内容读入内存。
fseek(文件资源,偏移量,偏移起始位置)
- 用于设置文件指针的位置
- 可以在文件中移动文件指针,以便从指定位置开始读取文件内容。
fpassthru(要传输的文件资源)
- 用于直接将文件内容传输给浏览器进行下载或输出。
- 避免将整个文件内容加载到内存中,特别适用于传输大文件或二进制文件。
filesize(要获取大小的文件名)
- 用于获取文件的大小(字节数)。
ob_flush()
- 用于刷新输出缓冲区,并将缓冲区的内容发送到浏览器。它没有参数。
- 在输出被缓冲时,使用
ob_flush()
可以确保缓冲区的内容立即发送到客户端。
flush()
- 它将输出缓冲区的内容立即发送给客户端,但仅刷新当前脚本的输出缓冲区。
readfile(要输出的文件名)
- 用于直接将文件内容输出给浏览器。适用于简单的文件下载或输出。
basename(要提取文件名的路径)
- 可以从文件路径中提取文件名,用于设置压缩文件中的文件名。
preg_match(正则表达式模式,要搜索的字符串)
- 使用
preg_match()
可以判断一个字符串是否与指定的正则表达式模式匹配,从而进行相应的操作。 glob(要搜索的文件模式)
- 可以获取符合指定模式的文件路径列表,用于遍历文件或进行文件操作。
uniqid(前缀,更改器)
- 生成一个基于当前时间的唯一字符串,可用于创建临时文件、目录或其他需要唯一标识符的场景。
header 请求头
header('HTTP/1.1 206 Partial Content')
:
- 这个请求头用于指示部分内容的响应,即只返回请求范围内的内容,而不是完整的文件内容。
- HTTP状态码
206
表示部分内容,用于支持文件分块传输或断点续传。 - 当客户端请求指定范围的文件内容时,服务器可以使用此响应头来通知客户端,只返回请求的部分内容。
header("Content-Range: bytes)
:
Content-Range
请求头用于指定响应中返回的内容范围。- 当使用分块传输或断点续传时,
Content-Range
可以指定具体的字节范围。 - 例如,
Content-Range: bytes 0-1023/2048
表示返回从0字节到1023字节的内容,总共有2048字节。
header('Content-Type: application/zip')
:
Content-Type
请求头用于指定响应内容的媒体类型。- 在这种情况下,
application/zip
表示响应的内容是一个ZIP压缩文件。 - 当客户端接收到响应时,根据
Content-Type
来确定如何处理和显示响应的内容。 - Content-type类型:
text/html
: 用于指示响应内容是 HTML 格式的文本。text/plain
: 用于指示响应内容是纯文本格式,不包含任何样式或标记。application/json
: 用于指示响应内容是 JSON 格式的数据。JSON 是一种常用的数据交换格式。application/xml
: 用于指示响应内容是 XML 格式的数据。XML 是一种用于表示结构化数据的标记语言。application/pdf
: 用于指示响应内容是 PDF 格式的文件。PDF 是一种用于跨平台文档显示的格式。image/jpeg
: 用于指示响应内容是 JPEG 图像格式。image/png
: 用于指示响应内容是 PNG 图像格式audio/mpeg
: 用于指示响应内容是 MP3 音频格式video/mp4
: 用于指示响应内容是 MP4 视频格式。multipart/form-data
: 用于指示响应内容是表单数据,通常用于文件上传。
header('Content-Disposition: attachment; filename="' . $username . '_all.zip"')
:
Content-Disposition
请求头用于指示客户端如何处理响应的内容。- 在这里,
attachment
表示客户端应将响应内容作为附件下载,而不是在浏览器中直接打开。 filename="' . $username . '_all.zip"
部分用于指定下载的文件名,将变量$username
加入文件名以个性化命名。
header('Content-Length: ' . filesize($zip_name))
:
Content-Length
请求头用于指定响应内容的长度(字节数)。- 在这种情况下,
filesize($zip_name)
用于获取压缩文件的大小,并将文件大小作为响应的长度进行设置。 - 客户端在接收到响应时,可以使用
Content-Length
来知道响应的总大小,从而进行进度显示或其他处理。
ZipArchive方法:
- ZipArchive::addEmptyDir — Add a new directory
- ZipArchive::addFile — Adds a file to a ZIP archive from the given path
- ZipArchive::addFromString — Add a file to a ZIP archive using its contents
- ZipArchive::addGlob — Add files from a directory by glob pattern
- ZipArchive::addPattern — Add files from a directory by PCRE pattern
- ZipArchive::close — Close the active archive (opened or newly created)
- ZipArchive::deleteIndex — delete an entry in the archive using its index
- ZipArchive::deleteName — delete an entry in the archive using its name
- ZipArchive::extractTo — Extract the archive contents
- ZipArchive::getArchiveComment — Returns the Zip archive comment
- ZipArchive::getCommentIndex — Returns the comment of an entry using the entry index
- ZipArchive::getCommentName — Returns the comment of an entry using the entry name
- ZipArchive::getExternalAttributesIndex — Retrieve the external attributes of an entry defined by its index
- ZipArchive::getExternalAttributesName — Retrieve the external attributes of an entry defined by its name
- ZipArchive::getFromIndex — Returns the entry contents using its index
- ZipArchive::getFromName — Returns the entry contents using its name
- ZipArchive::getNameIndex — Returns the name of an entry using its index
- ZipArchive::getStatusString — Returns the status error message, system and/or zip messages
- ZipArchive::getStream — Get a file handler to the entry defined by its name (read only).
- ZipArchive::locateName — Returns the index of the entry in the archive
- ZipArchive::open — Open a ZIP file archive
- ZipArchive::renameIndex — Renames an entry defined by its index
- ZipArchive::renameName — Renames an entry defined by its name
- ZipArchive::setArchiveComment — Set the comment of a ZIP archive
- ZipArchive::setCommentIndex — Set the comment of an entry defined by its index
- ZipArchive::setCommentName — Set the comment of an entry defined by its name
- ZipArchive::setExternalAttributesIndex — Set the external attributes of an entry defined by its index
- ZipArchive::setExternalAttributesName — Set the external attributes of an entry defined by its name
- ZipArchive::statIndex — Get the details of an entry defined by its index.
- ZipArchive::statName — Get the details of an entry defined by its name.
- ZipArchive::unchangeAll — Undo all changes done in the archive
- ZipArchive::unchangeArchive — Revert all global changes done in the archive.
- ZipArchive::unchangeIndex — Revert all changes done to an entry at the given index
- ZipArchive::unchangeName — Revert all changes done to an entry with the given name.
- 资料查询
参考文章:(98条消息) PHP扩展类ZipArchive实现压缩解压Zip文件和文件打包下载_php ziparchive解压缩文件_dreamboycx的博客-CSDN博客