PHP搭建大文件切割分块上传功能示例


Posted in PHP onJanuary 04, 2017

背景

在网站开发中,文件上传是很常见的一个功能。相信很多人都会遇到这种情况,想传一个文件上去,然后网页提示“该文件过大”。因为一般情况下,我们都需要对上传的文件大小做限制,防止出现意外的情况。
 但是在有些业务场景中,大文件上传又是必须的,比如邮箱附件,或者内部OA等等。

问题

服务端为什么不能直接传大文件?跟php.ini里面的几个配置有关

upload_max_filesize = 2M //PHP最大能接受的文件大小
post_max_size = 8M //PHP能收到的最大POST值'
memory_limit = 128M //内存上限
max_execution_time = 30 //最大执行时间

当然不能简单粗暴的把上面几个值调大,否则服务器内存资源吃光是迟早的问题。

解决思路

好在HTML5开放了新的FILE API,也可以直接操作二进制对象,我们可以直接在浏览器端实现文件切割,按照以前的做法就得用Flash的方案,实现起来会麻烦很多。

JS思路

1.监听上传按钮的onchange事件

2.获取文件的FILE对象

3.把文件的FILE对象进行切割,并且附加到FORMDATA对象中

4.把FORMDATA对象通过AJAX发送到服务器

5.重复3、4步骤,直到文件发送完。

PHP思路

1.建立上传文件夹

2.把文件从上传临时目录移动到上传文件夹

3.所有的文件块上传完成后,进行文件合成

4.删除文件夹

5.返回上传后的文件路径

DEMO代码

前端部分代码

<!doctype html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <meta name="viewport"
   content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
 <meta http-equiv="X-UA-Compatible" content="ie=edge">
 <title>Document</title>
 <style>
  #progress{
   width: 300px;
   height: 20px;
   background-color:#f7f7f7;
   box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);
   border-radius:4px;
   background-image:linear-gradient(to bottom,#f5f5f5,#f9f9f9);
  }

  #finish{
   background-color: #149bdf;
   background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);
   background-size:40px 40px;
   height: 100%;
  }
  form{
   margin-top: 50px;
  }
 </style>
</head>
<body>
<div id="progress">
 <div id="finish" style="width: 0%;" progress="0"></div>
</div>
<form action="./upload.php">
 <input type="file" name="file" id="file">
 <input type="button" value="停止" id="stop">
</form>
<script>
 var fileForm = document.getElementById("file");
 var stopBtn = document.getElementById('stop');
 var upload = new Upload();

 fileForm.onchange = function(){
  upload.addFileAndSend(this);
 }

 stopBtn.onclick = function(){
  this.value = "停止中";
  upload.stop();
  this.value = "已停止";
 }

 function Upload(){
  var xhr = new XMLHttpRequest();
  var form_data = new FormData();
  const LENGTH = 1024 * 1024;
  var start = 0;
  var end = start + LENGTH;
  var blob;
  var blob_num = 1;
  var is_stop = 0
  //对外方法,传入文件对象
  this.addFileAndSend = function(that){
   var file = that.files[0];
   blob = cutFile(file);
   sendFile(blob,file);
   blob_num += 1;
  }
  //停止文件上传
  this.stop = function(){
   xhr.abort();
   is_stop = 1;
  }
  //切割文件
  function cutFile(file){
   var file_blob = file.slice(start,end);
   start = end;
   end = start + LENGTH;
   return file_blob;
  };
  //发送文件
  function sendFile(blob,file){
   var total_blob_num = Math.ceil(file.size / LENGTH);
   form_data.append('file',blob);
   form_data.append('blob_num',blob_num);
   form_data.append('total_blob_num',total_blob_num);
   form_data.append('file_name',file.name);

   xhr.open('POST','./upload.php',false);
   xhr.onreadystatechange = function () {
    var progress;
    var progressObj = document.getElementById('finish');
    if(total_blob_num == 1){
     progress = '100%';
    }else{
     progress = Math.min(100,(blob_num/total_blob_num)* 100 ) +'%';
    }
    progressObj.style.width = progress;
    var t = setTimeout(function(){
     if(start < file.size && is_stop === 0){
      blob = cutFile(file);
      sendFile(blob,file);
      blob_num += 1;
     }else{
      setTimeout(t);
     }
    },1000);
   }
   xhr.send(form_data);
  }
 }

