代码改变世界

js文件分段上传

2017-10-13 14:25  muamaker  阅读(392)  评论(0编辑  收藏  举报

前端代码

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="zh-CN">
<head>
<title>分割大文件上传</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style>
 #test{
  width: 200px;
  height: 100px;
  border: 1px solid green;
  display: none;
 }
 #img{
  width: 50px;
  height: 50px;
  display: none;
 }
 #upimg{
  text-align: center;
  font: 8px/10px '微软雅黑','黑体',sans-serif;
  width: 300px;
  height: 10px;
  border: 1px solid green;
 }
 #load{
  width: 0%;
  height: 100%;
  background: green;
  text-align: center;
 }
</style>
</head>
 <body>
  <form enctype="multipart/form-data" action="file.php" method="post">
   <!-- 
   <input type="file" name="pic" />
   <div id="img"></div>
   <input type="button" value="uploadimg" onclick="upimg();" /><br />
   -->
   <div id="upimg">
    <div id="load"></div>
   </div>
   <input type="file" name="mof" multiple="multiple"/>
   <input type="button" value="uploadfile" onclick="upfile();" />
   <input type="submit" value="submit" />
  </form>
  <div id="test">
   测试是否DIV消失
  </div>
<script type="text/javascript">


 var xhr=null;
 
 if (window.XMLHttpRequest)
  {// code for all new browsers
  	xhr=new XMLHttpRequest();
  }
else if (window.ActiveXObject)
  {// code for IE5 and IE6
  	xhr=new ActiveXObject("Microsoft.XMLHTTP");
  }
  
  if(xhr == null){
  	 alert("Your browser does not support XMLHTTP.");
  	 
  }
  
if (window.File && window.FileReader && window.FileList && window.Blob) {
  // Great success! All the File APIs are supported.
} else {
  alert('The File APIs are not fully supported in this browser.');
}
  
  
 var fd;
 var des=document.getElementById('load');
 var file;
 const LENGTH=2*1024*1024;
 var start;
 var end;
 var blob;
 var pecent;
 var filename;
 //var pending;
 //var clock;
 function upfile(){
  start=0;
  end=LENGTH+start;
  //pending=false;
 
  file=document.getElementsByName('mof')[0].files[0];
  //filename = file.name;
  if(!file){
   alert('请选择文件');
   return;
  }
  //clock=setInterval('up()',1000);
  up();
 
 }
 
 function up(){
  /*
  if(pending){
   return;
  }
  */
  if(start<file.size){
   xhr.open('POST','file.php',true);
   //xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
   xhr.onreadystatechange=function(){
    if(this.readyState==4){
     if(this.status>=200&&this.status<300 || this.status == 304){   	
      if(this.responseText.indexOf('failed') >= 0){
      	if(this.responseText.indexOf('success') >= 0){
      		alert('文件发送成功');
      	}else{
      		alert('文件发送失败,请重新发送');
       		des.style.width='0%';
      	}
      
      }else{
       start=end;
       end=start+LENGTH;
       setTimeout('up()',100);
      }
 
     }
    }
   }
   xhr.upload.onprogress=function(ev){
    if(ev.lengthComputable){
     pecent=100*(ev.loaded+start)/file.size;
     if(pecent>100){
      pecent=100;
     }
     //num.innerHTML=parseInt(pecent)+'%';
     des.style.width=pecent+'%';
     des.innerHTML = parseInt(pecent)+'%'
    }
   }
       
  //分割文件核心部分slice
	if(file.slice){
		blob=file.slice(start,end);	
	}else if (file.webkitSlice) {
       blob = file.webkitSlice(start, stop + 1);
    } else if (file.mozSlice) {
       blob = file.mozSlice(start, stop + 1);
    }else{
    	alert('不支持slice上传');
    	return;
    }
   
   fd=new FormData();
   fd.append('mof',blob);
   fd.append('test',file.name);
   //console.log(fd);
   //pending=true;
   xhr.send(fd);
  }else{
  	alert('上传成功');
  }
 }
 
 
 xhr.onerror = function(e){
 	console.log(e);
 	alert('服务器错误');
 }
 function change(){
  des.style.width='0%';
 }
  
</script>
 </body>
</html>

  PHP代码

<?php 
/****
 waited
****/
//print_r($_FILES);exit;
 
$file = $_FILES['mof'];
 
$type = trim(strrchr($_POST['test'], '.'),'.');
 
// print_r($_POST['test']);exit;
 
if($file['error']==0){
 if(!file_exists('./upload/upload.'.$type)){
  if(!move_uploaded_file($file['tmp_name'],'./upload/.'.$type)){
   echo 'failed';
  }
 }else{
  $content=file_get_contents($file['tmp_name']);
  if (!file_put_contents('./upload/.'.$type, $content,FILE_APPEND)) {
   echo 'failed';
  }
 }
}else{
 echo 'failed success';
}
 
