【OSS】SpringBoot搭配线程池整合阿里云OSS实现图片异步上传

前言

在之前写过阿里OSS图片上传的案例和文章【OSS】服务端签名后直传实现阿里云存储上传文件,但是最近又发现了一个新的业务需求场景,简单流程如下:

  1. 前端传递单个或多个图片到后端;
  2. 后端对图片进行处理,并上传至图床;
  3. 上传完毕之后,返回图片链接给前端。

因此在这里写上这一篇文章进行补充,同时阿里云的OSS前置操作如注册和配置操作,和之前的文章:【OSS】服务端签名后直传实现阿里云存储上传文件之中的章节1到3.1相同,因此这里不再过多重复了。

1、配置类编写

1.1、线程池配置

在刚开始思考这个功能的时候一个很容易的方案就浮现到眼前,即将获取到的文件集合按顺序逐一上传到OSS之中,但是当我们的文件过多,或者网络不良时,容易造成上传时间过长,极其不优雅。

因此,这里选用了线程池的方式对文件上传任务进行异步操作,提高处理速度。在编写过程中做过简单的几次比较,使用线程池和未使用之前的响应时间竟相差了五十倍以上,就离谱,果断选择线程池操作。线程池相关的文章可查看前段时间整理的文章【多线程】优雅使用线程池结合CompletableFuture实现异步编排

操作步骤如下:

  • 首先在config包下添加线程池配置类ThreadPoolConfig:
@Configuration
public class ThreadPoolConfig {
    @Bean
    public ThreadPoolExecutor threadPoolExecutor(
            @Value("${thread.pool.coreSize}") Integer coreSize,
            @Value("${thread.pool.maxSize}") Integer maxSize,
            @Value("${thread.pool.keepalive}") Integer keepalive,
            @Value("${thread.pool.blockQueueSize}") Integer blockQueueSize
    ) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                coreSize,
                maxSize,
                keepalive,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(blockQueueSize),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );
        executor.prestartCoreThread();
        return executor;
    }
}
  • 配置文件中配置线程池的参数配置
thread:
  pool:
  	# 核心线程数
    coreSize: 10
    # 最大线程数
    maxSize: 50
    # 非核心线程的存活时间
    keepalive: 30
    # 阻塞队列大小
    blockQueueSize: 200

1.2、OSS配置

这里将阿里云OSS所需要的参数封装到了一个配置类中,并于配置文件所关联,具体步骤如下:

  • 添加配置类AliyunOssConfig
@ConfigurationProperties(prefix = "aliyun.oss")
@Data
@Configuration
public class AliyunOssConfig {
    private String endpoint;

    private String bucket;

    private String accessKey;

    private String secretKey;

    private String marketHost;

    @Bean
    public OSS ossClient() {
        return new OSSClientBuilder().build(endpoint, accessKey, secretKey);
    }
}
  • 配置文件中设置配置参数
aliyun:
  oss:
    access-key: 你的AK
    secret-key: 你的AS
    endpoint: 你的地域节点
    bucket: 你的Bucket名
    market-host: "https://${aliyun.oss.bucket}.${aliyun.oss.endpoint}"

2、工具类编写

因为在这一块图片上传中涉及到的逻辑繁多,因此将其进行拆分,然后统一封装到工具类AliyunOssUtils之中,因此大家伙在开始之前记得在项目中新建一个工具包,将其添加进去。

@Slf4j
@Component
public class AliyunOssUtils {

}

2.1、图片判断

2.1.1、需求分析

因为这里使用的OSS承担的是图床功能,因此在上传到OSS之前,应先对文件进行判断,只有判断为图片之后才能够继续上传,否则返回错误信息。

这里图片判断使用的是ImageI类进行图片判断,这个是Java自带的一个图片处理的类,存在于javax包中。但是其具有局限性,目前发现支持的图片后缀有bmp/gif/jpg/jfif/jpeg/png,虽然主流的图片后缀大部分都包含在内,但是并没有webp格式,这是谷歌后续推出的图片格式,能够无损压缩图片大小至png的2/3

在查询过资料后,发现只需要添加一个对webp格式操作的依赖进行拓展即可,整体步骤如下:

2.1.2、依赖导入

<!--webp格式工具-->
<dependency>
    <groupId>org.sejda.imageio</groupId>
    <artifactId>webp-imageio</artifactId>
    <version>0.1.6</version>
</dependency>

2.1.3、逻辑实现

/**
 * @param imageFile 需要判断的文件
 * @return boolean
 * @description 判断文件是否为图片
 * @method isImage
 * @author xbaozi
 * @date 2022/9/13 13:25
 **/
public boolean isImage(File imageFile) {
    if (!imageFile.exists()) {
        return false;
    }
    Image img = null;
    try {
        img = ImageIO.read(imageFile);
        return img != null && img.getWidth(null) > 0 && img.getHeight(null) > 0;
    } catch (Exception e) {
        return false;
    } finally {
        // 最终重置为空
        img = null;
        imageFile.delete();
    }
}

2.2、MultipartFile类型转换

2.2.1、需求分析

因为在接收前端数据时使用的是Spring家的MultipartFile类型,但是ImageIO使用的是Java自带的File类型,因此需要编写工具方法将两种类型进行转换(强转不了的,我试过了,很安详)

2.2.2、逻辑实现

/**
 * @return java.io.File
 * @description MultipartFile转File
 * @method multipartFileToFile
 * @author xbaozi
 * @date 2022/9/13 13:33
 **/
public File multipartFileToFile(MultipartFile multipartFile) {
    if (multipartFile.isEmpty()) {
        return null;
    }
    InputStream inputStream = null;
    try {
        inputStream = multipartFile.getInputStream();
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
    File file = new File(Objects.requireNonNull(multipartFile.getOriginalFilename()));
    try {
        OutputStream os = Files.newOutputStream(file.toPath());
        int bytesRead;
        byte[] buffer = new byte[8192];
        while ((bytesRead = inputStream.read(buffer, 0, 8192)) != -1) {
            os.write(buffer, 0, bytesRead);
        }
        os.close();
        inputStream.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return file;
}

2.3、图片上传

2.3.1、需求分析

重头戏来了。在线程池配置的时候就提及到,顺序阻塞式上传图片相对于线程池来说犹如龟速,因此这一块将会使用线程池对图片上传任务进行操作。

2.3.2、前置操作

因为在图片上传中需要获取图片的后缀名,因此这里将获取后缀名的逻辑封装成了一个方法进行调用,获取出来的后缀名是待了小点的,如.png

/**
 * @param filename 文件名
 * @description 获取文件拓展名
 * @method getExtensionName
 * @author xbaozi
 * @date 2022/9/21 21:21
 **/
private String getExtensionName(String filename) {
    if ((filename != null) && (filename.length() > 0)) {
        int dot = filename.lastIndexOf('.');
        if ((dot > -1) && (dot < (filename.length() - 1))) {
            return filename.substring(dot);
        }
    }
    return filename;
}

2.3.3、逻辑实现

@Autowired
private OSS ossClient;
@Autowired
private AliyunOssConfig aliyunOssConfig;
@Autowired
private ThreadPoolExecutor executor;

/**
 * @param multipartFile 需要上传的图片,可变参数
 * @description 上传图片
 * @method upload
 * @author xbaozi
 * @date 2022/9/13 13:21
 **/
public List<String> upload(List<MultipartFile> multipartFile) {
    log.info("开始上传照片……");
    // 图片上传至阿里云OSS,设置返回路径集合
    List<String> responseUrls = new ArrayList<>();
    // 用户上传文件时指定的前缀,即存放在以时间命名的文件夹内
    String dir = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
    CompletableFuture<PutObjectResult> future = null;
    for (MultipartFile file : multipartFile) {
        String originalFilename = file.getOriginalFilename();
        // 设置上传到云存储的文件名,规则为"当前时间-UUID.源文件后缀名"
        String cloudFileName = new StringBuilder()
                .append(new SimpleDateFormat("HH:mm:ss").format(new Date()))
                .append("-")
                .append(IdUtil.simpleUUID())
                //.append("-")
                //.append(originalFilename)
                .append(getExtensionName(originalFilename))
                .toString();
        // 设置上传到云存储的路径
        String cloudPath = dir + "/" + cloudFileName;
        // 线程池异步上传图片
        future = CompletableFuture.supplyAsync(() -> {
            try {
                // 图片上传
                return ossClient.putObject(aliyunOssConfig.getBucket(), cloudPath, file.getInputStream());
            } catch (IOException e) {
                throw new FileUploadException(
                        ResponseCodeEnum.FILE_UPLOAD_FAIL.getCode(),
                        ResponseCodeEnum.FILE_UPLOAD_FAIL.getMessage() + ":" + originalFilename);
            }
        }, executor).whenComplete((res, exception) -> {
            // 判断是否出现异常
            if (!ObjectUtils.isEmpty(exception)) {
                throw new FileUploadException(
                        ResponseCodeEnum.NETWORK_FAILURE.getCode(),
                        ResponseCodeEnum.NETWORK_FAILURE.getMessage() + ":" + originalFilename);
            }
            // 判断是否正确返回,将图片链接添加到集合中
            if (!ObjectUtils.isEmpty(res)) {
                log.info("处理照片{}", aliyunOssConfig.getMarketHost() + "/" + cloudPath);
                responseUrls.add(aliyunOssConfig.getMarketHost() + "/" + cloudPath);
            }
        });
    }
    // 等待线程执行完毕
    if (future != null) {
        future.join();
    }
    // 返回上传的图片链接集合
    log.info("返回的路径为{}", responseUrls);
    return responseUrls;
}

3、控制器编写

3.1、需求分析

在控制器中主要是对前端传递过来的文件集合进行处理判断,主要包含以下部分:

  • 判断集合是否为空;
  • 判断集合元素是否为空;
  • 判断集合元素是否为图片;
  • 上传文件过程中是否出现异常。

image-20220921233748188

3.2、逻辑实现

@RestController
@RequestMapping("/api/oss")
@Api("阿里云oss云存储控制器")
@Slf4j
public class OssController {
    @Autowired
    private AliyunOssUtils ossUtils;

    @PostMapping("/upload")
    @ApiOperation("多图片上传")
    public ResponseResult<List<String>> upload(@ApiParam("需要上传的图片") List<MultipartFile> multipartFile) {
        // 判空
        if (ObjectUtils.isEmpty(multipartFile)) {
            throw new FileUploadException(ResponseCodeEnum.FILE_IS_EMPTY.getCode(), ResponseCodeEnum.FILE_IS_EMPTY.getMessage());
        }

        // 图片校验
        for (MultipartFile file : multipartFile) {
            // 图片为空
            if (file.isEmpty()) {
                return new ResponseResult<>(ResponseCodeEnum.FILE_IS_CORRUPTED.getCode(), ResponseCodeEnum.FILE_IS_CORRUPTED.getMessage());
            }
            // 判断是否为图片,防止恶意使用
            File multipartFileToFile = ossUtils.multipartFileToFile(file);
            if (!ossUtils.isImage(multipartFileToFile)) {
                return new ResponseResult<>(ResponseCodeEnum.FORMAT_MISMATCH.getCode(), ResponseCodeEnum.FORMAT_MISMATCH.getMessage() + ",只支持bmp/gif/jpg/jfif/jpeg/png/webp格式");
            }
        }
        // 文件上传,获取上传得到的图片地址返回
        List<String> responseUrls = ossUtils.upload(multipartFile);
        return new ResponseResult<>(ResponseCodeEnum.OK.getCode(), ResponseCodeEnum.OK.getMessage(), responseUrls);
    }
}

4、结果演示

这里主要测试两种图片格式,分别是主流的png和特殊的webp图片后缀文件。

  • png格式

image-20220921234614510

  • webp格式

image-20220921234839975

  • 2
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
SpringBoot可以通过整合阿里云OSS对象存储服务来实现文件上传和管理功能。具体实现可以参考以下步骤: 1. 在service层定义FileService接口,该接口包含上传文件到阿里云OSS的方法。例如,可以使用MultipartFile作为参数,返回上传成功后的文件URL。 2. 在controller层编写FileApiController类,该类使用@RestController注解标识为控制器,并使用@RequestMapping注解指定请求路径。在该类中,通过@Autowired注入FileService,并在文件上传的接口方法中调用FileService的上传文件方法并返回上传成功后的文件URL。 3. 在配置文件中配置阿里云OSS的相关信息,包括accessKey、secretKey、bucketName等。可以使用SpringBoot提供的@ConfigurationProperties注解来读取配置文件中的信息。 4. 在pom.xml文件中添加阿里云OSS SDK的依赖。 5. 编写上传文件的前端界面,可以使用HTML或者前端框架如Vue.js、React等。 通过以上步骤的实现SpringBoot就可以整合阿里云OSS对象存储服务,实现文件上传和管理功能。这样可以将文件存储在阿里云OSS中,提高文件的安全性和可靠性。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [SpringBoot整合阿里云OSS对象存储服务的实现](https://download.csdn.net/download/weixin_38649091/12721580)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [全网最详细SpringBootSpringCloud整合阿里云OSS对象存储服务](https://blog.csdn.net/weixin_55076626/article/details/127924003)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

陈宝子

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

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

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

打赏作者

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

抵扣说明:

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

余额充值