zoukankan      html  css  js  c++  java
  • PHP 大文件操作

    文件分片上传

    借助js的Blob对象FormData对象可以实现大文件分片上传的功能,关于Blob和FormData的具体使用方法可以到如下地址去查看
    FormData 对象的使用
    Blob 对象的使用

    以下是实现代码,本例中后端代码使用php来实现,只是演示基本功能,具体一些文件验证逻辑先忽略。
    前段代码:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>upload</title>
        <script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script>
    </head>
    <body>
        <input type="file" name="file" id="file">
        <button id="upload" onClick="upload()">upload</button>
        <script type="text/javascript">
            var bytesPerPiece = 1024 * 1024; // 每个文件切片大小定为1MB .
            var totalPieces;
            //发送请求
            function upload() {
                var blob = document.getElementById("file").files[0];
                var start = 0;
                var end;
                var index = 0;
                var filesize = blob.size;
                var filename = blob.name;
    
                //计算文件切片总数
                totalPieces = Math.ceil(filesize / bytesPerPiece);
                while(start < filesize) {
                    end = start + bytesPerPiece;
                    if(end > filesize) {
                        end = filesize;
                    }
    
                    var chunk = blob.slice(start,end);//切割文件    
                    var sliceIndex= blob.name + index;
                    var formData = new FormData();
                    formData.append("file", chunk, filename);
                    $.ajax({
                        url: 'http://localhost:9999/test.php',
                        type: 'POST',
                        cache: false,
                        data: formData,
                        processData: false, //不需要将传输的数据序列化
                        contentType: false, //避免 JQuery 对其操作,从而失去分界符,而使服务器不能正常解析文件
                    }).done(function(res){ //上传成功
    
                    }).fail(function(res) {  //上传失败
    
                    });
                    start = end;
                    index++;
                }
            }
        </script>
    </body>
    </html>

    后端php代码:

    <?php
    header('Access-Control-Allow-Origin:*');
    header("Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept");
    
    $file = $_FILES['file'];
    $filename = $file['name'];
    file_put_contents($filename, file_get_contents($file['tmp_name']), FILE_APPEND);

    断点续传

    如果关闭浏览器或断网,可以将 上传文件名、已上传文件大小存储本地 window.localStorage.getItem,上传文件时查询一下同名文件与已上传大小。个人思路,未测试。

    远程下载大文件

    PHP 读取指定字节下载

    <?php
    set_time_limit(0);
    
    $read_file="https://github.com/tommy-muehle/puppet-vagrant-boxes/releases/download/1.1.0/centos-7.0-x86_64.box";
    $write_file="centos-7.0-x86_64.box";
    $host_file = fopen($read_file, 'r');
    $fh = fopen($write_file, 'w');
     
    while (!feof($host_file )) {
        $output = fread($host_file , 8192); // 读取指定字节,防止内存溢出
        fwrite($fh, $output);
    }
     
    fclose($host_file );
    fclose($fh);

    PHP curl 分块下载,核心是 curl_setopt($ch, CURLOPT_RANGE, $range)

    /**
     * 获取响应头某个Key的值
     * @param string $key header头的key
     * @param string $responseHead header头字符串
     * @return string
     */
    private function getResHeaderValue($key, $responseHead)
    {
        $value = '';
        $headArr = explode("
    ", $responseHead);
        foreach ($headArr as $loop)
        {
            if ($key == 'Http-Code')
            {
                if (preg_match('/HTTP/1.[0-9]{1} ([0-9]{3})/', $loop, $matches))
                {
                    return $matches['1'];
                }
            }
            else
            {
                if (strpos($loop, $key) !== false)
                {
                    $value = trim(str_replace($key . ':', '', $loop));
                }
            }
        }
        return $value;
    }

    private $siteUrl='';//下载文件地址
    /** * 获取远程文件的信息 * @return array * @throws Exception */ private function getSiteFiLeInfo($file_url) { $responseHeader = get_headers($file_url, 1); if (!$responseHeader) { throw new Exception('获取远程文件信息失败!'); } if (!empty($responseHeader['Location'])) { //处理文件下载302问题 $this->siteUrl = $responseHeader['Location']; return $this->getSiteFiLeInfo(); } return $responseHeader; } /** * 保存文件到本地 * @param string $fileName 保存到本地的文件名称 * @throws */ public function saveFile($fileName) { //获取远程文件的信息 $siteFileInfo = $this->getSiteFiLeInfo(); $siteFileLength = $siteFileInfo['Content-Length'] ?? 0; //根据文件是否存在创建文件句柄、计算断点下载开始字节 $fd = null; if (file_exists($fileName)) { $fd = fopen($fileName, 'ab'); } else { $fd = fopen($fileName, 'wb'); } if (!$fd) { throw new Exception('创建或打开本地文件失败!'); } //加上文件锁,防止刷新抢占资源句柄 if (!flock($fd, LOCK_EX | LOCK_NB)) { throw new Exception('已有相关进程操作执行下载本文件!'); } //检查文件是否已经下载完成 $fileSize = filesize($fileName); if ($fileSize && $fileSize >= $siteFileLength) { throw new Exception('原文件已下载完成,请勿重复下载!'); } //计算断点下载结束字节 $burstBytes=1024; $sByte = $fileSize; $eByte = $sByte + $burstBytes; //循环下载文件 while (true) { //文件下载完成 if ($fileSize >= $siteFileLength) { fclose($fd); break; } //传递分片范围 $xRange = "{$sByte}-$eByte"; //请求curl $result = $this->curl($xRange); //检查是否正常请求 $code = $result['code'] ?? 0; if (!$code) { throw new Exception('Http请求异常!'); } if ($code != 206) { throw new Exception('Http状态码异常,可能不支持断点的资源或已完成下载!'); } //返回流长度 $streamLength = $result['length'] ?? 0; //返回流内容 $streamContent = $result['stream'] ?? ''; if ($streamLength > 0) { file_put_contents('log.txt', $xRange . PHP_EOL, FILE_APPEND); $saveRes = fwrite($fd, $streamContent); if (!$saveRes) { throw new Exception('写入流到文件失败!'); } if ($saveRes != $streamLength) { //讲道理这种情况基本不会遇到,除非分段数设置过大,暂时未做兼容处理,重新执行就行 throw new Exception('数据异常:返回大小和写入大小不一致!'); } //递增range $sByte = $eByte + 1; $eByte = $sByte + $burstBytes; //记录文件大小 $fileSize = $fileSize + $saveRes; } } } /** * 获取下载文件流 * @param string $range 分片字节范围 * @param array $header Http请求头 * @return array * @throws */ private function curl($range, $header = []) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $this->siteUrl); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET'); curl_setopt($ch, CURLOPT_HEADER, TRUE); //设置关闭SSL curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); //设置分片 curl_setopt($ch, CURLOPT_RANGE, $range); //支持 x-y,传递一个你想指定的范围。它应该是”X-Y”格式,X或Y是被除外(省略)的。HTTP传送同样支持几个间隔,用逗句来分隔(X-Y,N-M)。 //设置header if ($header) { curl_setopt($ch, CURLOPT_HTTPHEADER, $header); } //执行请求 $response = curl_exec($ch); if (curl_errno($ch)) { throw new Exception('下载文件异常:' . curl_error($ch)); } //提取response_header和response_body $headSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE); $httpHeader = substr($response, 0, $headSize); if (!$httpHeader) { throw new Exception('下载文件异常:未获取到响应头'); } $fileStream = substr($response, $headSize); //解析header $length = $this->getResHeaderValue('Content-Length', $httpHeader); $httpCode = $this->getResHeaderValue('Http-Code', $httpHeader); curl_close($ch); //返回 return [ 'code' => $httpCode, 'length' => $length, 'stream' => $fileStream, ]; }

      

    PHP 读取大文件

    使用yield 迭代器实现,迭代器原理 PHP yield 生成器 

    // 使用 yield 迭代器实现
    function readYieldFile($fileName)
    {
      $handle = fopen($fileName, 'r');
      while (!feof($handle)) {
          yield fgets($handle);
      }
      fclose($handle);
    }
    
    $lines = readYieldFile('./all.txt');
    foreach ($lines as $row) {
       file_put_contens('test.txt',$row,FILE_APPEND); 
    }

    SplFileObject 读取指定行文件内容

    一般读取文件我们用fopen 或者 file_get_contents ,前者可以循环读取,后者可以一次性读取,但都是将文件内容一次性加载来操作。当前文件几百Mb,几G时性能会下降, SplFileObject 是对大文件的处理的类。

    /** 返回文件从X行到Y行的内容(支持php5、php4)  
     * @param string $filename 文件名
     * @param int $startLine 开始的行数
     * @param int $endLine 结束的行数
     * @return string
     */
    function getFileLines($filename, $startLine = 1, $endLine=50, $method='rb') {
        $content = array();
        $count = $endLine - $startLine;  
    
        $fp = new SplFileObject($filename, $method);
        $fp->seek($startLine-1);// 转到第N行, seek方法参数从0开始计数
        for($i = 0; $i <= $count; ++$i) {
             $content[]=$fp->current();// current()获取当前行内容
             $fp->next();// 下一行
        }
    
        return array_filter($content); // array_filter过滤:false,null,''
    }
  • 相关阅读:
    Office文档在线编辑的实现之二
    Office文档在线编辑的实现之一
    WebIM(5)----将WebIM嵌入到页面中
    WebIM(4)----Comet的特殊之处
    WebIM(3)----性能测试
    WebIM(2)---消息缓存
    WebIM(1)
    微信的通讯录首字母滑动的实现
    新浪微博认证新API调用
    Android小感悟-重写textview组件感悟
  • 原文地址:https://www.cnblogs.com/xuey/p/13204332.html
Copyright © 2011-2022 走看看