JS 异步分段上传文件

为了解决大文件上传 (PHP上传最大限制2GB)

同时为了解决文件上传是对服务器造成的压力

可以通过分段上传解决这个问题,这得益于HTML5开发的file API

前台代码:

引用了进度条插件myProgress.js

<link href="__PUBLIC__/admin/css/myProgress.css" rel="stylesheet"> 
<script src="__PUBLIC__/admin/js/jquery.myProgress.js"></script>

<div>
    <div>
        <form id="myForm">
          <div>
            //上传文件时由用户指定文件名
            <label for="FileName">File Name</label>
            <input type="text" name="title" class="form-control" id="FileName">
          </div>
          <div>
            <label for="myFile">Chose File</label>
            <input type="file" id="myFile">
            <div class="progress-out" id="progress">
                <div class="percent-show"><span>0</span>%</div>
                <div class="progress-in"></div>
            </div>
          </div>
        </form>
        <button type="button" class="btn btn-default" id="btn">Submit</button>
    </div>
</div>
<script>
//初始化上传
function initUpload() {
    var chunk = 1000*1024;   //每片大小
    var input = document.getElementById("myFile");  //input file
    input.onchange = function (e) {
        //获得上传的文件
        var file = this.files[0];
        //如果大于指定大小 提示错误
        if(file.size > 1*1024*1024*1024){
            $('#help_msg').removeClass('help-block').addClass('error-block');
            return ;
        }else{
            $('#help_msg').css('display','none');
        }
        // 开启进度条
        $("#progress").css('display','block');
        $("#progress").myProgress({speed: 1000, percent: 0, width: "200px", height: "12px"});
        
        var query = {};
        var chunks = [];

        if (!!file) {
            var start = 0;
            //文件分片
            for (var i = 0; i < Math.ceil(file.size / chunk); i++) {
                //最后一段取文件的真实大小
                var end = 0;
                if(i == (Math.ceil(file.size / chunk)-1)){
                    end = file.size;
                }else{
                    end = start + chunk;
                }
                chunks[i] = file.slice(start , end);
                start = end;
            }
            
            // 采用post方法上传文件
            // url query上拼接以下参数,用于记录上传偏移
            // post body中存放本次要上传的二进制数据
            query = {
                fileName : file.name,
                fileSize: file.size,
                dataSize: chunk,
                nextOffset: 0
            }

            upload(chunks, query, successPerUpload);
        }
    }
}

// 执行上传
function upload(chunks, query, cb) {
    //对象转字符串 用&连接
    var queryStr = Object.getOwnPropertyNames(query).map(key => {
        return key + "=" + query[key];
    }).join("&");

    var xhr = new XMLHttpRequest();
    xhr.open("POST", "/Shop/index.php/Admin/File/upload_file?" + queryStr);
    xhr.overrideMimeType("application/octet-stream");
    
    //获取post body中二进制数据
    var index = Math.floor(query.nextOffset / query.dataSize);
    getFileBinary(chunks[index], function (binary) {
        if (xhr.sendAsBinary) {
            xhr.sendAsBinary(binary);
        } else {
            xhr.send(binary);
        }

    });

    xhr.onreadystatechange = function (e) {
        if (xhr.readyState === 4) {
            if (xhr.status === 200) {
                var resp = JSON.parse(xhr.responseText);
                //通过返回数据更新进度条
                var precent = Math.ceil((resp.offset / query.fileSize) * 100);
                $("#progress").myProgress({speed: 1000, percent: precent, width: "200px", height: "12px"});
                // 接口返回nextoffset
                // resp = {
                //     isFinish:false,
                //     offset:100*1024
                // }
                if (typeof cb === "function") {
                    cb.call(this, resp, chunks, query)
                }
            }
        }
    }
}

// 每片上传成功后执行
function successPerUpload(resp, chunks, query) {
    if (resp.isFinish === true) {
        //上传完成给出提示
        $('#help_msg').css('display','block').addClass('error-block').html('success!');
    } else {
        //未上传完毕
        query.nextOffset = resp.offset;
        upload(chunks, query, successPerUpload);
    }
}

// 获取文件二进制数据
function getFileBinary(file, cb) {
    var reader = new FileReader();
    reader.readAsArrayBuffer(file);
    reader.onload = function (e) {
        if (typeof cb === "function") {
            cb.call(this, this.result);
        }
    }
}
//初始化上传
initUpload();

//ajax模拟提交表单
$(function(){
    $('#btn').click(function(){
        var fd = new FormData(document.querySelector('#myForm'));
        var input = document.getElementById("myFile");    //input file
        var file = input.files[0];
        if(!file){
            $('#help_msg').css('display','block').addClass('error-block').html('please chose the file !');
            return ;
        }
        fd.append('FileName',file.name);
        fd.append('size',file.size);
        $.ajax({
            url : "/Shop/index.php/Admin/File/add",
            type: "POST",
            async : true,
            data: fd,
            processData: false,  // 不处理数据
            contentType: false,   // 不设置内容类型
            success : function(result){
                console.log(result);
                if(result.res == 1){
                    window.location.href = "http://localhost:8080/Shop/index.php/Admin/File/index";
                }else{
                    $("#help_msg_1").css('display','block').html('upload faild ' + $result.msg);
                }
            }
        });
    })
})

</script>

后台PHP代码

public function add(){
        if($_POST){
            $Attach = D('Attachment');
            $file_path = './Upload/File/'.$_POST['FileName'];
            //如果是win系统将文件名改成GBK编码
            if(strtoupper(substr(PHP_OS, 0, 3)) === 'WIN'){
                $file_path = iconv('UTF-8', 'GBK', $file_path);
            }
            if(file_exists($file_path)){
                //获得拓展名
                $ext = strtolower(trim(substr(strrchr($_POST['FileName'], '.'), 1)));
                //生成新的文件名
                $url = './Upload/File/'.date("Ymdhms").rand(1000,9999).'.'.$ext;
                $_POST['url'] = $url ;
                //将上传的文件改名,将新的路径存入数据库
                if(rename($file_path, $url)){
                    $res = $Attach -> add_file($_POST);
                    if($res['res']){
                        $log['remark'] = session('userinfo')['name'].'在'.date("Y-m-d H:i:s").'上传了文件';
                        D('ActionLog') -> add_log($log);
                        $this -> ajaxReturn(array('res' => 1));
                    }else{
                        $this -> ajaxReturn(array('res' => 0 , 'msg' => $res['msg']));
                    }
                }
            }else{
                $this -> ajaxReturn(array('res' => 0, 'msg' => '上传文件不存在'));
            }    
        }else{
            $this -> show();
        }
    }
    //异步分段上传文件
    public function upload_file(){
        
        $path = './Upload/File/'.$_GET['fileName'];
        if(strtoupper(substr(PHP_OS, 0, 3)) === 'WIN'){
            $path = iconv('UTF-8', 'GBK', $path);
        }
        if(!file_exists($path)){
            $handle = fopen($path, "a+");
            fclose($handle);
        }
        file_put_contents($path, file_get_contents('php://input'),FILE_APPEND|LOCK_EX);
        $offset = filesize($path);
        if( $offset >= $_GET['fileSize'] ){
             $this -> ajaxReturn(array('isFinish' => true));
        }else{
            $this -> ajaxReturn(array('isFinish' => false , 'offset' => $offset));
        }
    }

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

微信扫一扫

微信扫一扫

微信扫一扫,分享到朋友圈

JS 异步分段上传文件