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

转载:https://www.jb51.net/article/101931.htmphp

背景html

在网站开发中,文件上传是很常见的一个功能。相信不少人都会遇到这种状况,想传一个文件上去,而后网页提示“该文件过大”。由于通常状况下,咱们都须要对上传的文件大小作限制,防止出现意外的状况。
 可是在有些业务场景中,大文件上传又是必须的,好比邮箱附件,或者内部OA等等。前端

问题json

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

upload_max_filesize = 2M //PHP最大能接受的文件大小浏览器

post_max_size = 8M //PHP能收到的最大POST值'服务器

memory_limit = 128M //内存上限app

max_execution_time = 30 //最大执行时间post

 

固然不能简单粗暴的把上面几个值调大,不然服务器内存资源吃光是早晚的问题。学习

解决思路

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

JS思路

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

2.获取文件的FILE对象

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

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

5.重复三、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;
    padding: 0px 0px 0px 5px; background-image: initial; background-position: initial; background-size: initial; background-repeat: initial; background-attachment: initial; background-origin: initial; background-clip: initial; border-left: 3px solid rgb(108, 226, 108); line-height: 20px; width: 640px; clear: both; outline: 0px !important; border-radius: 0px !important; border-top: 0px !important; border-right: 0px !important; border-bottom: 0px !important; border-image: initial !important; bottom: auto !important; float: none !important; height: auto !important; left: auto !important; overflow: visible !important; position: static !important; right: auto !important; top: auto !important; vertical-align: baseline !important; box-sizing: content-box !important; font-family: Consolas, "Bitstream Vera Sans Mono", "Courier New", Courier, monospace !important; min-height: auto !important; color: gray !important;">#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{
    padding: 0px 0px 0px 5px; background-image: initial; background-position: initial; background-size: initial; background-repeat: initial; background-attachment: initial; background-origin: initial; background-clip: initial; border-left: 3px solid rgb(108, 226, 108); line-height: 20px; width: 640px; clear: both; outline: 0px !important; border-radius: 0px !important; border-top: 0px !important; border-right: 0px !important; border-bottom: 0px !important; border-image: initial !important; bottom: auto !important; float: none !important; height: auto !important; left: auto !important; overflow: visible !important; position: static !important; right: auto !important; top: auto !important; vertical-align: baseline !important; box-sizing: content-box !important; font-family: Consolas, "Bitstream Vera Sans Mono", "Courier New", Courier, monospace !important; min-height: auto !important; color: gray !important;">#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采用的是同步模型,文件须要一块一块按顺序上传,会致使整个浏览器在上传的过程当中出于堵塞的状态,按了按钮可能须要几秒钟才能反应过来,用户体验很差。真正须要产品化的时候就要综合考虑多种状况,固然做为一个示例,引导你们了解分块上传的思路仍是不错的。

以上就是本文的所有内容,但愿对你们的学习有所帮助,也但愿你们多多支持脚本之家。

相关文章
相关标签/搜索