首页 > 技术文章 > php打包下载以及断点续传

lz0925 2019-10-23 14:44 原文

php下载单文件 以及 多文件打包下载,支持断点续传

  • 断点续传的功能未经验证
  • 需要nginx或者apache服务器指定静态文件,png, mp4, zip等后缀文件的目录,
  • 直接实例化并调用 download 方法, $file 与 $name为前台传过来的参数, 实验时,可以先设置默认值来调用。
<?php
/**
 * User: LiZheng  271648298@qq.com
 * Date: 2019/10/14
 */

namespace app\services;


use app\foundation\Service;

class DownloadService extends Service
{

    private $_speed = 4096;   // 下载速度, 未启用
    /**
     * 下载,$file 与 $name为前台传过来的参数, 实验时,可以先设置默认值来调用。
     * @param String $file 要下载的文件路径(此处我的路径为URL路径,非服务器上的绝对路径, 前期试验为了简化过程,可以直接使用使用服务器路径,删除路径转换的代码)
     * @param String $name 文件名称,为空则与下载的文件名称一样
     * @param boolean $reload 是否开启断点续传, 默认不开启
     * @return string
     * User: LiZheng  271648298@qq.com
     * Date: 2019/10/14
     */
    public function download($file, $name = '', $reload = false)
    {
        $filePath = explode(',', $file);
        $count = count($filePath);
        if($count > 1)
        {
            // 生成zip打包下载
            $zip = new \ZipArchive();
            //服务器类型, WIN是本地,否则linux, 
            $media_dir = strtoupper(substr(PHP_OS,0,3))==='WIN'?'d:/media_space':'/media_space';
            $tmp = "cache/".date('Y-m-d')."/";
            // 压缩文件的临时目录
            $root_dir = $media_dir."/".$tmp;
            //是否存在图片根目录,不存在则创建
            if(!is_dir($root_dir))
            {
                //权限是否OK
                mkdir($root_dir, 0777, true) && chmod($root_dir, 0777);
            }
            // 生成zip文件, 文件名要求唯一
            $unique = md5((time().mt_rand(10,99).mt_rand(10,99))/mt_rand(1,100)).'.zip';
            $root_dir .= $unique;
            $URLs = PIC_HOST.$tmp.$unique;
            touch($root_dir);
            // 必须先新建.zip文件才可以执行foreach中的代码
            if($zip->open($root_dir) === true){
                foreach ($filePath as $item){
                    $item = $media_dir.substr($item, strpos($item,'com.cn/')+6);
                    $zip->addFile($item, basename($item));
                }
                $zip->close();
                $file = $URLs;
            }else{
                $this -> setError('压缩失败!');
                return false;
            }
        }else
        {
            // 如果是单文件,直接使用文件名称
            $file = $filePath[0];
        }
        $fp = @fopen($file, 'rb');
        if ($fp) {
            if ($name == '') {
                $name = basename($file);
            }
            $header_array = get_headers($file, true);
            //var_dump($header_array);die;
            // 下载本地文件,获取文件大小
            if (!$header_array) {
                $file_size = filesize($file);
            } else {
                $file_size = $header_array['Content-Length'];
            }
            $ranges = $this->getRange($file_size);
            $ua = $_SERVER["HTTP_USER_AGENT"];//判断是什么类型浏览器
            header('cache-control:public');
            header('content-type:application/octet-stream');

            $encoded_filename = urlencode($name);
            $encoded_filename = str_replace("+", "%20", $encoded_filename);

            //解决下载文件名乱码
            if (preg_match("/MSIE/", $ua) || preg_match("/Trident/", $ua)) {
                header('Content-Disposition: attachment; filename="' . $encoded_filename . '"');
            } else if (preg_match("/Firefox/", $ua)) {
                header('Content-Disposition: attachment; filename*="utf8\'\'' . $name . '"');
            } else if (preg_match("/Chrome/", $ua)) {
                header('Content-Disposition: attachment; filename="' . $encoded_filename . '"');
            } else {
                header('Content-Disposition: attachment; filename="' . $name . '"');
            }
            //header('Content-Disposition: attachment; filename="' . $name . '"');

            if ($reload && $ranges != null) { // 使用续传
                header('HTTP/1.1 206 Partial Content');
                header('Accept-Ranges:bytes');

                // 剩余长度
                header(sprintf('content-length:%u', $ranges['end'] - $ranges['start']));

                // range信息
                header(sprintf('content-range:bytes %s-%s/%s', $ranges['start'], $ranges['end'], $file_size));
                //file_put_contents('test.log',sprintf('content-length:%u',$ranges['end']-$ranges['start']),FILE_APPEND);
                // fp指针跳到断点位置
                fseek($fp, sprintf('%u', $ranges['start']));
            } else {
//                file_put_contents('test.log', '2222', FILE_APPEND);
                header('HTTP/1.1 200 OK');
                header('Accept-Ranges:bytes');
                header('content-length:' . $file_size);
            }

            while (!feof($fp)) {
                //echo fread($fp, round($this->_speed*1024,0));  // 如限速,调用 setSpeed()
                //echo fread($fp, $file_size);
                echo fread($fp, 4096);
                ob_flush();
            }

            ($fp != null) && fclose($fp);
            // TODO 删除临时文件压缩包
            if(isset($root_dir))
            {
                unlink($root_dir);
            }
        } else {
            $this -> setError('下载失败!');
            return false;
        }
    }