?>

  

使用md5加密参数

 使用es6的封装,支持断点续传

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>文件上传</title>
    <script src="https://code.jquery.com/jquery-3.4.1.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/spark-md5/3.0.0/spark-md5.js"></script>
</head>
<body>
  <h1>大文件上传测试</h1>
  <div>
    <h3>自定义上传文件</h3>
    <input id="file" type="file" name="avatar"/>
    <div>
      <input id="submitBtn" type="button" value="提交">
    </div>
  </div>
  <script type="text/javascript">
	//开始插件代码
   const blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
   class FlieSlice {
       constructor(opt){
           let def = {
               progress: function(){},
               success: function(){},
               fail: function(){},
               chunkSize:2*1024*1024,  // 每个chunk的大小,设置为2兆
               userId:"" ,//如果传了用户的 id,通过 hash 记录在 localstorage ,就可以断点续传,不传 userid,则不考虑断点续传
               autoUpload:true //自动上传
           };
   
           def = Object.assign(def,opt);
           def.blockCount = Math.ceil(opt.file.size / def.chunkSize); // 分片总数
           this.def = def;
           
           this.paused = false;
           this.current = 0;
           this.iscontinue = false;
		  
           if(def.autoUpload){
               this.send();
           }
       }   
       async send(){
           try{
               this.hash =  await this.getHash(); //这个是文件的hash,可以再加上用户id,这样就可以做断点续传
               if(this.def.userId){
                   //需要断点续传的判断
                   const prevIndex = window.localStorage.getItem(this.hash);
                   const index = Number(prevIndex);
                   if(prevIndex && index < this.def.blockCount){
                       //如果存在,说明之前这个文件没有传完
					   this.iscontinue = true;
                       this.upload(index);
                   }else{
                       //如果不存在,则不会继续了
                       this.upload();
                   }
               }else{
                   this.upload();
               }
               
           }catch(e){
			   console.warn(e);
               this.paused = true;
           }
       }
       upload(i){
           i = i || 0;
           const me = this;
		   const def = me.def;
           const {file,blockCount,chunkSize} = def;
           const hash = me.hash;
           me.current = i;
   
           if(i >= blockCount || me.paused){
               return ;
           }
           const start = i * chunkSize;
           const end = Math.min(file.size, start + chunkSize);
		   if(start >= end){
			   return ;
		   }
		   console.log(`start:${start}_end:${end},${i}`);
           def.progress && def.progress({
			   name:file.name,
               file:blobSlice.call(file, start, end),  // 当前的块
               done: i >= blockCount-1,                   // 是否已经发送完
               hash:hash,                              // hash
               size:file.size,                         // 文件的总大小
               index:i,                                // 当前传到了第几块
               count:blockCount,                       // 总块数
			   iscontinue:me.iscontinue,				//这次上,属于断点续传
               next(err){                                 // 下次迭代.
                   if(err){
					    me.paused = true;
						window.localStorage.removeItem(hash);
						return ;
				   }
				   ++i;
                   if(i < blockCount){
                       //每次上传,都记录 hash
                       window.localStorage.setItem(hash,i);
                       me.upload(i);
                   }else{
                       window.localStorage.removeItem(hash);
                       def.success && def.success();
                   }
               }
           });
       }
       getHash(){
           const me = this;
           const {file,blockCount,chunkSize,userId} = me.def;
           return new Promise((resolve, reject) => {
               let currentChunk = 0;
               const spark = new SparkMD5.ArrayBuffer();
               const fileReader = new FileReader();
               function loadNext() {
                 const start = currentChunk * chunkSize;
                 const end = Math.min(file.size, start + chunkSize);
                 fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
               }
               fileReader.onload = e => {
                 spark.append(e.target.result); // Append array buffer
                 currentChunk += 1;
                 if (currentChunk < blockCount) {
                   loadNext();
                 } else {
                   const result = spark.end();
                   // 如果单纯的使用result 作为hash值的时候, 如果文件内容相同,而名称不同的时候
                   // 想保留两个文件无法保留。所以把文件名称加上。
                   const sparkMd5 = new SparkMD5();
                   sparkMd5.append(result);
                   sparkMd5.append(file.name);
                   if(userId){
                       sparkMd5.append(userId);
                   }
                   const hexHash = sparkMd5.end();
                   resolve(hexHash);
                 }
               };
               fileReader.onerror = (e) => {
                 console.warn('文件读取失败!');
                 reject(e);
               };
               loadNext();
             }).catch(err => {
                 console.log(err);
             });
       }
       paused(){
           //停止上传
           this.paused = true;
       }
       play(){
           //开始上传
           this.paused = false;
           this.upload(this.current);
       }
   }
   //插件代码结束
   
   
  const submitBtn = $('#submitBtn');
  const fileDom = $('#file')[0];
  submitBtn.on('click',() => {
	  
		// 获取到的files为一个File对象数组,如果允许多选的时候,文件为多个
		const files = fileDom.files;
		const file = files[0];
		if (!file) {
		  alert('没有获取文件');
		  return;
		}
		new FlieSlice({
			file:file,
			userId:"mannymyu",
			progress(obj){
				console.log(obj);
				
				const form = new FormData();
				form.append('file', obj.file);
				form.append("name",obj.name);
				form.append('count', obj.count);
				form.append('index', obj.index);
				form.append('size', obj.size);
				form.append('hash', obj.hash);
				form.append("done",obj.done);
				$.ajax({
					url: 'http://127.0.0.1:3000',
					type: 'post',
					data: form,
					contentType: false,
					processData: false,
					success: function (res) {
						console.info(res);
						//在这里 执行自己的 ajax 上传,执行成功后调用 obj.next 执行下次
						if(res.code == 200){
							obj.next();
						}else{
							obj.next(res);
						}
					},
					error: function (error) {
						console.info(error);
						obj.next(error);
					}
				})
				
			},
			success(){
				console.log("success");
			}
		})
	})
