主要的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; } }