当前位置:首页 > PHP教程 > php上传下载 > 列表

PHP+JS实现大文件切片上传功能实现实例源码

发布:smiling 来源: PHP粉丝网  添加日期:2023-09-16 15:56:04 浏览: 评论:0 

近期公司的项目中,涉及到上传大文件的问题,大文件上传用普通表单上传时出现的问题是,无法断点续存,一但中途中断上传,就要重头开始,这很明显不是我们想要的,所以经过一番查询,学习了一下大文件分割上传的方法。并且使用简单的php做服务端处理程序实现一个功能demo,供以后回顾使用。本人也是初出茅庐的前端小白,记录下各种功能的实现总结,代码有错误的地方,请多多指正。

1.简单文件上传

普通表单上传

表单上传是我们经常使用的功能,而且使用起来也是非常简单,我们只需要声明表单内容类型为enctype="multipart/form-data",表明表单上传文件的二进制数据。

点击上传按钮,就可以将表单发送到服务器,并使用index.php接受到对应的表单数据,存入$_GET/$_POST超级全局变量中,我们只需要使用move_uploaded_file方法,将接收到的文件数据,存储起来,就实现了文件上传功能了。

  1. $myfile = $_FILES['myfile']; 
  2.  //上传路径 
  3.  $path = "upload/" . $myfile['name'];  
  4.  if(move_uploaded_file($myfile['tmp_name'], $path)){ 
  5.    echo "上传成功"
  6.  } else
  7.    echo "上传失败"
  8.  }; 

ajax模拟表单上传文件

当我们有需求,需要异步提交表单或者需要对上传文件做一定修改(例如:裁剪尺寸)时,普通的表单上传就不能满足我们的需求,因为我们无法修改表单的file值,这时候就需要ajax出场了。这里我们使用jQuery使用ajax更方便快捷。

我们需要做如下修改:

HTML

我们不需要配置form,只需要配置相应的ID,用于获取DOM元素对象。

JQuery

注意,jQuery的ajax方法,会默认配置一些请求信息,所以我们需要重新配置放置jQuery的默认行为导致数据格式或请求头信息出现问题。

这里的contentType和processData为必须项。

  1. $('#submitForm').on('click'function(e){ 
  2.    // 阻止默认表单提交 
  3.    e.preventDefault(); 
  4.  ​ 
  5.    // 创建表单 
  6.    // 默认配置了enctype="multipart/form-data" 
  7.    var formData = new FormData(); 
  8.    formData.append('myfile',$('#myFile')[0].files[0]) 
  9.  ​ 
  10.    // 提交表单 
  11.    $.ajax({ 
  12.      type: "POST"
  13.      url: 'post.php'
  14.      data: formData, 
  15.      // 阻止jquery赋予默认属性,使用FormData默认配置enctype="multipart/form-data" 
  16.      contentType: false, 
  17.      // 阻止jquery自动序列化数据 
  18.      processData: false, 
  19.      success: function(data){ 
  20.        console.log('请求正常',data); 
  21.      } 
  22.    }) 
  23.  }) 

2.大文件分割上传

简单上传痛点

简单上传,使用表单提交文件到服务器时,如果网络不好或者中途中断,会使文件上传失败,试想一下如果要上传文件很大,当你上传到99%时,突然间中断,又要重新上传,那该有多崩溃,那时你可能电脑的想砸了。

实现思路

大文件上传,实现的方法,就是将上传文件的二进制文件通过分割的形式,逐个上传到服务器,在上传完成后,服务器再对文件进行拼接操作。

为了能识别上传的数据,是哪个文件,我们必须要拥有一个文件标识符,用于识别接收到的文件数据是属于哪个文件的,以及可以实现避免重复上传,实现秒传功能等。

不要忘记由于是异步操作,而且操作的数据段大小不一,会导致整合时无法确认拼接熟悉怒,所以我们需要一个index标识数据段的位置。

通过初步整理,我们就需要以下的参数

文件唯一标识符

分割后数据段

分割数据段的顺序索引值

经过思考,我们可以建立两个处理程序,来分别处理接受chunk数据段和合并chunk数据段。

file_getchunk.php

功能:将分割chunk数据,整理并保存,此处我们用文件形式实现。

file_integration.php

功能:接收到整合通知,将数据段拼接,并生成文件。

PHP.ini配置

由于PHP默认配合中,限制了POST与上传的大小,所以我们为了测试,需要修改php.ini中的默认配置。

