Skip to main content

存储服务组件 (Storage)

API文档​

https://tech.rydeen.com.cn/apidocs/storage/4.6.0/index.html

组件作用​

本组件主要是为了提供统一的存储服务,以便于对系统的文件存储进行统一管理。本组件提供了以存储中间件非侵入的方式进行文件存储的功能,只需要在项目中引入本组件的依赖,根据组件提供的StorageEngine接口即可完成文件的存取服务。

本组件共支持以下存储中间件:

  • 本地文件系统
  • FTP/FTPs
  • SFTP
  • SMB/Samba
  • MinIO
  • Amazon S3
  • 阿里云 OSS
  • 腾讯云 COS
  • 华为云 OBS
  • 金山云 KS3
  • 字节跳动火山引擎 TOS
  • Microsoft Azure Blob Storage

本组件的设计初衷是为了解决不同存储中间件的接入问题,使得业务系统可以更加方便的切换存储中间件,而不需要对业务系统进行大量的改动。并且在后续使用中如果有存储桶的个性化需求,比如:图片压缩、图片尺寸优化、图片水印自动添加等,这些功能使用AWS S3的默认驱动是无法实现的,当需要做类似功能时,我们就可以通过组件内聚相关功能,以达到业务系统无感知的方式实现。

组件用法​

引入依赖​

首先在项目所属的Maven模块中引入组件坐标:

:::success 版本提示

该组件的包依赖分拆自4.2.0版本开始可用 :::

为了减小打包后的jar包体积,从4.2.0版本开始,我们对当前组件按照存储中间件类型对依赖进行了分拆,从4.1.0的

<dependencies>
<dependency>
<groupId>cn.com.rydeen.component</groupId>
<artifactId>rydeen-component-storage</artifactId>
</dependency>
</dependencies>

分拆成

<dependencies>
<!-- 主服务包(必须,其余的按需引入) -->
<dependency>
<groupId>cn.com.rydeen.component</groupId>
<artifactId>rydeen-component-storage</artifactId>
</dependency>
<!-- Microsoft Azure Blob Storage -->
<dependency>
<groupId>cn.com.rydeen.component</groupId>
<artifactId>rydeen-component-storage-azure</artifactId>
</dependency>
<!-- 腾讯云COS -->
<dependency>
<groupId>cn.com.rydeen.component</groupId>
<artifactId>rydeen-component-storage-cos</artifactId>
</dependency>
<!-- 金山云KS3 -->
<dependency>
<groupId>cn.com.rydeen.component</groupId>
<artifactId>rydeen-component-storage-ks3</artifactId>
</dependency>
<!-- Minio -->
<dependency>
<groupId>cn.com.rydeen.component</groupId>
<artifactId>rydeen-component-storage-minio</artifactId>
</dependency>
<!-- 华为云OBS -->
<dependency>
<groupId>cn.com.rydeen.component</groupId>
<artifactId>rydeen-component-storage-obs</artifactId>
</dependency>
<!-- 阿里云OSS -->
<dependency>
<groupId>cn.com.rydeen.component</groupId>
<artifactId>rydeen-component-storage-oss</artifactId>
</dependency>
<!-- AWS S3 -->
<dependency>
<groupId>cn.com.rydeen.component</groupId>
<artifactId>rydeen-component-storage-s3</artifactId>
</dependency>
<!-- SFTP -->
<dependency>
<groupId>cn.com.rydeen.component</groupId>
<artifactId>rydeen-component-storage-sftp</artifactId>
</dependency>
<!-- SMB -->
<dependency>
<groupId>cn.com.rydeen.component</groupId>
<artifactId>rydeen-component-storage-smb</artifactId>
</dependency>
<!-- 火山引擎tos -->
<dependency>
<groupId>cn.com.rydeen.component</groupId>
<artifactId>rydeen-component-storage-tos</artifactId>
</dependency>
</dependencies>

假设,当我们需要使用阿里云的OSS时,我们需要引入2个依赖

<dependencies>
<dependency>
<groupId>cn.com.rydeen.component</groupId>
<artifactId>rydeen-component-storage</artifactId>
</dependency>
<dependency>
<groupId>cn.com.rydeen.component</groupId>
<artifactId>rydeen-component-storage-oss</artifactId>
</dependency>
</dependencies>

对象存储不支持同一个系统中同时引入2个的情况,但可以与SMB、sftp、ftp、local这几种存储方式共存。

配置文件​

针对已支持的存储类型,我们支持在同一个项目中同时存在“本地文件系统、FTP/FTPs、SFTP、SMB和对象存储”这5类存储方式的共存,而一个系统中不支持同时出现1个以上的对象存储类型,即只能选择一个对象存储方式使用。

在项目的application.yml中配置存储服务的相关信息,Storage组件会在程序启动的时候自动创建对应存储类型的目标目录或桶(在有权限的情况下),无需编码的方式去创建,具体配置如下:

本地存储配置示例​

# 技术平台配置
platform:
component:
storage:
local:
# 本地存储基础路径
path: /data/rydeen/storage

FTP/FTPs配置示例​

# 技术平台配置
platform:
component:
storage:
ftp:
# 是否启用
enable: true
# FTP基础路径
base-path: /
# FTP字符集
charset: utf-8
# FTP模式(可用值:active、passive)
ftp-mode: passive
# FTP类型(可用值:ftp、ftps)
ftp-type: ftp
# FTP主机
host: 10.200.0.71
# FTP端口
port: 21
# FTP用户名
username: rydeen
# FTP密码
password: rydeen
# FTP登录类型(可用值:normal、anonymous)
login-type: normal
# FTP系统键(可用值:参考 cn.com.rydeen.component.storage.bean.FtpConfig.SystemKey 类)
system-key: syst_unix
# 服务语言编码
server-language-code: zh

SFTP配置示例​

# 技术平台配置
platform:
component:
storage:
sftp:
# 是否启用
enable: false
# SFTP基础路径
base-path: /
# SFTP主机
host: 10.200.0.71
# SFTP端口
port: 22
# SFTP用户名
username: rydeen
# SFTP密码
password: rydeen
# 以下配置仅4.2.0支持
# 是否是SSH秘钥方式登录
# 当值为true时,SFTP登录采用identity-file作为秘钥
# 当值为false时,SFTP登录采用password作为秘钥
ssh-login: true
# 秘钥文件路径
identity-file: /opt/key/id_rsa

SMB/Samba配置示例​

# 技术平台配置
platform:
component:
storage:
smb3:
# 是否启用
enable: false
# SMB3基础路径
base-path: /
# SMB3主机
domain: 10.200.0.71
# 用户名
username: rydeen
# 密码
password: rydeen
# 是否启用DFS(默认:false)
dfs: false
# DFS超时时间(单位:s)
dfs-ttl: 300
# 是否启用严格视图(默认:false)
strict-view: false
# 是否启用SMB3签名(默认:false)
convert-to-f-qDN: false

对象存储配置示例​

# 技术平台配置
platform:
component:
storage:
object:
# 对象存储引擎(可用值:minio、aliyun_oss、tencent_cos、huawei_obs、aws_s3、ksyun_ks3、volcengine_tos)
engine: huawei_obs
# 【此特性自:4.4.0版本开始可用】存储桶类型(默认:标准类型,可选值参考:cn.com.rydeen.component.storage.enums.StorageTypeEnum 枚举类)
storageType: standard
# 对象存储区域
region: cn-north-4
# 对象存储基础路径
base-path: platform_test
# 对象存储桶名称
bucket-name: rydeen-sxx-bucket
# 对象存储访问KEY ID
access-key-id: 11111
# 对象存储访问KEY 密钥
access-key-secret: 22222
# 对象存储访问域名
endpoint: obs.cn-north-4.myhuaweicloud.com

完成上述配置后,即可在项目中使用存储服务。

StorageEngine接口使用​

在项目中使用存储服务,只需要注入StorageEngine接口即可,如下:

使用示例​

package cn.com.rydeen.component.template.storage.controller;

