天天看點

利用WebUploader實作大檔案上傳和視訊上傳

檔案上傳是網站開發必不可少的,常見的有圖檔上傳。但是大檔案和視訊上傳不常見。這裡我将自己寫的視訊上傳demo貼出來供大家參考:

利用是最新的WebUploader插件請 下載下傳使用最新版即可

js代碼

_extensions ='3gp,mp4,rmvb,mov,avi,m4v';
    _mimeTypes ='video/*,audio/*,application/*';
 
$(function(){            
    var chunkSize = 500 * 1024;        //分塊大小
    var uniqueFileName = null;          //檔案唯一辨別符
    var md5Mark = null;
 
    // var _backEndUrl = '';
 
    WebUploader.Uploader.register({
        "before-send-file": "beforeSendFile"
        , "before-send": "beforeSend"
        , "after-send-file": "afterSendFile"
    }, {
        beforeSendFile: function(file){
            console.log(file);
            //秒傳驗證
            var task = new $.Deferred();
            var start = new Date().getTime();
            (new WebUploader.Uploader()).md5File(file, 0, 10*1024*1024).progress(function(percentage){
            }).then(function(val){
 
                md5Mark = val;
                _userInfo.md5 = val;
 
                $.ajax({
                    type: "POST",
                    url: _backEndUrl,
                    data: {
                        status: "md5Check",
                        md5: val
                    },
                    cache: false,
                    timeout: 1000, //todo 逾時的話,隻能認為該檔案不曾上傳過
                    dataType: "json"
                }).then(function(data, textStatus, jqXHR){
 
                    if(data.ifExist){   //若存在,這傳回失敗給WebUploader,表明該檔案不需要上傳
                        task.reject();
 
                        uploader.skipFile(file);
                        file.path = data.path;
                        UploadComlate(file);
                    }else{
                        task.resolve();
                        //拿到上傳檔案的唯一名稱,用于斷點續傳
                        uniqueFileName = md5(_userInfo.openid+_userInfo.time);
                    }
                }, function(jqXHR, textStatus, errorThrown){    //任何形式的驗證失敗,都觸發重新上傳
                    task.resolve();
                    //拿到上傳檔案的唯一名稱,用于斷點續傳
                    uniqueFileName = md5(_userInfo.openid+_userInfo.time);
                });
            });
            return $.when(task);
        }
        , beforeSend: function(block){
            //分片驗證是否已傳過,用于斷點續傳
            var task = new $.Deferred();
            $.ajax({
                type: "POST"
                , url: _backEndUrl
                , data: {
                    status: "chunkCheck"
                    , name: uniqueFileName
                    , chunkIndex: block.chunk
                    , size: block.end - block.start
                }
                , cache: false
                , timeout: 1000 //todo 逾時的話,隻能認為該分片未上傳過
                , dataType: "json"
            }).then(function(data, textStatus, jqXHR){
                if(data.ifExist){   //若存在,傳回失敗給WebUploader,表明該分塊不需要上傳
                    task.reject();
                }else{
                    task.resolve();
                }
            }, function(jqXHR, textStatus, errorThrown){    //任何形式的驗證失敗,都觸發重新上傳
                task.resolve();
            });
 
            return $.when(task);
        }
        , afterSendFile: function(file){
            var chunksTotal = 0;
            if((chunksTotal = Math.ceil(file.size/chunkSize)) > 1){
                //合并請求
                var task = new $.Deferred();
                $.ajax({
                    type: "POST"
                    , url: _backEndUrl
                    , data: {
                        status: "chunksMerge"
                        , name: uniqueFileName
                        , chunks: chunksTotal
                        , ext: file.ext
                        , md5: md5Mark
                    }
                    , cache: false
                    , dataType: "json"
                }).then(function(data, textStatus, jqXHR){
 
                    //todo 檢查響應是否正常
 
                    task.resolve();
                    file.path = data.path;
                    UploadComlate(file);
 
                }, function(jqXHR, textStatus, errorThrown){
                    task.reject();
                });
 
                return $.when(task);
            }else{
                UploadComlate(file);
            }
        }
    });
 
	var uploader = WebUploader.create({
		swf: "./Uploader.swf",
        server: _backEndUrl,     //伺服器處理檔案的路徑
        pick: "#picker",        //指定選擇檔案的按鈕,此處放的是id
        resize: false, 
        dnd: "#theList",        //上傳檔案的拖拽容器(即,如果選擇用拖拽的方式選擇檔案進行上傳,應該要把檔案拖拽到的區域容器)
        paste: document.body,   //[可選] [預設值:undefined]指定監聽paste事件的容器,如果不指定,不啟用此功能。此功能為通過粘貼來添加截屏的圖檔。建議設定為document.body
        disableGlobalDnd: true, //[可選] [預設值:false]是否禁掉整個頁面的拖拽功能,如果不禁用,圖檔拖進來的時候會預設被浏覽器打開。
        compress: false,
        prepareNextFile: true, 
        chunked: true, 
        chunkSize: chunkSize,
        chunkRetry: 2,    //[可選] [預設值:2]如果某個分片由于網絡問題出錯,允許自動重傳多少次?
        threads: true,      //[可選] [預設值:3] 上傳并發數。允許同時最大上傳程序數。
        formData: function(){return $.extend(true, {}, _userInfo);}, 
        fileNumLimit: 1, 
        fileSingleSizeLimit: 50 * 1024 * 1024,// 限制在50M
        duplicate: true,
        accept: {      
            title: '大檔案上傳',  //文字描述
            extensions: _extensions,     //允許的檔案字尾,不帶點,多個用逗号分割。,jpg,png,
            mimeTypes: _mimeTypes,      //多個用逗号分割。image/*,
        },
	});
 
    /**
     * 驗證檔案格式以及檔案大小
     */
    uploader.on("error",function (type,handler){
        if (type=="Q_TYPE_DENIED"){
            swal({
                title:'',
                text: '請上傳MP4格式的視訊~',
                type: "warning",
                confirmButtonColor: "#DD6B55",
                confirmButtonText: "我知道了",
            });
        }else if(type=="F_EXCEED_SIZE"){
            swal({
                title:'',
                text: '視訊大小不能超過50M哦~',
                type: "warning",
                confirmButtonColor: "#DD6B55",
                confirmButtonText: "我知道了",
            });
        }
    });
 
	uploader.on("fileQueued", function(file){
        $('#theList').show();
		$("#theList").append('<li id="'+file.id+'" class="upload_li">' +
			' <img /> <span class="file_name upload_li">'+file.name+'</span></li><li class="upload_li"><span class="itemUpload weui-btn weui-btn_mini weui-btn_primary">上傳</span><span class="itemStop weui-btn weui-btn_mini weui-btn_default">暫停</span><span class="itemDel weui-btn weui-btn_mini weui-btn_warn">删除</span></li><li class="upload_li">' +
			'<div id="percentage'+file.id+'" class="percentage"><div class="weui-progress__bar"><div class="weui-progress__inner-bar js_progress" style="width: 0%;"></div> <b id="pers"></b> </div></div>' +
		'</li>');
		
		var $img = $("#" + file.id).find("img");
		
		uploader.makeThumb(file, function(error, src){
			if(error){
				$img.replaceWith("<span class='no_view'>視訊暫不能預覽</span>");
			}
 
			$img.attr("src", src);
		});
		
	});
	
	$("#theList").on("click", ".itemUpload", function(){
		uploader.upload();
 
        //"上傳"-->"暫停"
        $(this).hide();
        $(".itemStop").css('display','inline-block');
        $(".itemStop").show();
	});
 
    $("#theList").on("click", ".itemStop", function(){
        uploader.stop(true);
 
        //"暫停"-->"上傳"
        $(this).hide();
        $(".itemUpload").show();
    });
 
    //todo 如果要删除的檔案正在上傳(包括暫停),則需要發送給後端一個請求用來清除伺服器端的緩存檔案
	$("#theList").on("click", ".itemDel", function(){
		uploader.removeFile($('.upload_li').attr("id"));	//從上傳檔案清單中删除
 
        $('.upload_li').remove();	//從上傳清單dom中删除
	});
	
	uploader.on("uploadProgress", function(file, percentage){
        $(".percentage").find('.js_progress').css("width",percentage * 100 + "%");
		$(".percentage").find('#pers').text(parseInt(percentage * 100) + "%");
	});
 
    function UploadComlate(file){
        console.log(file);
        if(file && file.name){
            $('#vedio').val(file.name);
            $(".percentage").find('#pers').html("<span style='color:green;'>上傳完畢</span>");
            $(".itemStop").hide();
            $(".itemUpload").hide();
            $(".itemDel").hide();
        }else{
            $(".percentage").find('#pers').html("<span style='color:red;'>上傳失敗,請您檢查網絡狀況~</span>");
            $(".itemStop").hide();
            $(".itemUpload").hide();
        }
 
    }
 
})
           

