文件客户端

127 阅读4分钟

励志语录

  1. 能学、不断学习新知识、提升自己的能力、跟上时代的步伐。
  2. 能稳、行事稳重、不骄不躁、每一步都走的扎实、方能走得长远。
  3. 能静、 内心平静、不被外界的喧腾干扰、保持清醒头脑。
  4. 能熬、 熬是一种资本、也是一种财富、熬得住者必有所得。
  5. 能容、 容下不同的意见、三人行必有我师、有容乃大、心宽福自来

文件客户端

文件存储

目前市面上常见的文件存储方案

  • 本地磁盘存储 直接在服务器的本地磁盘上存储文件、适用于小型项目或对性能要求较高的场景
  • MinIo 一个高性能的分布式对象存储系统、兼容Amazon S3接口、适用于需要高可用和扩展性的场景。
  • FastDFS 一个开源的轻量级分布式文件系统、专为海量小文件存储设计、适用于需要高性能和高扩展性的场景
  • Hadoop HDFS 一个分布式文件系统、适用于大数据存储和处理、适用于需要处理海量数据的场景
  • 云存储服务 如阿里云 OSS、腾讯云、七牛云等、提供高可用、可扩展的存储服务、适用于需要快速部署和高可靠性的场景

Amazon S3 接口通常指的是 Amazon S3(Simple Storage Service)提供的一套基于 REST 的 API 接口。这套接口允许开发者通过 HTTP 请求来管理对象存储服务中的资源。

具体实现

参考项目地址

  1. 定义一个文件客户端
public interface FileClient {

    /**
     * 获得客户端编号
     *
     * @return 客户端编号
     */
    Long getId();

    /**
     * 上传文件
     *
     * @param content 文件流
     * @param path 相对路径
     * @return 完整路径,即 HTTP 访问地址
     */
    String upload(byte[] content, String path);

    /**
     * 删除文件
     *
     * @param path 相对路径
     */
    void delete(String path);

    /**
     * 获得文件的内容
     *
     * @param path 相对路径
     * @return 文件的内容
     */
    byte[] getContent(String path);

}
  1. 因为不同存储器的配置也会有所不同 这里定义一个AbstractFileClient<Connfig>抽象类用于存储不同的配置数据
import lombok.extern.slf4j.Slf4j;
/**
 * 文件客户端的抽象类,提供模板方法,减少子类的冗余代码
 */
@Slf4j
public abstract class AbstractFileClient <Config extends FileClientConfig> implements FileClient{
    /**
     * 配置编号
     */
    private final Long id;
    /**
     * 文件配置
     */
    protected Config config;

    public AbstractFileClient(Long id, Config config) {
        this.id = id;
        this.config = config;
    }

    /**
     * 初始化
     */
    public final void init() {
        doInit();
        log.debug("[init][配置({}) 初始化完成]", config);
    }

    /**
     * 自定义初始化
     */
    protected abstract void doInit();

    public final void refresh(Config config) {
        // 判断是否更新
        if (config.equals(this.config)) {
            return;
        }
        log.info("[refresh][配置({})发生变化,重新初始化]", config);
        this.config = config;
        // 初始化
        this.init();
    }
    @Override
    public Long getId() {
        return id;
    }

}

