springboot使用WebUploader實現大文件分片上傳

前段時間做了個網盤,所以添加了一個大文件上傳接口,這裏記錄一下

直接給出代碼:

前端頁面:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
   	<link href="https://cdn.bootcss.com/twitter-bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet">
   	<link href="/cloud/js/webuploader-0.1.5/webuploader.css" rel="stylesheet" type="text/css" />
   
   	<script src="https://cdn.bootcss.com/jquery/1.10.2/jquery.min.js"></script>
   	<script type="text/javascript" src="/cloud/js/webuploader-0.1.5/webuploader.min.js"></script>
	<script src="/cloud/js/layer/layer.js"></script>
	
	<style type="text/css">
        .wu-example {
            width: 847px;
 
            position: relative;
            padding: 45px 15px 15px;
            margin: 0 auto;
            background-color: #fafafa;
            box-shadow: inset 0 3px 6px rgba(0, 0, 0, .05);
            border-color: #e5e5e5 #eee #eee;
            border-style: solid;
            border-width: 1px 0;
        }
        #picker {
            display: inline-block;
            line-height: 1.428571429;
            vertical-align: middle;
            margin: 0 12px 0 0;
        }
    </style>
</head>
<body>
   	<div class="alert alert-success" role="alert" style="text-align: center;font-size: 18px;">大文件上傳接口</div>
 
	<div id="uploader" class="wu-example">
	    <!--用來存放文件信息-->
	    <div id="thelist" class="uploader-list"></div>
	    <div class="btns">
	        <div id="picker">選擇文件</div>
	        <button id="ctlBtn" class="btn btn-default">開始上傳</button>
	    </div>
	    <p>
	        <span>上傳所用時間:</span>
	        <span id="useTime">0</span>s
	    </p>
	</div>
</body>
<script type="text/javascript">
function webpageClose(){
	window.parent.location.reload()
	window.parent.layer.closeAll();
}

var path;
var localUrl = window.location.href;
var localUrlSplit = localUrl.split("?");
if(localUrlSplit.length==1){
	path = "/";
}else{
	path = decodeURIComponent(localUrlSplit[1].split("=")[1]);
}
console.log(path)

var fileMd5;
var $list = $("#thelist");
var $btn = $("#ctlBtn");
var state = 'pending'; // 上傳文件初始化
var timer;
var fileArray = [];
var GUID;
var chunkSize = 32 * 1024 * 1024; // 每片32M
var thisFile;

//監聽分塊上傳過程中的三個時間點
WebUploader.Uploader.register({
	"before-send-file" : "beforeSendFile",
	"before-send" : "beforeSend",
	"after-send-file" : "afterSendFile",
}, {
	//時間點1:所有分塊進行上傳之前調用此函數
	beforeSendFile : function(file) {
		thisFile = file;
		console.log(file);
		var deferred = WebUploader.Deferred();
		var owner = this.owner;
		//1、計算文件的唯一標記,用於斷點續傳
		(new WebUploader.Uploader()).md5File(file, 0, chunkSize)
				.progress(function(percentage) {
					$('#' + file.id).find('p.state').text("正在讀取文件信息...");
				}).then(function(val) {
					fileMd5 = val;
					$('#' + file.id).find("p.state").text("成功獲取文件信息...");
					 //2、判斷文件是否已存在
					 $.ajax({
						type : "GET",
						url : "/cloud/bigFile/findResourceFileExistsByMd5",
						data : {
							//文件唯一標記
							md5value : fileMd5,
							fileName : file.name,
							caozuoPath : path
						},
						dataType : "json",
						async:false,
						success : function(response) {
							if (response.statusCode==200) {
								layer.msg("上傳成功,即將關閉窗口!",{icon: 1});
								$('#' + file.id).find('p.state').text('已上傳');
								//文件已上傳,跳過
// 								console.log("文件已上傳");
								deferred.reject();
								owner.skipFile(file);
								setTimeout( webpageClose,3000)//4s鍾後關閉
							} else {
								//獲取文件信息後進入下一步
								deferred.resolve();
							}
						}
					});
					
				});
		
		return deferred.promise();
	},
	//時間點2:如果有分塊上傳,則每個分塊上傳之前調用此函數
	beforeSend : function(block) {
		var deferred = WebUploader.Deferred();
		$.ajax({
			type : "GET",
			url : "/cloud/bigFile/findResourceChunkExists",
			data : {
				//文件唯一標記
				guid : GUID,
				//當前分塊下標
				chunk : block.chunk
			},
			dataType : "json",
			async:false,
			success : function(response) {
				if (response.statusCode==200) {
					//分塊存在,跳過
					console.log("分塊存在,跳過");
					deferred.reject();
				} else {
					//分塊不存在或不完整,重新發送該分塊內容
					console.log("分塊不存在或不完整,重新發送該分塊內容");
					deferred.resolve();
				}
			}
		});

		this.owner.options.formData.md5value =fileMd5;
		this.owner.options.formData.chunk =block.chunk;
		deferred.resolve();
		return deferred.promise();
	},
	//時間點3:所有分塊上傳成功後調用此函數
	afterSendFile : function() {
		//如果分塊上傳成功,則獲取上傳結果
		$.post('/cloud/bigFile/saveFileInfo', { guid: GUID, fileName: thisFile.name, md5value : fileMd5, caozuoPath: path, fileSize:thisFile.size}, function (data) {
			if(data.statusCode == 200){
				setTimeout( webpageClose,3000)//4s鍾後關閉
	        	layer.msg("上傳成功,即將關閉窗口!",{icon: 1});
	        }else{
	        	setTimeout( webpageClose,3000)//4s鍾後關閉
	        	layer.msg("上傳失敗,即將關閉窗口!",{icon: 2});
	        }
	      });
	}
});

