1. 程式人生 > 程式設計 >spring boot如何實現切割分片上傳

spring boot如何實現切割分片上傳

這篇文章主要介紹了spring boot如何實現切割分片上傳,文中通過示例程式碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下

檔案上傳是web開發中經常會遇到的

springboot的預設配置為10MB,大於10M的是傳不上伺服器的,需要修改預設配置

但是如果修改支援大檔案又會增加伺服器的負擔。

當檔案大於一定程度時,不僅伺服器會佔用大量記憶體,而且http傳輸極可能會中斷。

可以採用切割分片上傳

html5提供的檔案API中可以輕鬆的對檔案進行分割切片,然後通過ajax非同步處理向伺服器傳輸資料,突破對大檔案上傳的限制,

同時非同步處理在一定程度上也提高了檔案上傳的效率。

過程描述:

  • 將檔案分割成N片
  • 處理分片,前臺會多次呼叫上傳介面,每次都會上傳檔案的一部分到服務端
  • N個分片都上傳完成後,將N個檔案合併為一個檔案,並將N個分片檔案刪除

1.服務端

(1)新增依賴

<dependency>
   <groupId>commons-fileupload</groupId>
   <artifactId>commons-fileupload</artifactId>
   <version>1.3.3</version>
</dependency>

(2)UploadController

package com.example.demo.controller;

import com.example.demo.core.Result;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FileUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
@CrossOrigin
@Controller
@RequestMapping("/api/upload")
public class UploadController {
  @PostMapping("/part")
  @ResponseBody
  public Result bigFile(HttpServletRequest request,HttpServletResponse response,String guid,Integer chunk,MultipartFile file,Integer chunks) {
    try {
      String projectUrl = System.getProperty("user.dir").replaceAll("\\\\","/");
      ;
      boolean isMultipart = ServletFileUpload.isMultipartContent(request);
      if (isMultipart) {
        if (chunk == null) chunk = 0;
        // 臨時目錄用來存放所有分片檔案
        String tempFileDir = projectUrl + "/upload/" + 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) {
      return Result.failMessage(400,e.getMessage());
    }
    return Result.successMessage(200,"上次成功");
  }

  @RequestMapping("merge")
  @ResponseBody
  public Result mergeFile(String guid,String fileName) {
    // 得到 destTempFile 就是最終的檔案
    String projectUrl = System.getProperty("user.dir").replaceAll("\\\\","/");
    try {
      String sname = fileName.substring(fileName.lastIndexOf("."));
      //時間格式化格式
      Date currentTime = new Date();
      SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMddHHmmssSSS");
      //獲取當前時間並作為時間戳
      String timeStamp = simpleDateFormat.format(currentTime);
      //拼接新的檔名
      String newName = timeStamp + sname;
      simpleDateFormat = new SimpleDateFormat("yyyyMM");
      String path = projectUrl + "/upload/";
      String tmp = simpleDateFormat.format(currentTime);
      File parentFileDir = new File(path + guid);
      if (parentFileDir.isDirectory()) {
        File destTempFile = new File(path + tmp,newName);
        if (!destTempFile.exists()) {
          //先得到檔案的上級目錄,並建立上級目錄,在建立檔案
          destTempFile.getParentFile().mkdir();
          try {
            destTempFile.createNewFile();
          } catch (IOException e) {
            e.printStackTrace();
          }
        }
        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 Result.successMessage(200,"合併成功");
      }else{
        return Result.failMessage(400,"沒找到目錄");
      }

    } catch (Exception e) {
      return Result.failMessage(400,e.getMessage());
    }

  }

}

說明:

註解 @CrossOrigin 解決跨域問題

(3)Result

package com.example.demo.core;

import com.alibaba.fastjson.JSON;

/**
 * Created by Beibei on 19/02/22
 * API響應結果
 */
public class Result<T> {
  private int code;
  private String message;
  private T data;

  public Result setCode(Integer code) {
    this.code = code;
    return this;
  }

  public int getCode() {
    return code;
  }

  public String getMessage() {
    return message;
  }

  public Result setMessage(String message) {
    this.message = message;
    return this;
  }

  public T getData() {
    return data;
  }

  public Result setData(T data) {
    this.data = data;
    return this;
  }

  @Override
  public String toString() {
    return JSON.toJSONString(this);
  }

  public static <T> Result<T> fail(Integer code,T data) {
    Result<T> ret = new Result<T>();
    ret.setCode(code);
    ret.setData(data);
    return ret;
  }

  public static <T> Result<T> failMessage(Integer code,String msg) {
    Result<T> ret = new Result<T>();
    ret.setCode(code);
    ret.setMessage(msg);
    return ret;
  }
  public static <T> Result<T> successMessage(Integer code,String msg) {
    Result<T> ret = new Result<T>();
    ret.setCode(code);
    ret.setMessage(msg);
    return ret;
  }

