springboot 整合 gridfs 、webUploader实现大文件分块上传、断点续传、秒传

首页 / 新闻资讯 / 正文

主要的pom.xml:

<dependency>             <groupId>mysql</groupId>             <artifactId>mysql-connector-java</artifactId>         </dependency> <!--mongodb-->         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-data-mongodb</artifactId>         </dependency> <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-data-jpa</artifactId>         </dependency>

大家发现多了一个mysql,主要是为了在里面存文件列表用

resource目录如下:

application.properties:

替换成自己的用户名密码

pring.datasource.url=jdbc:mysql://127.0.0.1:3306/hrms?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=GMT spring.datasource.username=root spring.datasource.password=123456 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.jpa.database=mysql #自动建表 spring.jpa.hibernate.ddl-auto=update #设置数据库方言 spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect #打印sql spring.jpa.show-sql=true  #连接mongodb spring.data.mongodb.uri=mongodb://127.0.0.1:27017/mydb spring.data.mongodb.username=root spring.data.mongodb.password=123456

 

 前端upload.html:

进度条仅为测试,样式有点丑请忽略(webUploader官网的例子有点问题,需要自己调一下)

<!DOCTYPE html> <html lang="en"> <head>     <meta charset="UTF-8">     <title>Title</title>     <!--引入CSS-->     <link  type="text/css" href="/css/webuploader.css">     <!--引入JS-->     <script type="text/javascript" src="/js/admin.js"></script>     <script type="text/javascript" src="/js/jquery-3.3.1.min.js"></script>     <script type="text/javascript" src="/js/webuploader.js"></script>     <!--SWF在初始化的时候指定,在后面将展示-->     <style>         #ctlBtn {             top: 0px;             left: 0px;             width: 94px;             height: 40.8px;             overflow: hidden;             bottom: auto;             right: auto;         }     </style> </head> <body> <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> </div> </body> <script>     var md5;     //监听分块上传过程中的三个时间点     WebUploader.Uploader.register({         "before-send-file": "beforeSendFile",         "before-send": "beforeSend",         "after-send-file": "afterSendFile"     }, {         //时间点1:所有分块进行上传之前调用此函数         beforeSendFile: function (file) {             var deferred = WebUploader.Deferred();             //1、计算文件的唯一标记,用于断点续传             (new WebUploader.Uploader()).md5File(file, 0, 10 * 1024 * 1024)                 .progress(function (percentage) {                     $('#' + file.id).find("p.state").text("正在读取文件信息...");                 })                 .then(function (val) {                     md5 = val;                     $('#' + file.id).find("p.state").text("成功获取文件信息...");                     //获取文件信息后进入下一步                     deferred.resolve();                 });             return deferred.promise();         },         //时间点2:如果有分块上传,则每个分块上传之前调用此函数         beforeSend: function (block) {             var deferred = WebUploader.Deferred();             $.ajax({                 type: "get",                 url: BASE_PATH + "/check",                 data: {                     //文件唯一标记                     "md5": md5,                     //当前分块下标                     "chunk": block.chunk,                     //当前分块大小                     "chunkSize": block.end - block.start                 },                 //无缓存                 cache: false,                 //无异步(必须加,否则异步上传会上传已经上传过的块)                 async: false,                 dataType: "json",                 success: function (data) {                     if (data) {                         //分块存在,跳过                         deferred.reject();                     } else {                         //分块不存在或不完整,重新发送该分块内容                         deferred.resolve();                     }                 }             });              this.owner.options.formData.md5 = md5;             deferred.resolve();             return deferred.promise();         }         //时间点3:所有分块上传成功后调用此函数         // afterSendFile: function () {         //     //如果分块上传成功,则通知后台合并分块         //     $.ajax({         //         type: "POST",         //         url: "<%=basePath%>Video?action=mergeChunks",         //         data: {         //             md5: md5,         //         },         //         success: function (response) {         //             alert("上传成功");         //             var path = "uploads/" + md5 + ".mp4";         //             $("#item1").attr("src", path);         //         }         //     });         // }     });     var uploader = WebUploader.create({         // swf文件路径         swf: BASE_PATH + "/js/Uploader.swf",         // 文件接收服务端。         server: BASE_PATH + "/upload",         // 选择文件的按钮。可选。         // 内部根据当前运行是创建,可能是input元素,也可能是flash.         pick: "#picker",         // 不压缩image, 默认如果是jpeg,文件上传前会压缩一把再上传!         resize: false,         //开启分片上传         chunked: true,         //分片大小         chunkSize: 10 * 1024 * 1024,         //并发数         threads: 10         //请求参数表         // formData:datalist,     });      $("#ctlBtn").on("click", function () {         uploader.upload();     });      // 当有文件被添加进队列的时候     uploader.on('fileQueued', function (file) {         $("#thelist").append("<div id=\"" + file.id + "\" class=\"item\">" +             "<h4 class=\"info\">" + file.name + "</h4>" +             "<p class=\"state\">等待上传...</p>" +             "</div>");         // //暂停上传的文件         // $("#thelist").on('click', '.stop', function () {         //     uploader.stop(true);         // }),         // //删除上传的文件         // $("#thelist").on('click', '.remove', function () {         //     if ($(this).parents(".item").attr('id') === file.id) {         //         uploader.removeFile(file);         //         $(this).parents(".item").remove();         //     }         // })     });      // 文件上传过程中创建进度条实时显示。     uploader.on('uploadProgress', function (file, percentage) {         var $li = $('#' + file.id);         var $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');             $percent = $('<div class="progress progress-striped active">' +                 '<div class="progress-bar" role="progressbar" style="width: 0%">' +                 '</div>' +                 '</div>').appendTo($li).find('.progress');         }         $li.find('p.state').text('上传中');         $percent.css({'width': percentage * 100 + '%', 'height': 10, 'background-color': 'blue'});     });       uploader.on('uploadSuccess', function (file) {         $('#' + 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();     });  </script> </html>

