定制软件通过S3协议实现通用的文件存储服务中间件

通过S3定制软件协议实现通用的文件存定制软件储服务中间件


引言

定制软件在日常开发文件上传相关服务时,定制软件通常都会选择腾讯云,阿里云,定制软件七牛云等提供的oss定制软件服务作为文件存储系统,定制软件如果需要自行搭建文件存储系统,定制软件通常则会采用等开源项目。

定制软件但是大家有没有考虑过,定制软件不同的厂商或者提供的客户端sdk定制软件都是不同的,如果项目开发过程中,需要切换底层文件系统,那么通常情况下意味着,我们需要完全替换掉相关文件上传代码,如果微服务项目,则需要替换掉所有使用到文件上传sdk微服务的代码,这显然会带来巨大的工作量。

为了解决上面这个问题,我们有如下两个思路:

  • 项目中针对文件上传写出一个单独的抽象层接口,底层不同文件存储系统,提供对应的实现即可:


这个思路很容易想到,利用门面模型向调用方屏蔽底层实现,但是其实这里还有更加简洁的实现方式。

  • 基本所有云服务厂商提供的oss服务和开源的oss项目都遵循了S3协议,是Simple Storage Service的缩写,即简单存储服务,因此其实我们这里利用这一点,写出一个通用的文件中间件,利用该中间件后,我们写的客户端api就对任何实现了S3协议的oss服务进行访问。


使用演示

这里我们以Minio作为演示案例,不清楚minio的可以查看minio官方文档学习一下,下面我们先用docker方式安装一下minio:

安装minio

docker pull minio/miniodocker run --name minio \-p 9000:9000 \-p 9090:9090 \-d --restart=always \-e "MINIO_ROOT_USER=admin" \-e "MINIO_ROOT_PASSWORD=admin123" \-v /usr/local/minio/data:/data \-v /usr/local/minio/config:/root/.minio \minio/minio server /data \--console-address '0.0.0.0:9090'
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

注意,这里要单独设置console的端口,不然会报错,且无法访问

这种安装方式 MinIO 自定义 Access 和 Secret 密钥要覆盖 MinIO 的自动生成的密钥

登录客户端(浏览器):注意—>此处的端口,是你设置的console的端口:9090

此处的用户名密码为启动服务时,设置的用户名密码:admin admin123。

minio基本bucket操作不再详述,和普通的oss服务一样。


构建Starter

  • gitee仓库地址

  • 模块结构
  • pom配置
<dependencies>    <dependency>        <groupId>com.amazonaws</groupId>        <artifactId>aws-java-sdk-s3</artifactId>        <version>1.12.267</version>    </dependency>    <dependency>        <groupId>org.projectlombok</groupId>        <artifactId>lombok</artifactId>        <version>1.18.24</version>    </dependency>    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-autoconfigure</artifactId>        <version>2.3.12.RELEASE</version>    </dependency>    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-configuration-processor</artifactId>        <version>2.7.3</version>        <optional>true</optional>    </dependency></dependencies>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 构建通用的遵循S3协议的oss服务接口
package com.oss.client;import com.amazonaws.services.s3.AmazonS3;import com.amazonaws.services.s3.model.PutObjectResult;import com.amazonaws.services.s3.model.S3Object;import java.io.IOException;import java.io.InputStream;/** * Oss 基础操作 * 想要更复杂操作可以直接获取AmazonS3,通过AmazonS3 来进行复杂的操作 * https://docs.aws.amazon.com/zh_cn/sdk-for-java/v1/developer-guide/examples-s3-buckets.html */public interface OssClient{    /**     * 创建bucket     * @param bucketName     */    void createBucket(String bucketName);    /**     * 获取url     * @param bucketName     * @param objectName     * @return     */    String getObjectURL(String bucketName, String objectName);    /**     * 获取存储对象信息     * @param bucketName     * @param objectName     * @return     */    S3Object getObjectInfo(String bucketName, String objectName);    /**     * 上传文件     * @param bucketName     * @param objectName     * @param stream     * @param size     * @param contextType     * @return     * @throws IOException     */    PutObjectResult putObject(String bucketName, String objectName, InputStream stream, long size, String contextType) throws IOException;    default PutObjectResult putObject(String bucketName, String objectName, InputStream stream) throws IOException{        return putObject(bucketName,objectName,stream, stream.available(), "application/octet-stream");    }    AmazonS3 getS3Client();}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 实现类
package com.oss.client;import com.amazonaws.services.s3.AmazonS3;import com.amazonaws.services.s3.model.ObjectMetadata;import com.amazonaws.services.s3.model.PutObjectRequest;import com.amazonaws.services.s3.model.PutObjectResult;import com.amazonaws.services.s3.model.S3Object;import lombok.RequiredArgsConstructor;import java.io.IOException;import java.io.InputStream;import java.net.URL;/** * s3 是一个协议 * S3是Simple Storage Service的缩写,即简单存储服务 * @author zdh */@RequiredArgsConstructorpublic class S3OssClient implements OssClient {    private final AmazonS3 amazonS3;    @Override    public void createBucket(String bucketName) {        if (!amazonS3.doesBucketExistV2(bucketName)) {            amazonS3.createBucket((bucketName));        }    }    @Override    public String getObjectURL(String bucketName, String objectName) {        URL url = amazonS3.getUrl(bucketName, objectName);        return url.toString();    }    @Override    public S3Object getObjectInfo(String bucketName, String objectName) {        return amazonS3.getObject(bucketName, objectName);    }    @Override    public PutObjectResult putObject(String bucketName, String objectName, InputStream stream, long size, String contextType) throws IOException {        ObjectMetadata objectMetadata = new ObjectMetadata();        objectMetadata.setContentLength(size);        objectMetadata.setContentType(contextType);        PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, stream, objectMetadata);        putObjectRequest.getRequestClientOptions().setReadLimit(Long.valueOf(size).intValue() + 1);        return amazonS3.putObject(putObjectRequest);    }    @Override    public AmazonS3 getS3Client() {        return amazonS3;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 对应的配置类
package com.oss.config;import lombok.Data;import org.springframework.boot.context.properties.ConfigurationProperties;@ConfigurationProperties(prefix = "oss")@Datapublic class OssProperties {    private boolean enable = true;    private String accessKey;    private String accessSecret;    /**     * endpoint 配置格式为     * 通过外网访问OSS服务时,以URL的形式表示访问的OSS资源,详情请参见OSS访问域名使用规则。OSS的URL结构为[$Schema]://[$Bucket].[$Endpoint]/[$Object]     * 。例如,您的Region为华东1(杭州),Bucket名称为examplebucket,Object访问路径为destfolder/example.txt,     * 则外网访问地址为https://examplebucket.oss-cn-hangzhou.aliyuncs.com/destfolder/example.txt     * https://help.aliyun.com/document_detail/375241.html     */    private String endpoint;    /**     * refer com.amazonaws.regions.Regions;     * 阿里云region 对应表     * https://help.aliyun.com/document_detail/31837.htm?spm=a2c4g.11186623.0.0.695178eb0nD6jp     */    private String region;    private boolean pathStyleAccess = true;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 自动配置类
package com.oss;import com.amazonaws.auth.AWSCredentials;import com.amazonaws.auth.AWSCredentialsProvider;import com.amazonaws.auth.AWSStaticCredentialsProvider;import com.amazonaws.auth.BasicAWSCredentials;import com.amazonaws.client.builder.AwsClientBuilder;import com.amazonaws.services.s3.AmazonS3;import com.amazonaws.services.s3.AmazonS3Client;import com.oss.client.OssClient;import com.oss.client.S3OssClient;import com.oss.config.OssProperties;import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;import org.springframework.boot.context.properties.EnableConfigurationProperties;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import java.util.Objects;import java.util.stream.Stream;/** * OSS服务自动配置类 * @author zdh */@Configuration@EnableConfigurationProperties(OssProperties.class)public class OssAutoConfiguration {    @Bean    @ConditionalOnMissingBean(S3OssClient.class)    public OssClient ossClient(AmazonS3 amazonS3) {        return new S3OssClient(amazonS3);    }    /**     * 参考文档     * https://docs.aws.amazon.com/zh_cn/sdk-for-java/v1/developer-guide/credentials.html     * 区域选择这块     * https://docs.aws.amazon.com/zh_cn/sdk-for-java/v1/developer-guide/java-dg-region-selection.html     * @param ossProperties     * @return     */    @Bean    @ConditionalOnMissingBean(AmazonS3.class)    @ConditionalOnProperty(prefix = "oss", name = "enable", havingValue = "true")    public AmazonS3 amazonS3(OssProperties ossProperties) {        long nullSize = Stream.<String>builder()                .add(ossProperties.getEndpoint())                .add(ossProperties.getAccessSecret())                .add(ossProperties.getAccessKey())                .build()                .filter(s -> Objects.isNull(s))                .count();        if (nullSize > 0) {            throw new RuntimeException("oss 配置错误,请检查");        }        AWSCredentials awsCredentials = new BasicAWSCredentials(ossProperties.getAccessKey(),                ossProperties.getAccessSecret());        AWSCredentialsProvider awsCredentialsProvider = new AWSStaticCredentialsProvider(awsCredentials);        return AmazonS3Client.builder()                .withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration(ossProperties.getEndpoint(), ossProperties.getRegion()))                .withCredentials(awsCredentialsProvider)                .disableChunkedEncoding()                .withPathStyleAccessEnabled(ossProperties.isPathStyleAccess())                .build();    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71

测试

  • 将starter进行打包安装到本地仓库
  • 创建一个springboot项目,并在该工程导入该starter进行单元测试

打包的时候,可以将starter项目里面的lombok依赖去掉

  • 添加配置属性
#对于minio来说,配置如下oss:  endpoint: http://minio服务器所在ip:9000  access-key: admin  access-secret: admin123  enable: true
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 编码测试
    @Test    public void testCreateBucket(){        //hutool提供的spring快捷工具        OssClient ossClient = SpringUtil.getBean(OssClient.class);        ossClient.createBucket("sale");    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

    @Test    public void testUploadImg() throws IOException {        //hutool提供的spring快捷工具        OssClient ossClient = SpringUtil.getBean(OssClient.class);        ossClient.putObject("sale","dhy.img",new FileInputStream("xpy.png"));    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

    @Test    public void testgetImgUrl() throws IOException {        //hutool提供的spring快捷工具        OssClient ossClient = SpringUtil.getBean(OssClient.class);        String objectURL = ossClient.getObjectURL("sale", "dhy.img");        System.out.println(objectURL);    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7


网站建设定制开发 软件系统开发定制 定制软件开发 软件开发定制 定制app开发 app开发定制 app开发定制公司 电商商城定制开发 定制小程序开发 定制开发小程序 客户管理系统开发定制 定制网站 定制开发 crm开发定制 开发公司 小程序开发定制 定制软件 收款定制开发 企业网站定制开发 定制化开发 android系统定制开发 定制小程序开发费用 定制设计 专注app软件定制开发 软件开发定制定制 知名网站建设定制 软件定制开发供应商 应用系统定制开发 软件系统定制开发 企业管理系统定制开发 系统定制开发