php中的大文件下载的方法,支持2G以上大小并且支持断点续传。

tech2023-07-20  111

1.HTTP Header里的Range和Content-Range参数

Range用于请求头中,指定第一个字节的位置和最后一个字节的位置,一般格式: Range:(unit=first byte pos)-[last byte pos]

Content-Range用于响应头,指定整个实体中的一部分的插入位置,他也指示了整个实体的长度。在服务器向客户返回一个部分响应,它必须描述响应覆盖的范围和整个实体长度。一般格式: Content-Range: bytes (unit first byte pos) - [last byte pos]/[entity legth]

请求下载整个文件: GET /test.rar HTTP/1.1 Connection: close Host: 116.1.219.219 Range: bytes=0-801 //一般请求下载整个文件是bytes=0- 或不用这个头。

一般正常回应 HTTP/1.1 200 OK Content-Length: 801 Content-Type: application/octet-stream Content-Range: bytes 0-800/801 //801:文件总大小。

2.下边是封装的一个class类方法。

类名:DownloadClass.php

<?php //文件传输,支持断点续传,2g以上超大文件也有效 namespace app\statistics\controller;//命名空间,根据实际使用情况进行更改,目前是在Thinkphp5框架中使用 class DownloadClass { //缓冲单元 const BUFF_SIZE = 5120;//1024 * 5 //文件名称 private $fileName; //文件地址 private $filePath; //文件大小,php超大数字字符串形式描述。 private $fileSize; //文件类型 private $mimeType; //请求区域(范围) private $range; //是否写入日志 private $isLog = false; function __construct($fileName,$filePath,$mimeType = null,$range = null) { $this->fileName = $fileName; $this->filePath = $filePath; $this->fileSize = sprintf('%u', filesize($filePath)); $this->mimeType = ($mimeType != null) ? $mimeType : "application/octet-stream";//bin $this->range = trim($range); } //获取文件区域 private function getRange() { if (!empty($this->range)) { $range = preg_replace('/[\s|,].*/', '', $this->range); $range = explode('-', substr($range, 6)); if (count($range) < 2) { $range[1] = $this->fileSize;//Range: bytes=-100 } $range = array_combine(array('start', 'end'), $range); if (empty($range['start'])) { $range['start'] = 0; } if (!isset($range['end']) || empty($range['end'])) { $range['end'] = $this->fileSize; } return $range; } return null; } //向客户端发送文件 public function send() { $fileHandle = fopen($this->filePath, 'rb'); if ($fileHandle) { ob_end_clean();//clean cache ob_start(); ini_set('output_buffering', 'Off'); ini_set('zlib.output_compression', 'Off'); $magicQuotes = get_magic_quotes_gpc(); $lastModified = gmdate('D, d M Y H:i:s', filemtime($this->filePath)) . ' GMT'; $etag = sprintf('w/"%s:%s"', md5($lastModified), $this->fileSize); $ranges = $this->getRange(); //header头文件 header(sprintf('Last-Modified: %s', $lastModified)); header(sprintf('ETag: %s', $etag)); header(sprintf('Content-Type: %s', $this->mimeType)); $disposition = 'attachment'; if (strpos($this->mimeType, 'image/') !== FALSE) { $disposition = 'inline'; } header(sprintf('Content-Disposition: %s; filename="%s"',$disposition,basename($this->fileName))); if ($ranges != null) { //写入错误日志信息 if ($this->isLog) { $this->log(json_encode($ranges) . ' ' . $_SERVER['HTTP_RANGE']); } header('HTTP/1.1 206 Partial Content'); header('Accept-Ranges: bytes'); header(sprintf('Content-Length: %u', $ranges['end'] - $ranges['start'])); header(sprintf('Content-Range: bytes %s-%s/%s', $ranges['start'], $ranges['end'], $this->fileSize)); fseek($fileHandle, sprintf('%u', $ranges['start'])); } else { header("HTTP/1.1 200 OK"); header(sprintf('Content-Length: %s', $this->fileSize)); } while (!feof($fileHandle) && !connection_aborted()) { $lastSize = sprintf("%u",bcsub($this->fileSize, sprintf("%u", ftell($fileHandle)))); if (bccomp($lastSize, self::BUFF_SIZE) > 0) { $lastSize = self::BUFF_SIZE; } echo fread($fileHandle, $lastSize); ob_flush(); flush(); } /** * set_magic_quotes_runtime 用来设置php.ini文件中的magic_quotes_runtime值,当遇到反斜杆(\)、单引号(')、双引号(")这样一些的字符定入到数据库里。 * 又不想被过滤掉,使用这个函数,将会自动加上一个反斜杆(\),保护系统和数据库的安全。 **/ ini_set("magic_quotes_runtime",$magicQuotes); ob_end_flush(); } if ($fileHandle != null) { fclose($fileHandle); } } //设置记录 public function setIsLog($isLog = true) { $this->isLog = $isLog; } //记录 private function log($msg) { try { $handle = fopen('transfer_log.txt', 'a'); fwrite($handle, sprintf('%s : %s' . PHP_EOL, date('Y-m-d H:i:s'), $msg)); fclose($handle); } catch (\Throwable $e) { return $e->getMessage(); } } }

3.使用方法

在同等级目录下可以直接new类名进行使用,不同目录需要进行use引用。

//下载上传的文件,大文件视频。 public function downLoadFile(Request $request) { //定义文件名,作为例子使用 $fileName = 'example.mp4'; //将路径全部转为中文,防止出现乱码 $fileName = iconv("utf-8", "gb2312//IGNORE", $fileName); //文件路径,做例子使用,这里使用绝对路径。 $file_path = 'G:\BaiduNetdiskDownload\example.mp4'; $change_file_info = pathinfo($file_path); //文件后缀 $extension = $change_file_info['extension']; //设定默认时区 date_default_timezone_set('Asia/Shanghai'); error_reporting(E_STRICT); $mimeType = 'audio/x-matroska';定义HTTP content-type中的文件类型。 $range = isset($_SERVER['HTTP_RANGE']) ? $_SERVER['HTTP_RANGE'] : null; set_time_limit(0); $transfer = new DownloadClass($fileName.'.'.$extension,$file_path,$mimeType,$range); $transfer->send(); }

附上HTTP content-type中的所有的文件类型。

https://www.runoob.com/http/http-content-type.html

最新回复(0)