PHP ZipArchive 大文件分片下载压缩 支持断点续传

安装扩展

压缩使用的是 PHP扩展 ZipArchive,确保当前的PHP版本已经安装了这个拓展类;

<?php
// 使用phpinfo()查看有没有zip扩展
echo phpinfo();
?>

有了这个则是已经有这个扩展了

image.png

  • 没有的下载对应版本的扩展包,
  • 下载地址
  • 我的版本是7.4.3的所以我下载的是7.4
    image.png
    • 把解压的php_zip.dll文件放到php-5.6.27-nts/ext目录
    • 在php.ini添加以下配置:extension=php_zip.dll ; 打开zlib.output_compression=On off改成On
    • 重启Apache

实现原理

  1. 创建临时目录:

    • 使用mkdir()创建一个临时目录,并设置权限为777。
    • 使用uniqid()生成一个唯一的ID,将其与临时目录路径拼接形成唯一的目录名。
  2. 压缩文件操作:

    • 使用ZipArchive类创建一个压缩文件对象。
    • 使用open方法打开压缩文件,并指定文件名和创建模式为ZipArchive::CREATE
      • 使用open方法的时候,第二个参数是可选的,用来指定对打开的zip文件的处理方式,共有四种情况
        • ZIPARCHIVE::OVERWRITE总是创建一个新的文件,如果指定的zip文件存在,则会覆盖掉
        • ZIPARCHIVE::CREATE 如果指定的zip文件不存在,则新建一个
        • ZIPARCHIVE::EXCL 如果指定的zip文件存在,则会报错
        • ZIPARCHIVE::CHECKCONS
    • 使用addFile方法将文件添加到压缩文件中,使用basename($file_path)作为文件在压缩文件中的名称。
  3. 文件分块传输:

    • 定义了文件分块传输的相关参数,其中$chunkSize表示每个文件块的大小。
    • 通过$_SERVER['HTTP_RANGE']获取请求头中的HTTP_RANGE值,判断是否支持文件分块传输。
    • 使用正则表达式匹配请求头中的bytes=(\d+)格式,提取起始位置$startPos
  4. 读取文件内容:

    • 使用fopen打开文件,模式为二进制读取。
    • 使用fseek设置文件指针位置为起始位置$startPos
    • 使用fread从文件中读取指定大小的内容,将文件内容读入内存。
  5. 压缩文件下载:

    • 使用header函数设置响应头信息,包括文件类型、文件名和文件大小。

    • 使用filesize获取压缩文件的大小,并设置响应头中的文件大小。

    • 如果请求头中存在HTTP_RANGE值,则进行分块传输:

      • 对请求头中的HTTP_RANGE进行处理,获取起始位置和结束位置。
      • 使用fopen打开压缩文件,并设置文件指针位置为起始位置。
      • 使用fpassthru将文件内容直接发送给浏览器进行下载或输出。
    • 如果请求头中不存在HTTP_RANGE值,则直接使用readfile函数将压缩文件内容输出给浏览器。

  6. 删除临时文件和目录:

    • 使用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方法:

参考文章:(98条消息) PHP扩展类ZipArchive实现压缩解压Zip文件和文件打包下载_php ziparchive解压缩文件_dreamboycx的博客-CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小柴没吃饱

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值