var uploader = WebUploader.create({
    // swf文件路徑
    swf: '/cloud/js/webuploader-0.1.5/Uploader.swf',
    // 文件接收服務端。
    server: '/cloud/bigFile/upload',
    // 選擇文件的按鈕。可選。
    // 內部根據當前運行是創建,可能是input元素,也可能是flash.
    pick: '#picker',
   	chunked : true, // 分片處理
   	chunkSize : chunkSize,
   	chunkRetry : false,// 如果失敗,則不重試
   	threads : 1,// 上傳併發數。允許同時最大上傳進程數。
   	// 不壓縮image, 默認如果是jpeg,文件上傳前會壓縮一把再上傳!
    resize: false
});

//點擊上傳之前調用的方法
uploader.on("uploadStart", function (file) {
	GUID = WebUploader.Base.guid();
    var paramOb = {"guid": GUID, "filedId": file.source.ruid}
    uploader.options.formData.guid = GUID;
    fileArray.push(paramOb);
});

//當有文件被添加進隊列的時候
uploader.on('beforeFileQueued', function(file) {
	 //清空隊列
    uploader.reset();
});
//當有文件被添加進隊列的時候
uploader.on('fileQueued', function (file) {
    $list.append('<div id="' + file.id + '" class="item">' +
        '<h4 class="info">' + file.name + '</h4>' +
        '<p class="state">等待上傳...</p>' +
        '</div>');
});

//文件上傳過程中創建進度條實時顯示。
uploader.on('uploadProgress', function (file, percentage) {
    var $li = $('#' + file.id),
        $percent = $li.find('.progress .progress-bar');
    // 避免重複創建
    if (!$percent.length) {
        $percent = $('<div class="progress progress-striped active">' +
            '<div class="progress-bar" role="progressbar" style="width: 0%">' +
            '</div>' +
            '</div>').appendTo($li).find('.progress-bar');
    }
    $li.find('p.state').text('上傳中');
    $percent.css('width', percentage * 100 + '%');
});

$("#ctlBtn").click(function () {
   uploader.upload();
});
//文件成功、失敗處理
uploader.on('uploadSuccess', function (file) {
    var successFileId = file.source.ruid;
    var successDuid;
    if (fileArray.length > 0) {
        for (var i = 0; i < fileArray.length; i++) {
            if (fileArray[i].filedId === successFileId) {
                successDuid=fileArray[i].guid;
                fileArray.splice(i, 1);
            }
        }
    }
    clearInterval(timer);
    $('#' + file.id).find('p.state').text('已上傳');
});

uploader.on('uploadError', function (file) {
    $('#' + file.id).find('p.state').text('上傳出錯');
});

uploader.on('uploadComplete', function (file) {
    $('#' + file.id).find('.progress').fadeOut();
});

$btn.on('click', function () {
    if (state === 'uploading') {
        uploader.stop();
    } else {
        uploader.upload();
        timer = setInterval(function () {
            var useTime = parseInt($("#useTime").html());
            useTime = useTime + 1;
            $("#useTime").html(useTime);
        }, 1000);
    }
});

</script>
</html>

後端代碼:

先在application.yml中添加配置:

spring:
  http:
    encoding:
      force: true
      charset: utf-8
    multipart:
      max-file-size: 100MB
      max-request-size: 100MB
