百度的webUploader的前端开源插件实现的大文件分片上传功能
前端部分
前端页面代码如下,只需要修改自己的文件上传地址接口地址:
DOCTYPE html>
<html lang="zh-CN">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
Web Uploader
title>
<link rel="shortcut icon" href="http://fex.baidu.com/webuploader/images/favicon.ico">
<link rel="stylesheet" type="text/css" href="js/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="js/bootstrap-theme.min.css">
<link rel="stylesheet" type="text/css" href="js/font-awesome.min.css">
<link rel="stylesheet" type="text/css" href="js/syntax.css">
<link rel="stylesheet" type="text/css" href="js/style.css">
<link rel="stylesheet" type="text/css" href="js/webuploader.css">
<link rel="stylesheet" type="text/css" href="js/demo.css">
head>
<body>
<div id="wrapper">
<div class="page-body">
<div id="post-container" class="container">
<div class="page-container">
<h1 id="demo">Demoh1>
<p>您可以尝试文件拖拽,使用QQ截屏工具,然后激活窗口后粘贴,或者点击添加图片按钮p>
<div id="uploader" class="wu-example">
<div class="queueList">
<div id="dndArea" class="placeholder">
<div id="filePicker" class="webuploader-container">
<div class="webuploader-pick">点击选择图片div>
<div id="rt_rt_1ctrotb75hv81prnco2vi318qc1" style="position: absolute; top: 0px; left: 448px; width: 168px; height: 44px; overflow: hidden; bottom: auto; right: auto;"><input type="file" name="file" class="webuploader-element-invisible" multiple="multiple" accept="image/*">
<label style="opacity: 0; width: 100%; height: 100%; display: block; cursor: pointer; background: rgb(255, 255, 255);">label>
div>
div>
<p>或将照片拖到这里,单次最多可选300张p>
div>
<ul class="filelist">ul>
div>
<div class="statusBar" style="display:none;">
<div class="progress" style="display: none;">
<span class="text">0%span>
<span class="percentage" style="width: 0%;">span>
div><div class="info">共0张(0B),已上传0张div>
<div class="btns">
<div id="filePicker2" class="webuploader-container"><div class="webuploader-pick">继续添加div><div id="rt_rt_1ctrotb7j4opkv31e231b79k0a6" style="position: absolute; top: 0px; left: 0px; width: 1px; height: 1px; overflow: hidden;"><input type="file" name="file" class="webuploader-element-invisible" multiple="multiple" accept="image/*"><label style="opacity: 0; width: 100%; height: 100%; display: block; cursor: pointer; background: rgb(255, 255, 255);">label>div>div><div class="uploadBtn state-pedding">开始上传div>
div>
div>
div>
div>
div>
div>
div>
<script type="text/javascript">
var BASE_URL = '/webuploader';
script>
<script type="text/javascript" src="js/jquery-1.10.2.min.js">script>
<script type="text/javascript" src="js/bootstrap.min.js">script>
<script type="text/javascript" src="js/global.js">script>
<script type="text/javascript" src="js/webuploader.js">script>
<script type="text/javascript" src="js/demo.js">script>
body>
html>
前端js代码 demo.js,需要修改插件初始化的参数,和文件上传成功后的合并通知的地址。
1 jQuery(function() { 2 var $ = jQuery, // just in case. Make sure it's not an other libaray. 3 $wrap = $('#uploader'), 4 // 图片容器 5 $queue = $('
' + file.name + '
' + 94 ''+ 95 '' + 96 '
' +
98 '删除' +
99 '向右旋转' +
100 '向左旋转
').appendTo( $li ),
101 $prgress = $li.find('p.progress span'),
102 $wrap = $li.find( 'p.imgWrap' ),
103 $info = $(''),
104 showError = function( code ) {
105 switch( code ) {
106 case 'exceed_size':
107 text = '文件大小超出';
108 break;
109 case 'interrupt':
110 text = '上传暂停';
111 break;
112 default:
113 text = '上传失败,请重试';
114 break;
115 }
116 $info.text( text ).appendTo( $li );
117 };
118 if ( file.getStatus() === 'invalid' ) {
119 showError( file.statusText );
120 } else {
121 // @todo lazyload
122 $wrap.text( '预览中' );
123 uploader.makeThumb( file, function( error, src ) {
124 if ( error ) {
125 $wrap.text( '不能预览' );
126 return;
127 }
128 var img = $('后台Controller层代码:
1 package com.webFileUploader.Controller; 2 3 import java.util.Map; 4 5 import javax.servlet.http.HttpServletRequest; 6 import javax.servlet.http.HttpServletResponse; 7 import javax.servlet.http.HttpSession; 8 9 import org.springframework.beans.factory.annotation.Autowired; 10 import org.springframework.stereotype.Controller; 11 import org.springframework.web.bind.annotation.RequestMapping; 12 import org.springframework.web.bind.annotation.RequestMethod; 13 import org.springframework.web.bind.annotation.RequestParam; 14 import org.springframework.web.bind.annotation.ResponseBody; 15 import org.springframework.web.bind.annotation.RestController; 16 import org.springframework.web.multipart.MultipartFile; 17 18 import com.webFileUploader.exception.ErrorPremetersException; 19 import com.webFileUploader.service.FileManagerService; 20 import com.webFileUploader.utils.MutilFileUploadUtils; 21 import com.webFileUploader.vo.MutilFileInfo; 22 23 /** 24 * @version 1.0 25 * @author liangxh 26 * @since 2018-12-04 27 * @return 文件上传 28 */ 29 @Controller 30 public class uploadController { 31 @Autowired 32 private FileManagerService fileManagerService; 33 /** 34 * 文件上传 35 * @param fileInfo:文件参数实体类 36 * @param file 附件字节码文件 37 * @return 返回处理结果,请求头200:成功,500:失败 38 * @throws Exception 39 */ 40 @RequestMapping(value = "/uploadFile", method = RequestMethod.POST) 41 @ResponseBody 42 public MapMutiluploadFile(MutilFileInfo fileinfo,@RequestParam(required=false,value="file")MultipartFile file,HttpServletResponse response) throws Exception { 43 try { 44 if(file != null && !file.isEmpty()){ 45 if(MutilFileUploadUtils.checkMutiFilePremeter(fileinfo)){ //切片上传 46 fileManagerService.saveMutiBurstFiletoDir(fileinfo,file); 47 }else if(MutilFileUploadUtils.checkSingleFilePremeter(fileinfo)){//单文件整体上传 48 fileManagerService.saveSingleFiletoDir(fileinfo,file); 49 }else { 50 throw new ErrorPremetersException("文件上传参数不合法"); 51 } 52 }else { 53 throw new ErrorPremetersException("文件上传附件字节流内容为空"); 54 } 55 } catch (Exception e) { 56 e.printStackTrace(); 57 response.setStatus(500); 58 } 59 return null; 60 } 61 /** 62 * 文件分片合并 63 * @return 返回处理结果,请求头200:成功,500:失败 64 * @throws Exception 65 */ 66 @RequestMapping(value = "/mergingChunks", method = RequestMethod.POST) 67 @ResponseBody 68 public Map MutilMergingChunksForFile(MutilFileInfo fileinfo,HttpServletResponse response) throws Exception { 69 try { 70 fileManagerService.MutilMergingChunks(fileinfo); 71 } catch (Exception e) { 72 e.printStackTrace(); 73 response.setStatus(500); 74 } 75 return null; 76 } 77 78 /** 79 * 文件分片合并 80 * @return 返回处理结果,请求头200:成功,500:失败 81 * @throws Exception 82 */ 83 @RequestMapping(value = "/") 84 public String uploaderView(HttpServletRequest request) throws Exception { 85 HttpSession session = request.getSession(); 86 System.out.println(session); 87 return "redirect:/index.html"; 88 } 89 90 }
后台Service实现层;先在服务端硬盘上创建一个要上传的文件相同的大文件,利用RandomAccessFile实现文件的随机读写。从而实现在分片上传时直接覆盖空白文件中的一小段数据,从而避免分片保存为临时文件再合并的IO消耗,
1 package com.webFileUploader.service.impl; 2 3 import java.io.File; 4 import java.util.ArrayList; 5 import java.util.Collections; 6 import java.util.List; 7 import java.util.concurrent.ConcurrentHashMap; 8 import java.util.concurrent.locks.ReentrantLock; 9 10 import org.springframework.beans.factory.annotation.Value; 11 import org.springframework.stereotype.Service; 12 import org.springframework.web.multipart.MultipartFile; 13 14 import com.webFileUploader.service.FileManagerService; 15 import com.webFileUploader.utils.MutilFileUploadUtils; 16 import com.webFileUploader.utils.SHA256Util; 17 import com.webFileUploader.utils.SessionUtils; 18 import com.webFileUploader.vo.MutilFileInfo; 19 20 @Service 21 public class FileManagerServiceImpl implements FileManagerService { 22 23 @Value("${filePath.tempWorkBasePath}") 24 private String tempWorkPath;//分片临时文件存放目录 25 @Value("${filePath.saveFileBasePath}") 26 private String saveFilePath;//文件存放目录 27 private ReentrantLock filetempLock = new ReentrantLock(); 28 29 @Override 30 public void saveMutiBurstFiletoDir(MutilFileInfo fileinfo, MultipartFile file) throws Exception { 31 this.checkBaseDir(tempWorkPath); 32 File tempFile = this.GenerateDirPathForCurrFile(fileinfo,"chunks"); 33 MutilFileUploadUtils.spaceFileWriter(file,tempFile,fileinfo); 34 } 35 @Override 36 public void saveSingleFiletoDir(MutilFileInfo fileinfo, MultipartFile file) throws Exception { 37 this.checkBaseDir(saveFilePath); 38 File targetFile = this.GenerateDirPathForCurrFile(fileinfo,"single"); 39 MutilFileUploadUtils.saveFile2DirPath(file,targetFile); 40 } 41 42 @Override 43 synchronized public File GenerateDirPathForCurrFile(MutilFileInfo fileinfo,String flag) throws Exception { 44 String fileName = fileinfo.getName(); 45 String lastModifiedDate = fileinfo.getLastModifiedDate(); 46 long fileSize = fileinfo.getSize(); 47 String type = fileinfo.getType(); 48 String id = fileinfo.getId(); 49 String extName = fileName.substring(fileName.lastIndexOf(".")); 50 long timeStemp=System.currentTimeMillis(); 51 if("single".equals(flag)) { 52 String fileNameSource = fileName+lastModifiedDate+fileSize+type+id+timeStemp; 53 String fileDirName = SHA256Util.getSHA256StrJava(fileNameSource)+extName; 54 File targetFile = new File(saveFilePath,fileDirName); 55 while(targetFile.exists()){ 56 fileNameSource=fileNameSource+"1"; 57 fileDirName = SHA256Util.getSHA256StrJava(fileNameSource)+extName; 58 targetFile = new File(fileDirName); 59 } 60 return targetFile; 61 }else if("chunks".equals(flag)) { 62 String fileNameSource = fileSize+"_"+fileName+id+lastModifiedDate; 63 String fileDirName = tempWorkPath+"/"+SHA256Util.getSHA256StrJava(fileNameSource)+extName+".temp"; 64 File tempFile = new File(fileDirName);//禁用FileInfo.exists()类, 防止缓存导致并发问题 65 if(!(tempFile.exists()&&tempFile.isFile())){ 66 filetempLock.lock();//上锁 67 if(!(tempFile.exists()&&tempFile.isFile())) { 68 MutilFileUploadUtils.readySpaceFile(fileinfo,tempFile); 69 } 70 filetempLock.unlock();//释放锁 71 } 72 tempFile = new File(fileDirName); 73 return tempFile; 74 }else{ 75 throw new Exception("目标文件生成失败"); 76 } 77 } 78 public void checkBaseDir(String baseDir) throws Exception { 79 File file = new File(baseDir); 80 if(!file.exists()&&!file.isDirectory()) { 81 file.mkdirs(); 82 } 83 } 84 @Override//文件合并 85 public void MutilMergingChunks(MutilFileInfo fileinfo) throws Exception { 86 // TODO Auto-generated method stub 87 String fileName = fileinfo.getName(); 88 String lastModifiedDate = fileinfo.getLastModifiedDate(); 89 long fileSize = fileinfo.getSize(); 90 String id = fileinfo.getId(); 91 String extName = fileName.substring(fileName.lastIndexOf(".")); 92 String fileNameSource = fileSize+"_"+fileName+id+lastModifiedDate; 93 String fileDirName = tempWorkPath+"/"+SHA256Util.getSHA256StrJava(fileNameSource)+extName+".temp"; 94 File tempFile = new File(fileDirName); 95 if(tempFile.exists()&&tempFile.isFile()) { 96 checkBaseDir(saveFilePath); 97 String targetDirName = saveFilePath+"/"+SHA256Util.getSHA256StrJava(fileNameSource); 98 File targetFile=new File(targetDirName+extName); 99 while(targetFile.exists()&&targetFile.isFile()) { 100 targetDirName = targetDirName+"1"; 101 targetFile=new File(targetDirName+extName); 102 } 103 System.out.println(targetFile.getAbsolutePath()); 104 if(tempFile.renameTo(targetFile)) { 105 System.out.println("文件重命名成功!"); 106 //数据库操作 107 //数据库操作 108 }else { 109 System.out.println("文件重命名失败!"); 110 throw new Exception("临时文件重命名失败"); 111 } 112 113 } 114 } 115 }
实体类,为了便与操作,这里将所有的参数分装成了一个实体类;
1 package com.webFileUploader.vo; 2 3 import java.io.File; 4 import java.util.Arrays; 5 6 /** 7 * @version 1.0 8 * @author liangxh 9 * @since 2018-12-04 10 * @return 大文件上传实体类 11 */ 12 public class MutilFileInfo { 13 private String id; //文件id 14 private String name; //文件名称 15 private String type;//文件类型 16 private String lastModifiedDate;//文件最后一次修改时间 17 private Long size;//文件总大小 18 //private Byte[] file;//副本字节流文件 19 private Integer chunk;//当前分片序号 20 private Integer chunks;//分片总数 21 private File fileChunk;//文件临时分片 22 private Boolean saved=false;//分片是否保存成功 默认值:false 23 public Boolean getSaved() { 24 return saved; 25 } 26 public void setSaved(Boolean saved) { 27 this.saved = saved; 28 } 29 public String getId() { 30 return id; 31 } 32 public File getFileChunk() { 33 return fileChunk; 34 } 35 public void setFileChunk(File fileChunk) { 36 this.fileChunk = fileChunk; 37 } 38 public void setId(String id) { 39 this.id = id; 40 } 41 public String getName() { 42 return name; 43 } 44 public void setName(String name) { 45 this.name = name; 46 } 47 public String getType() { 48 return type; 49 } 50 public void setType(String type) { 51 this.type = type; 52 } 53 public String getLastModifiedDate() { 54 return lastModifiedDate; 55 } 56 public void setLastModifiedDate(String lastModifiedDate) { 57 this.lastModifiedDate = lastModifiedDate; 58 } 59 public Long getSize() { 60 return size; 61 } 62 public void setSize(Long size) { 63 this.size = size; 64 } 65 public Integer getChunk() { 66 return chunk; 67 } 68 public void setChunk(Integer chunk) { 69 this.chunk = chunk; 70 } 71 public Integer getChunks() { 72 return chunks; 73 } 74 public void setChunks(Integer chunks) { 75 this.chunks = chunks; 76 } 77 @Override 78 public String toString() { 79 return "MutilFileInfo [id=" + id + ", name=" + name + ", type=" + type + ", lastModifiedDate=" 80 + lastModifiedDate + ", size=" + size + ", chunk=" + chunk + ", chunks=" + chunks + ", fileChunk=" 81 + fileChunk + ", saved=" + saved + "]"; 82 } 83 84 85 }
文件上传工具类封装;
1 package com.webFileUploader.utils; 2 3 import java.io.BufferedInputStream; 4 import java.io.BufferedOutputStream; 5 import java.io.File; 6 import java.io.FileFilter; 7 import java.io.FileInputStream; 8 import java.io.FileNotFoundException; 9 import java.io.FileOutputStream; 10 import java.io.IOException; 11 import java.io.InputStream; 12 import java.io.RandomAccessFile; 13 import java.util.ArrayList; 14 import java.util.Collections; 15 import java.util.Comparator; 16 import java.util.List; 17 import java.util.Map.Entry; 18 import java.util.Set; 19 import java.util.concurrent.ConcurrentHashMap; 20 21 import org.springframework.web.multipart.MultipartFile; 22 23 import com.webFileUploader.vo.MutilFileInfo; 24 /** 25 * @version 1.0 26 * @author liangxh 27 * @since 2018-12-04 28 * @return 文件上传工具类 29 */ 30 public class MutilFileUploadUtils { 31 32 /** 33 * 校验文件切片上传参数(字节流文件不能为空) 34 * @param fileinfo:上传参数实体类 35 * @return 判断是否文件切片上传,true:切片上传,false:单文件整体上传 36 * @throws Exception 37 */ 38 public static Boolean checkMutiFilePremeter(MutilFileInfo fileinfo) { 39 if(fileinfo!=null) { 40 if(fileinfo.getChunks()!=null&&fileinfo.getChunk()!=null&&fileinfo.getChunks()>1&&fileinfo.getChunk()>=0&&fileinfo.getChunks()>fileinfo.getChunk()) { 41 return true; 42 }else { 43 return false; 44 } 45 }else { 46 return false; 47 } 48 } 49 /** 50 * 校验文件单文件上传参数(字节流文件不能为空) 51 * @param fileinfo:上传参数实体类 52 * @return 判断参数上传是否合法,true:符合单文件上传参数格式,false:不符合单文件格式 53 * @throws Exception 54 */ 55 public static Boolean checkSingleFilePremeter(MutilFileInfo fileinfo) { 56 if(fileinfo!=null) { 57 if(fileinfo.getChunks()==null&&fileinfo.getChunk()==null) { 58 return true; 59 }else { 60 return false; 61 } 62 }else { 63 return false; 64 } 65 } 66 /** 67 * 保存文件到指定目录 68 * @param fileinfo:上传参数实体类 69 * @throws Exception 70 */ 71 public static void saveFile2DirPath(MultipartFile file,File targetFile) throws Exception { 72 if(targetFile.createNewFile()){ 73 file.transferTo(targetFile); 74 } 75 } 76 /** 77 * 创建空目标文件 78 * @throws IOException 79 * @throws Exception 80 */ 81 public static void readySpaceFile(MutilFileInfo fileinfo,File tempFile) throws IOException{ 82 RandomAccessFile targetSpaceFile = new RandomAccessFile(tempFile, "rws"); 83 targetSpaceFile.setLength(fileinfo.getSize()); 84 System.out.println("创建文件:"+fileinfo.getSize()); 85 targetSpaceFile.close(); 86 } 87 /** 88 * 向空文件写入二进制数据 89 * @param targetFile:目标文件 90 * @param appenderFile:数据源文件 91 * @throws Exception 92 */ 93 @SuppressWarnings("resource") 94 public static void spaceFileWriter(MultipartFile file, File tempFile,MutilFileInfo fileInfo) throws Exception { 95 long totalSpace = tempFile.getTotalSpace(); 96 RandomAccessFile raf = new RandomAccessFile(tempFile, "rw"); 97 BufferedInputStream sourceBuffer = new BufferedInputStream(file.getInputStream()); 98 Long startPointer = getFileWriterStartPointer(file, fileInfo); 99 raf.seek(startPointer);//初始化文件指针起始位置 100 byte[] bt = new byte[1024]; 101 int n=0; 102 try { 103 while ((n=sourceBuffer.read(bt))!=-1){ 104 raf.write(bt); 105 } 106 } catch (IOException e) { 107 e.printStackTrace(); 108 }finally { 109 if(sourceBuffer!=null) { 110 sourceBuffer.close(); 111 } 112 if(raf!=null) { 113 raf.close(); 114 } 115 } 116 } 117 /** 118 * 计算指针开始位置 119 * @param fileInfo:分片实体类 120 * @param MultipartFile:文件流 121 * @throws IOException 122 * @throws Exception 123 */ 124 synchronized public static Long getFileWriterStartPointer(MultipartFile file, MutilFileInfo fileInfo) throws Exception { 125 // TODO Auto-generated method stub 126 long chunkSize = file.getSize(); 127 Integer currChunk = fileInfo.getChunk(); 128 Integer allChunks = fileInfo.getChunks(); 129 Long allSize = fileInfo.getSize(); 130 if(currChunk<(allChunks-1)){ 131 long starter = chunkSize*currChunk; 132 return starter; 133 }else if(currChunk==(allChunks-1)){ 134 long starter = allSize-chunkSize; 135 return starter; 136 }else { 137 throw new Exception("分片参数异常"); 138 } 139 } 140 }