SpringBoot整合MinIO实现文件上传 附demo

版本选择

  • JDK 1.8

1. CentOS安装MinIO

https://blog.csdn.net/llwy1428/article/details/99618252

2. 创建Bucket

在MinIO控制台中创建Bucket:springboot-minio-demo

3. 创建Maven项目

3.1. 项目结构

3.2. pom.xml引入依赖

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.demo</groupId> <artifactId>springboot-minio-demo</artifactId> <version>1.0.0</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.2.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.3.2</version> </dependency> <dependency> <groupId>io.minio</groupId> <artifactId>minio</artifactId> <version>7.0.2</version> </dependency> </dependencies> </project>

3.3. application.yaml 配置MinIO

server: port: 8080 tomcat: connection-timeout: 10000 # 10s minio: endpoint: 192.168.0.121 port: 9000 accessKey: minioadmin secretKey: minioadmin secure: false bucketName: springboot-minio-dev

3.4. SpringBoot启动类

@SpringBootApplication(scanBasePackages = "com.demo.*") public class MinioDemoApplication { public static void main(String[] args) { SpringApplication.run(MinioDemoApplication.class, args); } }

3.5. MinioProperties

获取yaml中的minio配置信息

package com.demo.entity; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; /** * MinIO配置获取 * @author ch3nw3i@gitee */ @ConfigurationProperties(prefix = "minio") @Component @Data public class MinioProperties { private String endpoint; private Integer port; private String accessKey; private String secretKey; private Boolean secure; private String bucketName; }

3.6. Minio配置类

package com.demo.config; import com.demo.entity.MinioProperties; import io.minio.MinioClient; import io.minio.errors.InvalidEndpointException; import io.minio.errors.InvalidPortException; import lombok.Data; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; /** * MinIO配置类 * @author ch3nw3i@gitee */ @Data @Component public class MinioConfig { @Autowired private MinioProperties minioProperties; @Bean public MinioClient getMinioClient() throws InvalidEndpointException, InvalidPortException { MinioClient minioClient = new MinioClient(minioProperties.getEndpoint(), minioProperties.getPort(), minioProperties.getAccessKey(), minioProperties.getSecretKey(), minioProperties.getSecure()); return minioClient; } }

3.7. MinioUtils工具类

