OSS實現檔案下載進度條顯示

一米陽光zw發表於2020-10-28

前言

  • oss的檔案下載進度條功能官方是提供了各種語言的SDK,但是想要返回資料給前端顯示使用那是不可能的還需要進行修改,樓主專案中後面的模組上傳和下載會用到大檔案上傳下載,所以提前研究一下官方的進度條顯示功能。
  • 官方SDK地址: 阿里雲oss下載進度條



實現思路

具體程式碼實現思路的話樓主程式碼註釋十分詳細,可以耐心檢視,畢竟實現功能都是要時間的

  • 建立類實現ProgressListener介面: 建立GetObjectProgressListener類實現ProgressListener介面,重寫progressChanged(ProgressEvent progressEvent)方法,該方法會重複回撥顯示下載事件的進度(頻率很高),然後通過成員變數bytesRead記錄下載的位元組數,使用percent記錄當前進度,代表百分之percent,taskId用於記錄當前下載任務的唯一標識,在進入到 TRANSFER_STARTED_EVENT 事件之中時賦值,GetObjectProgressListener用於監聽下載任務的進度,物件中主要包含以下成員變數。
    在這裡插入圖片描述
  • 建立AsyncMethod類封裝oss下載的函式,用於非同步呼叫方法: 建立AsyncMethod類封裝oss下載的方法(一定要建立類,不然後面非同步呼叫的時候會失效),方法中主要實現oss的帶有進度條下載,傳入GetObjectProgressListener物件用於記錄下載進度
    在這裡插入圖片描述
  • 建立對應介面用於呼叫oss下載進度條函式: 下載功能都是web請求下載,所以為了測試功能建立對應介面來呼叫測試
    在這裡插入圖片描述
  • 開通springboot的非同步呼叫機制: 由於oss建立下載任務(如下程式碼)時,會進入阻塞狀態,所以我們需要非同步呼叫AsyncMethod中的progressBarDownload方法。而springboot預設未開啟非同步呼叫,所以需要開啟springboot的非同步呼叫機制
ossClient.getObject(
                new GetObjectRequest(bucketName, objectName).
                        withProgressListener(progressListener),
                new File(pathname));
  1. 配置同步引數類AsyncConfig,其中主要是新增執行緒池操作
    在這裡插入圖片描述

  2. 啟動類中新增@EnableAsync註解,代表開啟非同步呼叫
    在這裡插入圖片描述

  3. 需要非同步呼叫的方法上新增@Async註解
    在這裡插入圖片描述

  4. 瀏覽器中訪問介面,建立下載任務並且報儲存下載進度
    在這裡插入圖片描述

  5. 檢視後臺日誌輸出
    在這裡插入圖片描述



相關程式碼實現

  • oss依賴和版本
		<dependency>
            <groupId>com.aliyun.oss</groupId>
            <artifactId>aliyun-sdk-oss</artifactId>
            <version>3.10.2</version>
        </dependency>
  • 建立GetObjectProgressListener,實現ProgressListener.裡面需要配置accessKeyId和accessKeySecret。響應位元組轉換事件中的日誌可以開啟,檢視下載過程
package com.luntek.commons.utils.oss;


import com.aliyun.oss.event.ProgressEvent;
import com.aliyun.oss.event.ProgressEventType;
import com.aliyun.oss.event.ProgressListener;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

import java.util.UUID;

/**
 * OSS下載進度條功能,用於記錄指定下載任務的下載進度
 *
 * @author: Czw
 * @create: 2020-10-26 13:41
 **/
@Slf4j
@Lazy
@Component
@Data
public class GetObjectProgressListener implements ProgressListener {
    //已經存好的資料長度
    private long bytesRead = 0;
    //下載的資料總長度
    private long totalBytes = -1;
    //下載的當前進度,例如50時,代表下載進度為50%
    private int percent = 0;
    //是否下載完成
    private boolean succeed = false;
    //資料在oss中的objectName,也相當於任務ID
    private String taskId = "";
    // Endpoint以杭州為例,其它Region請按實際情況填寫。
    private static final String endpoint = "******";
    // RAM賬號進行API訪問或日常運維,請登入RAM控制檯建立RAM賬號。
    private static final String accessKeyId = "******";
    private static final String accessKeySecret = "******";

    //用來儲存所有下載任務進度
    public static Map<String,GetObjectProgressListener> progressBarMap=new HashMap<>();

    @Override
    public void progressChanged(ProgressEvent progressEvent) {
        long bytes = progressEvent.getBytes();
        ProgressEventType eventType = progressEvent.getEventType();
        switch (eventType) {
            case TRANSFER_STARTED_EVENT:
                //響應開始下載事件
                String taskId = UUID.randomUUID().toString();
                this.taskId = taskId;
                //儲存下載任務
                progressBarMap.put(taskId,this);
                log.info("***開始下載,progressChanged中的taskId={}***", this.taskId);
                break;
            case RESPONSE_CONTENT_LENGTH_EVENT:
                //響應下載的位元組長度事件
                this.totalBytes = bytes;
                log.info("***響應下載的位元組長度事件***");
                log.info("【{}】 bytes in total will be downloaded to a local file", this.totalBytes);
                break;
            case RESPONSE_BYTE_TRANSFER_EVENT:
                //響應位元組轉換事件
                this.bytesRead += bytes;
                if (this.totalBytes != -1) {
//                    log.info("***響應位元組轉換事件***");
                    int percent = (int) (this.bytesRead * 100.0 / this.totalBytes);
                    this.percent = percent;
//                    log.info("***下載進度: {}***", percent + "%(" + this.bytesRead + "/" + this.totalBytes + ")");
                } else {
                    log.info(bytes + " bytes have been read at this time, download ratio: unknown" +
                            "(" + this.bytesRead + "/...)");
                }
                break;
            case TRANSFER_COMPLETED_EVENT:
                //下載完成事件
                this.succeed = true;
                //移除任務
                progressBarMap.remove(this.taskId);
                log.info("下載完成, " + this.bytesRead + " bytes have been transferred in total");
                break;
            case TRANSFER_FAILED_EVENT:
                //下載失敗事件
                log.info("下載失敗, " + this.bytesRead + " bytes have been transferred");
                break;
            default:
                break;
        }
    }

}

  • 執行緒池非同步配置AsyncConfig(已經有的可以跳過)
