天天看點

springboot:實作檔案上傳下載下傳實時進度條功能【附帶源碼】

作者:小小怪下士的架構攻略
springboot:實作檔案上傳下載下傳實時進度條功能【附帶源碼】

0. 引言

記得剛入行的時候,做了一個檔案上傳的功能,因為上傳時間較久,為了使用者友好性,想要添加一個實時進度條,顯示進度。奈何當時技術有限,查了許久也沒用找到解決方案,最後不了了之。

近來偶然想到這個問題,于是決定整理一下實作方式,也為和我曾經一樣碰壁的同學,提供一些思路。

1. 思路

1、首先我們這裡實作的是一個實時的進度條,并不是一個純前端的進度條,它需要根據後端的處理進度來實時回報進度條長度,那麼必然要與後端互動。

當然這裡容易陷入一個誤區,覺得與後端互動的,那麼這個功能的重點一定在後端,但實際上這個功能的重點在前端。

不難想到,我們要知道實時進度,那麼一定需要不斷的請求後端,得到響應回報,前後端請求比較常用的是ajax,但除它之外,我們還有更基礎的xhr(XMLHttpRequest)。作為後端同學可能對xhr有些陌生,實際上ajax就是基于xhr實作的。

2、xhr可以讓我們在不重新加載頁面的情況下更新網頁,在頁面已經加載後從後端請求并接受資料,這樣就可以無感的讓我們後端檔案的上傳進度了。

3、為了監聽檔案上傳下載下傳進度,我們主要使用到xhr的三個進度事件:

  • progress: 在接收響應期間持續不斷地觸發
  • load: 在接收到完整的響應資料時觸發
  • error: 在請求發生錯誤時觸發

當然除上述三個事件之外,還有其他的進度事件,這不是本文的重點,大家可自行拓展學習XHR對象的進度事件

XMLHttpRequest簡介

4、基于上述三個進度事件,我們可以通過process事件持續不斷地發送請求擷取檔案上傳下載下傳的進度,load事件用于檔案上傳下載下傳完成後的處理,比如提示成功。error用于請求發送錯誤時的處理。

springboot:實作檔案上傳下載下傳實時進度條功能【附帶源碼】

5、有了上述的思路之後,我們來進行實際示範。

2. 實操

2.1 實作檔案上傳實時進度條功能

1、建立springboot項目,引入spring web、lombok、檔案上傳commons-fileupload依賴

xml複制代碼<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
</dependency>

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

2、建立MultipartResolver的bean,用來将普通的請求封裝成擁有檔案上傳功能的請求

java複制代碼@Component
public class FileUpLoadConfig {

    @Bean(name="multipartResolver")
    public MultipartResolver multipartResolver(){
        return new CommonsMultipartResolver();
    }
}
           

3、建立一個檔案上傳接口:這裡我單純做個示範,就直接在controller層中書寫了,實際生産要将上傳方法提取為工具類,在service中進行具體業務處理。

如下代碼為将檔案上傳後,儲存到資源檔案夾下

java複制代碼@RestController
@RequestMapping("file")
public class FileController {

    private final static Logger log = LoggerFactory.getLogger(FileController.class);