import cn.com.rydeen.base.common.api.vo.ResultVO;
import cn.com.rydeen.component.storage.core.StorageEngine;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.OffsetDateTime;
import java.util.Map;

/**
* MQTT功能测试接口
*
* @author richie696
* @version 1.0
* @since 2022-10-02 10:24:08
*/
@Tag(name = "Storage功能测试接口")
@RestController
@RequestMapping("/storage")
@RequiredArgsConstructor
public class StorageController {

@Qualifier("objectStorageEngine")
private final StorageEngine storageEngine;

public record StorageData(String key, String value) {
}

@Operation(summary = "下发MQTT普通消息的接口", method = "GET", description = "指定消息体内容,通过本接口给所有队列订阅方发下消息的接口。")
@PutMapping("/data/put")
public ResultVO<Void> doSend(@RequestBody StorageData data) {
storageEngine.putData(STR."\{System.currentTimeMillis()}/\{data.key}", Map.of("time", OffsetDateTime.now().toString(), "value", data.value));
return ResultVO.getSuccess("保存成功", null);
}

}

接口说明​

StorageEngine接口目前提供的能力如下:

package cn.com.rydeen.component.storage.core;

import cn.com.rydeen.component.storage.bean.DownloadResponse;
import cn.com.rydeen.component.storage.bean.UploadResponse;
import com.fasterxml.jackson.core.type.TypeReference;
import org.springframework.lang.NonNull;

import java.io.File;
import java.io.InputStream;
import java.util.Collection;
import java.util.Map;

/**
* 文件存储服务接口
*
* @author richie696
* @version 1.0
* @since 2023-09-04 16:56:45
*/
public interface StorageEngine {

/**
* 推送文件到文件服务器的方法
*
* @param key 文件在OS上的绝对路径
* @param collection 文件内容
* @return 返回推送结果
*/
UploadResponse putData(String key, Map<?, ?> collection);

/**
* 推送文件到文件服务器的方法
*
* @param key 文件在OS上的绝对路径
* @param collection 文件内容
* @return 返回推送结果
*/
UploadResponse putData(String key, Collection<?> collection);

/**
* 推送文件到文件服务器的方法
*
* @param key 文件在OS上的绝对路径
* @param object 文件内容
* @return 返回推送结果
*/
UploadResponse putData(String key, Object object);

/**
* 推送文件到文件服务器的方法
*
* @param key 文件在OS上的绝对路径
* @param file 文件内容
* @return 返回推送结果
*/
UploadResponse putObject(String key, @NonNull File file);

/**
* 推送文件到文件服务器的方法
*
* @param key 文件在OS上的绝对路径
* @param inputStream 文件的输入流
* @return 返回推送结果
*/
UploadResponse putObject(String key, @NonNull InputStream inputStream);

/**
* 推送文件到文件服务器的方法
*
* @param key 文件在OS上的绝对路径
* @param file 文件内容
* @param options 图片处理选项
* @return 返回推送结果
*/
UploadResponse putImage(@NonNull String key, @NonNull File file, ImageOptions options);

/**
* 推送文件到文件服务器的方法
*
* @param key 文件在OS上的绝对路径
* @param inputStream 文件的输入流
* @param options 图片处理选项
* @return 返回推送结果
*/
UploadResponse putImage(@NonNull String key, @NonNull InputStream inputStream,ImageOptions options);

/**
* 获取文件服务器上的Json文件内容并转换为指定类型对象的方法
*
* @param key 文件在OS上的绝对路径
* @param typeReference 内省对象
* @return 返回文件内容
* @param <T> Json文件内容对应的Java类型
*/
<T> DownloadResponse<T> getData(String key, TypeReference<T> typeReference);

/**
* 下载文件到本地服务器的方法
* <p>(注:本方法不会将下载的文件转为字节数组返回)</p>
*
* @param key 文件在OS上的绝对路径
* @param targetPath 文件的本地路径
* @param returnData 是否返回文件内容<p>(如果文件太大可能导致JVM对外内存溢出)</p>
* @return 返回推送结果
*/
DownloadResponse<byte[]> getObject(String key, File targetPath, boolean returnData);

/**
* 下载文件到本地的方法(支持断点续传)
*
* @param key 文件在OS上的绝对路径
* @param targetPath 用于保存文件的本地路径
* @param returnData 是否返回文件内容<p>(如果文件太大可能导致JVM对外内存溢出)</p>
* @return 返回下载结果
*/
DownloadResponse<byte[]> getResumableObject(String key, String targetPath, boolean returnData);

/**
* 判断指定资源在OS上是否存在的方法
*
* @param key 文件在OS上的绝对路径
* @return 返回判断结果
*/
boolean existsObject(String key);

}