前端download.html

<!DOCTYPE html> <html lang="en"> <head>     <meta charset="UTF-8">     <title>download</title>     <script type="text/javascript" src="/js/admin.js"></script>     <script type="text/javascript" src="/js/jquery-3.3.1.min.js"></script> </head> <body> <div id="fileList">  </div> </body> <script type="text/javascript">     function download(id) {         window.location.href = BASE_PATH + "/download?md5=" + id;     }      $.ajax({         url: BASE_PATH + "/fileList",         type: "get",         success: function (data) {             $.each(data, function (index, val) {                 var html = "<p>" + val.name + "</p><button onclick=\"download('" + val.id + "')\">下载</button>";                 $("#fileList").append(html);             })         }     })  </script> </html>

存BASE_PATH的admin.js:

var obj = window.document.location; var BASE_PATH = obj.href.substring(0, obj.href.indexOf(obj.pathname));

最重要的后端FileController:

文件上传至gridfs,直接存的块

package com.example.demo.controller;  import com.example.demo.entity.FileEntity; import com.example.demo.jpa.FileJpa; import com.mongodb.BasicDBObject; import com.mongodb.gridfs.GridFS; import com.mongodb.gridfs.GridFSDBFile; import com.mongodb.gridfs.GridFSInputFile; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Example; import org.springframework.data.mongodb.MongoDbFactory; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile;  import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.InputStream; import java.net.URLEncoder; import java.util.List;  /**  * @author FastKing  * @version 1.0  * @date 2018/10/26 9:56  **/ @RestController public class FileController {  	@Autowired 	private MongoDbFactory mongoDbFactory;  	@Autowired 	private FileJpa fileJpa;  	@GetMapping("/fileList") 	public Object list() { 		return fileJpa.findAll(); 	}  	@PostMapping("/upload") 	public void upload(@RequestParam(value = "file") MultipartFile file, 					   @RequestParam(value = "md5") String md5, 					   @RequestParam(value = "chunk", defaultValue = "0", required = false) String chunk, 					   @RequestParam(value = "name") String name) { 		FileEntity fileEntity = new FileEntity(); 		fileEntity.setId(md5); 		fileEntity.setName(name); 		if (!fileJpa.findOne(Example.of(fileEntity)).isPresent()) { 			fileJpa.save(fileEntity); 		} 		BasicDBObject basicDBObject = new BasicDBObject(); 		//块下标,下载文件时需要合并块,用这个参数排序 		basicDBObject.append("chunk", Short.valueOf(chunk)); 		//块大小,断点续传时,用这个参数判断该块是否完整上传 		basicDBObject.append("chunkSize", file.getSize()); 		//文件唯一标识,用来筛选对应文件的所有块 		basicDBObject.append("md5", md5); 		GridFS gridFS = new GridFS(mongoDbFactory.getLegacyDb()); 		try { 			GridFSInputFile inputFile = gridFS.createFile(file.getInputStream(), name.substring(0, name.lastIndexOf("."))); 			//设置文件类型 			inputFile.setContentType(name.substring(name.lastIndexOf(".") + 1)); 			//存入元数据 			inputFile.setMetaData(basicDBObject); 			inputFile.save(); 		} catch (IOException e) { 			e.printStackTrace(); 		} 	}  	@GetMapping("/download") 	public void download(@RequestParam(value = "md5") String md5, HttpServletResponse response) { 		//1.设置文件ContentType类型,这样设置,会自动判断下载文件类型 		response.setContentType("text/html;charset=utf-8"); 		response.setContentType("application/x-download"); 		response.addHeader("Cache-Control", "no-cache"); 		GridFS gridFS = new GridFS(mongoDbFactory.getLegacyDb()); 		//两个BasicDBObject,第一个为筛选条件,第二个为排序(-1为DESC倒序,1为ASC正序) 		List<GridFSDBFile> gridFSDBFiles = gridFS.find(new BasicDBObject("metadata.md5", md5), new BasicDBObject("metadata.chunk", 1)); 		byte[] buffer = new byte[1024]; 		try { 			ServletOutputStream outputStream = response.getOutputStream(); 			Long lenth = 0L; 			for (GridFSDBFile gridFSDBFile : gridFSDBFiles) { 				//2.设置文件头: 				response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(gridFSDBFile.getFilename() + "." + gridFSDBFile.getContentType(), "utf-8")); 				lenth += gridFSDBFile.getLength(); 				int count; 				InputStream inputStream = gridFSDBFile.getInputStream(); 				while ((count = inputStream.read(buffer)) > 0) { 					outputStream.write(buffer, 0, count); 				} 				inputStream.close(); 			} 			response.setHeader("Content-Length", "" + lenth); 		} catch (IOException e) { 			e.printStackTrace(); 		} 	}  	@GetMapping("/check") 	public Object check(@RequestParam(value = "md5") String md5, 						@RequestParam(value = "chunk", required = false) String chunk, 						@RequestParam(value = "chunkSize", required = false) String chunkSize) { 		GridFS gridFS = new GridFS(mongoDbFactory.getLegacyDb()); 		BasicDBObject basicDBObject = new BasicDBObject(); 		//根据文件唯一标识和块下标确定唯一一个块 		basicDBObject.append("metadata.md5", md5); 		basicDBObject.append("metadata.chunk", Short.valueOf(chunk)); 		GridFSDBFile file = gridFS.findOne(basicDBObject); 		//判断该块是否上传过,或是否上传完整 		if (null != file && Short.valueOf(file.getMetaData().get("chunkSize").toString()).equals(Short.valueOf(chunkSize))) { 			return true; 		} else if (null != file) { 			//如果上传不完整则重新上传 			gridFS.remove(basicDBObject); 		} 		return false; 	}  }