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;
	   }
	 
}

 

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值