package com.luntek.commons.async;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * 非同步執行緒池
 *
 * @author: Czw
 * @create: 2020-10-27 18:30
 **/
@Configuration
@EnableAsync
public class AsyncConfig {

    @Bean
    public Executor myAsync() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //獲取電腦核數
        int core = Runtime.getRuntime().availableProcessors();
        executor.setCorePoolSize(core);
        executor.setMaxPoolSize(core * 2 + 1);
        executor.setQueueCapacity(8192);
        //執行緒名稱字首
        String threadNamePrefix = "MyExecutor-";
        executor.setThreadNamePrefix(threadNamePrefix);
        // rejection-policy:當pool已經達到max size的時候,如何處理新任務
        // CALLER_RUNS:不在新執行緒中執行任務,而是有呼叫者所在的執行緒來執行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }

}

  • 封裝非同步方法AsyncMethod(不要懶放在其他類中,一定要加,不聽勸的也可以試試看)
package com.luntek.commons.async;


import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.GetObjectRequest;
import com.luntek.commons.utils.oss.GetObjectProgressListener;
import com.luntek.commons.utils.oss.OSSUtil;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import java.io.File;

/**
 * 非同步方法
 * @author: Czw
 * @create: 2020-10-27 20:12
 **/
@Component
@Lazy
public class AsyncMethod {

    /**
     * oss帶有進度條功能下載
     *
     * @param progressListener 當前下載任務物件
     * @param bucketName       儲存名稱
     * @param objectName       資料夾路徑+檔名稱,例如abc/efg/123.jpg
     * @param pathname         oss下載下來的檔案臨時儲存路徑
     */
    @Async(value = "myAsync")
    public void progressBarDownload(GetObjectProgressListener progressListener,
                                    String bucketName,
                                    String objectName,
                                    String pathname) {
        OSS ossClient = new OSSClientBuilder().build("YouEndpoint","YourAccessKeyId","YourAccessKeySecret");
        // 下載檔案的同時指定了進度條引數。
        ossClient.getObject(
                new GetObjectRequest(bucketName, objectName).
                        withProgressListener(progressListener),
                new File(pathname));
        // 關閉OSSClient。
        ossClient.shutdown();
    }
}

  • 建立呼叫的api介面和service層
    controller
//通過下載任務的taskId獲取下載進度
    @GetMapping("/getDownloadProgress")
    public ResponseResult<?> getDownloadProgress(String taskId) {
        log.info("***getDownloadProgress***");
        //返回的資料為空時,代表下載任務未建立或者已經下載完成
        //若想區分狀態可以在GetObjectProgressListener類中新增status屬性記錄
        return ResponseResult.success(GetObjectProgressListener.progressBarMap.get(taskId));
    }

//非同步呼叫oss下載帶有進度條方法,建立下載任務
@Resource
private TestService testService;
@GetMapping("/testOSSSAsynchronous")
    public ResponseResult<?> testOSSSAsynchronous() {
        log.info("***testOSSSAsynchronous***");
        GetObjectProgressListener progressListener = testService.testOSSAsynchronous();
        try {
            //通過調整休眠時間來檢視下載進度
            //第一次生成下載任務返回撥用時也需要休眠200毫秒左右,確保下載進度進入到TRANSFER_STARTED_EVENT事件生成taskId中
            Thread.sleep(3000);
            log.info("***Thread.sleep***");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return ResponseResult.success(progressListener);
    }

TestService

GetObjectProgressListener testOSSAsynchronous();

TestServiceImpl

@Override
    public GetObjectProgressListener testOSSAsynchronous() {
        GetObjectProgressListener objectListener = new GetObjectProgressListener();
        asyncMethod.progressBarDownload(
                objectListener,
                "luntek-resource-cloud-homework-practice",
                "test/navicat_39234.zip",
                "C:\\Users\\Administrator\\Desktop\\navicat_39234.zip");
        log.info("下載完成返回的GetObjectProgressListener ID=【{}】,percent=【{}】",
                objectListener.getTaskId(), objectListener.getPercent());
        return objectListener;
    }
  • 啟動類新增註解@EnableAsync//掃描類下非同步方法
    在這裡插入圖片描述

  • 專案包結構

在這裡插入圖片描述



測試效果

測試的資料167M左右,下載速度一般在2-4M/S,我們在程式碼中設定非同步呼叫之後的等待時間,然後返回資料

設定下載的檔案
在這裡插入圖片描述

設定下載檔案
在這裡插入圖片描述
oss客戶端上檢視到的檔案資訊
在這裡插入圖片描述

設定等待時間
在這裡插入圖片描述

前端效果

  • 生成taskId
    在這裡插入圖片描述
  • 檢視任務下載進度
    在這裡插入圖片描述

** *基本上下載的程式碼都貢獻出來了,資料整理了4小時,最後希望大家點個贊* **

** *創作不易,轉發註明出處* **


相關問題

  • 日誌列印: 引用的sdk中會使用阿帕奇相關方法,所以需要設定org.apache.http.wire的相關日誌級別,具體可以檢視這篇帖子:配置檔案logback-spring.xml

相關文章