PHP控制器

public function vupload(){
        set_time_limit (0);
        //關閉緩存
        header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
        header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
        header("Cache-Control: no-store, no-cache, must-revalidate");
        header("Cache-Control: post-check=0, pre-check=0", false);
        header("Pragma: no-cache");
 
        $ip_path = './uploads/'.$_SESSION['userinfo']['id'];
        $save_path = 'uploads/'.$_SESSION['userinfo']['id'];
        $uploader =  new \Org\Util\Vupload;
        $uploader->set('path',$ip_path);
        //用于斷點續傳,驗證指定分塊是否已經存在,避免重複上傳
        if(isset($_POST['status'])){
            if($_POST['status'] == 'chunkCheck'){
                $target =  $ip_path.'/'.$_POST['name'].'/'.$_POST['chunkIndex'];
                if(file_exists($target) && filesize($target) == $_POST['size']){
                    die('{"ifExist":1}');
                }
                die('{"ifExist":0}');
 
            }elseif($_POST['status'] == 'md5Check'){
 
                //todo 模拟持久層查詢
                $dataArr = array(
                    'b0201e4d41b2eeefc7d3d355a44c6f5a' => 'kazaff2.jpg'
                );
 
                if(isset($dataArr[$_POST['md5']])){
                    die('{"ifExist":1, "path":"'.$dataArr[$_POST['md5']].'"}');
                }
                die('{"ifExist":0}');
            }elseif($_POST['status'] == 'chunksMerge'){
 
                if($path = $uploader->chunksMerge($_POST['name'], $_POST['chunks'], $_POST['ext'])){
                    //todo 把md5簽名存入持久層,供未來的秒傳驗證
                    session('video_path', $save_path.'/'.$path);
                    die('{"status":1, "path": "'.$save_path.'/'.$path.'"}');
                }
                die('{"status":0}');
            }
        }
 
        if(($path = $uploader->upload('file', $_POST)) !== false){
            if(!session('video_path')){
                session('video_path', $save_path.'/'.$path);
            }
            die('{"status":1, "path": "'.$save_path.'/'.$path.'"}');
        }
        die('{"status":0}');
    }
           

