暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

SpringBoot整合minio

原创 我为啥没洁癖 2023-06-02
949

三、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进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

文章被以下合辑收录

评论