OSS实现文件下载进度条显示

前言

  • oss的文件下载进度条功能官方是提供了各种语言的SDK,但是想要返回数据给前端显示使用那是不可能的还需要进行修改,楼主项目中后面的模块上传和下载会用到大文件上传下载,所以提前研究一下官方的进度条显示功能。
  • 官方SDK地址: 阿里云oss下载进度条



实现思路

OSS中每一次上传都会生成一个taskId相当于上传任务的id,在创建oss客户端时可以将自己实现的ProgressListener添加到客户端中,它可以实时的去显示你传入的任务id上传进度(想要在代码中查看进度的可以打开我分享的代码中log.info("***下载进度: {}***", percent + "%(" + this.bytesRead + "/" + this.totalBytes + ")");这个代码来查看后台上传过程中的日志打印,lz自己做的时候关闭了这些日志。),但是由于我们访问上传请求时,oss的上传接口它会处于等待文件上传的状态,会阻塞住导致我们无法去立即响应当前http请求的结果,所以我们需要将oss上传任务放入到线程池中,做一个异步操作,然后想办法在这个生成taskId时就传出去并响应给前端,然后前端拿到这个taskId后每隔20ms请求另外一个接口,这个接口专门负责查询taskId的下载进度,这样的话就实现了进度条的功能。具体代码实现思路的话楼主代码注释十分详细,可以耐心查看,毕竟实现功能都是要时间的

  • 创建类实现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. 浏览器中访问接口,创建下载任务并且报保存下载进度,percent代表下载进度55%,一直刷新的话就会发现percent一直增大,等到100%的时候代表下载完成,不过到了100%时taskId会销毁需要另外处理一下
    在这里插入图片描述

  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
  • 15
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一米阳光zw

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值