post_max_size = 50M

upload_max_filesize = 50M

HTML

  1. <script src="http://code.jquery.com/jquery-1.11.1.min.js"></script> 
  2.  <form id="myForm"> 
  3.    <input type="file" name="myfile" id="myFile" /> 
  4.    <input type="submit" value="上传" id="submitForm"/> 
  5.  </form> 

JQuery

获取文件对象,文件标识符,分割文件,通过ajax发送切割好的blob数据段。

  1. $('#submitForm').on('click'function(e){ 
  2.    // 阻止默认表单提交 
  3.    e.preventDefault(); 
  4.    var myfile = $('#myFile')[0].files[0]; 
  5.    // 定义文件标识符   
  6.    var fileId = getFileIdentifier(myfile); 
  7.    // 数据切片 
  8.    var chunks = fileSlice(myfile); 
  9.    // 发送分割数据段 
  10.  sendChunk(fileId, chunks); 
  11.  }) 

生成文件唯一标识getFileIdentifier()

此处可以使用md5,生成文件唯一的md5(相同文件md5相同),作为标识符。这里只初略的处理了一下文件标识。

  1. function getFileIdentifier(file){ 
  2.      // 获取文件标识符 
  3.      return file.size + file.name; 
  4.    } 

分割方法fileSlice()

先将文件使用blob文件继承的方法slice进行切割,生成blob字串。

  1. function fileSlice(file, chunkSize = 1024*1024*0.2){ 
  2.     // 1.初始化数据 
  3.     var totalSize = file.size; 
  4.   var start = 0; 
  5.     var end = start + chunkSize; 
  6.     var chunks = []; 
  7.     // 2.使用bolb提供的slice方法切片 
  8.     while(start < totalSize){ 
  9.       var chunk = file.slice(start, end); 
  10.       chunks.push(chunk); 
  11.       start = end
  12.       end += chunkSize; 
  13.     } 
  14.     // 3.返回切片组chunk[] 
  15.     return chunks; 
  16.   } 

发送chunk方法sendChunk()

使用ajax依次发送已经分割好的chunk,并提供对应的数据,请求file_getchunk.php进行处理。此处task列表,用于保证文件分隔符全部已经完成上传。

  1. function sendChunk(id, chunks){ 
  2.    // 逐个提交 
  3.    // 用于保证ajax发送完毕 
  4.    var task = []; 
  5.  ​ 
  6.    chunks.forEach(function(chunk, index){ 
  7.      var formData = new FormData(); 
  8.      formData.append('fileId', id); 
  9.      formData.append('myFileChunk', chunk); 
  10.      formData.append('chunkIndex', index); 
  11.      $.ajax({ 
  12.        type: "POST"
  13.        url: 'file_getchunk.php'
  14.        data: formData, 
  15.        contentType: false, 
  16.        processData: false, 
  17.        success: function(done){ 
  18.          // 移除已完成任务 
  19.          task.pop(); 
  20.          console.log(done,' 已完成'); 
  21.          if (task.length === 0) { 
  22.            // 发送完毕,整合文件 
  23.            console.log('通知整合'); 
  24.            makeFileIntegration(id, chunks.length); 
  25.          } 
  26.        } 
  27.      }) 
  28.      task.push('file Working'); 
  29.    }) 
  30.  } 

通知整合方法makeFileIntegration()

接收到整合通知,请求file_integration.php进行文件的整合处理。

  1. function makeFileIntegration(id, size){ 
  2.    // 通知已传输完成 
  3.    $.post( 
  4.      "file_integration.php"
  5.      { 
  6.        id: id, 
  7.        size: size 
  8.      }, 
  9.      function(data){ 
  10.        console.log(data); 
  11.      } 
  12.    ); 
  13.  } 

PHP- file_getchunk.php

当PHP监听到请求时,获取对应的数据,生成文件夹,按照chunkIndex存储数据段。

  1. if(!is_dir('upload')){ 
  2.    mkdir('upload', 0777); 
  3.  } 
  4.  ​ 
  5.  $chunk = $_FILES['myFileChunk']; 
  6.  // 文件唯一标识 
  7.  $fileId = $_POST['fileId']; 
  8.  // 临时文件夹名称 
  9.  $length = strlen($fileId) - (strlen($fileId) - strpos($fileId'.')); 
  10.  $filedir = substr($fileId, 0, $length); 
  11.  ​ 
  12.  $chunkIndex = $_POST['chunkIndex']; 
  13.  ​ 
  14.  $filepath = 'upload/' . $filedir
  15.  ​ 
  16.  $filename = $filepath . '/' . $chunkIndex
  17.  ​ 
  18.  if(!is_dir($filepath)){ 
  19.    mkdir($filepath, 0777); 
  20.  } 
  21.  move_uploaded_file($chunk['tmp_name'], $filename); 
  22.  ​ 
  23.  echo $chunkIndex