</script>
</body>
</html>

PHP部分代码

<?php
class Upload{
 private $filepath = './upload'; //上传目录
 private $tmpPath; //PHP文件临时目录
 private $blobNum; //第几个文件块
 private $totalBlobNum; //文件块总数
 private $fileName; //文件名

 public function __construct($tmpPath,$blobNum,$totalBlobNum,$fileName){
  $this->tmpPath = $tmpPath;
  $this->blobNum = $blobNum;
  $this->totalBlobNum = $totalBlobNum;
  $this->fileName = $fileName;
  
  $this->moveFile();
  $this->fileMerge();
 }
 
 //判断是否是最后一块,如果是则进行文件合成并且删除文件块
 private function fileMerge(){
  if($this->blobNum == $this->totalBlobNum){
   $blob = '';
   for($i=1; $i<= $this->totalBlobNum; $i++){
    $blob .= file_get_contents($this->filepath.'/'. $this->fileName.'__'.$i);
   }
   file_put_contents($this->filepath.'/'. $this->fileName,$blob);
   $this->deleteFileBlob();
  }
 }
 
 //删除文件块
 private function deleteFileBlob(){
  for($i=1; $i<= $this->totalBlobNum; $i++){
   @unlink($this->filepath.'/'. $this->fileName.'__'.$i);
  }
 }
 
 //移动文件
 private function moveFile(){
  $this->touchDir();
  $filename = $this->filepath.'/'. $this->fileName.'__'.$this->blobNum;
  move_uploaded_file($this->tmpPath,$filename);
 }
 
 //API返回数据
 public function apiReturn(){
  if($this->blobNum == $this->totalBlobNum){
    if(file_exists($this->filepath.'/'. $this->fileName)){
     $data['code'] = 2;
     $data['msg'] = 'success';
     $data['file_path'] = 'http://'.$_SERVER['HTTP_HOST'].dirname($_SERVER['DOCUMENT_URI']).str_replace('.','',$this->filepath).'/'. $this->fileName;
    }
  }else{
    if(file_exists($this->filepath.'/'. $this->fileName.'__'.$this->blobNum)){
     $data['code'] = 1;
     $data['msg'] = 'waiting for all';
     $data['file_path'] = '';
    }
  }
  header('Content-type: application/json');
  echo json_encode($data);
 }
 
 //建立上传文件夹
 private function touchDir(){
  if(!file_exists($this->filepath)){
   return mkdir($this->filepath);
  }
 }
}

//实例化并获取系统变量传参
$upload = new Upload($_FILES['file']['tmp_name'],$_POST['blob_num'],$_POST['total_blob_num'],$_POST['file_name']);
//调用方法,返回结果
$upload->apiReturn();

存在的问题

这只是一个简单的DEMO,有很多地方需要改进,比如上传的文件夹与临时文件放在一起,用户中途取消也没有发请求进行清理,容易造成文件冗余。JS采用的是同步模型,文件需要一块一块按顺序上传,会导致整个浏览器在上传的过程中出于堵塞的状态,按了按钮可能需要几秒钟才能反应过来,用户体验不好。真正需要产品化的时候就要综合考虑多种情况,当然作为一个示例,引导大家了解分块上传的思路还是不错的。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

