springboot 整合 gridfs 、webUploader實現大檔案分塊上傳、斷點續傳、秒傳
阿新 • • 發佈:2018-12-03
主要的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 rel="stylesheet" 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;
}
}