    @PostMapping("/upload")
    @ResponseBody
    public ResponseEntity<String> fileUpload(@RequestParam("file") MultipartFile file) {
        try {
            // 擷取資源檔案存放路徑,用于臨時存放生成的excel檔案
            String path = Objects.requireNonNull(this.getClass().getClassLoader().getResource("")).getPath();
            // 檔案名
            String fileName = path + file.getOriginalFilename();
            // 建立目标檔案
            File dest = new File(fileName);
            // 向指定路徑寫入檔案
            file.transferTo(dest);
            // 傳回檔案通路路徑
            return new ResponseEntity<>(fileName, HttpStatus.OK);
        } catch (Exception e) {
            e.printStackTrace();
            log.info(String.format("檔案上傳失敗,原因:%s",e));
            return new ResponseEntity<>("檔案上傳失敗", HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }
}
           

4、後端接口完成後,進入我們的重點,我們來實作前端進度條

5、首先引入jQuery,我這裡使用了3.6.1版本

springboot:實作檔案上傳下載下傳實時進度條功能【附帶源碼】

6、實作一個上傳的頁面,這裡利用了html5的progress标簽,該标簽用于實作進度條,支援兩個屬性:value和max,分别為目前進度值和最大進度值

html複制代碼<div class="modal-body form ">
    <!-- 檔案上傳   -->
    <form id="dialogForm" class="form-horizontal">
        <div class="form-group">
            <label class="control-label">檔案:</label>
            <div >
                <input type="file" name="file" id="file" onchange="upload()">
            </div>
        </div>
        <div class="form-group">
            <label class="control-label">上傳進度:</label>
            <div >
                <!--進度條-->
                <div id="progress-body">
                    <progress></progress>
                    <div id="progress-bar">0%</div>
                </div>
            </div>
        </div>
    </form>

</div>
           

7、書寫進度監聽方法,即progress方法

js複制代碼        //進度條更新
        function progressHandle(e) {
            $('#progress-body progress').attr({
                value : e.loaded,
                max : e.total
            });
            var percent = (e.loaded / e.total * 100).toFixed(2);
            $('#progress-body #progress-bar').html(percent + "%");
        };
           

8、書寫load,error方法

js複制代碼        //上傳完成處理函數
        function uploadSuccess(e) {
            alert("上傳完成");
        };
        //上傳出錯處理函數
        function uploadFail(e) {
            alert("上傳失敗");
        };
           

9、實作上傳方法upload

js複制代碼        // 檔案上傳
        function upload() {
            var formData = new FormData();
            formData.append("file", $("#file")[0].files[0]);
            $.ajax({
                url : "/file/upload",
                type : "POST",
                data : formData,
                processData : false, // 告訴jQuery不要去處理發送的資料
                contentType : false, // 告訴jQuery不要去設定Content-Type請求頭
                success : function(data) {
                    console.log(data);
                },
                xhr : function() {
                    var xhr = $.ajaxSettings.xhr();
                    // xhr.upload專用于上傳事件監聽
                    if (xhr.upload) {
                        //處理進度條的事件
                        xhr.upload.addEventListener("progress", progressHandle,
                            false);
                        //加載完成的事件
                        xhr.addEventListener("load", uploadSuccess, false);
                        //加載出錯的事件
                        xhr.addEventListener("error", uploadFail, false);
                        return xhr;
                    }
                }
            });
        }
           

10、運作項目,通路上傳頁,我這裡直接書寫在index.html中了

springboot:實作檔案上傳下載下傳實時進度條功能【附帶源碼】

11、測試:如下圖所示,可以正常顯示進度

springboot:實作檔案上傳下載下傳實時進度條功能【附帶源碼】

12、上傳成功後,背景資源檔案夾中也能看到對應的上傳檔案,示範成功!

springboot:實作檔案上傳下載下傳實時進度條功能【附帶源碼】

2.2 實作檔案下載下傳實時進度條功能

上述我們講解了如何實作上傳進度條功能,有了這個思路,我們再實作下載下傳功能:

1、同樣,我們實作一個簡單的進度條頁面

html複制代碼    <!-- 檔案下載下傳   -->
    <form id="dialogForm" class="form-horizontal">
        <div class="form-group">
            <label class="control-label">下載下傳進度:
            </label>
            <div>
                <!--進度條-->
                <div id="progress-body">
                    <progress></progress>
                    <div id="progress-bar">0%</div>
                </div>
            </div>
        </div>
        <button type="button" onclick="download()">下載下傳</button>
    </form>
           

2、實作下載下傳方法

這裡我們不再采用ajax的方法,而是直接通過xhr請求,并且因為要在浏覽器中下載下傳該檔案,是以以window.URL.revokeObjectURL方法來下載下傳并釋放該檔案。

js複制代碼   // 檔案下載下傳
    function download() {
        var xhr = new XMLHttpRequest();
        //處理進度條的事件
        xhr.addEventListener("progress", progressHandle, false);
        //加載出錯的事件
        xhr.addEventListener("error", uploadFail, false);
        xhr.open("POST","/file/download");
        //設定響應類型
        xhr.responseType = 'blob';
        xhr.onload = function (e) {
            if (this.status === 200) {
                // 截取掉'attachment;filename='
                var filename = xhr.getResponseHeader("Content-disposition").slice(20);
                var blob = this.response;
                var a = document.createElement('a');
                var url = URL.createObjectURL(blob);
                a.href = url;
                a.download = filename;
                document.body.appendChild(a);
                a.click();
                window.URL.revokeObjectURL(url);
            }
        }
        xhr.send();
    }

    //進度條更新
    function progressHandle(e) {
        $('#progress-body progress').attr({
            value: e.loaded,
            max: e.total
        });
        var percent = (e.loaded / e.total * 100).toFixed(2);
        $('#progress-body #progress-bar').html(percent + "%");
    };
    
    //上傳出錯處理函數
    function uploadFail(e) {
        alert("下載下傳失敗");
    };
           

3、實作後端下載下傳檔案接口

這裡與上傳檔案不同的是,前端在進行檔案上傳時,是可以擷取到檔案的總大小的,而下載下傳檔案時因為是流式下載下傳,前端是不知道要下載下傳的檔案一共有多少大小的。

是以也就無法估算總體的進度比例。是以我們後端接口中要通過Content-Length響應頭指定檔案的總大小

我這裡為了示範友善,直接下載下傳上述上傳的檔案。實際應用可更改為你自己的檔案下載下傳路徑。

java複制代碼    @PostMapping("/download")
    @ResponseBody
    public ResponseEntity<String> download(HttpServletResponse response) throws IOException {
        // 擷取資源檔案存放路徑,用于臨時存放生成的excel檔案
        String path = Objects.requireNonNull(this.getClass().getClassLoader().getResource("")).getPath();
        File pathFile = new File(path);
        File[] files = pathFile.listFiles();
        if (ObjectUtils.isEmpty(files)) {
            return new ResponseEntity<>("檔案為空,請先上傳檔案", HttpStatus.OK);
        }
        InputStream inputStream = null;
        ServletOutputStream ouputStream = null;
        try {
            for (File file : files) {
                if(file.isDirectory()){
                    continue;
                }
                inputStream = new FileInputStream(file);
                response.setContentType("application/x-msdownload");
                response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(file.getName(), "UTF-8"));
                // 設定一個總長度,否則無法估算進度
                response.setHeader("Content-Length",String.valueOf(file.length()));
                ouputStream = response.getOutputStream();
                byte b[] = new byte[1024];
                int n;
                while ((n = inputStream.read(b)) != -1) {
                    ouputStream.write(b, 0, n);
                }
                ouputStream.flush();
                break;
            }
        } catch (Exception e) {
            e.printStackTrace();

        } finally {
            if(inputStream != null){
                inputStream.close();
            }
            if(ouputStream != null){
                ouputStream.close();
            }
        }
        return new ResponseEntity<>("檔案下載下傳成功", HttpStatus.OK);
    }
           

4、運作項目

springboot:實作檔案上傳下載下傳實時進度條功能【附帶源碼】

5、測試:檔案成功下載下傳,進度也實時顯示

springboot:實作檔案上傳下載下傳實時進度條功能【附帶源碼】

3. 項目源碼

以上示範源碼可在如下位址下載下傳: https://gitee.com/wuhanxue/progress_bar_demo

4. 總結

以上我們就完成了檔案的上傳和下載下傳的實時進度監控,雖然這個功能的重點在前端,但是後端通過這個功能點,也能更好的了解前後端請求的互動。

最後我們抛出一個思考問題:如何實時監控後端自定義功能的執行進度?

作者:wu55555

連結:https://juejin.cn/post/7238886731058642999

繼續閱讀