SpringBoot整合Minio
在企业开发中,我们经常会使用到文件存储的业务,Minio就是一个不错的文件存储工具,下面我们来看看如何在SpringBoot中整合Minio
POM
pom文件指定SpringBoot项目所依赖的软件工具包
<?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>
<groupId>com.zsxq</groupId>
<artifactId>csg_idc_zsxq</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>csg_idc_zsxq</name>
<description>csg_idc_zsxq</description>
<properties>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.7.3</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxws</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.12</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.7.7</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.16</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.12</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.26</version>
</dependency>
<!--minio-->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.0.3</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>17</source>
<target>17</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<mainClass>com.idc.zsxq.CsgIdcZsxqApplication</mainClass>
<skip>true</skip>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
YML
SpringBoot配置文件
server:
port: 11112
# springdoc-openapi项目配置
knife4j:
enable: true
setting:
language: zh_cn
spring:
redis:
password:
host: 127.0.0.1
port: 6379
username:
# minio
minio:
endpoint: http://127.0.0.1:9000
accessKey: minioadmin
secretKey: minioadmin
bucketName: bucket
MinioClientConfig
Minio的配置类
package com.idc.zsxq.config;
import io.minio.MinioClient;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
@Data
@Component
public class MinIoClientConfig {
@Value("${minio.endpoint}")
private String endpoint;
@Value("${minio.accessKey}")
private String accessKey;
@Value("${minio.secretKey}")
private String secretKey;
/**
* 注入minio 客户端
*
* @return
*/
@Bean
public MinioClient minioClient() {
return MinioClient.builder()
.endpoint(endpoint)
.credentials(accessKey, secretKey)
.build();
}
}
MinioUtil
操作Minio的工具类,实现了判断Bucket是否存在,创建Bucket,上传文件,下载文件等功能
package com.idc.zsxq.util;
import com.idc.zsxq.model.ObjectItem;
import io.minio.*;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* @description: minio工具类
* @version:3.0
*/
@Component
public class MinioUtil {
@Resource
private MinioClient minioClient;
@Value("${minio.bucketName}")
private String bucketName;
/**
* description: 判断bucket是否存在,不存在则创建
*
* @return: void
*/
public void existBucket(String name) {
try {
boolean exists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(name).build());
if (!exists) {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(name).build());
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 创建存储bucket
*
* @param bucketName 存储bucket名称
* @return Boolean
*/
public Boolean makeBucket(String bucketName) {
try {
minioClient.makeBucket(MakeBucketArgs.builder()
.bucket(bucketName)
.build());
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* 删除存储bucket
*
* @param bucketName 存储bucket名称
* @return Boolean
*/
public Boolean removeBucket(String bucketName) {
try {
minioClient.removeBucket(RemoveBucketArgs.builder()
.bucket(bucketName)
.build());
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* description: 上传文件
*
* @param multipartFile
* @return: java.lang.String
*/
public List<String> upload(MultipartFile[] multipartFile) {
List<String> names = new ArrayList<>(multipartFile.length);
for (MultipartFile file : multipartFile) {
String fileName = file.getOriginalFilename();
String[] split = fileName.split("\\.");
if (split.length > 1) {
fileName = split[0] + "_" + System.currentTimeMillis() + "." + split[1];
} else {
fileName = fileName + System.currentTimeMillis();
}
InputStream in = null;
try {
in = file.getInputStream();
minioClient.putObject(PutObjectArgs.builder()
.bucket(bucketName)
.object(fileName)
.stream(in, in.available(), -1)
.contentType(file.getContentType())
.build()
);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
names.add(fileName);
}
return names;
}
/**
* description: 上传文件流
*
* @param in
* @return: InputStream
*/
public void upload(InputStream in, String bucketName, String fileName) {
try {
minioClient.putObject(PutObjectArgs.builder()
.bucket(bucketName)
.object(fileName)
.stream(in, in.available(), -1)
.contentType("application/octet-stream;charset=UTF-8")
.build()
);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* description: 下载文件
*
* @param fileName
* @return: org.springframework.http.ResponseEntity<byte [ ]>
*/
public ResponseEntity<byte[]> download(String fileName, String bucketName) {
ResponseEntity<byte[]> responseEntity = null;
InputStream in = null;
ByteArrayOutputStream out = null;
try {
in = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(fileName).build());
out = new ByteArrayOutputStream();
IOUtils.copy(in, out);
//封装返回值
byte[] bytes = out.toByteArray();
HttpHeaders headers = new HttpHeaders();
try {
headers.add("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
//需要设置此属性,否则浏览器默认不会读取到响应头中的Accept-Ranges属性,因此会认为服务器端不支持分片,所以会直接全文下载
headers.add("Access-Control-Expose-Headers", "Accept-Ranges,Content-Range");
headers.setContentLength(bytes.length);
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
headers.setAccessControlExposeHeaders(Arrays.asList("*"));
responseEntity = new ResponseEntity<byte[]>(bytes, headers, HttpStatus.OK);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (out != null) {
out.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return responseEntity;
}
/**
* 查看文件对象
*
* @param bucketName 存储bucket名称
* @return 存储bucket内文件对象信息
*/
public List<ObjectItem> listObjects(String bucketName) {
Iterable<Result<Item>> results = minioClient.listObjects(
ListObjectsArgs.builder().bucket(bucketName).build());
List<ObjectItem> objectItems = new ArrayList<>();
try {
for (Result<Item> result : results) {
Item item = result.get();
ObjectItem objectItem = new ObjectItem();
objectItem.setObjectName(item.objectName());
objectItem.setSize(item.size());
objectItems.add(objectItem);
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
return objectItems;
}
/**
* 批量删除文件对象
*
* @param bucketName 存储bucket名称
* @param objects 对象名称集合
*/
public Iterable<Result<DeleteError>> removeObjects(String bucketName, List<String> objects) {
List<DeleteObject> dos = objects.stream().map(e -> new DeleteObject(e)).collect(Collectors.toList());
Iterable<Result<DeleteError>> results = minioClient.removeObjects(RemoveObjectsArgs
.builder().bucket(bucketName).objects(dos).build());
return results;
}
}
案例
在SpringBoot中如何使用MinioUtil操作Minio,一般我们会使用Minio当做文件缓存
下面这个案例实现了下载文件的功能,逻辑:先从Minio查找是否存在文件,如果存在则从Minio下载文件,如果不存在则从远程文件服务器下载文件
//获取文件
public byte[] downloadFile(String bucketName, String fileId, String newFileId) {
log.info("下载文件 开始");
// 测试
bucketName = "library";
fileId = "6cc750db08c943b48a259020ce4e6794";
newFileId = "0fe8cb9eb5954cc2876c3ac8496692a7";
String fileName = bucketName + "_" + fileId + "_" +
(newFileId == null ? "" : newFileId);
log.info("文件名 {}", fileName);
try {
//如果没有bucket,则创建
minioUtil.existBucket(bucketName);
//判断Minio中是否有该文件
List<ObjectItem> objectItems = minioUtil.listObjects(bucketName);
boolean flag = false;
if (CollectionUtil.isNotEmpty(objectItems)) {
for (ObjectItem o : objectItems) {
if (StringUtils.equals(o.getObjectName(), fileName)) {
flag = true;
break;
}
}
}
byte[] bytes = null;
//如果有,则从Minio中获取文件
if (flag) {
log.info("从Minio获取文件");
ResponseEntity<byte[]> download = minioUtil.download(fileName, bucketName);
bytes = download.getBody();
} else {
log.info("从文件服务器获取文件");
//如果Minio没有,则从文件服务器获取文件,如果报错,则抛出异常
//封装基本查询参数url
String withParamsUrl = CnkiUrlEnum.DOWNLOAD_FILE.getUrl() + "?fileId=" + fileId +
(StringUtils.isEmpty(newFileId) ? "" : "&newFileId=" + newFileId) +
"&bucketName=" + bucketName;
HttpResponse httpResponse = HttpRequest.post(withParamsUrl).
header("accessToken", ConstantEnum.TOKEN.getValue()).body("").execute();
String status = httpResponse.getStatus() + "";
//判断是否请求成功
if (!"200".equals(status) && !"204".equals(status)) {
log.error("数据库查询异常, CNKI接口返回状态码为:" + status);
throw new BusinessException(ResultEnum.ERROR);
}
//空文件抛出异常
if (StringUtils.equals(httpResponse.header("Content-Type"), "application/json")) {
String jsonBody = httpResponse.body();
JSONObject dataJson = JSONObject.parseObject(jsonBody);
String dataCode = dataJson.getString("code");
log.error("文件内容为空 : " + dataCode);
throw new BusinessException(ResultEnum.ERROR);
}
//获取文件字节流
bytes = httpResponse.bodyBytes();
//写入Minio
minioUtil.upload(new ByteArrayInputStream(bytes), bucketName, fileName);
}
log.info("下载文件 结束");
//写到响应流中
return bytes;
} catch (Exception e) {
log.error("下载文件报错 : {}", e);
}
return new byte[]{};
}
效果
通过postman调用接口,返回文件流