  public static <T> Result<T> success(Integer code,T data) {
    Result<T> ret = new Result<T>();
    ret.setCode(code);
    ret.setData(data);
    return ret;
  }

}

2.前端

(1)使用外掛

webuploader,下載https://github.com/fex-team/webuploader/releases

<!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="css/webuploader.css" rel="external nofollow" rel="stylesheet" type="text/css" />
  <script type="text/javascript" src="jquery-1.10.1.min.js"></script>
  <script type="text/javascript" src="dist/webuploader.min.js"></script>
</head>
<body>
  <div id="uploader">
   <div class="btns">
     <div id="picker">選擇檔案</div>
     <button id="startBtn" class="btn btn-default">開始上傳</button>
   </div>
  </div>
</body>
<script type="text/javascript">
var GUID = WebUploader.Base.guid();//一個GUID
var uploader = WebUploader.create({
  // swf檔案路徑
  swf: 'dist/Uploader.swf',// 檔案接收服務端。
  server: 'http://localhost:8080/api/upload/part',formData:{
    guid : GUID
  },pick: '#picker',chunked : true,// 分片處理
  chunkSize : 1 * 1024 * 1024,// 每片1M,chunkRetry : false,// 如果失敗,則不重試
  threads : 1,// 上傳併發數。允許同時最大上傳程序數。
  resize: false
});
$("#startBtn").click(function () {
  uploader.upload();
});
//當檔案上傳成功時觸發。
uploader.on( "uploadSuccess",function( file ) {
  $.post('http://localhost:8080/api/upload/merge',{ guid: GUID,fileName: file.name},function (data) {
    if(data.code == 200){
     alert('上傳成功!');
    }
   });
});
</script>
</html>

(2)不使用外掛

直接用HTML5的File API

<!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>
    <script src="jquery-1.10.1.min.js" type="text/javascript">
    </script>
    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
  </head>
  <body>
    <div id="uploader">
      <div class="btns">
        <input id="file" name="file" type="file"/>
        <br>
          <br>
            <button id="startBtn">
              開始上傳
            </button>
          </br>
        </br>
      </div>
      <div id="output">
      </div>
    </div>
  </body>
  <script type="text/javascript">
    var status = 0;
    var page = {
    init: function(){
      $("#startBtn").click($.proxy(this.upload,this));
    },upload: function(){
      status = 0;
      var GUID = this.guid();
      var file = $("#file")[0].files[0],//檔案物件
        name = file.name,//檔名
        size = file.size;    //總大小
      var shardSize = 20 * 1024 * 1024,//以1MB為一個分片
        shardCount = Math.ceil(size / shardSize); //總片數
      for(var i = 0;i < shardCount;++i){
        //計算每一片的起始與結束位置
        var start = i * shardSize,end = Math.min(size,start + shardSize);
        var partFile = file.slice(start,end);
        this.partUpload(GUID,partFile,name,shardCount,i);
      }
    },partUpload:function(GUID,chunks,chunk){
      //構造一個表單,FormData是HTML5新增的
      var now = this;
      var form = new FormData();
      form.append("guid",GUID);
      form.append("file",partFile); //slice方法用於切出檔案的一部分
      form.append("fileName",name);
      form.append("chunks",chunks); //總片數
      form.append("chunk",chunk);    //當前是第幾片
        //Ajax提交
        $.ajax({
          url: "http://localhost:8080/api/upload/part",type: "POST",data: form,async: true,//非同步
          processData: false,//很重要,告訴jquery不要對form進行處理
          contentType: false,//很重要,指定為false才能形成正確的Content-Type
          success: function(data){
            status++;
            if(data.code == 200){
              $("#output").html(status+ " / " + chunks);
            }
            if(status==chunks){
              now.mergeFile(GUID,name);
            }
          }
        });
    },mergeFile:function(GUID,name){
      var formMerge = new FormData();
      formMerge.append("guid",GUID);
      formMerge.append("fileName",name);
      $.ajax({
        url: "http://localhost:8080/api/upload/merge",data: formMerge,processData: false,//很重要,告訴jquery不要對form進行處理
        contentType: false,//很重要,指定為false才能形成正確的Content-Type
        success: function(data){
          if(data.code == 200){
            alert('上傳成功!');
          }
        }
      });
    },guid:function(prefix){
        var counter = 0;
        var guid = (+new Date()).toString( 32 ),i = 0;
        for ( ; i < 5; i++ ) {
          guid += Math.floor( Math.random() * 65535 ).toString( 32 );
        }
        return (prefix || 'wu_') + guid + (counter++).toString( 32 );
    }
  };

  $(function(){
    page.init();
  });
  </script>
</html>

3.優化 

springboot的預設配置為10MB,前端分片改為20M時,就會報錯

org.apache.tomcat.util.http.fileupload.FileUploadBase$SizeLimitExceededException: the request was rejected because its size (10486839) exceeds the configured maximum (10485760)

解決方法:

在 src/main/resources 下的 application.properties裡新增

spring.servlet.multipart.max-file-size=30MB
spring.servlet.multipart.max-request-size=35MB

說明:

設定的數值雖好比前端傳過來的大,要不容易報錯

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。