PHP-file_integration.php

监听到整合请求,对文件夹下面的所有文件,进行依次拼接,并生成最终还原出来的文件。

  1. $fileId = $_POST['id']; 
  2.  // 临时文件夹名称 
  3.  $length = strlen($fileId) - (strlen($fileId) - strpos($fileId'.')); 
  4.  $filedir = substr($fileId, 0, $length); 
  5.  ​ 
  6.  $size = $_POST['size']; 
  7.  $file = './upload/' . $fileId
  8.  ​ 
  9.  // 创建最终文件 
  10.  if(!file_exists($file)){ 
  11.    // 最终文件不存在,创建文件 
  12.    $myfile = fopen($file'w+'); 
  13.    fclose($myfile); 
  14.  }  
  15.  // 用增加方式打开最终文件 
  16.  $myfile = fopen($file'a'); 
  17.  ​ 
  18.  for ($i = 0; $i < $size$i++) { 
  19.    // 单文件路径 
  20.    $filePart = 'upload/' . $filedir . '/' . $i
  21.  ​ 
  22.    if(file_exists($filePart)){ 
  23.      $chunk = file_get_contents($filePart); 
  24.      // 写入chunk 
  25.      fwrite($myfile$chunk); 
  26.    } else
  27.      echo "缺少Part$i 文件,请重新上传"
  28.      break
  29.    } 
  30.  } 
  31.  ​ 
  32.  fclose($myfile); 
  33.  echo "整合完成"
 3.更进一步

大文件分割上传功能已经基本实现,但是我们还可以拥有很多优化的地方

1.断点续存。

我们需要的文件已经可以正常的分割上传,服务端也可以正常接收切片,完成数据段切片的合并了。此时我们就可以进一步实现断点续存了。

断点续存,实现方法很简单,我们只需要获取到上传完成的数据段切片信息,就可以判断我们应该从哪个数据段开始继续传输数据。

获取已经完成数据段切片的信息,我们可以使用前端保存或者服务端获取。此处我们使用服务端接口检测,返回数据缺失位置来实现断点续存。

思路整理

我们要在上传前,请求服务端查询出中断时的位置,利用位置信息,筛选上传的数据段切片。

那么我们要增加的逻辑就是:

offset中断位置信息

查询中断位置接口:file_get_breakpoint.php

实现

getFileBreakpoint()获取文件断点函数

此处要保证ajax执行顺序,才能正确获取offset偏移量,实现思路有很多。此处只使用jquery提供的将ajax请求变为同步,进行处理。

注:同步请求时,success函数返回值不可以直接return,要保存在一个变量中,在ajax请求外return才能生效。

  1. // 获取文件断点 
  2.  function getFileBreakpoint(id, size){ 
  3.    var offset = ''
  4.    $.ajax({ 
  5.      type:"post"
  6.      url:"file_get_breakpoint.php"
  7.      data: { 
  8.        id: id, 
  9.        size: size 
  10.      }, 
  11.      async: false
  12.      success:function(res){ 
  13.        offset = parseInt(res); 
  14.      } 
  15.    }) 
  16.    return offset; 
  17.  } 

在sendChunk()发送数据前获取offset

// 上传前,请求file_integration.php接口获取数据段开始传输的位置

var offset = getFileBreakpoint(id, chunks.length);

遍历chunks发送数据段时,增加筛选逻辑

  1. chunks.forEach(function(chunk, index){ 
  2.     // ==============新增================= 
  3.     // 从offset开始传输 
  4.     if (index < offset) { 
  5.       return
  6.     } 
  7.     // ==============新增================= 
  8.     var formData = new FormData(); 
  9.     formData.append('fileId', id); 
  10.     formData.append('myFileChunk', chunk); 
  11.     formData.append('chunkIndex', index); 
  12.     $.ajax({ 
  13.       type: "POST"
  14.       url: 'file_getchunk.php'
  15.       data: formData, 
  16.       contentType: false, 
  17.       processData: false, 
  18.       success: function(done){ 
  19.         task.pop(); 
  20.         console.log(done,' 已完成'); 
  21.         if (task.length === 0) { 
  22.           console.log('通知整合'); 
  23.           makeFileIntegration(id, chunks.length); 
  24.         } 
  25.       } 
  26.     }) 
  27.     task.push(index+' is Working'); 
  28.   }) 

