三、SpringBoot整合
3.1 配置
3.1.1 POM依赖
服务端版本:2023-05-27T05-56-19Z
。
客户端使用最新版的8.5.2
,客户端内部http调用使用的okhttp
,但是版本太低3.14.9
导致直接项目起不来,指定okhttp版本到4.11.0
。
回退版本可以让项目起来,但是有一些功能会有问题,所以还是需要升级okhttp的版本。
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.5.2</version>
<exclusions>
<exclusion>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.11.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
复制
3.1.2 SpringBoot配置
定义外部化配置类
@Data
@ConfigurationProperties(prefix = "minio")
public class MinioProperty implements Serializable {
private static final long serialVersionUID = 8158370057432113158L;
/**
* minio服务端地址
*/
private String endpoint;
/**
* username/访问key
*/
private String accessKey;
/**
* pwd/密钥key
*/
private String secretKey;
/**
* 生成sts临时凭证的子用户用户名,有此参数就提供sts生成实例
*/
private String stsUsername;
/**
* 生成sts临时凭证的子用户密码
*/
private String stsPassword;
/**
* STS有效时间,小于3600默认设置为3600
*/
private Integer stsDurationSeconds;
/**
* STS凭证策略,二次限定
* 可限定test-spring桶的只写
* {"Version": "2012-10-17", "Statement": [{"Effect": "Allow", "Action": ["s3:PutObject"], "Resource": ["arn:aws:s3:::test-spring/*"]}]}
*/
String stsPolicy;
/**
* STS凭证区域,默认为空串
*/
String stsRegion;
/**
* STS凭证roleArn参数,默认为null
*/
String stsRoleArn;
/**
* STS凭证roleSessionName参数,默认为null
*/
String stsRoleSessionName;
}
复制
定义AutoConfiguration类
@Configuration
@ConditionalOnProperty(prefix = "minio", name = "endpoint")
@EnableConfigurationProperties(value = MinioProperty.class)
public class MinioAutoConfiguration {
private final MinioProperty minioProperty;
public MinioAutoConfiguration(MinioProperty minioProperty) {
this.minioProperty = minioProperty;
}
//默认客户端
@Bean
@ConditionalOnMissingBean
public MinioClient minioClient() {
return MinioClient.builder().
endpoint(minioProperty.getEndpoint()).
credentials(minioProperty.getAccessKey(), minioProperty.getSecretKey()).build();
}
//默认工具类
@Bean
@ConditionalOnMissingBean
public MinioTemplate minioTemplate(MinioClient client, ObjectProvider<Provider> provider) {
return new MinioTemplate(client, minioProperty, provider.getIfAvailable());
}
//默认sts临时凭证提供实例
@Bean
@ConditionalOnProperty(value = "minio.sts-username")
@ConditionalOnMissingBean
public Provider provider() throws NoSuchAlgorithmException {
return new AssumeRoleProvider(
minioProperty.getEndpoint(),
minioProperty.getStsUsername(),
minioProperty.getStsPassword(),
minioProperty.getStsDurationSeconds(),
minioProperty.getStsPolicy(),
minioProperty.getStsRegion(),
minioProperty.getStsRoleArn(),
minioProperty.getStsRoleSessionName(),
null,
null);
}
}
复制
定义META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.xrj.config.MinioAutoConfiguration
复制
定义yml
spring:
#配置文件上传大小限制
servlet:
multipart:
max-file-size: 100MB
max-request-size: 100MB
minio:
endpoint: http://192.168.10.109:9000
access-key: uX3AwnHq8UzhjKDqWXvY
secret-key: M0RrCGfPShQ2SfUU9svb0RPuxUxENTlsqmTrrycG
sts-username: sts_user
sts-password: sts_user12345678
sts-role-arn: arn:aws:s3:::*
sts-role-session-name: StsToken
sts-duration-seconds: 3600
sts-policy: |
{"Version": "2012-10-17", "Statement": [{"Effect": "Allow", "Action": ["s3:PutObject"], "Resource": ["arn:aws:s3:::test-spring/*"]}]}
复制
3.2 MinioTemplate
@Slf4j
public class MinioTemplate {
//预签名url默认有效时间
private static final int DEFAULT_EXPIRY_TIME = (int) TimeUnit.MINUTES.toSeconds(5);
private final MinioClient minioClient;
private final Provider provider;
private final MinioProperty minioProperty;
public MinioTemplate(MinioClient minioClient, MinioProperty minioProperty, Provider provider) {
this.minioClient = minioClient;
this.minioProperty = minioProperty;
this.provider = provider;
}
/**
* 创建bucket
*/
public void createBucket(String bucketName) {
try {
if (!bucketExists(bucketName)) {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
}
} catch (Exception e) {
log.error("创建bucket异常,bucket:{}", bucketName);
throw new MinioException("创建bucket异常,bucket:" + bucketName, e);
}
}
/**
* 判断Bucket是否存在,true:存在,false:不存在
*/
public boolean bucketExists(String bucketName) {
try {
return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
} catch (Exception e) {
log.error("判断bucket存在异常,bucket:{}", bucketName);
throw new MinioException("判断bucket存在异常,bucket:" + bucketName, e);
}
}
/**
* 获得Bucket的策略
* 可以配置对应bucket的访问策略
*/
public String getBucketPolicy(String bucketName) {
try {
return minioClient.getBucketPolicy(GetBucketPolicyArgs.builder().bucket(bucketName).build());
} catch (Exception e) {
log.error("获得bucket的策略异常,bucket:{}", bucketName);
throw new MinioException("获得bucket的策略异常,bucket:" + bucketName, e);
}
}
/**
* 获得所有Bucket列表
*/
public List<Bucket> getAllBuckets() {
try {
return minioClient.listBuckets();
} catch (Exception e) {
log.error("查询bucket列表方法异常,{}", e.getMessage());
throw new MinioException("查询bucket列表方法异常", e);
}
}
/**
* 获取指定bucket信息
*/
public Optional<Bucket> getBucket(String bucketName) {
return getAllBuckets().stream().filter(b -> b.name().equals(bucketName)).findFirst();
}
/**
* 默认删除桶方法,不删除内部文件
*
* @param bucketName 桶名称
*/
public void removeBucket(String bucketName) {
removeBucket(bucketName, false);
}
/**
* @param bucketName 删除桶名称
* @param force 强制删除 递归删除bucket内文件
* @description 无法删除非空bucket, 强制删除时先递归递归删除桶内所有内容
*/
public void removeBucket(String bucketName, boolean force) {
try {
if (force) {
Iterable<Result<Item>> results = listObjects(bucketName, null, false);
for (Result<Item> result : results) {
Item item = result.get();
removeFile(bucketName, item.objectName(), item.isDir());
}
}
minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
log.info("bucket删除成功,bucketName:{}", bucketName);
} catch (Exception e) {
log.error("删除bucket异常,bucketName:{}", bucketName, e);
throw new MinioException("删除bucket异常", e);
}
}
/**
* 判断文件是否存在
*
* @param bucketName 存储桶
* @param objectName 文件名
*/
public boolean isObjectExist(String bucketName, String objectName) {
boolean exist = false;
try {
minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build());
exist = true;
} catch (Exception e) {
log.error("判断文件存在异常,bucket:{},objectName:{}", bucketName, objectName, e);
}
return exist;
}
/**
* 判断文件夹是否存在
*
* @param bucketName 存储桶
* @param objectName 文件夹名称
*/
public boolean isFolderExist(String bucketName, String objectName) {
try {
Iterable<Result<Item>> results = minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).prefix(objectName).recursive(false).build());
for (Result<Item> result : results) {
Item item = result.get();
if (item.isDir() && objectName.equals(item.objectName())) {
return true;
}
}
} catch (Exception e) {
log.error("判断目录存在异常,bucket:{},objectName:{}", bucketName, objectName, e);
}
return false;
}
/**
* 根据文件前置查询文件
*
* @param bucketName 存储桶
* @param prefix 前缀,从bucket的一级目录开始,如:test/1.jpg,test是一级目录
* @param recursive 是否使用递归查询
* @return MinioItem 列表
*/
public List<Item> getAllObjectsByPrefix(String bucketName, String prefix, boolean recursive) {
List<Item> itemList = new ArrayList<>();
Iterable<Result<Item>> itemIterable = listObjects(bucketName, prefix, recursive);
try {
if (itemIterable != null) {
for (Result<Item> itemResult : itemIterable) {
itemList.add(itemResult.get());
}
}
} catch (Exception e) {
log.error("前缀查询获取item对象异常,bucketName:{},prefix:{},recursive:{}", bucketName, prefix, recursive);
throw new MinioException("前缀查询获取item对象异常", e);
}
return itemList;
}
/**
* 获取文件流
*
* @param bucketName 存储桶
* @param objectName 文件名
* @return 二进制流
*/
public InputStream getObject(String bucketName, String objectName) {
try {
return minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());
} catch (Exception e) {
log.error("获取文件流异常,bucketName:{},objectName:{}", bucketName, objectName);
throw new MinioException("获取文件流异常", e);
}
}
/**
* 断点下载
*
* @param bucketName 存储桶
* @param objectName 文件名称
* @param offset 起始字节的位置
* @param length 要读取的长度
* @return 二进制流
*/
public InputStream getObject(String bucketName, String objectName, long offset, long length) {
try {
return minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).offset(offset).length(length).build());
} catch (Exception e) {
log.error("断点下载异常,bucketName:{},objectName:{},offset:{},length:{}", bucketName, objectName, offset, length);
throw new MinioException("断点下载异常", e);
}
}
/**
* 获取路径下文件列表迭代器
*
* @param bucketName 存储桶
* @param prefix 文件名称
* @param recursive 是否递归查找,false:模拟文件夹结构查找
* @return 迭代器对象
*/
public Iterable<Result<Item>> listObjects(String bucketName, String prefix, boolean recursive) {
return minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).prefix(prefix).recursive(recursive).build());
}
/**
* 使用MultipartFile进行文件上传
*
* @param bucketName 存储桶
* @param file 文件名
* @param objectName 对象名
* @param contentType 类型
*/
public ObjectWriteResponse uploadFile(String bucketName, MultipartFile file, String objectName, String contentType) {
try {
InputStream inputStream = file.getInputStream();
return minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectName).contentType(contentType).stream(inputStream, inputStream.available(), -1).build());
} catch (Exception e) {
log.error("MultipartFile文件上传异常,bucketName:{},objectName:{},contentType:{}", bucketName, objectName, contentType);
throw new MinioException("MultipartFile文件上传异常", e);
}
}
/**
* 上传本地文件
*
* @param bucketName 存储桶
* @param objectName 对象名称
* @param fileName 本地文件路径
*/
public ObjectWriteResponse uploadFile(String bucketName, String objectName, String fileName) {
try {
return minioClient.uploadObject(UploadObjectArgs.builder().bucket(bucketName).object(objectName).filename(fileName).build());
} catch (Exception e) {
log.error("本地文件上传异常,bucketName:{},objectName:{},fileName:{}", bucketName, objectName, fileName);
throw new MinioException("本地文件上传异常", e);
}
}
/**
* 通过流上传文件
*
* @param bucketName 存储桶
* @param objectName 文件对象
* @param inputStream 文件流
*/
public ObjectWriteResponse uploadFile(String bucketName, String objectName, InputStream inputStream) {
try {
return minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectName).stream(inputStream, inputStream.available(), -1).build());
} catch (Exception e) {
log.error("流文件上传异常,bucketName:{},objectName:{}", bucketName, objectName);
throw new MinioException("流文件上传异常", e);
}
}
/**
* 创建文件夹或目录
*
* @param bucketName 存储桶
* @param objectName 目录路径
*/
public ObjectWriteResponse createDir(String bucketName, String objectName) {
try {
return minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectName).stream(new ByteArrayInputStream(new byte[]{}), 0, -1).build());
} catch (Exception e) {
log.error("创建目录对象异常,bucketName:{},objectName:{}", bucketName, objectName);
throw new MinioException("创建目录对象异常", e);
}
}
/**
* 获取文件信息
*
* @param bucketName 存储桶
* @param objectName 文件名称
*/
public String getFileStatusInfo(String bucketName, String objectName) {
try {
//不存在抛异常
return minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build()).toString();
} catch (Exception e) {
log.error("文件信息获取失败,bucketName:{},objectName:{}", bucketName, objectName);
throw new MinioException("文件信息获取失败", e);
}
}
/**
* 拷贝文件
*
* @param destBucketName 目标存储桶
* @param destObjectName 目标文件名
* @param srcBucketName 源存储桶
* @param srcObjectName 源目标文件名
*/
public ObjectWriteResponse copyFile(String destBucketName, String destObjectName, String srcBucketName, String srcObjectName) {
try {
return minioClient.copyObject(CopyObjectArgs.builder().source(CopySource.builder().bucket(srcBucketName).object(srcObjectName).build()).bucket(destBucketName).object(destObjectName).build());
} catch (Exception e) {
log.error("拷贝文件失败,destBucketName:{},destObjectName:{},srcBucketName:{},srcObjectName:{}", destBucketName, destObjectName, srcBucketName, srcObjectName);
throw new MinioException("拷贝文件失败", e);
}
}
/**
* 删除文件
*
* @param bucketName 存储桶
* @param objectName 文件名称
* @description 默认删除非目录文件
*/
public void removeFile(String bucketName, String objectName) {
removeFile(bucketName, objectName, false);
}
/**
* 删除文件
*
* @param bucketName 存储桶
* @param objectName 文件名称
* @param isDir 目录标识
* @description 目录文件先递归删除内部文件
*/
public void removeFile(String bucketName, String objectName, boolean isDir) {
try {
if (isDir) {
Iterable<Result<Item>> results = listObjects(bucketName, objectName, false);
for (Result<Item> result : results) {
Item item = result.get();
removeFile(bucketName, item.objectName(), item.isDir());
}
}
minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build());
log.info("文件删除成功,objectName:{}", objectName);
} catch (Exception e) {
log.error("删除文件失败,bucketName:{},objectName:{}", bucketName, objectName);
throw new MinioException("删除文件失败", e);
}
}
/**
* 批量删除文件
*
* @param bucketName 存储桶
* @param objectNameList 需要删除的文件列表
*/
public void removeFiles(String bucketName, List<String> objectNameList) {
objectNameList.forEach(objectName -> removeFile(bucketName, objectName));
}
/**
* 获取文件外链
*
* @param bucketName 存储桶
* @param objectName 文件名
* @param expires 过期时间秒 默认设置5分钟
* @param method 签名方法,GET用于临时预览,PUT用于临时上传
* @return 外链url
*/
public String getPresignedObjectUrl(String bucketName, String objectName, int expires, Method method) {
try {
return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder().expiry(expires).bucket(bucketName).object(objectName).method(method).build());
} catch (Exception e) {
log.error("获取文件外链失败,bucketName:{},objectName:{}", bucketName, objectName);
throw new MinioException("获取文件外链失败", e);
}
}
/**
* 获取文件预览外链,设置过期时间
*/
public String getPresignedObjectUrl(String bucketName, String objectName, int expires) {
return getPresignedObjectUrl(bucketName, objectName, expires, Method.GET);
}
/**
* 获取文件外链,设置签名Method
*/
public String getPresignedObjectUrl(String bucketName, String objectName, Method method) {
return getPresignedObjectUrl(bucketName, objectName, DEFAULT_EXPIRY_TIME, method);
}
/**
* 获得预览文件外链
*/
public String getPresignedObjectUrl(String bucketName, String objectName) {
return getPresignedObjectUrl(bucketName, objectName, DEFAULT_EXPIRY_TIME);
}
/**
* 将URLDecoder编码转成UTF8
*/
public String getUtf8ByURLDecoder(String str) throws UnsupportedEncodingException {
String url = str.replaceAll("%(?![0-9a-fA-F]{2})", "%25");
return URLDecoder.decode(url, "UTF-8");
}
/**
* 获取sts临时凭证
*/
public StsCredentials getStsCredentials() {
Credentials credentials = getCredentials();
return StsCredentials.builder().accessKey(credentials.accessKey())
.secretKey(credentials.secretKey())
.sessionToken(credentials.sessionToken())
.isExpired(credentials.isExpired()).build();
}
/**
* 获取sts生成的客户端实例
*/
public MinioClient getStsClient() {
Credentials credentials = getCredentials();
StaticProvider staticProvider = new StaticProvider(credentials.accessKey(), credentials.secretKey(), credentials.sessionToken());
return MinioClient.builder().endpoint(minioProperty.getEndpoint()).credentialsProvider(staticProvider).build();
}
private Credentials getCredentials() {
if (provider == null){
throw new MinioException("请设置sts参数再使用相关功能",null);
}
return provider.fetch();
}
}
复制
3.2.1 依赖Model
STS临时凭证包装,方便返回给前端使用
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class StsCredentials implements Serializable {
private static final long serialVersionUID = 8484283180967327036L;
private String accessKey;
private String secretKey;
private String sessionToken;
private Boolean isExpired;
}
复制
3.2.2 依赖异常
MinioException包装minio客户端实例抛出的受检异常,方便接口调用。
public class MinioException extends RuntimeException {
public MinioException(String message, Throwable e) {
super(message, e);
}
}
复制
3.3 接口测试
@RestController
@Slf4j
public class TestController {
@Autowired
private MinioTemplate minioTemplate;
String bucketName = "test-spring";
@PostMapping("createBucket")
public void createBucket() {
minioTemplate.createBucket(bucketName);
}
@PostMapping("existBucket")
public Boolean existBucket() {
return minioTemplate.bucketExists(bucketName);
}
@PostMapping("getBucketPolicy")
public String getBucketPolicy() {
return minioTemplate.getBucketPolicy(bucketName);
}
@PostMapping("getAllBucket")
public List<String> getAllBucket() {
List<Bucket> allBuckets = minioTemplate.getAllBuckets();
return allBuckets.stream().map(Bucket::name).collect(Collectors.toList());
}
@PostMapping("getBucket")
public String getBucket() {
return minioTemplate.getBucket(bucketName).orElse(new Bucket()).name();
}
@PostMapping("delBucket")
public void delBucket() {
minioTemplate.removeBucket(bucketName);
}
@PostMapping("uploadMultipartFile")
public void uploadMultipartFile(MultipartFile file) {
minioTemplate.uploadFile(bucketName, file, file.getOriginalFilename(), file.getContentType());
}
@PostMapping("deleteFile")
public void deleteFile() {
minioTemplate.removeFile(bucketName, "file");
}
@PostMapping("copyFile")
public void copyFile() {
minioTemplate.copyFile(bucketName, "test/copy.md", bucketName, "spring项目问题.md");
}
@PostMapping("getPreSignedUrl")
public String getSignedObjectUrl() {
//预览纯文本时浏览器可能中文乱码,需要设置浏览器的字符集编码为utf-8,和后端没有关系
return minioTemplate.getPresignedObjectUrl(bucketName, "spring项目问题.md");
}
@PostMapping("putPreSignedUrl")
public String postPreSignedUrl() {
return minioTemplate.getPresignedObjectUrl(bucketName, "spring项目问题.md", Method.PUT);
}
@GetMapping(value = "getSts")
public StsCredentials getSts() {
return minioTemplate.getStsCredentials();
}
@SneakyThrows
@GetMapping(value = "testSts")
public void testSts() {
MinioClient stsClient = minioTemplate.getStsClient();
GetObjectResponse object = stsClient.getObject(GetObjectArgs.builder().bucket(bucketName).object("spring项目问题.md").build());
log.info("sts测试生效:{}", object);
}
}
复制
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。