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("\r\n", $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,''
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章