获取中断位置接口file_get_breakpoint.php

这里使用的获取中断位置的逻辑很简单(不是最优),只需要检测文件夹是否存在,再依次检测数据段是否缺失。缺失时返回缺失段的index,已存在返回chunks长度size,不存在时返回0

  1. // 1.检测数据文件是否存在(文件标识,数据段总数) 
  2.  $fileId = $_POST['id']; 
  3.  $size = $_POST['size']; 
  4.  // 临时文件夹名称 
  5.  $length = strlen($fileId) - (strlen($fileId) - strpos($fileId'.')); 
  6.  $filedir = substr($fileId, 0, $length); 
  7.  ​ 
  8.  // 2.按顺序检测缺失的数据段的位置 
  9.  // 检测是否存在文件夹 
  10.  if (is_dir("upload/$filedir")) { 
  11.    $offset = $size
  12.    // 检测数据段缺失下标 
  13.    for ($i = 0; $i < $size$i++) { 
  14.      $filepath = "upload/$filedir/$i"
  15.      if(!file_exists($filepath)){ 
  16.        // 缺失i部分 
  17.        $offset = $i
  18.        break
  19.      } 
  20.    } 
  21.    // 输出偏移量 
  22.    echo $offset
  23.  }  
  24.  else { 
  25.    // 是否存在已合并文件 
  26.    if(file_exists("upload/$fileId")){ 
  27.      echo $size
  28.    } else
  29.      // 文件尚未上传 
  30.      echo 0; 
  31.    } 
  32.  } 

2.文件秒传

文件秒传的概念,按照我的理解,就是在上传文件请求后,服务器端检测数据库中是否存在相同的文件,如果存在相同的文件,就可以告诉用户上传完成了。

此处在获取offset后,增加一个判断就可以实现

  1. var offset = getFileBreakpoint(id, chunks.length); 
  2.  // 增加判断 
  3.  if(chunks.length === offset) { 
  4.    console.log('文件已经上传完成'); 
  5.    return
  6.  } 

当然,这里仅仅是非常简单的处理,我们还可以使用MD5来作为文件标识符,在在服务器端使用这个标识符是否存在相同文件。

3.MD5检测文件完整性。

通过md5对文件加密,传输到服务器端,服务器端实现合并后对文件再进行一次md5加密,比对两串md5字串是否相同,就可以知道文件传输过程中是否完整。

3.上传完成后,存储数据段文件夹进行删除操作。

我们最后做一步就是将临时文件移除操作,在整合完成后,我们只需要在file_integration.php接口中,整合完成后,移除文件夹及其下面的所有文件。

  1. function deldir($path){ 
  2.     //如果是目录则继续 
  3.    if(is_dir($path)){ 
  4.        //扫描一个文件夹内的所有文件夹和文件并返回数组 
  5.      $p = scandir($path); 
  6.      foreach($p as $val){ 
  7.        //排除目录中的.和.. 
  8.        if($val !="." && $val !=".."){ 
  9.          //如果是目录则递归子目录,继续操作 
  10.          if(is_dir($path.$val)){ 
  11.            //子目录中操作删除文件夹和文件 
  12.            deldir($path.$val.'/'); 
  13.            //目录清空后删除空文件夹 
  14.            @rmdir($path.$val.'/'); 
  15.          }else
  16.            //如果是文件直接删除 
  17.            unlink($path.$val); 
  18.          } 
  19.        } 
  20.      } 
  21.      // 删除文件夹 
  22.      rmdir($path); 
  23.    } 
  24.  } 
  25.  //删除临时文件夹 
  26.  deldir("upload/$filedir/"); 

4.总结

按照上述步骤,可以跟着实现简单上传、大文件分割上传、断点续存等知识,起码下次遇到上传文件,心里也有了点底气。由于本人是前端小白,所以写的代码比较简陋,只是实现了功能,还有许多可以优化的地方,如果代码有误,还望指正。

Tags: PHP+JS大文件切片上传

分享到: