SpringBoot框架中使用云存储,如何优雅地在亚马逊、华为、阿里、腾讯等云中按环境切换?

Hello,各位小伙伴,好久又没有更新文章了,不是我不更新,而是产品经理太懒了。

最近产品经理又与我扛上了,因为系统使用云环境不同,产品经理提出按环境变化的一个需求:

我们系统有些资料,比如文件、图片要使用云存储,测试环境使用的是亚马逊云存储;生产环境要使用华为云存储。所以系统必须要实现这2套存储,而且还要按环境动态切换。如果后面要实用阿里或者腾讯云,都可以动态切换。让系统不做太多的改变。

当时听到这需求,心想不是很简单嘛,只需要下面三步:

1、定义一个接口

2、按不同的云厂商实现即可

3、在使用的时候,根据使用的厂商名字注入 Service 即可

可后来仔细一想,这样虽然能完成产品经理提出的功能需求,可每次都要修改注入的 Service 代码,还需要重新打包,实在很麻烦。作为一名 JAVA 懒汉编程人员,这肯定不是我想要的。

有没有更好的方式呢?

有!

作为开发人员一定要干翻产品经理,不能丢了开发人员的颜面,只要你提的出来,我就有对策。所以在产品经理提出需要的时候,我想也没想就回答有。

回答虽然很爽快,但要怎么去实现呢?

整理下面的过程:

1、定义一个接口

2、按厂商实现接口

3、根据厂商注入接口实现

So Easy!  简单三步完成,下面一起来看代码实现:

定义接口:

package com.hx.module.system.v2.service;

import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.net.URISyntaxException;


public interface AmazonS3OperationService {

    /**
     * 上传文件到S3服务器
     * <p>
     * key唯一,否则会变成覆盖相同key的内容
     * </p>
     *
     * @param file /
     * @param key  /
     * @throws IOException /
     */
    void uploadFile(MultipartFile file, String key) throws IOException;

    /**
     * 下载文件
     *
     * @param key /
     */
    byte[] downloadFile(String key) throws IOException;

    /**
     * 删除文件
     *
     * @param key /
     */
    void deleteFile(String key);

    /**
     * 获取文件匿名访问URL
     *
     * @param key /
     * @return /
     * @throws URISyntaxException /
     */
    String getFileUrl(String key) throws URISyntaxException;
}

我们现在只有亚马逊、华为云,其实现如下:

亚马逊云实现:

package com.hx.module.system.v2.service.impl;

import com.hx.module.system.v2.service.AmazonS3OperationService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.multipart.MultipartFile;
import software.amazon.awssdk.core.ResponseInputStream;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest;
import software.amazon.awssdk.services.s3.presigner.model.PresignedGetObjectRequest;
import javax.validation.constraints.NotBlank;
import java.io.IOException;
import java.net.URISyntaxException;
import java.time.Duration;
import java.util.Objects;


@Service
public class AmazonS3OperationServiceImpl implements AmazonS3OperationService {

    @Value("${amazon-server.bucket}")
    private String TEST_BUCKET = "hx-aqgl-test";

    @Value("${amazon-server.url-out-time}")
    private Integer URL_OUT_TIME = 10;

    private final S3Client client;

    private final S3Presigner presigner;

    public AmazonS3OperationServiceImpl(S3Client client, S3Presigner presigner) {
        this.client = client;
        this.presigner = presigner;
    }

    @Override
    public void uploadFile(MultipartFile file, @NotBlank String key) throws IOException {
        if (Objects.isNull(file)) {
            return;
        }
        String originalFilename = file.getOriginalFilename();
        if (Objects.isNull(originalFilename)) {
            return;
        }
//        int indexOf = originalFilename.lastIndexOf(".");
//        String suffix = originalFilename.substring(indexOf);
//        key = key + suffix;
        PutObjectRequest objectRequest = PutObjectRequest.builder()
                .bucket(TEST_BUCKET)
                .key(key)
                .build();
        client.putObject(objectRequest, RequestBody.fromBytes(file.getBytes()));
    }

    @Override
    public byte[] downloadFile(@NotBlank String key) throws IOException {
        GetObjectRequest getObjectRequest = GetObjectRequest.builder()
                .bucket(TEST_BUCKET)
                .key(key)
                .build();
        ResponseInputStream<GetObjectResponse> object = client.getObject(getObjectRequest);
        return object.readAllBytes();

    }

    @Override
    public void deleteFile(@NotBlank String key) {
        DeleteObjectRequest deleteObjectRequest = DeleteObjectRequest.builder()
                .bucket(TEST_BUCKET)
                .key(key)
                .build();
        client.deleteObject(deleteObjectRequest);
    }

    @Override
    public String getFileUrl(@NotBlank String key) throws URISyntaxException {
        GetObjectRequest getObjectRequest = GetObjectRequest.builder()
                .bucket(TEST_BUCKET)
                .key(key)
                .build();

        GetObjectPresignRequest getObjectPresignRequest = GetObjectPresignRequest.builder()
                .signatureDuration(Duration.ofMinutes(URL_OUT_TIME))
                .getObjectRequest(getObjectRequest)
                .build();

        PresignedGetObjectRequest presignedGetObjectRequest =
                presigner.presignGetObject(getObjectPresignRequest);
        return presignedGetObjectRequest.url().toURI().toString();
    }
}

华为云实现:

package com.hx.module.system.v2.service.impl;

import com.hx.config.ObsConfig;
import com.hx.module.system.utils.UploadFileUtil;
import com.hx.module.system.v2.service.AmazonS3OperationService;
import com.obs.services.ObsClient;
import com.obs.services.model.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.IOException;
import java.io.InputStream;

@Service
public class HuaWeiServerImpl implements AmazonS3OperationService {

    @Resource
    private ObsConfig obsConfig;

    @Override
    public void uploadFile(MultipartFile file, String key) throws IOException {
        PutObjectResult putObjectResult = UploadFileUtil.uploadNetworkStream(obsConfig.createObsClient(), obsConfig.getBucketName(), file.getInputStream(), key);
        log.info("上传文件:{}", putObjectResult);
    }

    @Override
    public byte[] downloadFile(String key) throws IOException {
        ObsObject obsObject = obsConfig.createObsClient().getObject(obsConfig.getBucketName(), key);
        InputStream input = obsObject.getObjectContent();
        log.info("下载文件:{}", input.toString());
        return input.readAllBytes();
    }

    @Override
    public void deleteFile(String key) {
        DeleteObjectResult deleteObjectResult = obsConfig.createObsClient().deleteObject( obsConfig.getBucketName(), key);
        log.info("删除文件:{}", deleteObjectResult);
    }

    @Override
    public String getFileUrl(String key) {
        TemporarySignatureRequest request = new TemporarySignatureRequest(HttpMethodEnum.PUT, obsConfig.getExpireSeconds() > 60 ? obsConfig.getExpireSeconds() : 300L);
        request.setBucketName(obsConfig.getBucketName());
        request.setObjectKey(key);

        // 创建ObsClient实例
        ObsClient obsClient = obsConfig.createObsClient();
        TemporarySignatureResponse response = obsClient.createTemporarySignature(request);

        return response.getSignedUrl();
    }
}

不同厂商的云实现已经有了,现在需要在配制文件里添加配制参数:

## 配制亚马逊存储或者华为云存储
service:
  # Amazon 亚马逊存储
  # huaWei 华为云存储
  type: Amazon

最后根据参数修改亚马逊云、华为云实现的注解:

亚马逊云实现: 

// 将 @Service 替换为
@Configuration
​​​​​​​@ConditionalOnProperty(name = "service.type", havingValue="Amazon”)

​​​​​​​华为云实现: ​​​​​​​

// 将 @Service 替换为
@Configuration
@ConditionalOnProperty(name = "service.type", havingValue="HuaWei”)

现在使用的 亚马逊云 启动系统:

OK,项目启动成功!!

后面即使使用阿里去,腾讯云或者其它,我只需要pugm实现一个接口,在修改配制文件即可启动对应的云Servie

总结:

动态切换主要用到了2个注解:

1、@Configuration   是 Spring-conten 中的一个注解,将该类声明为一个配置类

2、@ConditionalOnProperty  是 spring-boot-autoconfigure 中的一个注解,根据注解从配制文件中读取到的值,决定是否把当前类注入到 Spring 中

扩展, 以 Conditional 开头的还有以下注解:

@ConditionalOnBean:仅在当前上下文中存在某个对象时,才会实例化一个Bean。

@ConditionalOnClass:某个class位于类路径上,才会实例化一个Bean。

@ConditionalOnExpression:当表达式值为true的时候,才会实例化一个Bean。

@ConditionalOnMissingBean:仅仅在当前上下文中不存在某个对象时,才会实例化一个Bean。

@ConditionalOnMissingClass:某个class类路径上不存在的时候,才会实例化一个Bean。

@ConditionalOnNotWebApplication:非web应用,才会实例化一个Bean。

@ConditionalOnBean:当容器中有指定Bean的条件下进行实例化。

@ConditionalOnMissingBean:当容器里没有指定Bean的条件下进行实例化。

@ConditionalOnClass:当classpath类路径下有指定类的条件下进行实例化。

@ConditionalOnMissingClass:当类路径下没有指定类的条件下进行实例化。

@ConditionalOnWebApplication:当项目是一个Web项目时进行实例化。

@ConditionalOnNotWebApplication:当项目不是一个Web项目时进行实例化。

@ConditionalOnProperty:当指定的属性有指定的值时进行实例化。

@ConditionalOnExpression:基于SpEL表达式的条件判断。

@ConditionalOnJava:当JVM版本为指定的版本范围时触发实例化。

@ConditionalOnResource:当类路径下有指定的资源时触发实例化。

@ConditionalOnJndi:在JNDI存在的条件下触发实例化。

@ConditionalOnSingleCandidate:当指定的Bean在容器中只有一个,或者有多个但是指定了首选的Bean时,才会触发实例化。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

周周的JAVA技术栈

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

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

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

打赏作者

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

抵扣说明:

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

余额充值