内容简介:在上一节文章中,我们介绍了前端文件分片上传,了解vue-simple-uploader组件自带分片上传功能,大文件一片片依次上传到后端服务器后,后端程序要将分片合成一个完整的文件,那么PHP是如何处理合成分片的呢?请看本节讲解。
在上一节文章中,我们介绍了前端文件分片上传,了解vue-simple-uploader组件自带分片上传功能,大文件一片片依次上传到后端服务器后,后端程序要将分片合成一个完整的文件,那么 PHP 是如何处理合成分片的呢?请看本节讲解。
为了阅读和开发方便,我将文件上传系列相关文章章节列出来:
- 01.使用vue-simple-uploader上传文件和文件夹
- 02.文件分片上传之前端文件分片
- 03.文件分片上传之后端PHP合成文件
- 04.超大文件上传之计算文件MD5值
- 05.文件上传之秒传文件
- 06.文件上传之断点续传和跨端续传
前端什么时候发送合并请求
在上一节文章中,我们知道,当所有的分片都上传完成时,会调用 onFileSuccess()
方法。
onFileSuccess(rootFile, file, response, chunk) { let resp = JSON.parse(response); if (resp.code === 0 && resp.merge === false) { console.log('上传成功,不需要合并'); } else { axios.post('http://localhost:9999/up.php?action=merge', { filename: file.name, identifier: file.uniqueIdentifier, totalSize: file.size, totalChunks: chunk.offset + 1 }).then(function(res){ if (res.code === 0) { console.log('上传成功') } else { console.log(res.message); } }) .catch(function(error){ console.log(error); }); } },
从后台返回的 response
包含了是否需要合并的指令 merge
,如果 resp.merge === true
,那就发送合并请求,告诉后端可以合成分片了。如果上传的文件只有一片,就不需要合并。
Uplader.php
我们计划用PHP写一个处理上传的类,负责检测文件、接收上传分片、合并分片等。当中还要用到数据库存储文件信息,这些我们在后面章节完成,先看结构:
<?php class Uploader { private static $tmpDir = 'D:\www\helloweba\demo\files_tmp'; //分片临时文件目录 private static $saveDir = 'D:\www\helloweba\demo\files'; //文件最终保存目录 private static $mysql = null; public $fileInfo = [ 'identifier' => '', //文件的唯一标识 'chunkNumber' => 1, //当前是第几个分片 'totalChunks' => 1, //总分片数 'filename' => '', //文件名称 'totalSize' => 0 //文件总大小 ]; //检测断点和md5 public function checkFile() { // } //上传分片 public function upload() { // } //合并文件 public function merge() { } //计算时间 private function getmicrotime() { list($usec, $sec) = explode(" ",microtime()); return ((float)$usec + (float)$sec); } //返回提示消息 private function message($code, $msg) { $res = [ 'code' => $code, 'message' => $msg ]; return $res; } }
我们先定义上传目录,整个目录可以是在你的web目录,也可以是web访问不到的目录,一个临时目录files_tmp/用来保存临时分片文件,一个是真正保存文件的目录files/,注意我们是在Wind平台运行,如果是为 Linux 下,路径应该写成像这样:/opt/data/files。此外这两个目录要有写权限。
上传分片
首先我们接收前端上传上来的分片文件,当然在正式接收上传分片前,应该检测文件是否已经上传过了,检测文件合法性等等,这些我们在后续文章中会讲到。我们先来看PHP如何接收分片文件。
public function upload() { if (!empty($_FILES)) { $in = @fopen($_FILES["file"]["tmp_name"], "rb"); if (!$in = @fopen($_FILES["file"]["tmp_name"], "rb")) { return $this->message(1002, '打开临时文件失败'); } } else { if (!$in = @fopen("php://input", "rb")) { return $this->message(1003, '打开输入流失败'); } } if ($this->fileInfo['totalChunks'] === 1) { //如果只有1片,则不需要合并,直接将临时文件转存到保存目录下 $filename = $this->fileInfo['filename']; $saveDir = self::$saveDir . DIRECTORY_SEPARATOR . date('Y-m-d'); if (!is_dir($saveDir)) { @mkdir($saveDir); } $uploadPath = $saveDir . DIRECTORY_SEPARATOR .$filename; $res['merge'] = false; } else { //需要合并 $filePath = self::$tmpDir. DIRECTORY_SEPARATOR . $this->fileInfo['identifier']; //临时分片文件路径 $uploadPath = $filePath . '_' . $this->fileInfo['chunkNumber']; //临时分片文件名 $res['merge'] = true; } if (!$out = @fopen($uploadPath, "wb")) { return $this->message(1004, '文件不可写'); } while ($buff = fread($in, 4096)) { fwrite($out, $buff); } @fclose($in); @fclose($out); $res['code'] = 0; return $res; }
前端是通过 multipart/form-data;
将文件以二进制形式传给PHP,所以我们用 $_FILES
接收文件信息。
接收到文件后,我们判断这个文件是否就只有1个分片,如果只有1个分片就没必要再合成了,直接将该分片保存到files/下,并且告诉前端不需要合并文件: $res['merge'] = false;
。
如果是有多个分片,那就将这些分片保存到临时目录下,分片的命名应该是“文件唯一标识_当前分片”,如abcd_1,标识文件abcd的第一个分片,这样我们接下来合并文件就好办了。
合并文件
合并之前,先检查下该文件的所有分片是否都上传完毕,就是检测分片文件是否都存在。
public function merge() { $filePath = self::$tmpDir. DIRECTORY_SEPARATOR . $this->fileInfo['identifier']; $totalChunks = $this->fileInfo['totalChunks']; //总分片数 $filename = $this->fileInfo['filename']; //文件名 $done = true; //检查所有分片是否都存在 for ($index = 1; $index <= $totalChunks; $index++ ) { if (!file_exists("{$filePath}_{$index}")) { $done = false; break; } } if ($done === false) { return $this->message(1005, '分片信息错误'); } //如果所有文件分片都上传完毕,开始合并 $timeStart = $this->getmicrotime(); //合并开始时间 $saveDir = self::$saveDir . DIRECTORY_SEPARATOR . date('Y-m-d'); if (!is_dir($saveDir)) { @mkdir($saveDir); } $uploadPath = $saveDir . DIRECTORY_SEPARATOR .$filename; if (!$out = @fopen($uploadPath, "wb")) { return $this->message(1004, '文件不可写'); } if (flock($out, LOCK_EX) ) { // 进行排他型锁定 for($index = 1; $index <= $totalChunks; $index++ ) { if (!$in = @fopen("{$filePath}_{$index}", "rb")) { break; } while ($buff = fread($in, 4096)) { fwrite($out, $buff); } @fclose($in); @unlink("{$filePath}_{$index}"); //删除分片 } flock($out, LOCK_UN); // 释放锁定 } @fclose($out); $timeEnd = $this->getmicrotime(); //合并完成时间 $res['code'] = 0; $res['time'] = $timeEnd - $timeStart; //合并总耗时 return $res; }
如果分片文件都存在,开始合并所有分片,现将要最终合并的文件锁定,然后遍历所有分片,将分片文件依次写入合并的文件中,最后释放锁定。
每个分片被合并后,应当立即删除该分片。
这里我测试用了计算合并过程的耗时,真实应用可以将计时代码去掉。
合并大文件
我用自己的机器测试(8G内存,SSD),上传了一个约800MB的文件,2M一个分片,约400个分片,合并总耗时3秒钟,合并一个3G的文件耗时30秒钟。也就是说文件越大,分片越多,合成文件所花费的时间越长。但是通过观察内存变化,上面的代码在合并文件时内存消耗很低。那如果是特别大的文件,就会有大量分片,那这样的话合并过程是不是很耗时耗性能呢?
对于特大号的文件合并,有人提出建立一套算法,一个文件有N个分片,先建立一个序列,序列分成N个片段,每个分片占用一个片段,文件上传时就把对应的分片塞到对应的片段中,最终分片文件上传完了文件也就合成好了。这个方法也不错,将合并的时间分摊到每个分片上传上去了。
还有人提出,使用追加的方式将分片一片片往文件里塞,整个方法不可取,因为如果设置并发数大的话,不能保证文件是否按分片顺序合成的,最终有可能得到的文件是个乱序的不可用的文件。
那么我给大家建议使用Swoole来处理文件合成这一步,让耗时的操作在后台运行,不让前端等待,悄悄的在后台合成文件即可,如何?
好了,接下来我们要了解文件上传前计算MD5的操作以便实现秒传的功能,以及超大文件如何快速计算出md5值呢?敬请关注后续文章。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 根据 .key和.pem文件合成pfx证书 (openssl)
- 极大提升合成速度,百度提出首个全并行语音合成模型ParaNet
- 探索 React 合成事件
- 浅谈 react 基本合成事件类
- 基于学习的证据合成方法研究
- LearningAVFoundation之视频合成+转场过渡动画
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
深入浅出MFC (第二版)
侯俊杰 / 华中科技大学出版社 / 2001-1 / 80.00元
《深入浅出MFC》分为四大篇。第一篇提出学习MFC程序设计之前的必要基础,包括Widnows程序的基本观念以及C++的高阶议题。“学前基础”是相当主观的认定,但作者是甚于自己的学习经验以及教学经验,其挑选应该颇具说服力。第二篇介绍Visual C++整合环境开发工具。此篇只是提纲挈领,并不企图取代Visual C++使用手册;然而对于软件使用的老手,此篇或已足以帮助掌握Visual C++整合环境......一起来看看 《深入浅出MFC (第二版)》 这本书的介绍吧!