PHP 相关文章推荐
mysql下创建字段并设置主键的php代码
May 16 PHP
php中如何使对象可以像数组一样进行foreach循环
Aug 09 PHP
php实现的zip文件内容比较类
Sep 24 PHP
php截取中文字符串函数实例
Feb 23 PHP
php使用curl打开https网站的方法
Jun 17 PHP
浅谈PHP中JSON数据操作
Jul 01 PHP
thinkphp下MySQL数据库读写分离代码剖析
Apr 18 PHP
Yii框架创建cronjob定时任务的方法分析
May 23 PHP
PHP 应用容器化以及部署方法
Feb 12 PHP
ThinkPHP3.2.3框架邮件发送功能图文实例详解
Apr 23 PHP
PHP Swoole异步MySQL客户端实现方法示例
Oct 24 PHP
php 命名空间(namespace)原理与用法实例小结
Nov 13 PHP
php实现的简单中文验证码功能示例
Jan 03 #PHP
php与c 实现按行读取文件实例代码
Jan 03 #PHP
浅谈PHP安全防护之Web攻击
Jan 03 #PHP
php中遍历二维数组并以表格的形式输出的方法
Jan 03 #PHP
解析PHP之提取多维数组指定列的方法
Jan 03 #PHP
PHP实现RTX发送消息提醒的实例代码
Jan 03 #PHP
php cookie用户登录的详解及实例代码
Jan 03 #PHP
You might like
php分页函数
2006/07/08 PHP
PHP中MD5函数使用实例代码
2008/06/07 PHP
php 数组的合并、拆分、区别取值函数集
2010/02/15 PHP
Symfony2安装第三方Bundles实例详解
2016/02/04 PHP
Mac系统完美安装PHP7详细教程
2017/06/06 PHP
php读取出一个文件夹及其子文件夹下所有文件的方法示例
2017/06/15 PHP
Thinkphp 5.0实现微信企业付款到零钱
2018/09/30 PHP
PhpStorm 如何优雅的调试Hyperf的方法步骤
2019/11/24 PHP
两种简单实现菜单高亮显示的JS类代码
2010/06/27 Javascript
关于用Jquery的height()、width()计算动态插入的IMG标签的宽高的问题
2010/12/08 Javascript
js调试工具Console命令详解
2014/10/21 Javascript
angularJS结合canvas画图例子
2015/02/09 Javascript
jQuery基于扩展简单实现倒计时功能的方法
2016/05/14 Javascript
js实现的页面加载完毕之前loading提示效果完整示例【附demo源码下载】
2016/08/02 Javascript
微信小程序-拍照或选择图片并上传文件
2017/01/06 Javascript
Bootstrap表单使用方法详解
2017/02/17 Javascript
JS中的作用域链
2017/03/01 Javascript
初探js和简单隐藏效果的实例
2017/11/23 Javascript
javascript实现简单页面倒计时
2021/03/02 Javascript
python连接mysql调用存储过程示例
2014/03/05 Python
Python获取服务器信息的最简单实现方法
2015/03/05 Python
python发送多人邮件没有展示收件人问题的解决方法
2019/06/21 Python
使用Python制作缩放自如的圣诞老人(圣诞树)
2019/12/25 Python
python库skimage给灰度图像染色的方法示例
2020/04/27 Python
keras实现调用自己训练的模型,并去掉全连接层
2020/06/09 Python
深入理解css属性的选择对动画性能的影响
2016/04/20 HTML / CSS
HTML5 weui使用笔记
2019/11/21 HTML / CSS
美国最大的城市服装和运动鞋零售商:Jimmy Jazz
2016/11/19 全球购物
采购主管工作职责
2013/12/12 职场文书
学雷锋的心得体会
2014/09/04 职场文书
党员查摆四风问题思想汇报
2014/10/25 职场文书
教师先进个人材料
2014/12/17 职场文书
婚礼庆典答谢词
2015/01/20 职场文书
邀请书格式范文
2015/02/02 职场文书
《珍珠鸟》教学反思
2016/02/16 职场文书
PHP判断是否是json字符串
2021/04/01 PHP