package com.demo.utils; import io.minio.MinioClient; import io.minio.ObjectStat; import io.minio.PutObjectOptions; import io.minio.Result; import io.minio.errors.ErrorResponseException; import io.minio.errors.InvalidExpiresRangeException; import io.minio.messages.Bucket; import io.minio.messages.DeleteError; import io.minio.messages.Item; import lombok.SneakyThrows; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; /** * MinIO工具类 * @author ch3nw3i@gitee */ @Component public class MinioUtils { @Autowired private MinioClient minioClient; private static final int MIN_MULTIPART_SIZE = 0; private static final int DEFAULT_EXPIRY_TIME = 7 * 24 * 3600; /** * 检查存储桶是否存在 * * @param bucketName 存储桶名称 * @return */ @SneakyThrows public boolean bucketExists(String bucketName) { boolean flag = false; flag = minioClient.bucketExists(bucketName); if (flag) { return true; } return false; } /** * 创建存储桶 * * @param bucketName 存储桶名称 */ @SneakyThrows public boolean makeBucket(String bucketName) { boolean flag = bucketExists(bucketName); if (!flag) { minioClient.makeBucket(bucketName); return true; } else { return false; } } /** * 列出所有存储桶名称 * * @return */ @SneakyThrows public List<String> listBucketNames() { List<Bucket> bucketList = listBuckets(); List<String> bucketListName = new ArrayList<>(); for (Bucket bucket : bucketList) { bucketListName.add(bucket.name()); } return bucketListName; } /** * 列出所有存储桶 * * @return */ @SneakyThrows public List<Bucket> listBuckets() { return minioClient.listBuckets(); } /** * 删除存储桶 * * @param bucketName 存储桶名称 * @return */ @SneakyThrows public boolean removeBucket(String bucketName) { boolean flag = bucketExists(bucketName); if (flag) { Iterable<Result<Item>> myObjects = listObjects(bucketName); for (Result<Item> result : myObjects) { Item item = result.get(); // 有对象文件,则删除失败 if (item.size() > 0) { return false; } } // 删除存储桶,注意,只有存储桶为空时才能删除成功。 minioClient.removeBucket(bucketName); flag = bucketExists(bucketName); if (!flag) { return true; } } return false; } /** * 列出存储桶中的所有对象名称 * * @param bucketName 存储桶名称 * @return */ @SneakyThrows public List<String> listObjectNames(String bucketName) { List<String> listObjectNames = new ArrayList<>(); boolean flag = bucketExists(bucketName); if (flag) { Iterable<Result<Item>> myObjects = listObjects(bucketName); for (Result<Item> result : myObjects) { Item item = result.get(); listObjectNames.add(item.objectName()); } } return listObjectNames; } /** * 列出存储桶中的所有对象 * * @param bucketName 存储桶名称 * @return */ @SneakyThrows public Iterable<Result<Item>> listObjects(String bucketName) { boolean flag = bucketExists(bucketName); if (flag) { return minioClient.listObjects(bucketName); } return null; } /** * 通过文件上传到对象 * * @param bucketName 存储桶名称 * @param objectName 存储桶里的对象名称 * @param fileName File name * @return */ @SneakyThrows public boolean putObject(String bucketName, String objectName, String fileName) { boolean flag = bucketExists(bucketName); if (flag) { minioClient.putObject(bucketName, objectName, fileName, null); ObjectStat statObject = statObject(bucketName, objectName); if (statObject != null && statObject.length() > 0) { return true; } } return false; } /** * 文件上传 * * @param bucketName * @param multipartFile */ @SneakyThrows public void putObject(String bucketName, MultipartFile multipartFile, String filename) { PutObjectOptions putObjectOptions = new PutObjectOptions(multipartFile.getSize(), MinioUtils.MIN_MULTIPART_SIZE); putObjectOptions.setContentType(multipartFile.getContentType()); minioClient.putObject(bucketName, filename, multipartFile.getInputStream(), putObjectOptions); } /** * 通过InputStream上传对象 * * @param bucketName 存储桶名称 * @param objectName 存储桶里的对象名称 * @param stream 要上传的流 * @return */ @SneakyThrows public boolean putObject(String bucketName, String objectName, InputStream stream) { boolean flag = bucketExists(bucketName); if (flag) { minioClient.putObject(bucketName, objectName, stream, new PutObjectOptions(stream.available(), -1)); ObjectStat statObject = statObject(bucketName, objectName); if (statObject != null && statObject.length() > 0) { return true; } } return false; } /** * 以流的形式获取一个文件对象 * * @param bucketName 存储桶名称 * @param objectName 存储桶里的对象名称 * @return */ @SneakyThrows public InputStream getObject(String bucketName, String objectName) { boolean flag = bucketExists(bucketName); if (flag) { ObjectStat statObject = statObject(bucketName, objectName); if (statObject != null && statObject.length() > 0) { InputStream stream = minioClient.getObject(bucketName, objectName); return stream; } } return null; } /** * 以流的形式获取一个文件对象(断点下载) * * @param bucketName 存储桶名称 * @param objectName 存储桶里的对象名称 * @param offset 起始字节的位置 * @param length 要读取的长度 (可选,如果无值则代表读到文件结尾) * @return */ @SneakyThrows public InputStream getObject(String bucketName, String objectName, long offset, Long length) { boolean flag = bucketExists(bucketName); if (flag) { ObjectStat statObject = statObject(bucketName, objectName); if (statObject != null && statObject.length() > 0) { InputStream stream = minioClient.getObject(bucketName, objectName, offset, length); return stream; } } return null; } /** * 下载并将文件保存到本地 * * @param bucketName 存储桶名称 * @param objectName 存储桶里的对象名称 * @param fileName File name * @return */ @SneakyThrows public boolean getObject(String bucketName, String objectName, String fileName) { boolean flag = bucketExists(bucketName); if (flag) { ObjectStat statObject = statObject(bucketName, objectName); if (statObject != null && statObject.length() > 0) { minioClient.getObject(bucketName, objectName, fileName); return true; } } return false; } /** * 删除一个对象 * * @param bucketName 存储桶名称 * @param objectName 存储桶里的对象名称 */ @SneakyThrows public boolean removeObject(String bucketName, String objectName) { boolean flag = bucketExists(bucketName); if (flag) { minioClient.removeObject(bucketName, objectName); return true; } return false; } /** * 删除指定桶的多个文件对象,返回删除错误的对象列表,全部删除成功,返回空列表 * * @param bucketName 存储桶名称 * @param objectNames 含有要删除的多个object名称的迭代器对象 * @return */ @SneakyThrows public List<String> removeObject(String bucketName, List<String> objectNames) { List<String> deleteErrorNames = new ArrayList<>(); boolean flag = bucketExists(bucketName); if (flag) { Iterable<Result<DeleteError>> results = minioClient.removeObjects(bucketName, objectNames); for (Result<DeleteError> result : results) { DeleteError error = result.get(); deleteErrorNames.add(error.objectName()); } } return deleteErrorNames; } /** * 生成一个给HTTP GET请求用的presigned URL。 * 浏览器/移动端的客户端可以用这个URL进行下载,即使其所在的存储桶是私有的。这个presigned URL可以设置一个失效时间,默认值是7天。 * * @param bucketName 存储桶名称 * @param objectName 存储桶里的对象名称 * @param expires 失效时间(以秒为单位),默认是7天,不得大于七天 * @return */ @SneakyThrows public String presignedGetObject(String bucketName, String objectName, Integer expires) { boolean flag = bucketExists(bucketName); String url = ""; if (flag) { if (expires < 1 || expires > DEFAULT_EXPIRY_TIME) { throw new InvalidExpiresRangeException(expires, "expires must be in range of 1 to " + DEFAULT_EXPIRY_TIME); } url = minioClient.presignedGetObject(bucketName, objectName, expires); } return url; } /** * 生成一个给HTTP PUT请求用的presigned URL。 * 浏览器/移动端的客户端可以用这个URL进行上传,即使其所在的存储桶是私有的。这个presigned URL可以设置一个失效时间,默认值是7天。 * * @param bucketName 存储桶名称 * @param objectName 存储桶里的对象名称 * @param expires 失效时间(以秒为单位),默认是7天,不得大于七天 * @return */ @SneakyThrows public String presignedPutObject(String bucketName, String objectName, Integer expires) { boolean flag = bucketExists(bucketName); String url = ""; if (flag) { if (expires < 1 || expires > DEFAULT_EXPIRY_TIME) { throw new InvalidExpiresRangeException(expires, "expires must be in range of 1 to " + DEFAULT_EXPIRY_TIME); } url = minioClient.presignedPutObject(bucketName, objectName, expires); } return url; } /** * 获取对象的元数据 * * @param bucketName 存储桶名称 * @param objectName 存储桶里的对象名称 * @return */ @SneakyThrows public ObjectStat statObject(String bucketName, String objectName) { boolean flag = bucketExists(bucketName); if (flag) { ObjectStat statObject = minioClient.statObject(bucketName, objectName); return statObject; } return null; } /** * 文件访问路径 * * @param bucketName 存储桶名称 * @param objectName 存储桶里的对象名称 * @return */ @SneakyThrows public String getObjectUrl(String bucketName, String objectName) { boolean flag = bucketExists(bucketName); String url = ""; if (flag) { url = minioClient.getObjectUrl(bucketName, objectName); } return url; } public void downloadFile(String bucketName, String fileName, String originalName, HttpServletResponse response) { try { InputStream file = minioClient.getObject(bucketName, fileName); String filename = new String(fileName.getBytes("ISO8859-1"), StandardCharsets.UTF_8); if (StringUtils.isNotEmpty(originalName)) { fileName = originalName; } response.setHeader("Content-Disposition", "attachment;filename=" + filename); ServletOutputStream servletOutputStream = response.getOutputStream(); int len; byte[] buffer = new byte[1024]; while ((len = file.read(buffer)) > 0) { servletOutputStream.write(buffer, 0, len); } servletOutputStream.flush(); file.close(); servletOutputStream.close(); } catch (ErrorResponseException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } }

3.8. OssController文件上传接口

package com.demo.controller; import com.demo.entity.MinioProperties; import com.demo.utils.MinioUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import java.text.SimpleDateFormat; import java.util.Date; import java.util.UUID; /** * @author ch3nw3i@gitee */ @RestController @RequestMapping("") public class OssController { @Autowired private MinioProperties minioProperties; @Autowired private MinioUtils minioUtils; @PostMapping(value = "/uploadFile") public String uploadFile(@RequestBody MultipartFile multipartFile) { String date = new SimpleDateFormat("yyyy-MM-dd").format(new Date()); String newFilename = UUID.randomUUID().toString(); String suffix = multipartFile.getOriginalFilename().split("\\.")[1]; minioUtils.putObject(minioProperties.getBucketName(), multipartFile, date + "/" + newFilename + "." + suffix); String url = "http://" + minioProperties.getEndpoint() + ":" + minioProperties.getPort() + "/" + minioProperties.getBucketName() + "/" + date + "/" + newFilename + "." + suffix; return url; } }

4. Postman测试

将接口返回的URL复制到浏览器中即可查看。

附:Demo下载

Gitee克隆地址:https://gitee.com/ch3nw3i/springboot-minio-demo.git


__EOF__

本文作者stonechen
本文链接https://www.cnblogs.com/stonechen/p/14298748.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   没事摸摸小肚子  阅读(2654)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
历史上的今天:
2019-01-19 Python爬虫的简易流程
点击右上角即可分享
微信分享提示