package com.hm.pan.controller;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.io.FileUtils;
import org.apache.tomcat.util.http.fileupload.servlet.ServletFileUpload;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import com.hm.pan.annotation.CheckLogin;
import com.hm.pan.model.FileInfo;
import com.hm.pan.model.ResultObj;
import com.hm.pan.model.User;
import com.hm.pan.model.UserFile;
import com.hm.pan.service.FileUploadService;
import com.hm.pan.service.UserFileService;

@RestController
@RequestMapping("bigFile")
@CheckLogin
public class BigFileUploadController {

	@Resource
	private FileUploadService fileUploadService;
	@Resource
	private UserFileService userFileService;
	@Value("${upload-path}")
	private String uploadPath;
	
	// 通過MD5查找文件信息,如果有的話,就不用上傳文件,只要向用戶文件裏插入文件信息就行
	@GetMapping("findResourceFileExistsByMd5")
	public Object findMd5(String md5value, String fileName, String caozuoPath, HttpSession session)
			throws Exception {
		Timestamp uploadTime = new Timestamp(System.currentTimeMillis());
		UserFile oneFileByPath2 = userFileService.getOneFileByPath(caozuoPath);// 通過前端傳過來的操作路徑找到該用戶文件對象
		Long fatherId2 = oneFileByPath2.getUserfileId();
//		log.info("================"+md5);
		User user = (User) session.getAttribute("user"); // 通過session拿到前端用戶信息
//		log.info("-----------------"+user);
		Long userId = user.getUserId(); // 獲取前端傳過來的用戶id
//		log.info("+++++++++++++++++"+userId);

		String newName = getName(fileName);

		Object filemd5 = fileUploadService.findMd5(md5value);
		// 如果數據裏有MD5,就不用上傳,但是用戶文件需要插入。
		if (filemd5 != null) {
			Object fileInfo = fileUploadService.findByNameFatherId(fileName, fatherId2);
			if (fileInfo != null) {
				fileUploadService.insertUserFile(uploadTime, newName, md5value, fatherId2, userId);
			} else {
				fileUploadService.insertUserFile(uploadTime, fileName, md5value, fatherId2, userId);
			}
			return new ResultObj(200, fileInfo);
		} else {
			return new ResultObj(400, filemd5);
		}

	}
	
    //這裏通過文件編號guid和分片數chunk來檢查分片文件,其實嚴格一點還需要檢查分片文件的偏移位置和文件大小,以防止分片文件上傳失敗的情況,這裏表示一下就行,需要自己改改
	@GetMapping("findResourceChunkExists")
    public ResultObj findResourceChunkExists(String guid,Integer chunk){
		// 臨時目錄用來存放所有分片文件  
        String tempFileDir = uploadPath+ "/bigFileTemp/" + guid;  
        File parentFileDir = new File(tempFileDir); 
		File tempPartFile = new File(parentFileDir, guid + "_" + chunk + ".part"); 
		if(tempPartFile.exists()) {
			return new ResultObj(200, "存在"); 
		}else {
			return new ResultObj(404, "不存在"); 
		}
    }

	// 如果前面的MD5沒有找到文件信息的話,就會返回一個400到前端,前端再根據判斷,把文件給上傳。
	@PostMapping("saveFileInfo")
	public ResultObj aa(String guid, String md5value, Long fileSize, String fileName,
			String caozuoPath, HttpSession session, HttpServletRequest req) throws Exception {
		Timestamp uploadTime = new Timestamp(System.currentTimeMillis());
		System.out.println("saveFileInfo:");
		System.out.println("------------guid:"+guid);
		System.out.println("------------md5value:"+md5value);
		System.out.println("------------fileSize:"+fileSize);
		System.out.println("------------fileName:"+fileName);
		System.out.println("------------caozuoPath:"+caozuoPath);
		String newName = getName(fileName);

		// 將文件存到指定項目文件下,沒有的話就創建
//		File realpath = ResourceUtils.getFile("");
//		String realpath = req.getServletContext().getRealPath("/upload");
		// String filePath = realpath+fileName;
		File dest = mergeFile(guid, fileName);
//		log.info("<<<<<<<<<<<<<<<< "+file2.getAbsolutePath());
		FileInfo fileInfo = new FileInfo();
		fileInfo.setFilePath(dest.getPath()); // 獲取文件的相對路徑
		fileInfo.setFileMd5(md5value);
		fileInfo.setFileSize(fileSize);

		User user = (User) session.getAttribute("user"); // 通過session拿到前端用戶信息
//		log.info("-----------------"+user);
		Long userId = user.getUserId(); // 獲取前端傳過來的用戶id
//		log.info("+++++++++++++++++"+userId);
		Object fileinfo = fileUploadService.insertFileInfo(fileInfo);

		UserFile oneFileByPath = userFileService.getOneFileByPath(caozuoPath); // 通過操作路徑找到fatherId

		if (oneFileByPath != null && fileinfo != null) {
			Long fatherId = oneFileByPath.getUserfileId();
			Object findByName = fileUploadService.findByNameFatherId(fileName, fatherId);
			if (findByName != null) {
				fileUploadService.insertUserFile(uploadTime, newName, md5value, fatherId, userId);
			} else {
				fileUploadService.insertUserFile(uploadTime, fileName, md5value, fatherId, userId);
			}
			return new ResultObj(200, fileinfo);
		}

		return new ResultObj(400, fileinfo);
	}

