首页 > 其他分享 >oss模块设计之适配器模式改造minio

oss模块设计之适配器模式改造minio

时间:2024-07-23 17:43:41浏览次数:12  
标签:minio objectName 适配器 bucket public import oss String

在进行本节的笔记之前,我们先进行对oss服务与minio做一个简单介绍,方便大家更便于理解;

OSS服务(Object Storage Service)

OSS服务,即对象存储服务,是一种用于云端的大规模、高可用、安全、低成本的数据存储服务。它主要用于存储非结构化的数据,如图片、音频、视频、文档等文件。OSS服务具有以下特点:

  1. 海量存储:能够存储几乎无限量的数据。
  2. 高可用性:通常采用冗余存储策略,保证数据的持久性和高可用性。
  3. 弹性扩展:根据需求自动扩展存储容量和带宽。
  4. 安全性:提供数据加密、访问控制和审计等功能,保障数据安全。
  5. 成本效益:按需付费,无需预先购买硬件或担心硬件维护。

MinIO

MinIO是一个高性能、分布式、兼容Amazon S3 API的对象存储服务器。它被设计用于云基础设施中,提供与OSS服务类似的功能,但作为一个开源项目,它允许用户在自己的基础设施上部署和管理对象存储服务。MinIO的特点包括:

  1. 高性能:利用现代硬件的特性,如多核CPU和高速网络,提供极高的I/O性能。
  2. 兼容S3 API:这使得现有的S3客户端可以直接与MinIO交互,无需修改代码。
  3. 可扩展性:支持水平扩展,可以跨多个节点和数据中心部署。
  4. 安全性:支持数据加密、身份验证和授权机制。
  5. 开源:遵循Apache License 2.0,可以自由下载、修改和分发。

作用

OSS服务和MinIO的主要作用是提供一种简单、高效的方式存储和检索大量非结构化数据。它们可以应用于各种场景,包括但不限于:

  • 备份和归档:长期保存重要数据。
  • 内容分发:存储和分发网站的静态资源,如图片和视频。
  • 大数据分析:作为Hadoop、Spark等大数据处理框架的数据仓库。
  • 媒体应用:存储和处理流媒体文件。

首先,我们先写一个简单的oss模块调用minio的实现步骤

docker部署minio

docker pull minio/minio

docker run -p 9000:9000 -p 9090:9090 \
 --name minio \
 -d --restart=always \
 -e "MINIO_ACCESS_KEY=minioadmin" \
 -e "MINIO_SECRET_KEY=minioadmin" \
 -v /mydata/minio/data:/data \
 minio/minio server \
 /data --console-address ":9090" -address ":9000"

docker部署指令如上。关于docker镜像拉取问题可以看博主前面的文章上有具体的换源介绍。

 application如上

首先封装一个fileinfo类

import lombok.Data;

/**
 * 文件类
 */
@Data
public class FileInfo {
    private String fileName;

    private Boolean directoryFlag;

    private String etag;


}

然后实现minio的工具类

import com.jingdianjichi.oss.entity.FileInfo;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.Item;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.io.InputStream;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * minio文件操作工具
 */
@Component
public class MinioUtil {
    @Resource
    private MinioClient minioClient;

    /**
     * 创建bucket桶
     */
    public void createBucket(String bucket) throws Exception {

        boolean exists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucket).build());
        if (!exists) {
            minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucket).build());
        }
    }

    /**
     * 上传文件
     */
    public void uploadFile(InputStream inputStream, String bucket, String objectName) throws Exception {
        ObjectWriteResponse objectWriteResponse = minioClient.putObject(PutObjectArgs.builder()
                .bucket(bucket)
                .object(objectName)
                .stream(inputStream, -1, Integer.MAX_VALUE)
                .build());

    }

    /**
     * 列出所有桶
     */
    public List<String> getAllBucket() throws Exception {
        List<Bucket> buckets = minioClient.listBuckets();
        return buckets.stream().map(Bucket::name).collect(Collectors.toList());
    }

    /**
     * 列出当前桶及文件
     */
    public List<FileInfo> getAllFile(String bucket) throws Exception {
        Iterable<Result<Item>> results = minioClient.listObjects(
                ListObjectsArgs.builder().bucket(bucket).build());
        List<FileInfo> fileInfoList = new LinkedList<>();
        for (Result<Item> result : results) {
            FileInfo fileInfo = new FileInfo();
            Item item = result.get();
            fileInfo.setFileName(item.objectName());
            fileInfo.setDirectoryFlag(item.isDir());
            fileInfo.setEtag(item.etag());
            fileInfoList.add(fileInfo);
        }
        return fileInfoList;

    }

    /**
     * 下载文件
     */
    public InputStream downLoad(String bucket, String objectName) throws Exception {
        return minioClient.getObject(
                GetObjectArgs.builder().bucket(bucket).object(objectName).build()
        );
    }

    /**
     * 删除桶
     */
    public void deleteBucket(String bucket) throws Exception {
        minioClient.removeBucket(
                RemoveBucketArgs.builder().bucket(bucket).build()
        );
    }

    /**
     * 删除文件
     */
    public void deleteObject(String bucket, String objectName) throws Exception {
        minioClient.removeObject(
                RemoveObjectArgs.builder().bucket(bucket).object(objectName).build()
        );
    }

    /**
     * 获取文件url
     */
    public String getPreviewFileUrl(String bucketName, String objectName) throws Exception {
        GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder()
                .method(Method.GET)
                .bucket(bucketName).object(objectName).build();
        return minioClient.getPresignedObjectUrl(args);
    }
}

 写一个配置类用于接收我们minio的application的具体参数。

import io.minio.MinioClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * minio配置管理
 */
@Configuration
public class MinioConfig {

    /**
     * miniourl
     */
    @Value("${minio.url}")
    private String url;
    /**
     * minio账户
     */
    @Value("${minio.accessKey}")
    private String accessKey;
    /**
     * minio密码
     */
    @Value("${minio.secretKey}")
    private String secretKey;

    /**
     * 构造minio客户端
     *
     * @return
     */
    @Bean
    public MinioClient getMinioClient() {
        return MinioClient.builder().endpoint(url).credentials(accessKey, secretKey).build();
    }
}

为了不直接对我们的MinioUtil进行直接的与controller进行关联,降低代码耦合度,我们引入一个接口类,便于后期业务的变化以及无需直接处理底层存储服务的复杂细节

如果需要更换对象存储服务提供商,只需要实现相同的接口即可,而无需修改调用这些方法的业务逻辑代码。

 构建接口类

import com.jingdianjichi.oss.entity.FileInfo;
import org.springframework.web.multipart.MultipartFile;

import java.io.InputStream;
import java.util.List;

public interface StorageService {


    /**
     * 创建bucket桶
     */
    void createBucket(String bucket);

    /**
     * 上传文件
     */
    void uploadFile(MultipartFile uploadFile, String bucket, String objectName);

    /**
     * 列出所有桶
     */
    List<String> getAllBucket();

    /**
     * 列出当前桶及文件
     */
    List<FileInfo> getAllFile(String bucket);

    /**
     * 下载文件
     */
    InputStream downLoad(String bucket, String objectName);

    /**
     * 删除桶
     */
    void deleteBucket(String bucket);

    /**
     * 删除文件
     */
    void deleteObject(String bucket, String objectName);




}


构建两个实现接口类
import com.jingdianjichi.oss.entity.FileInfo;
import com.jingdianjichi.oss.service.StorageService;
import com.jingdianjichi.oss.util.MinioUtil;
import lombok.SneakyThrows;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.InputStream;
import java.util.List;

/**
 * @author: zwz
 * @date: 2024
 * @description:
 */
@Service("miniostorageServiceImpl")
public class MinioStorageServiceImpl implements StorageService {

    @Resource
    private MinioUtil minioUtil;

    @Override
    @SneakyThrows
    public void createBucket(String bucket) {
        minioUtil.createBucket(bucket);

    }

    @Override
    @SneakyThrows
    public void uploadFile(MultipartFile uploadFile, String bucket, String objectName) {
        minioUtil.createBucket(bucket);
        if (objectName != null) {
            minioUtil.uploadFile(uploadFile.getInputStream(), bucket, objectName + "/" + uploadFile.getName());
        } else {
            minioUtil.uploadFile(uploadFile.getInputStream(), bucket, uploadFile.getName());
        }
    }

    @Override
    @SneakyThrows
    public List<String> getAllBucket() {
        return minioUtil.getAllBucket();
    }

    @Override
    @SneakyThrows
    public List<FileInfo> getAllFile(String bucket) {
        return minioUtil.getAllFile(bucket);
    }

    @Override
    @SneakyThrows
    public InputStream downLoad(String bucket, String objectName) {
        return minioUtil.downLoad(bucket, objectName);
    }

    @Override
    @SneakyThrows
    public void deleteBucket(String bucket) {
        minioUtil.deleteBucket(bucket);
    }

    @Override
    @SneakyThrows
    public void deleteObject(String bucket, String objectName) {
        minioUtil.deleteObject(bucket, objectName);

    }
}

import com.jingdianjichi.oss.entity.FileInfo;
import com.jingdianjichi.oss.service.StorageService;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.InputStream;
import java.util.LinkedList;
import java.util.List;
@Service("aliStorageServiceImpl")
public class AliStorageServiceImpl implements StorageService {
    @Override
    public void createBucket(String bucket) {

    }

    @Override
    public void uploadFile(MultipartFile uploadFile, String bucket, String objectName) {

    }

    @Override
    public List<String> getAllBucket() {
        List<String> bucketNameList = new LinkedList<>();
        bucketNameList.add("aliyun");
        return bucketNameList;
    }

    @Override
    public List<FileInfo> getAllFile(String bucket) {
        return null;
    }

    @Override
    public InputStream downLoad(String bucket, String objectName) {
        return null;
    }

    @Override
    public void deleteBucket(String bucket) {

    }

    @Override
    public void deleteObject(String bucket, String objectName) {

    }


}

controller层

import com.jingdianjichi.oss.service.StorageService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.List;

@RestController
public class  FileController {
    @Resource
    private StorageService miniostorageServiceImpl;

    @RequestMapping("/testGetAllBuckets")
    public String testGetAllBuckets() throws Exception {
        List<String> allBucket = miniostorageServiceImpl.getAllBucket();
        return allBucket.get(0);
    }
}

  

当完成此步骤时,我们在controller层通过调用接口类便可以进行更好的更换对象存储服务提供商来实现oss模块的具体处理。

 

但是,仍然存在一个问题便是,当我们新加入新的oss服务提供商的时候,

  @Resource
    private StorageService miniostorageServiceImpl;
 miniostorageServiceImpl.getAllBucket();
我们需要不断的进行修改此类部分内容,这仍然需要较大的任务量以及设计了内部逻辑的修改。
因此,我们可以在StorageService与controller层再次抽出新的一层FileService层使得前面两层之间降低耦合度,同时假如StorageConfig进行业务的选取
import com.jingdianjichi.oss.service.StorageService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.Resource;

@Configuration
public class StorageConfig {

    @Value("${storage.service.type}")
    private String storageType;
    @Resource
    private StorageService aliStorageServiceImpl;
    @Resource
    private StorageService miniostorageServiceImpl;

    @Bean
    public StorageService storageService() {
        if ("minio".equals(storageType)) {
            return miniostorageServiceImpl;
        } else if ("aliyun".equals(storageType)) {
            return aliStorageServiceImpl;
        } else {
            throw new IllegalArgumentException("未找到相应的文件存储管理器");
        }
    }
}

  

package com.jingdianjichi.oss.service;

import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class FileService {

    private final StorageService storageService;

    public FileService(StorageService storageService) {
        this.storageService = storageService;
    }

    /**
     * 列出所有存储桶
     */
    public List<String> getAllBucket() {
        return storageService.getAllBucket();
    }
}

  

 

 现在,我们可以直接通过修改type类便进行不同供应商的选取。

但是,这样的解决方案就是最佳的了么?
如果当我需要引入新的业务商的时候,依旧需要修改多个地方,比如以下内容

 因此,笔者决定采用适配器的模式再次优化此方案设计。

首先,我们先简单介绍一下适配器模式:

适配器模式(Adapter Pattern)是面向对象设计模式中的一种,它允许不兼容的接口之间可以协同工作。适配器模式通常用于“适配”一个已有类的接口,使其看起来像是另一个接口。这样做的目的是为了复用现有类的功能,但又不想或者不能修改现有类的代码。

适配器模式的基本概念

适配器模式涉及到以下几种角色:

  1. 目标(Target):这是期望的接口,即客户端希望使用的接口。它定义了客户端可以调用的方法。

  2. 适配者(Adaptee):这是已有接口,拥有客户端不能直接使用的接口。适配器模式的目的就是让适配者接口能够与目标接口兼容。

  3. 适配器(Adapter):这是关键的部分,它持有适配者对象的实例,并将适配者接口转换成目标接口。适配器对外呈现目标接口,内部则调用适配者的功能。

在本文中,以oss模块举例,我们更换服务商或者业务目标不同时,可能并不需要下面图片上所展示的这些方法

 因此,通过适配器模式,将彼此间业务不兼容或者不同的接口彼此都可以使用,根据需求选择不同的接口而不用修改过多代码是十分有必要的。

首先重构StorageService如下

package com.jingdianjichi.oss.service;

import com.jingdianjichi.oss.entity.FileInfo;
import org.springframework.web.multipart.MultipartFile;

import java.io.InputStream;
import java.util.List;

public interface StorageAdapter {


    /**
     * 创建bucket桶
     */
    void createBucket(String bucket);

    /**
     * 上传文件
     */
    void uploadFile(MultipartFile uploadFile, String bucket, String objectName);

    /**
     * 列出所有桶
     */
    List<String> getAllBucket();

    /**
     * 列出当前桶及文件
     */
    List<FileInfo> getAllFile(String bucket);

    /**
     * 下载文件
     */
    InputStream downLoad(String bucket, String objectName);

    /**
     * 删除桶
     */
    void deleteBucket(String bucket);

    /**
     * 删除文件
     */
    void deleteObject(String bucket, String objectName);




}

删除原接口实现类的注解

 

 修改storageConfig配置类

package com.jingdianjichi.oss.config;

import com.jingdianjichi.oss.service.StorageAdapter;
import com.jingdianjichi.oss.service.impl.AliStorageAdapter;
import com.jingdianjichi.oss.service.impl.MinioStorageAdapter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class StorageConfig {

    @Value("${storage.service.type}")
    private String storageType;

    @Bean
    public StorageAdapter storageService() {
        if ("minio".equals(storageType)) {
            return new MinioStorageAdapter();
        } else if ("aliyun".equals(storageType)) {
            return new AliStorageAdapter();
        } else {
            throw new IllegalArgumentException("未找到相应的文件存储管理器");
        }
    }
}

我们可以清楚的看到,经过·适配器模式的改造后,引入新的服务商提供类,我们只需要再次添加一个else if 语句即可。当然,可能有的小伙伴会说,为什么不采用枚举或者mapper映射注入的形式呢?

当然,这些解决方案都是可以的,但是目前在我们的oss模块中,数量上也不会太多,不必过度进行设计。

经过重构后,代码整体架构如下

 

标签:minio,objectName,适配器,bucket,public,import,oss,String
From: https://www.cnblogs.com/azwz/p/18319137

相关文章

  • Binary Cross Entropy(BCE) loss function
    二分分类器模型中用到的损失函数原型。该函数中,预测值p(yi),是经过sigmod激活函数计算之后的预测值。log(p(yi)),求对数,p(yi)约接近1,值越接近0.后半部分亦然,当期望值yi为0,p(yi)越接近1,则1-p(yi)约接近0.在pytorch中,对应的函数为torch.nn.BCELossWithLogits和torch.nn......
  • PyTorch LSTM 模型上的 CrossEntropyLoss,每个时间步一分类
    我正在尝试创建一个LSTM模型来检测时间序列数据中的异常情况。它需要5个输入并产生1个布尔输出(如果检测到异常则为True/False)。异常模式通常连续3-4个时间步长。与大多数LSTM示例不同,它们预测未来数据或对整个数据序列进行分类,我尝试在每个时间步输出True/False检......
  • 使用 minio 将文件上传到 s3 存储时出错
    我正在尝试上传两个文件。两者具有相同的扩展名,但其中一个比另一个小得多。一个只有100kb,另一个是100MB。扩展名为.bp,这些文件是机器学习模型的一部分。较小的文件已成功上传,没有任何问题。较大的文件给了我以下错误:InvalidXMLError:message:"Error"XMLisnotpar......
  • Docker Minio rclone数据迁移
    dockerminio进行数据迁移使用rclone进行数据迁移是一种非常灵活且强大的方式,特别是在处理大规模数据集或跨云平台迁移时。rclone是一款开源的命令行工具,用于同步文件和目录到多种云存储服务,包括MinIO。下面是使用rclone进行数据迁移至MinIO的步骤和示例。使用rclone迁......
  • oss
                                                  ......
  • 使用Nginx反向代理minio,提供文件公共访问
    MinIO是一个基于ApacheLicensev2.0开源协议的对象存储服务。它兼容亚马逊S3云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等。在之前的一篇文章介绍了《使用Docker搭建minio对象存储与mc客户端常用命令》,这篇文章......
  • 使用NGINX + MINIO + IMAGEPROXY 搭建私有图床
    MinIO是一个基于ApacheLicensev2.0开源协议的对象存储服务。它兼容亚马逊S3云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几kb到最大5T不等。图床的基础是Minio,但minio并不提......
  • 常用损失函数 LossFunction
    文章结构损失函数在神经网络中的位置常用的损失函数(结构:解释,公式,缺点,适用于,pytorch函数)MAE/L1LossMSE/L2LossHuberLoss对信息量、熵的解释relativeentropy相对熵/Kullback-LeiblerKLLoss CrossEntropyLoss交叉熵(包含对softmax层的解释)相对熵、熵、和交叉......
  • Mike11前处理—如何快速简便的提取断面文件Cross sections?——ZDM法
    前言:近期接触了一些关于MIKE11提取断面的技巧,当然很多人应该知道这种方法——ZDM软件提取(一款水工设计软件)。我们一般拿到都是CAD版本的断面文件,如果一个一个去输入的话,繁琐又耗时,还容易出错,今天我们在这里介绍一种简单的断面提取方法—ZDM法此方法适用很普遍,小编抽个时......
  • PyTorch 中 loss.grad_fn 解释
    在PyTorch中,loss.grad_fn属性是用来访问与loss张量相关联的梯度函数的。这个属性主要出现在使用自动微分(automaticdifferentiation)时,特别是在构建和训练神经网络的过程中。当你构建一个计算图(computationalgraph)时,PyTorch会跟踪所有参与计算的操作(比如加法、乘法、激活函数等),......