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 相关文章推荐
弄了个检测传输的参数是否为数字的Function
Dec 06 PHP
PHP chmod 函数与批量修改文件目录权限
May 10 PHP
ajax 的post方法实例(带循环)
Jul 04 PHP
php 深入理解strtotime函数的使用详解
May 23 PHP
CodeIgniter采用config控制的多语言实现根据浏览器语言自动转换功能
Jul 18 PHP
PHP处理Json字符串解码返回NULL的解决方法
Sep 01 PHP
php表单提交实例讲解
Nov 12 PHP
Yii清理缓存的方法
Jan 06 PHP
php简单构造json多维数组的方法示例
Jun 08 PHP
Laravel5.0+框架邮件发送功能实现方法图文与实例详解
Apr 23 PHP
使用laravel根据用户类型来显示或隐藏字段
Oct 17 PHP
laravel5.1 ajax post 传值_token示例
Oct 24 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
PHP4实际应用经验篇(9)
2006/10/09 PHP
php学习之 循环结构实现代码
2011/06/09 PHP
php导出csv数据在浏览器中输出提供下载或保存到文件的示例
2014/04/24 PHP
PHP include任意文件或URL介绍
2014/04/29 PHP
PHP连接SQL Server的方法分析【基于thinkPHP5.1框架】
2019/05/06 PHP
JavaScript 输入框内容格式验证代码
2010/02/11 Javascript
Jquery:ajax实现翻页无刷新功能代码
2013/08/05 Javascript
JS实现切换标签页效果实例代码
2013/11/01 Javascript
javascript的回调函数应用示例
2014/02/20 Javascript
教你如何使用node.js制作代理服务器
2014/11/26 Javascript
如何防止JavaScript自动插入分号
2015/11/05 Javascript
分享几种比较简单实用的JavaScript tabel切换
2015/12/31 Javascript
基于JavaScript实现拖动滑块效果
2017/02/16 Javascript
JS闭包用法实例分析
2017/03/27 Javascript
angular.JS实现网页禁用调试、复制和剪切
2017/03/31 Javascript
浅谈关于.vue文件中style的scoped属性
2017/08/19 Javascript
通过源码分析Vue的双向数据绑定详解
2017/09/24 Javascript
浅谈vuepress 踩坑记
2018/04/18 Javascript
vue项目创建并引入饿了么elementUI组件的步骤
2019/04/11 Javascript
JS实现压缩上传图片base64长度功能
2019/12/03 Javascript
Node.js API详解之 Error模块用法实例分析
2020/05/14 Javascript
JavaScript实现猜数字游戏
2020/05/20 Javascript
PyQt5 pyqt多线程操作入门
2018/05/05 Python
详解Python做一个名片管理系统
2019/03/14 Python
Django处理多用户类型的方法介绍
2019/05/18 Python
Python实现新型冠状病毒传播模型及预测代码实例
2020/02/05 Python
简单介绍HTML5中的文件导入
2015/05/08 HTML / CSS
微软马来西亚官方网站:Microsoft马来西亚
2019/11/22 全球购物
绩效考核实施方案
2014/03/18 职场文书
维稳工作情况汇报
2014/10/27 职场文书
涨价通知
2015/04/23 职场文书
项目验收申请报告
2015/05/15 职场文书
仰望星空观后感
2015/06/10 职场文书
信息技术课教学反思
2016/02/23 职场文书
MySQL中InnoDB存储引擎的锁的基本使用教程
2021/05/26 MySQL
关于JavaScript 中 if包含逗号表达式
2021/11/27 Javascript