ausheng

thinkphp+H5文件分片(切块)上传

应用截图:

前端部分

<!DOCTYPE html>
<html>
<head>
<title>PHP大文件分片上传</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<style type="text/css">
#percent-bg{position:relative;width:100%;height:20px;border:1px solid #ccc;}
#percent{position:absolute;display:block;width:0;height:100%;left:0;background:green;z-index:100;}
#percent_num{position:absolute;display:block;width:30px;height:100%;left:50%;margin-left:-15px;z-index:200; text-align:center;}
</style>
<script type="text/javascript" src="jquery-3.3.1.min.js"></script>
<script type="text/javascript">
var pz = {
  "maxSize":1024*1024*200, //文件大小限制20M
  "bufferSize":1024*512, //单个分片大小
  "blocks":[], //分片数据集合
  "blockName":"newfile",//表单 file 的名字
  "filename":"",//上传的本地文件真实文件名
  "threadNum":2,//上传线程数量
  "reset":function(fname){
    this.blocks = [];
    this.filename = fname;
  }

}
function afterselect(){
  if(document.getElementById("x").files.length<1){
    return false;
  }
  var file = document.getElementById("x").files[0];
  console.log(file);
  if(file.size>pz.maxSize){
    showmsg("文件大小限制:"+pz.maxSize+",实际文件大小:"+file.size);
    return;
  }
  pz.reset(file.name);

  __index = __activeThreadCount = __sendedBlockCount = 0;

  var endByte = 0,startByte = 0;

  while(true){
    startByte = endByte;
    if(endByte + pz.bufferSize >= file.size){
      endByte = file.size;
    }else{
      endByte = endByte+pz.bufferSize;
    }
    var block = sliceFile(file,startByte,endByte);
    if(!block){
      showmsg("分片失败");
      return;
    }
    pz.blocks.push(block);
    if(endByte >= file.size){ break; }
  }
  showmsg("文件名:"+file.name);
  showmsg("文件大小:"+file.size);
  showmsg("总分片数量:"+pz.blocks.length);

  var dis = Math.min(pz.threadNum,pz.blocks.length);
  for(var i=0;i<dis;i++){
    (function(i){
      setTimeout(function(){
        __activeThreadCount++;
        fenpei(i);
      },500);
    })(i);
  }
  showmsg("启动线程:"+dis+" 个");
  showmsg("------------------------");
}

//只要是操作主线程中的变量或其他,就一定会自动同步到主线程来。相当于自动lock()
var __index = 0;
var __activeThreadCount = 0; //当前活跃线程数量
var __sendedBlockCount = 0; //已上传的block数量
function fenpei(i){
  //参数:线程名
  if(__index>=pz.blocks.length){
    showmsg("线程"+i+' 结束');
    __activeThreadCount--;
    if(__activeThreadCount==0){
      showmsg("------------------------");
      showmsg('多线程分片上传完毕,正在处理分片数据...');
      combineFile();
    }
    return;
  }
  uploadBlock(i,__index);
  __index++;
}
//发送拼接分块命令
function combineFile(){
  $.post(
    'http://www.exhibitioncloud.com/api/v1/Uploadfile/up_video',
    {"act":"combine","location":"12","chunks":pz.blocks.length,"filename":pz.filename},
    function(e){
      if(e.data.flag){
        showmsg("分片数据处理完成,任务结束,URl:"+e.data.url);
      }else{
        showmsg(e.data.info);
      }
    },'json'
  );
}
//显示进度
function showPercent(){
  var percent = parseInt(__sendedBlockCount / pz.blocks.length *100);
  if(percent>100){ percent = 100; }

  $("#percent").stop(true,true).animate({"width":percent+"%"},10);
  console.log(percent);
  $("#percent_num").html(percent+"%");
}
//上传一个分片
function uploadBlock(i,index){
  var chunk = index;
  showmsg('线程'+i+' 分片'+chunk+' start');
  var fd = new FormData();
    fd.append("newfile", pz.blocks[chunk]);//文件
    fd.append("filename", pz.filename);//文件名
    fd.append("chunk", chunk);//分片索引
    fd.append("chunks", pz.blocks.length);//分片总数
    $.ajax({
    url:'http://www.exhibitioncloud.com/api/v1/Uploadfile/up_video',
    type:"post",
    data: fd,
    dataType:"json",
    cache: false,
    contentType:false,
    processData:false,//设置为true的时候,jquery ajax 提交的时候不会序列化 data,而是直接使用data
    success:function(data){
      //timer线程中的异步,此时同步了
      showmsg("线程"+i+" 分片"+chunk+" end");
      __sendedBlockCount++;
      showPercent();
      fenpei(i);
    },
    complete:function(XMLHttpRequest,textStatus){
    },
    error:function(){}
  });
}
//分割file成分片
function sliceFile(file,startByte,endByte){
    if(file.slice){
        return  file.slice(startByte,endByte);
    }
    if(file.webkitSlice){
        return  file.webkitSlice(startByte,endByte);
    }
    if(file.mozSlice){
        return  file.mozSlice(startByte,endByte);
    }
    return null;
}
//输出消息
function showmsg(msg){
  var _t = $("#msg").val();
  _t += msg+"\r\n";
  $("#msg").val(_t);
  var scrollTop = $("#msg")[0].scrollHeight;
  $("#msg").scrollTop(scrollTop);
}
</script>
<body>
<input type="file" id="x" >
<button onclick="afterselect();">dianji</button>
<textarea id="msg" style="width:99%;height:400px;overflow-y:auto;"></textarea>
<p></p>
<div id="percent-bg">
  <span id="percent"></span>
  <span id="percent_num">0%</span>
</div>
</body>
</html>

PHP接口部分

