springboot2.X整合MinIO对象存储:上传、下载,windows搭建MinIO服务

it2025-10-30  1

springboot2.X整合MinIO对象存储:上传、下载,windows搭建MinIO服务

MinIO官网下载 link 为什么选择MinIO 在之前开发中曾使用了分布式文件服务FASTDFS和阿里云的OSS对象存储来存储。奈何OSS太贵,FASTDFS搭建配置又太繁琐,今天给大家推荐一款极易上手的高性能对象存储服务MinIo。 MinIO 是高性能的对象存储,兼容 Amazon S3接口,充分考虑开发人员的需求和体验;支持分布式存储,具备高扩展性、高可用性;部署简单但功能丰富。官方的文档也很详细。它有多种不同的部署模式(单机部署,分布式部署)。

推荐使用 docker 一键安装:

docker run -it -p 9000:9000 --name minio \ -d --restart=always \ -e "MINIO_ACCESS_KEY=admin" \ -e "MINIO_SECRET_KEY=admin123456" \ -v /mnt/minio/data:/data \ -v /mnt/minio/config:/root/.minio \ minio/minio server /data

注意:

密钥必须大于8位,否则会创建失败文件目录和配置文件一定要映射到主机,你懂得

windows安装: 将下载好的MinIO.exe放到新建的minio文件夹,在minio文件夹新建data文件夹,使用dos命令启动

minio.exe server D:\minio\data

安装成功!!!

默认的 MINIO_ACCESS_KEY=minioadmin,MINIO_SECRET_KEY = minioadmin

访问http://localhost:9000/ 输入默认的Access Key:minioadmin,Secret Key:minioadmin

整合Nginx:

server{ listen 80; server_name minio.javakf.com.cn; location /{ proxy_set_header Host $http_host; proxy_pass http://localhost:9000; } location ~ /\.ht { deny all; } }

整合SpringBoot

示例的结构

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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.4.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>demo</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>io.minio</groupId> <artifactId>minio</artifactId> <version>7.0.2</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>

application.yml

#服务端口 server: port: 9100 #MinIo文件服务器 min: io: endpoint: http://localhost:9000 accessKey: minioadmin secretKey: minioadmin

com.example.demo.config.MinIoProperties

package com.example.demo.config; import io.minio.MinioClient; import io.minio.errors.InvalidEndpointException; import io.minio.errors.InvalidPortException; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; /** * 实体类 * 爪哇笔记:https://blog.52itstyle.vip */ @Data @ConfigurationProperties(prefix = "min.io") public class MinIoProperties { private String endpoint; private String accessKey; private String secretKey; @Bean public MinioClient getMinioClient() throws InvalidEndpointException, InvalidPortException { MinioClient minioClient = new MinioClient(endpoint, accessKey, secretKey); return minioClient; } }

com.example.demo.utils.MinIoUtils

package com.example.demo.utils; /** * @description: * @author: wangtaotao * @time: 2020/10/21 11:25 */ import com.example.demo.config.MinIoProperties; import com.example.demo.dtos.MinIoUploadResDTO; import io.minio.MinioClient; import io.minio.PutObjectOptions; import io.minio.Result; import io.minio.errors.*; import io.minio.messages.Bucket; import io.minio.messages.DeleteError; import io.minio.messages.Item; import org.apache.tomcat.util.http.fileupload.IOUtils; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.InputStream; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.List; import java.util.UUID; /** * @Description minio工具类 * * @author exe.wangtaotao * @date 2020/10/21 16:33 */ @Component @Configuration @EnableConfigurationProperties({MinIoProperties.class}) public class MinIoUtils { @Resource private MinioClient instance; private static final String SEPARATOR_DOT = "."; private static final String SEPARATOR_ACROSS = "-"; private static final String SEPARATOR_STR = ""; /** * @Description 判断 bucket是否存在 * * @author exe.wangtaotao * @date 2020/10/21 16:33 * @param bucketName * @return boolean */ public boolean bucketExists(String bucketName){ try { return instance.bucketExists(bucketName); } catch (Exception e) { e.printStackTrace(); } return false; } /** * 创建 bucket * @param bucketName */ public void makeBucket(String bucketName){ try { boolean isExist = instance.bucketExists(bucketName); if(!isExist) { instance.makeBucket(bucketName); } } catch (Exception e) { e.printStackTrace(); } } /** * @Description 删除桶 * * @author exe.wangtaotao * @date 2020/10/21 16:46 * @param bucketName * @return boolean */ public boolean removeBucket(String bucketName) throws InvalidKeyException, ErrorResponseException, IllegalArgumentException, InsufficientDataException, InternalException, InvalidBucketNameException, InvalidResponseException, NoSuchAlgorithmException, XmlParserException, IOException { 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; } } // 删除存储桶,注意,只有存储桶为空时才能删除成功。 instance.removeBucket(bucketName); flag = bucketExists(bucketName); if (!flag) { return true; } } return false; } /** * @Description 获取文件存储服务的所有存储桶名称 * * @author exe.wangtaotao * @date 2020/10/21 16:35 * @param * @return java.util.List<java.lang.String> */ public List<String> listBucketNames() throws IllegalArgumentException, IOException, InvalidResponseException, InvalidKeyException, NoSuchAlgorithmException, ErrorResponseException, XmlParserException, InvalidBucketNameException, InsufficientDataException, InternalException { List<Bucket> bucketList = listBuckets(); List<String> bucketListName = new ArrayList<>(); for (Bucket bucket : bucketList) { bucketListName.add(bucket.name()); } return bucketListName; } /** * @Description 列出所有存储桶 * * @author exe.wangtaotao * @date 2020/10/21 16:35 * @param * @return java.util.List<io.minio.messages.Bucket> */ public List<Bucket> listBuckets() throws IllegalArgumentException, IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException { return instance.listBuckets(); } /** * @Description 列出存储桶中的所有对象名称 * * @author exe.wangtaotao * @date 2020/10/21 16:39 * @param bucketName * @return java.util.List<java.lang.String> */ public List<String> listObjectNames(String bucketName) throws InvalidKeyException, ErrorResponseException, IllegalArgumentException, InsufficientDataException, InternalException, InvalidBucketNameException, InvalidResponseException, NoSuchAlgorithmException, XmlParserException, IOException { 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; } /** * @Description 列出存储桶中的所有对象 * * @author exe.wangtaotao * @date 2020/10/21 16:39 * @param bucketName * @return java.lang.Iterable<io.minio.Result<io.minio.messages.Item>> */ public Iterable<Result<Item>> listObjects(String bucketName) throws InvalidKeyException, ErrorResponseException, IllegalArgumentException, InsufficientDataException, InternalException, InvalidBucketNameException, InvalidResponseException, NoSuchAlgorithmException, XmlParserException, IOException { boolean flag = bucketExists(bucketName); if (flag) { return instance.listObjects(bucketName); } return null; } /** * @Description 通过文件地址,保留原始文件名 文件上传 * * @author exe.wangtaotao * @date 2020/10/21 15:16 * @param bucketName * @param objectName * @param stream * @param options * @return java.lang.String */ public MinIoUploadResDTO upload(String bucketName, String objectName, InputStream stream, PutObjectOptions options) throws Exception { if(!this.bucketExists(bucketName)){ this.makeBucket(bucketName); } instance.putObject(bucketName,objectName,stream,options); // 返回生成文件名、访问路径 return new MinIoUploadResDTO(objectName,instance.presignedGetObject(bucketName, objectName)); } /** * @Description 文件上传 * * @author exe.wangtaotao * @date 2020/10/21 13:45 * @param multipartFile * @return java.lang.String */ public MinIoUploadResDTO upload(String bucketName, MultipartFile multipartFile) throws Exception { // bucket 不存在,创建 if (!this.bucketExists(bucketName)) { this.makeBucket(bucketName); } InputStream inputStream = multipartFile.getInputStream(); // PutObjectOptions,上传配置(文件大小,内存中文件分片大小) PutObjectOptions putObjectOptions = new PutObjectOptions(inputStream.available(), -1); // 文件的ContentType putObjectOptions.setContentType(multipartFile.getContentType()); String minFileName = minFileName(multipartFile.getOriginalFilename()); instance.putObject(bucketName, minFileName, inputStream, putObjectOptions); //上传文件到指定目录 String directory = "image/"; // 返回生成文件名、访问路径 return new MinIoUploadResDTO(minFileName,instance.presignedGetObject(bucketName, directory+minFileName)); } /** * @Description 下载文件 * * @author exe.wangtaotao * @date 2020/10/21 15:18 * @param response * @return java.lang.String */ public void download(HttpServletResponse response, String bucketName,String minFileName ) throws Exception { InputStream fileInputStream = instance.getObject(bucketName, minFileName); response.setHeader("Content-Disposition", "attachment;filename=" + minFileName); response.setContentType("application/force-download"); response.setCharacterEncoding("UTF-8"); IOUtils.copy(fileInputStream,response.getOutputStream()); } /** * 删除一个文件 * @param bucketName * @param objectName */ public boolean removeObject(String bucketName, String objectName) throws InvalidKeyException, ErrorResponseException, IllegalArgumentException, InsufficientDataException, InternalException, InvalidBucketNameException, InvalidResponseException, NoSuchAlgorithmException, XmlParserException, IOException { boolean flag = bucketExists(bucketName); if (flag) { instance.removeObject(bucketName, objectName); return true; } return false; } /** * @Description 删除指定桶的多个文件对象,返回删除错误的对象列表,全部删除成功,返回空列表 * * @author exe.wangtaotao * @date 2020/10/21 16:43 * @param bucketName * @param objectNames * @return java.util.List<java.lang.String> */ public List<String> removeObject(String bucketName, List<String> objectNames) throws InvalidKeyException, ErrorResponseException, IllegalArgumentException, InsufficientDataException, InternalException, InvalidBucketNameException, InvalidResponseException, NoSuchAlgorithmException, XmlParserException, IOException { List<String> deleteErrorNames = new ArrayList<>(); boolean flag = bucketExists(bucketName); if (flag) { Iterable<Result<DeleteError>> results = instance.removeObjects(bucketName, objectNames); for (Result<DeleteError> result : results) { DeleteError error = result.get(); deleteErrorNames.add(error.objectName()); } } return deleteErrorNames; } /** * @Description 生成上传文件名 * * @author exe.wangtaotao * @date 2020/10/21 15:07 * @param originalFileName * @return java.lang.String */ private String minFileName(String originalFileName) { String suffix = originalFileName; if(originalFileName.contains(SEPARATOR_DOT)){ suffix = originalFileName.substring(originalFileName.lastIndexOf(SEPARATOR_DOT)); } return UUID.randomUUID().toString().replace(SEPARATOR_ACROSS,SEPARATOR_STR).toUpperCase()+suffix; } }

com.example.demo.controllers.MinIoController

package com.example.demo.controllers; import com.example.demo.base.Result; import com.example.demo.utils.MinIoUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; import javax.servlet.http.HttpServletResponse; import java.io.File; /** * @description: * @author: wangtaotao * @time: 2020/10/21 11:07 */ @Slf4j @RestController @RequestMapping("/minio") public class MinIoController { private static final String MINIO_BUCKET = "miniobucket"; @Resource private MinIoUtils minIoUtils; @RequestMapping(value = "/upload", method = RequestMethod.POST) public Result upload(@RequestParam("files") MultipartFile files) { try { return Result.ok(minIoUtils.upload(MINIO_BUCKET,files)); } catch (Exception e) { log.error("上传失败,msg={}",e.getMessage()); e.printStackTrace(); return Result.error(e.getMessage()); } } @RequestMapping(value = "/download", method = RequestMethod.POST) public void download(HttpServletResponse response, String minFileName) { try { minIoUtils.download(response, MINIO_BUCKET, minFileName); } catch (Exception e) { log.error("下载失败,msg={}",e.getMessage()); e.printStackTrace(); } } }

com.example.demo.dtos.MinIoUploadResDTO

package com.example.demo.dtos; import lombok.Data; import java.io.Serializable; /** * @description: * @author: wangtaotao * @time: 2020/10/21 15:28 */ @Data public class MinIoUploadResDTO implements Serializable { private static final long serialVersionUID = 475040120689218785L; private String minFileName; private String minFileUrl; public MinIoUploadResDTO(String minFileName, String minFileUrl) { this.minFileName = minFileName; this.minFileUrl = minFileUrl; } }

com.example.demo.base.Result

package com.example.demo.base; import java.io.Serializable; import java.util.HashMap; import java.util.Map; /** * @description: * @author: wangtaotao * @time: 2020/10/21 15:34 */ public class Result <T> implements Serializable { private static final long serialVersionUID = 6273326371984994386L; private Integer code; private String msg; private T data; private Result() { this.code = 200; this.msg = "OK"; } private Result(T data) { this.code = 200; this.msg = "OK"; this.setData(data); } private Result(Integer code, String msg) { this.code = code; this.msg = msg; } private Result(Integer code, String msg, T data) { this.code = code; this.msg = msg; this.data = data; } public Result<T> setError(Integer code, String msg) { this.setCode(code); this.setMsg(msg); return this; } public boolean isSuccess() { return this.getCode().equals(200); } public static Result ok() { return new Result(); } public static <T> Result ok(T data) { return new Result(data); } public static <T> Result ok(Integer code, String msg) { return new Result(code, msg); } public static <T> Result ok(Integer code, String msg, T data) { return new Result(code, msg, data); } public static <T> Result error() { return new Result(500, "failed"); } public static <T> Result error(String msg) { return new Result(500, msg); } public static <T> Result error(Integer code, String msg) { return new Result(code, msg); } public static <T> Result error(Integer code, String msg, T data) { return new Result(code, msg, data); } public Integer getCode() { return this.code; } public void setCode(Integer code) { this.code = code; } public String getMsg() { return this.msg; } public void setMsg(String msg) { this.msg = msg; } public T getData() { return this.data; } public void setData(T data) { this.data = data; } public Map<String,Object> toJsonMap(){ Map<String,Object> map = new HashMap<>(); map.put("data",this.data); map.put("msg",this.msg); map.put("code",this.code); return map; } }

com.example.demo.DemoApplication

package com.example.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }

源码整合了swagger方便测试:https://gitee.com/taogemail/springboot-minio.git

最新回复(0)