	// 獲取不同的文件名
	public String getName(String fileName) {
		// 解決文件名重名,以時間戳+隨機數
		// 獲得當前時間

		SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmss");
		// 轉換爲字符串
		String formDate = format.format(new Date());
		// 隨機生成文件編號
		int random = new Random().nextInt(1000);
		String[] split = fileName.split("\\.");

		// 1.2.3.4.cc
		// mm
		StringBuffer sb = new StringBuffer(split[0]);
		for (int i = 1; i < split.length - 1; i++) {
			sb.append('.').append(split[i]);
		}

		sb.append('_').append(formDate).append(random);

		if (fileName.indexOf(".") != -1) {
			sb.append('.').append(split[split.length - 1]);
		}

		String name = sb.toString();

		return name;
	}

	
	/**
	    * 上傳文件
	    * @param request
	    * @param response
	    * @param guid
	    * @param chunk
	    * @param file
	    * @param chunks
	    */
	   @RequestMapping("upload")
	   public void bigFile(HttpServletRequest request, HttpServletResponse response,String guid,Integer chunk, MultipartFile file,Integer chunks){
	      try {  
	            boolean isMultipart = ServletFileUpload.isMultipartContent(request);  
	            if (isMultipart) {  
	                // 臨時目錄用來存放所有分片文件  
	                String tempFileDir = uploadPath+ "/bigFileTemp/" + guid;  
	                File parentFileDir = new File(tempFileDir);  
	                if (!parentFileDir.exists()) {  
	                    parentFileDir.mkdirs();  
	                }  
	                // 分片處理時,前臺會多次調用上傳接口,每次都會上傳文件的一部分到後臺  
	                File tempPartFile = new File(parentFileDir, guid + "_" + chunk + ".part");  
	                FileUtils.copyInputStreamToFile(file.getInputStream(), tempPartFile);  
	            }  
	        } catch (Exception e) {  
	           e.printStackTrace();
	        }  
	   }
	   
	   /**
	    * 合併文件
	    * @param guid
	    * @param fileName
	    * @throws Exception
	    */
	   public File mergeFile(String guid,String fileName){
	       // 得到 destTempFile 就是最終的文件  
	      try {
	         File parentFileDir = new File(uploadPath+ "/bigFileTemp/" + guid);
	         if(parentFileDir.isDirectory()){
//	            File destTempFile = new File(uploadPath+ "/bigFileTemp/merge/", fileName);
	            SimpleDateFormat format = new SimpleDateFormat("yyyy/MM/dd");
	    		// 轉換爲字符串
	    		String formDate = format.format(new Date());
	    		File destTempFile = new File(uploadPath+"/"+formDate, fileName);
	            System.out.println(destTempFile.getPath());
	            
	            if(!destTempFile.exists()){
	               //先得到文件的上級目錄,並創建上級目錄,在創建文件,
	               destTempFile.getParentFile().mkdirs();
	               try {
	                  //創建文件
	                  destTempFile.createNewFile(); //上級目錄沒有創建,這裏會報錯
	               } catch (IOException e) {
	                  e.printStackTrace();
	               }
	            }
	            System.out.println(parentFileDir.listFiles().length);
	              for (int i = 0; i < parentFileDir.listFiles().length; i++) {  
	                  File partFile = new File(parentFileDir, guid + "_" + i + ".part");
	                  FileOutputStream destTempfos = new FileOutputStream(destTempFile, true);
	                  //遍歷"所有分片文件"到"最終文件"中  
	                  FileUtils.copyFile(partFile, destTempfos);  
	                  destTempfos.close();  
	              }  
	              // 刪除臨時目錄中的分片文件  
	              FileUtils.deleteDirectory(parentFileDir);
	              return destTempFile;
	         }
	      } catch (Exception e) {
	         e.printStackTrace();
	         return null;
	      }
	      return null;
	   }
	 
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章