1. 程式人生 > >springboot 整合 gridfs 、webUploader實現大檔案分塊上傳、斷點續傳、秒傳

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

}