</script>
</body>
</html>

  

使用node 的 koa 来完成后端

const os = require('os');
const path = require('path');
const koaBody = require('koa-body');
const Koa = require('koa');
const app = new Koa();
 const cors = require('koa2-cors');
const promisify = require("util").promisify;
const fs = require("fs");
const readFile = promisify(fs.readFile);
 const fsextra = require('fs-extra')
//递归的创建文件夹
function mkdirs(dirpath) {
    if (!fs.existsSync(path.dirname(dirpath))) {
      mkdirs(path.dirname(dirpath));
    }
    fs.mkdirSync(dirpath);
}
  
function createDir(myPath){
    fs.existsSync(myPath) == false && mkdirs(myPath);
}

//合并文件
function mergeFile(target,arr){
	return new Promise((resolve,reject)=>{
		function write(curr){
			fs.stat(target,function (err,stat) {
				if(err){
					let WStream = fs.createWriteStream(target);
					let readStream = fs.createReadStream(curr);
					readStream.pipe(WStream);
					run()
				}else if(stat.isFile()){
					let size = stat.size;
					let WSoptions = {
						start: size,
						flags: "r+"
					}
					let WStream = fs.createWriteStream(target,WSoptions)
					let readStream = fs.createReadStream(curr);
					readStream.pipe(WStream);
					 run()
				}else{
					reject("不是一个文件");
				}
			})
		}
		function run(){
			if(arr.length > 0){
				write(arr.shift());
			}else{
				resolve(target);
			}
		}
		 run();
	});
}





const main = async function(ctx) {
const filePaths = [];
const files = ctx.request.files || {};
const params = ctx.request.body;

 
 const temp = path.join(__dirname, "tmp" ,params.hash);
 const filePath = path.join(temp , `${params.hash}_${params.index}`);
 
 
   createDir(temp);
   for (let key in files) {
		const file = files[key];
		if(Object.prototype.toString.call(file) == '[object Array]'){
			ctx.body = {
				code:403,
				msg:"分片上传不允许多文件上传"
			}
			return ;
		}else{
			const reader = fs.createReadStream(file.path);
			const writer = fs.createWriteStream(filePath);
			reader.pipe(writer);
			let  staticDir;
			console.log(params);
			if(params.done == "true"){
				//如果已经完成了---合成文件
				staticDir = path.join(__dirname, "static",params.name); //最后文件存的地址
				var arr = [];
				for(var i = 0; i <= params.index; i++){
					arr.push(
						 path.join(temp , `${params.hash}_${i}`)
					);
				}
				try{
					let res = await mergeFile(staticDir,arr);
					ctx.body = {
						  code:200,
						  data: {
							  done:true,
							  path:staticDir 
						  },
						  msg:"成功"
					};
					
				}catch(e){
					console.log("错误--",e);
					ctx.body = {
						  code:500,
						  msg:"合并文件错误"
					};
				}
				
				//删除临时文件
				fsextra.remove(temp, err => {
				  if (err) return console.error("删除文件是失败",err)

				  console.log('删除文件成功!')
				});
			}else{
				ctx.body = {
					  code:200,
					  data: {
						  done:false,
						  path: filePath
					  },
					  msg:"成功"
				};
			}
			
		}
		return ;
  }
};

app.use(cors());
app.use(koaBody({ multipart: true }));
app.use(main);
app.listen(3000);

  

 

  

 typescript 前端版本

 import SparkMD5 from "spark-md5";
 //开始插件代码
 const blobSlice = File.prototype.slice ;