    /** 设置下载速度
     * @param int $speed
     */
    public function setSpeed($speed)
    {
        if (is_numeric($speed) && $speed > 16 && $speed < 4096) {
            $this->_speed = $speed;
        }
    }

    /** 获取header range信息
     * @param  int $file_size 文件大小
     * @return Array
     */
    private function getRange($file_size)
    {
        //file_put_contents('range.log', json_encode($_SERVER), FILE_APPEND);
        if (isset($_SERVER['HTTP_RANGE']) && !empty($_SERVER['HTTP_RANGE'])) {
            $range = $_SERVER['HTTP_RANGE'];
            $range = preg_replace('/[\s|,].*/', '', $range);
            $range = explode('-', substr($range, 6));
            if (count($range) < 2) {
                $range[1] = $file_size;
            }
            $range = array_combine(array('start', 'end'), $range);
            if (empty($range['start'])) {
                $range['start'] = 0;
            }
            if (empty($range['end'])) {
                $range['end'] = $file_size;
            }
            return $range;
        }
        return null;
    }
}

上面为php下载的代码,正常在浏览器访问该接口,设置上默认的参数file:文件URL,会弹出下载框

  • 请使用前端调用该下载接口,而不是PHP
  • 如果使用ajax调用时仅返回二进制数据流不弹出下载框,请使用隐藏表单进行请求,不建议使用get打开新窗口下载
- 仅为演示代码,

<form id="downloadForm" style="display: none" method="post">
                <input name="filePath" id="filePath" type="hidden"> 
                <input name="newName" id="newName" type="hidden">
 </form>
<script>
        //文件下载
        $("#down").click(function () {
            var data = [];
            $('input.both-all:checked').map(function (i,v) {
                data.push($(v).val());
            });
            if(data.length==0){
                alert('请勾选!');
                return false;
            }
            $.ajax({
                type:"post"
                ,dataType:"Json"
                ,url:"pan/files/down"                                         // 这里的php代码主要用来整理下载时用到的参数,如果不想使用php整理,也可以直接在JS中整理。
                ,data:{id:data}
                ,success:function (result) {
                    console.log(result)
                    var host = result.data.host;                          // 文件服务器的接口地址(下载)
                    var filePath = result.data.filePath;               // 通过文件的ID获取文件的url,以,为分隔符拼接为字符串
                    var newName = result.data.newName;       // 文件下载后的名称,不填默认为原文件名称(多文件时生成特定的文件名,后缀为.zip即可)
                    var form = $("#downloadForm");                 // 隐藏的下载表单
                    var input_filePath = $("#filePath");              // 表单中的input  filePath 字段,
                    var input_newName = $("#newName");      // 表单中的input  newName 字段
                    form.attr('target','');                                      // _blank 也可
                    form.attr('action',host);                                // 文件服务器的接口地址(下载)
                    input_filePath.attr('value',filePath);
                    input_newName.attr('value',newName);
                    alert("submit")
                    form.submit();                                              // 提交表单
                }
                ,error:function (e) {
        // alert(e.msg)
                }

            })
        })
</script>


推荐阅读