封裝的上傳類庫

<?php
/**
 *
 * 版權所有:重慶市環境保護資訊中心
 * 作    者:Sqc
 * 日    期:2016-12-06
 * 版    本:1.0.0
 * 功能說明:用于視訊等上傳。
 *
 **/
namespace Org\Util;
Class Vupload
{   
    //要配置的内容
    private $path = "./uploads";
    private $allowtype = array('jpg', 'gif', 'png', 'mp4', 'mp3','3gp','rmvb','mov','avi','m4v');
    private $maxsize = 104857600;//
    private $israndname = true;
 
    private $originName;
    private $tmpFileName;
    private $fileType;
    private $fileSize;
    private $newFileName;
    private $errorNum = 0;
    private $errorMess = "";
    
    private $isChunk = false;
    private $indexOfChunk = 0;
 
    public function _initialize(){
        parent::_initialize();
    }
 
    /**
     * 用于設定成員屬性($path, $allowtype, $maxsize, $israndname)
     * 可以通過連貫操作一次設定多個屬性值
     * @param $key  成員屬性(不區分大小寫)
     * @param $val  為成員屬性設定的值
     * @return object 傳回自己對象$this, 可以用于連貫操作
     */
    function set($key, $val){
        $key = strtolower($key);
        if (array_key_exists($key, get_class_vars(get_class($this)))){
            $this->setOption($key, $val);
        }
        return $this;
    }
 
    /**
     * 調用該方法上傳檔案
     * Enter description here ...
     * @param $fileField    上傳檔案的表單名稱
     *
     */
    function upload($fileField, $info){
    
        //判斷是否為分塊上傳
        $this->checkChunk($info);
        
        if (!$this->checkFilePath($this->path)){
            $this->errorMess = $this->getError();
            return false;
        }
 
        //将檔案上傳的資訊取出賦給變量
        $name = $_FILES[$fileField]['name'];
        $tmp_name = $_FILES[$fileField]['tmp_name'];
        $size = $_FILES[$fileField]['size'];
        $error = $_FILES[$fileField]['error'];
 
 
        //設定檔案資訊
        if ($this->setFiles($name, $tmp_name, $size, $error)){
        
            //如果是分塊,則建立一個唯一名稱的檔案夾用來儲存該檔案的所有分塊
            if($this->isChunk){
                $uploadDir = $this->path;
                if($info){
                    $tmpName = $this->setDirNameForChunks();
                   
                     if(!$this->checkFilePath($uploadDir . '/' . $tmpName)){
                        $this->errorMess = $this->getError();
                        return false;
                    }
                }
        //         $tmpName = $this->setDirNameForChunks($info);
        //         if(!$this->checkFilePath($uploadDir . '/' . $tmpName)){
                    // $this->errorMess = $this->getError();
        //          return false;
        //         }
                
                //建立一個對應的檔案,用來記錄上傳分塊檔案的修改時間,用于清理長期未完成的垃圾分塊
                touch($uploadDir.'/'.$tmpName.'.tmp');
            }
 
            if($this->checkFileSize() && $this->checkFileType()){
                $this->setNewFileName();
                if ($this->copyFile()){
                    return $this->newFileName;
                }
            }
        }
 
        $this->errorMess = $this->getError();
        return false;
    }
 
    public function chunksMerge($uniqueFileName, $chunksTotal, $fileExt){
        $targetDir = $this->path.'/'.$uniqueFileName;
        //檢查對應檔案夾中的分塊檔案數量是否和總數保持一緻
        if($chunksTotal > 1 && (count(scandir($targetDir)) - 2) == $chunksTotal){
            //同步鎖機制
            $lockFd = fopen($this->path.'/'.$uniqueFileName.'.lock', "w");
            if(!flock($lockFd, LOCK_EX | LOCK_NB)){
                fclose($lockFd);
                return false;
            }
 
            //進行合并
            $this->fileType = $fileExt;
            $finalName = $this->path.'/'.($this->setOption('newFileName', $this->proRandName()));
            $file = fopen($finalName, 'wb');
            for($index = 0; $index < $chunksTotal; $index++){
                $tmpFile = $targetDir.'/'.$index;
                $chunkFile = fopen($tmpFile, 'rb');
                $content = fread($chunkFile, filesize($tmpFile));
                fclose($chunkFile);
                fwrite($file, $content);
 
                //删除chunk檔案
                unlink($tmpFile);
            }
            fclose($file);
            //删除chunk檔案夾
            rmdir($targetDir);
            unlink($this->path.'/'.$uniqueFileName.'.tmp');
 
            //解鎖
            flock($lockFd, LOCK_UN);
            fclose($lockFd);
            unlink($this->path.'/'.$uniqueFileName.'.lock');
 
            return $this->newFileName;
 
        }
        return false;
    }
 
    //擷取上傳後的檔案名稱
    public function getFileName(){
        return $this->newFileName;
    }
 
    //上傳失敗後,調用該方法則傳回,上傳出錯資訊
    public function getErrorMsg(){
        return $this->errorMess;
    }
 
    //設定上傳出錯資訊
    public function getError(){
        $str = "上傳檔案<font color='red'>{$this->originName}</font>時出錯:";
        switch ($this->errorNum) {
            case 4:
                $str.= "沒有檔案被上傳";
                break;
            case 3:
                $str.= "檔案隻有部分被上傳";
                break;
            case 2:
                $str.= "上傳檔案的大小超過了HTML表單中MAX_FILE_SIZE選項指定的值";
                break;
            case 1:
                $str.= "上傳的檔案超過了php.ini中upload_max_filesize選項限制的值";
                break;
            case -1:
                $str.= "未允許的類型";
                break;
            case -2:
                $str.= "檔案過大, 上傳的檔案夾不能超過{$this->maxsize}個位元組";
                break;
            case -3:
                $str.= "上傳失敗";
                break;
            case -4:
                $str.= "建立存放上傳檔案目錄失敗,請重新指定上傳目錄";
                break;
            case -5:
                $str.= "必須指定上傳檔案的路徑";
                break;
 
            default:
                $str .= "未知錯誤";
        }
        return $str."<br>";
    }
 
    //根據檔案的相關資訊為分塊資料建立檔案夾
    //md5(目前登入使用者的資料庫id + 檔案原始名稱 + 檔案類型 + 檔案最後修改時間 + 檔案總大小)
    private function setDirNameForChunks(){
        $str = $_SESSION['userinfo']['openid'].$_SESSION['userinfo']['report_time'];
        return md5($str);
        return $str; 
    }
 
    //設定和$_FILES有關的内容
    private function setFiles($name="", $tmp_name="", $size=0, $error=0){
        $this->setOption('errorNum', $error);
        if ($error) {
            return false;
        }
        $this->setOption('originName', $name);
        $this->setOption('tmpFileName', $tmp_name);
        $aryStr = explode(".", $name);
        $this->setOption("fileType", strtolower($aryStr[count($aryStr)-1]));
        $this->setOption("fileSize", $size);
        return true;
    }
 
    private function checkChunk($info){
        if(isset($info['chunks']) && $info['chunks'] > 0){
            $this->setOption("isChunk", true);
            
            if(isset($info['chunk']) && $info['chunk'] >= 0){
                $this->setOption("indexOfChunk", $info['chunk']);
                
                return true;
            }
            
            throw new Exception('分塊索引不合法');
        }
        
        return false;
    }
 
    //為單個成員屬性設定值
    private function setOption($key, $val){
        $this->$key = $val;
        return $val;
    }
 
    //設定上傳後的檔案名稱
    private function setNewFileName(){
        if($this->isChunk){     //如果是分塊,則以分塊的索引作為檔案名稱儲存
            $this->setOption('newFileName', $this->indexOfChunk);
        }elseif($this->israndname) {
            $this->setOption('newFileName', $this->proRandName());
        }else{
            $this->setOption('newFileName', $this->originName);
        }
    }
 
    //檢查上傳的檔案是否是合法的類型
    private function checkFileType(){
        if (in_array(strtolower($this->fileType), $this->allowtype)) {
            return true;
        }else{
            $this->setOption('errorNum', -1);
            return false;
        }
    }
 
 
    //檢查上傳的檔案是否是允許的大小
    private function checkFileSize(){
        if ($this->fileSize > $this->maxsize) {
            $this->setOption('errorNum', -5);
            return false;
        }else{
            return true;
        }
    }
 
    //檢查是否有存放上傳檔案的目錄
    private function checkFilePath($target){
        
        if (empty($target)) {
            $this->setOption('errorNum', -5);
            return false;
        }
        
        if (!file_exists($target) || !is_writable($target)) {
            if ([email protected]($target, 0755)) {
                $this->setOption('errorNum', -4);
                return false;
            }
        }
 
        $this->path = $target;
        return true;
    }
 
    //設定随機檔案名
    private function proRandName(){
        $fileName = date('YmdHis')."_".rand(100,999);
        return $fileName.'.'.$this->fileType;
    }
 
    //複制上傳檔案到指定的位置
    private function copyFile(){
        if (!$this->errorNum) {
            $path = rtrim($this->path, '/').'/';
            $path.= $this->newFileName;
            if (@move_uploaded_file($this->tmpFileName, $path)) {
                return true;
            }else{
                $this->setOption('errorNum', -3);
                return false;
            }
        }else{
            return false;
        }
    }
}
 
 
           

繼續閱讀