interface IDEF{
    progress?: (params:IProgress)=>void;
    success?:()=>void;
    fail?:()=>void;
    chunkSize?: number;
    userId?:string;
    autoUpload?:boolean;
    blockCount?: number;
    file: File | Blob ;
}

interface IProgress{
    next:(err:Error)=>void;
    file: File | Blob ;
    iscontinue:boolean;
    hash:string;
    name:string;
    done:boolean;
    size: number;
    index: number;
    count: number;
}

 export default class FlieSlice{
     def:IDEF;
     paused:boolean;
     current:number;
     iscontinue:boolean;
     hash:string;
     constructor(opt:IDEF){
         let def = {
             progress: function(a:IProgress){},
             success: function(){},
             fail: function(){},
             chunkSize:2*1024*1024,  // 每个chunk的大小,设置为2兆
             userId:"" ,//如果传了用户的 id,通过 hash 记录在 localstorage ,就可以断点续传,不传 userid,则不考虑断点续传
             autoUpload:true , //自动上传
             blockCount: 0,
            file:null,
         };
  
         def = Object.assign(def,opt);
         def.blockCount = Math.ceil(opt.file.size / def.chunkSize); // 分片总数
         this.def = def;
          
         this.paused = false;
         this.current = 0;
         this.iscontinue = false;
         
         if(def.autoUpload){
             this.send();
         }
     }  
     async send(){
         try{
             this.hash =  await this.getHash() as string; //这个是文件的hash,可以再加上用户id,这样就可以做断点续传
             if(this.def.userId){
                 //需要断点续传的判断
                 const prevIndex = window.localStorage.getItem(this.hash);
                 const index = Number(prevIndex);
                 if(prevIndex && index < this.def.blockCount){
                     //如果存在,说明之前这个文件没有传完
                     this.iscontinue = true;
                     this.upload(index);
                 }else{
                     //如果不存在,则不会继续了
                     this.upload();
                 }
             }else{
                 this.upload();
             }
              
         }catch(e){
             console.warn(e);
             this.paused = true;
         }
     }
     upload(i:number = 0){
         const me = this;
         const def = me.def;
         const {file,blockCount,chunkSize} = def;
         const hash = me.hash ;
         me.current = i;
  
         if(i >= blockCount || me.paused){
             return ;
         }
         const start = i * chunkSize;
         const end = Math.min(file.size, start + chunkSize);
         if(start >= end){
             return ;
         }
         console.log(`start:${start}_end:${end},${i}`);
         def.progress && def.progress({
             name: (file as File).name,
             file:blobSlice.call(file, start, end),  // 当前的块
             done: i >= blockCount-1,                   // 是否已经发送完
             hash:hash,                              // hash
             size:file.size,                         // 文件的总大小
             index:i,                                // 当前传到了第几块
             count:blockCount,                       // 总块数
             iscontinue:me.iscontinue,                //这次上,属于断点续传
             next(err:Error){                                 // 下次迭代.
                 if(err){
                      me.paused = true;
                      window.localStorage.removeItem(hash);
                      return ;
                 }
                 ++i;
                 if(i < blockCount){
                     //每次上传,都记录 hash
                     window.localStorage.setItem(hash,String(i));
                     me.upload(i);
                 }else{
                     window.localStorage.removeItem(hash);
                     def.success && def.success();
                 }
             }
         });
     }
     getHash(){
         const me = this;
         const {file,blockCount,chunkSize,userId} = me.def;
         return new Promise((resolve, reject) => {
             let currentChunk = 0;
             const spark = new SparkMD5.ArrayBuffer();
             const fileReader = new FileReader();
             function loadNext() {
               const start = currentChunk * chunkSize;
               const end = Math.min(file.size, start + chunkSize);
               fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
             }
             fileReader.onload = e => {
               spark.append(e.target.result); // Append array buffer
               currentChunk += 1;
               if (currentChunk < blockCount) {
                 loadNext();
               } else {
                 const result = spark.end();
                 // 如果单纯的使用result 作为hash值的时候, 如果文件内容相同,而名称不同的时候
                 // 想保留两个文件无法保留。所以把文件名称加上。
                 const sparkMd5 = new SparkMD5();
                 sparkMd5.append(result);
                 sparkMd5.append((file as File).name);
                 if(userId){
                     sparkMd5.append(userId);
                 }
                 const hexHash = sparkMd5.end();
                 resolve(hexHash);
               }
             };
             fileReader.onerror = (e) => {
               console.warn('文件读取失败!');
               reject(e);
             };
             loadNext();
           }).catch(err => {
               console.log(err);
           });
     }
     pause(){
         //停止上传
         this.paused = true;
     }
     play(){
         //开始上传
         this.paused = false;
         this.upload(this.current);
     }
 }
 //插件代码结束