接口中,提供了常用的文件、文本的上传、下载和判断文件是否存在的方法,并对请求桶的行为和返回值进行了封装,使得业务系统可以更加方便的使用存储服务以及和存储中间件的解耦。

UploadResponse 详解​

UploadResponse是一个上传文件的返回结果对象,其结构如下:

package cn.com.rydeen.component.storage.bean;

import lombok.Builder;
import lombok.Data;

import java.io.Serializable;
import java.time.OffsetDateTime;

/**
* 上传结果
*
* @author richie696
* @version 1.0
* @since 2023-09-06 13:19:58
*/
@Data
@Builder(toBuilder = true)
public class UploadResponse implements Serializable {

/**
* 执行结果
*/
public boolean success;

/**
* 错误消息
*/
public String errorMessage;

/**
* 请求ID
*/
public String requestId;

/**
* 桶名称
*/
public String bucketName;

/**
* 对象存储的键
*/
private String key;

/**
* 文件版本号
*/
private String versionId;

/**
* 云端计算的文件哈希值
*/
private String hashValue;

/**
* 上传时间
*/
private OffsetDateTime uploadTime;

/**
* 文件下载地址
*/
private String url; // 4.2.0 版本开始可用
}

根据Javadoc对字段的解释可知,UploadResponse是一个上传文件的返回结果对象,其主要包含了上传文件的结果信息,包括:执行结果、错误消息、请求ID、桶名称、对象存储的键、文件版本号、云端计算的文件哈希值和上传时间等。我们可以明确的获得文件上传后可能需要了解的所有信息。

DownloadResponse 详解​

DownloadResponse是一个下载文件的返回结果对象,其结构如下:

package cn.com.rydeen.component.storage.bean;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

/**
* 下载数据
*
* @author richie696
* @version 1.0
* @since 2023-10-16 17:52:04
* @param <T> 下载数据类型
*/
@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
@Accessors(chain = true)
public class DownloadResponse<T> {

/**
* 执行结果
*/
private boolean success;
/**
* 错误消息
*/
private String errorMessage;

/**
* 请求ID
*/
@Builder.Default
private String requestId = "";

/**
* 桶名称
*/
@Builder.Default
private String bucketName = "";

/**
* 文件版本号
*/
@Builder.Default
private String versionId = "";

/**
* 对象存储的键
*/
private String key;

/**
* 文件类型
*/
private String contentType;

/**
* 文件MD5
*/
private String contentMD5;

/**
* 文件编码
*/
private String contentEncoding;

/**
* 文件内容
* (注:请勿下载过大的文件,否则可能导致JVM堆外内存溢出)
*/
private T data;

}

根据Javadoc对字段的解释可知,DownloadResponse是一个下载文件的返回结果对象,其主要包含了下载文件的结果信息,包括:执行结果、错误消息、请求ID、桶名称、对象存储的键、文件版本号、文件类型、文件MD5、文件编码和文件内容等。我们可以明确的获得文件下载后可能需要了解的所有信息。

但值得注意的是,如果你下载的文件过大,并且还需要将文件内容返回给调用方,那么可能会导致JVM堆外内存溢出,所以在使用DownloadResponse时,需要注意文件大小,如果文件过大建议将文件先保存到磁盘上,然后再返回文件路径或以流的方式一边读取内容,一边返回,以降低内存压力。

至此,我们已经完成了存储服务的使用。