use app\common\utility\Uploadfile as Uploadfiles

    /**
     * @title 视频文件上传
     * @description 视频文件上传接口说明
     * @author 毕昌远
     * @url /api/v1/Uploadfile/up_video
     * @method post
     * 
     * @header name:user-token require:1 default: desc:token
     * 
     * @param name:newfile type:file require:0 default: other:form文件 desc:form文件
     * @param name:filename type:string require:0 default: other:文件名称 desc:文件名称
     * @param name:chunk type:int require:0 default: other:当前块数 desc:当前块数
     * @param name:chunks type:int require:0 default: other:总块数 desc:总块数
     * @param name:act type:string require:0 default: other:combine合并 desc:combine合并
     * @param name:loaction type:string require:0 default: other:文件存放地址 desc:文件存放地址
     * 
     * @return flag:状态
     * @return info:信息体
     * @return url:文件存放位置
    */
    public function up_video()
    {

        $res = array(
            'code' => 200,
            'message'=>"",
            'data' =>[
                'flag'        =>false,
                'url'        =>'',//上传得到的新路径
                'info'        =>'',
                'msg'         =>''   
            ]

        );
        // 组织上传参数
        $inout_data = [
            'act'           =>  input('act'),
            'chunk'         =>  input('chunk'),
            'chunks'        =>  input('chunks'),
            'filename'      =>  input('filename'),
            'location'      =>  input('location'),
            'formdataname'  =>  'newfile',
            "file_path"     =>  ''
        ];
        // 文件后缀数组
        $file_type_arr = array("mp4");
        // 获取文件后缀
        $file_type = substr(strrchr(input("filename"), '.'), 1);
        // 监测文件后缀
        $file_type_status = in_array($file_type, $file_type_arr);
        if (!$file_type_status) {
            $res['data']['msg']="文件类型有误";
            echo json_encode($res); die();
        }
        // 文件存放地址
        $inout_data['file_path'] = 'uploads/up_video';
        $Uploader = new Uploadfiles();

        $arr = $Uploader->uploads($inout_data);
        if(empty($arr[0])){
            $res['data']['info'] = $arr[1];
        }else{
            if ($arr[0]==2) {
                $pathurl = "http://".$_SERVER['HTTP_HOST'].$_SERVER['PHP_SELF'];
                $pathurl = substr($pathurl,0,strripos($pathurl,"/"));
                $pathurl = "";
                $res['data']['flag'] = false;
                $res['data']['url'] = $pathurl.'/'.$arr[1];
                $res['data']['info'] = "success";
            }else{
                $pathurl = "http://".$_SERVER['HTTP_HOST'].$_SERVER['PHP_SELF'];
                $pathurl = substr($pathurl,0,strripos($pathurl,"/"));
                $pathurl = "";
                $res['data']['flag'] = true;
                $res['data']['url'] = $pathurl.'/'.$arr[1];
                $res['data']['info'] = "success";
            }
        }
        echo json_encode($res); die();

    }

接口引用类

<?php

namespace app\common\utility;
session_start();
//文件分片上传
class Uploadfile{

    //执行上传(的name属性,保存路径-相对当前路径)
    public function uploads($data){
        $savedir = $data['file_path'];
        $return_arr = array('0','');
        $_SESSION['uploader_id']=1;
        $userID = $_SESSION['uploader_id']; //用户标识

        if(!empty($data['act']) && trim($data['act'])=='combine'){
            //-------------合并文件
            $chunks = intval($data['chunks']);//总分块个数

            //文件后缀
            $type = substr($data['filename'],strripos($data['filename'],'.'));

            //保存临时文件
            $tmppath = $savedir.'/tmp'; //临时目录,
            if(!file_exists($tmppath)){ @mkdir($tmppath,0777,true); }

            $filenamemd5 = md5($data['filename']);

            $savedir = $savedir.'/'.date('Ymd',time());
            // 文件存放地址
            // $savedir = $data['location'];

            if(!file_exists($savedir)){
                @mkdir($savedir,0777,true);
            }
            $newname = date('mdH',time()).rand(10000,99999).'_'.rand(100000,999999).$type;
            // 文件保存的名称
            // $newname = $data['filename'];

            $writer = fopen("{$savedir}/{$newname}","ab"); //合并后的文件名
            for($i=0;$i<$chunks;$i++){
                $file2read = "{$tmppath}/{$userID}_{$filenamemd5}_{$i}";
                $reader = fopen($file2read,"rb");
                fwrite($writer,fread($reader,filesize($file2read)));
                fclose($reader);
                unset($reader);
                @unlink($file2read);//删除分块临时文件
            }
            fclose($writer);
            $return_arr[0]='1';
            $return_arr[1]="{$savedir}/{$newname}";

        }else{
            if(empty($_FILES[$data['formdataname']]) || $_FILES[$data['formdataname']]["error"] > 0){
                return array( '0','上传失败' );
            }

            //-------------保存临时文件
            $chunks = intval($data['chunks']);//总分块个数
            $chunk  = intval($data['chunk']);//当前分块索引

            //临时目录
            $tmppath = $savedir.'/tmp'; 
            if(!file_exists($tmppath)){ 
                @mkdir($tmppath,0777,true); 
            }

            $filenamemd5 = md5($data['filename']);
            $tmpname = "{$userID}_{$filenamemd5}_{$chunk}";//临时文件名
            @move_uploaded_file($_FILES[$data['formdataname']]["tmp_name"],"{$tmppath}/{$tmpname}");

            $return_arr[0]='2';
            $return_arr[1]='needmore';
        }

        return $return_arr;
    }
}

ausheng

爱生活,爱编程

AUSHENG – BLOG

不要害羞,保持联系。我们喜欢结识有趣的人,结交新朋友。