java文件分片上传,断点续传
百度的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">Demo</h1> <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 = $('<ul class="filelist"></ul>') 6 .appendTo( $wrap.find('.queueList') ), 7 // 状态栏,包括进度和控制按钮 8 $statusBar = $wrap.find('.statusBar'), 9 // 文件总体选择信息。 10 $info = $statusBar.find('.info'), 11 12 // 上传按钮 13 $upload = $wrap.find('.uploadBtn'), 14 15 // 没选择文件之前的内容。 16 $placeHolder = $wrap.find('.placeholder'), 17 18 // 总体进度条 19 $progress = $statusBar.find('.progress').hide(), 20 21 // 添加的文件数量 22 fileCount = 0, 23 24 // 添加的文件总大小 25 fileSize = 0, 26 27 // 优化retina, 在retina下这个值是2 28 ratio = window.devicePixelRatio || 1, 29 30 // 缩略图大小 31 thumbnailWidth = 110 * ratio, 32 thumbnailHeight = 110 * ratio, 33 34 // 可能有pedding, ready, uploading, confirm, done. 35 state = 'pedding', 36 37 // 所有文件的进度信息,key为file id 38 percentages = {}, 39 supportTransition = (function(){ 40 var s = document.createElement('p').style, 41 r = 'transition' in s || 42 'WebkitTransition' in s || 43 'MozTransition' in s || 44 'msTransition' in s || 45 'OTransition' in s; 46 s = null; 47 return r; 48 })(), 49 uploader; 50 if ( !WebUploader.Uploader.support() ) { 51 alert( 'Web Uploader 不支持您的浏览器!如果你使用的是IE浏览器,请尝试升级 flash 播放器'); 52 throw new Error( 'WebUploader does not support the browser you are using.' ); 53 } 54 // 实例化 55 uploader = WebUploader.create({ 56 pick: { 57 id: '#filePicker', 58 label: '点击选择图片' 59 }, 60 dnd: '#uploader .queueList', 61 paste: document.body, 62 /* accept: { 63 title: 'Images', 64 extensions: 'gif,jpg,jpeg,bmp,png', 65 mimeTypes: 'image/*' 66 },*/ 67 // swf文件路径 68 swf: BASE_URL + '/js/Uploader.swf', 69 70 disableGlobalDnd: true, 71 //分片 72 chunked: true, 73 //每片大小 2M 74 chunkSize: 20097152, 75 //单片失败后重试次数 76 chunkRetry: 10, 77 //上传并发线程数 78 thread: 1, 79 // server: 'http://webuploader.duapp.com/server/fileupload.php', 80 server: 'http://localhost:8080/uploadFile', 81 fileNumLimit: 300, 82 fileSizeLimit: 2000* 1024 * 1024, // 2000 M 83 fileSingleSizeLimit: 1000 * 1024 * 1024 // 1000 M 84 }); 85 // 添加“添加文件”的按钮, 86 uploader.addButton({ 87 id: '#filePicker2', 88 label: '继续添加' 89 }); 90 // 当有文件添加进来时执行,负责view的创建 91 function addFile( file ) { 92 var $li = $( '<li id="' + file.id + '">' + 93 '<p class="title">' + file.name + '</p>' + 94 '<p class="imgWrap"></p>'+ 95 '<p class="progress"><span></span></p>' + 96 '</li>' ), 97 $btns = $('<div class="file-panel">' + 98 '<span class="cancel">删除</span>' + 99 '<span class="rotateRight">向右旋转</span>' + 100 '<span class="rotateLeft">向左旋转</span></div>').appendTo( $li ), 101 $prgress = $li.find('p.progress span'), 102 $wrap = $li.find( 'p.imgWrap' ), 103 $info = $('<p class="error"></p>'), 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 = $('<img src="'+src+'">'); 129 $wrap.empty().append( img ); 130 }, thumbnailWidth, thumbnailHeight ); 131 132 percentages[ file.id ] = [ file.size, 0 ]; 133 file.rotation = 0; 134 } 135 file.on('statuschange', function( cur, prev ) { 136 if ( prev === 'progress' ) { 137 $prgress.hide().width(0); 138 } else if ( prev === 'queued' ) { 139 $li.off( 'mouseenter mouseleave' ); 140 $btns.remove(); 141 } 142 // 成功 143 if ( cur === 'error' || cur === 'invalid' ) { 144 console.log( file.statusText ); 145 showError( file.statusText ); 146 percentages[ file.id ][ 1 ] = 1; 147 } else if ( cur === 'interrupt' ) { 148 showError( 'interrupt' ); 149 } else if ( cur === 'queued' ) { 150 percentages[ file.id ][ 1 ] = 0; 151 } else if ( cur === 'progress' ) { 152 $info.remove(); 153 $prgress.css('display', 'block'); 154 } else if ( cur === 'complete' ) { 155 $li.append( '<span class="success"></span>' ); 156 alert("此文件已成功"); 157 console.log("========="+file.size); 158 if(file.size>20097152){ 159 $.post("http://localhost:8080/mergingChunks",{"id":file.id,"name":file.name,"size":file.size,"lastModifiedDate":file.lastModifiedDate}, 160 function(data,status){ 161 alert("Data: " + data + "\nStatus: " + status); 162 }); 163 } 164 } 165 $li.removeClass( 'state-' + prev ).addClass( 'state-' + cur ); 166 }); 167 $li.on( 'mouseenter', function() { 168 $btns.stop().animate({height: 30}); 169 }); 170 $li.on( 'mouseleave', function() { 171 $btns.stop().animate({height: 0}); 172 }); 173 $btns.on( 'click', 'span', function() { 174 var index = $(this).index(), 175 deg; 176 switch ( index ) { 177 case 0: 178 uploader.removeFile( file ); 179 return; 180 case 1: 181 file.rotation += 90; 182 break; 183 case 2: 184 file.rotation -= 90; 185 break; 186 } 187 if ( supportTransition ) { 188 deg = 'rotate(' + file.rotation + 'deg)'; 189 $wrap.css({ 190 '-webkit-transform': deg, 191 '-mos-transform': deg, 192 '-o-transform': deg, 193 'transform': deg 194 }); 195 } else { 196 $wrap.css( 'filter', 'progid:DXImageTransform.Microsoft.BasicImage(rotation='+ (~~((file.rotation/90)%4 + 4)%4) +')'); 197 } 198 }); 199 200 $li.appendTo( $queue ); 201 } 202 203 // 负责view的销毁 204 function removeFile( file ) { 205 var $li = $('#'+file.id); 206 delete percentages[ file.id ]; 207 updateTotalProgress(); 208 $li.off().find('.file-panel').off().end().remove(); 209 } 210 function updateTotalProgress() { 211 var loaded = 0, 212 total = 0, 213 spans = $progress.children(), 214 percent; 215 216 $.each( percentages, function( k, v ) { 217 total += v[ 0 ]; 218 loaded += v[ 0 ] * v[ 1 ]; 219 } ); 220 221 percent = total ? loaded / total : 0; 222 spans.eq( 0 ).text( Math.round( percent * 100 ) + '%' ); 223 spans.eq( 1 ).css( 'width', Math.round( percent * 100 ) + '%' ); 224 updateStatus(); 225 } 226 function updateStatus() { 227 var text = '', stats; 228 if ( state === 'ready' ) { 229 text = '选中' + fileCount + '张图片,共' + 230 WebUploader.formatSize( fileSize ) + '。'; 231 } else if ( state === 'confirm' ) { 232 stats = uploader.getStats(); 233 if ( stats.uploadFailNum ) { 234 text = '已成功上传' + stats.successNum+ '张照片至XX相册,'+ 235 stats.uploadFailNum + '张照片上传失败,<a class="retry" href="#">重新上传</a>失败图片或<a class="ignore" href="#">忽略</a>' 236 } 237 } else { 238 stats = uploader.getStats(); 239 text = '共' + fileCount + '张(' + 240 WebUploader.formatSize( fileSize ) + 241 '),已上传' + stats.successNum + '张'; 242 if ( stats.uploadFailNum ) { 243 text += ',失败' + stats.uploadFailNum + '张'; 244 } 245 } 246 $info.html( text ); 247 } 248 function setState( val ) { 249 var file, stats; 250 if ( val === state ) { 251 return; 252 } 253 $upload.removeClass( 'state-' + state ); 254 $upload.addClass( 'state-' + val ); 255 state = val; 256 switch ( state ) { 257 case 'pedding': 258 $placeHolder.removeClass( 'element-invisible' ); 259 $queue.parent().removeClass('filled'); 260 $queue.hide(); 261 $statusBar.addClass( 'element-invisible' ); 262 uploader.refresh(); 263 break; 264 case 'ready': 265 $placeHolder.addClass( 'element-invisible' ); 266 $( '#filePicker2' ).removeClass( 'element-invisible'); 267 $queue.parent().addClass('filled'); 268 $queue.show(); 269 $statusBar.removeClass('element-invisible'); 270 uploader.refresh(); 271 break; 272 case 'uploading': 273 $( '#filePicker2' ).addClass( 'element-invisible' ); 274 $progress.show(); 275 $upload.text( '暂停上传' ); 276 break; 277 case 'paused': 278 $progress.show(); 279 $upload.text( '继续上传' ); 280 break; 281 case 'confirm': 282 $progress.hide(); 283 $upload.text( '开始上传' ).addClass( 'disabled' ); 284 stats = uploader.getStats(); 285 if ( stats.successNum && !stats.uploadFailNum ) { 286 setState( 'finish' ); 287 return; 288 } 289 break; 290 case 'finish': 291 stats = uploader.getStats(); 292 if ( stats.successNum ) { 293 alert( '文件上传已完成!' );//文件上传后服务器合并文件 294 } else { 295 // 没有成功的图片,重设 296 state = 'done'; 297 location.reload(); 298 } 299 break; 300 } 301 updateStatus(); 302 } 303 uploader.onUploadProgress = function( file, percentage ) { 304 var $li = $('#'+file.id), 305 $percent = $li.find('.progress span'); 306 $percent.css( 'width', percentage * 100 + '%' ); 307 percentages[ file.id ][ 1 ] = percentage; 308 updateTotalProgress(); 309 }; 310 uploader.onFileQueued = function( file ) { 311 fileCount++; 312 fileSize += file.size; 313 if ( fileCount === 1 ) { 314 $placeHolder.addClass( 'element-invisible' ); 315 $statusBar.show(); 316 } 317 addFile( file ); 318 setState( 'ready' ); 319 updateTotalProgress(); 320 }; 321 322 uploader.onFileDequeued = function( file ) { 323 fileCount--; 324 fileSize -= file.size; 325 if ( !fileCount ) { 326 setState( 'pedding' ); 327 } 328 removeFile( file ); 329 updateTotalProgress(); 330 }; 331 uploader.on( 'all', function( type ) { 332 var stats; 333 switch( type ) { 334 case 'uploadFinished': 335 setState( 'confirm' ); 336 break; 337 case 'startUpload': 338 setState( 'uploading' ); 339 break; 340 case 'stopUpload': 341 setState( 'paused' ); 342 break; 343 } 344 }); 345 uploader.onError = function( code ) { 346 alert( 'Eroor: ' + code ); 347 }; 348 349 $upload.on('click', function() { 350 if ( $(this).hasClass( 'disabled' ) ) { 351 return false; 352 } 353 if ( state === 'ready' ) { 354 uploader.upload(); 355 } else if ( state === 'paused' ) { 356 uploader.upload(); 357 } else if ( state === 'uploading' ) { 358 uploader.stop(); 359 } 360 }); 361 $info.on( 'click', '.retry', function() { 362 uploader.retry(); 363 } ); 364 $info.on( 'click', '.ignore', function() { 365 alert( 'todo' ); 366 } ); 367 $upload.addClass( 'state-' + state ); 368 updateTotalProgress(); 369 });
后台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 Map<String, String> MutiluploadFile(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<String, String> 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 }
补充:不好意思,前段时间没上博客,评论没有看到,现在补充一下 github的源码地址:https://github.com/recently-lxh/webFileUploader.git
有需要的源码的伙伴可以参考一下这只是一个demo,代码风格需要自己整理,这是当时做文件管理时,预研可行性的demo,真正正式代码因为公司机密问题,不便于发出来,但整体流程思路是一致的
本文来自博客园,作者:夜半风起,转载请注明原文链接:https://www.cnblogs.com/yebanfengqi/p/10108914.html