这里的FileClientConfig接口是一个标识接口、方便用户实现该接口进行修改配置

  1. 实现客户端
    • 配置类 : 这里定义的是minIo需要配置信息
          @Data
          public class S3FileClientConfig implements FileClientConfig {
      
      
              @NotNull(message = "endpoint 不能为空")
              private String endpoint;
      
              /**
               * 自定义域名
               */
              @URL(message = "domain 必须是 URL 格式")
              private String domain;
      
              /**
               * 存储 Bucket
               */
              @NotNull(message = "bucket 不能为空")
              private String bucket;
      
              /**
               * 访问 Key
               */
              @NotNull(message = "accessKey 不能为空")
              private String accessKey;
              /**
               * 访问 Secret
               */
              @NotNull(message = "accessSecret 不能为空")
              private String accessSecret;
          }
      
    • 实现类
         /**
          * 基于 S3 协议的文件客户端,实现 MinIO、阿里云、腾讯云、七牛云、华为云等云服务
          * <p>
          * S3 协议的客户端,采用亚马逊提供的 software.amazon.awssdk.s3 库
          *
          * @author 芋道源码
          */
         public class S3FileClient extends AbstractFileClient<S3FileClientConfig> {
             private MinioClient client;
      
             public S3FileClient(Long id, S3FileClientConfig config) {
                 super(id, config);
             }
      
             @Override
             protected void doInit() {
                 // 补全 domain
                 if (StrUtil.isEmpty(config.getDomain())) {
                     config.setDomain(buildDomain());
                 }
                 // 初始化客户端
                 client = MinioClient.builder()
                         .endpoint(buildEndpointURL()) // Endpoint URL
                         .region(buildRegion()) // Region
                         .credentials(config.getAccessKey(), config.getAccessSecret()) // 认证密钥
                         .build();
             }
      
             /**
              * 基于 bucket + endpoint 构建访问的 Domain 地址
              *
              * @return Domain 地址
              */
             private String buildDomain() {
                 // 如果已经是 http 或者 https , 则不进行拼接,主要适配 MinIo
                 if (HttpUtil.isHttp(config.getEndpoint()) || HttpUtil.isHttps(config.getEndpoint())) {
                     return StrUtil.format("{}/{}", config.getEndpoint(), config.getBucket());
                 }
                 return StrUtil.format("https://{}.{}", config.getBucket(), config.getEndpoint());
             }
      
             /**
              * 基于 endpoint 构建调用云服务的 URL 地址
              *
              * @return URI 地址
              */
             private String buildEndpointURL() {
                 // 如果已经是 http 或者 https,则不进行拼接.主要适配 MinIO
                 if (HttpUtil.isHttp(config.getEndpoint()) || HttpUtil.isHttps(config.getEndpoint())) {
                     return config.getEndpoint();
                 }
                 return StrUtil.format("https://{}", config.getEndpoint());
             }
      
             /**
              * 基于 bucket 构建 region 地区
              *
              * @return region 地区
              */
             private String buildRegion() {
                 // 阿里云必须有 region,否则会报错
                 if (config.getEndpoint().contains(S3FileClientConfig.ENDPOINT_ALIYUN)) {
                     return StrUtil.subBefore(config.getEndpoint(), '.', false)
                             .replaceAll("-internal", "")// 去除内网 Endpoint 的后缀
                             .replaceAll("https://", "");
                 }
                 // 腾讯云必须有 region,否则会报错
                 if (config.getEndpoint().contains(S3FileClientConfig.ENDPOINT_TENCENT)) {
                     return StrUtil.subAfter(config.getEndpoint(), "cos.", false)
                             .replaceAll("." + S3FileClientConfig.ENDPOINT_TENCENT, ""); // 去除 Endpoint
                 }
                 return null;
             }
      
             @Override
             public String upload(byte[] content, String path, String type) throws Exception {
                 // 执行上传
                 client.putObject(PutObjectArgs.builder()
                         .bucket(config.getBucket()) // bucket 必须传递
                         .contentType(type)
                         .object(path) // 相对路径作为 key
                         .stream(new ByteArrayInputStream(content), content.length, -1) // 文件内容
                         .build());
                 // 拼接返回路径
                 return config.getDomain() + "/" + path;
             }
      
             @Override
             public byte[] getContent(String path) throws Exception {
                 GetObjectResponse response = client.getObject(GetObjectArgs.builder()
                         .bucket(config.getBucket()) // bucket 必须传递
                         .object(path) // 相对路径作为 key
                         .build());
                 return IoUtil.readBytes(response);
             }
      
             @Override
             public void delete(String path) throws Exception {
      
             }
         }
      

测试

public class S3FileClientTest {
  @Test
  @Disabled // MinIO,如果要集成测试,可以注释本行
  public void testMinIO() throws Exception {
      S3FileClientConfig config = new S3FileClientConfig();
      // 配置成你自己的
      config.setAccessKey("admin");
      config.setAccessSecret("12345678111");
      config.setBucket("zhou.wenfeng");
      config.setDomain(null);
      // 默认 9000 endpoint
      config.setEndpoint("http://127.0.0.1:9000");

      // 执行上传
      testExecuteUpload(config);
  }

  private void testExecuteUpload(S3FileClientConfig config) throws Exception {
      // 创建 Client
      S3FileClient client = new S3FileClient(0L, config);
      client.init();
      // 上传文件
      String path = IdUtil.fastSimpleUUID() + ".jpg";
      byte[] content = ResourceUtil.readBytes("file/erweima.jpg");
      String fullPath = client.upload(content, path, "image/jpeg");
      System.out.println("访问地址:" + fullPath);
      // 读取文件
      if (true) {
          byte[] bytes = client.getContent(path);
          System.out.println("文件内容:" + bytes.length);
      }
  }
}