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採用的是同步模型,文件須要一塊一塊按順序上傳,會致使整個瀏覽器在上傳的過程當中出於堵塞的狀態,按了按鈕可能須要幾秒鐘才能反應過來,用戶體驗很差。真正須要產品化的時候就要綜合考慮多種狀況,固然做爲一個示例,引導你們瞭解分塊上傳的思路仍是不錯的。

以上就是本文的所有內容,但願對你們的學習有所幫助,也但願你們多多支持腳本之家。

相關文章
相關標籤/搜索