1、引入依赖
POM文件如下:
<?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>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
<parent>
<artifactId>spring-boot</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.7.5</version>
</parent>
<groupId>org.example</groupId>
<artifactId>MinioDemo</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.5</version>
</dependency>
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.5.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
<version>2.7.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
</dependencies>
</project>
核心依赖:
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.5.1</version>
</dependency>辅助依赖:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
2、Minio配置类
- 配置文件
min:
io:
endpoint: http://ip:port
accessKey: minioadmin
secretKey: minioadmin
bucket: test
imageType: .jpeg
spring:
application:
name: minioDmeo
servlet:
multipart:
max-file-size: 5MB
max-request-size: 15MB
server:
port: 8080
package MinioDemo.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 MinIoConfig {
@Value("${min.io.endpoint}")
private String endpoint;
@Value("${min.io.accessKey}")
private String accessKey;
@Value("${min.io.secretKey}")
private String secretKey;
@Bean
public MinioClient minioClient(){
return MinioClient.builder().endpoint(endpoint).credentials(accessKey,secretKey).build();
}
}
3、Minio工具类
package MinioDemo.utils;
import MinioDemo.dto.MinIoUploadResDTO;
import io.minio.*;
import io.minio.errors.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import lombok.SneakyThrows;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.*;
@Component
public class MinIoUtils {
@Resource
private MinioClient client;
private static final String SEPARATOR_DOT = ".";
private static final String SEPARATOR_ACROSS = "-";
private static final String SEPARATOR_STR = "";
// 存储桶名称
private static final String chunkBucKet = "img";
/**
* 不排序
*/
public final static boolean NOT_SORT = false;
/**
* 排序
*/
public final static boolean SORT = true;
/**
* 默认过期时间(分钟)
*/
private final static Integer DEFAULT_EXPIRY = 60;
/**
* @param bucketName
* @return boolean
* @Description 判断 bucket是否存在
* @author exe.wangtaotao
* @date 2020/10/21 16:33
*/
public boolean bucketExists(String bucketName) {
try {
return client.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 创建存储桶
* 创建 bucket
*
* @param bucketName
*/
public void makeBucket(String bucketName) {
try {
boolean isExist = bucketExists(bucketName);
if (!isExist) {
client.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* @param
* @return java.util.List<java.lang.String>
* @Description 获取文件存储服务的所有存储桶名称
* @author exe.wangtaotao
* @date 2020/10/21 16:35
*/
public List<String> listBucketNames() {
List<Bucket> bucketList = listBuckets();
List<String> bucketListName = new ArrayList<>();
for (Bucket bucket : bucketList) {
bucketListName.add(bucket.name());
}
return bucketListName;
}
/**
* @return java.util.List<io.minio.messages.Bucket>
* @Description 列出所有存储桶
*/
@SneakyThrows
private List<Bucket> listBuckets() {
return client.listBuckets();
}
/**
* 获取对象文件名称列表
*
* @param bucketName 存储桶名称
* @param prefix 对象名称前缀(文件夹 /xx/xx/xxx.jpg 中的 /xx/xx/)
* @return objectNames
*/
public List<String> listObjectNames(String bucketName, String prefix) {
return listObjectNames(bucketName, prefix, NOT_SORT);
}
/**
* 获取对象文件名称列表
*
* @param bucketName 存储桶名称
* @param prefix 对象名称前缀(文件夹 /xx/xx/xxx.jpg 中的 /xx/xx/)
* @param sort 是否排序(升序)
* @return objectNames
*/
@SneakyThrows
public List<String> listObjectNames(String bucketName, String prefix, Boolean sort) {
boolean flag = bucketExists(bucketName);
if (flag) {
ListObjectsArgs listObjectsArgs;
if (null == prefix) {
listObjectsArgs = ListObjectsArgs.builder()
.bucket(bucketName)
.recursive(true)
.build();
} else {
listObjectsArgs = ListObjectsArgs.builder()
.bucket(bucketName)
.prefix(prefix)
.recursive(true)
.build();
}
Iterable<io.minio.Result<Item>> chunks = client.listObjects(listObjectsArgs);
List<String> chunkPaths = new ArrayList<>();
for (io.minio.Result<Item> item : chunks) {
chunkPaths.add(item.get().objectName());
}
if (sort) {
chunkPaths.sort(new Str2IntComparator(false));
}
return chunkPaths;
}
return new ArrayList<>();
}
/**
* 在桶下创建文件夹,文件夹层级结构根据参数决定
*
* @param bucket 桶名称
* @param WotDir 格式为 xxx/xxx/xxx/
*/
@SneakyThrows
public String createDirectory(String bucket, String WotDir) {
if (!this.bucketExists(bucket)) {
return null;
}
client.putObject(PutObjectArgs.builder().bucket(bucket).object(WotDir).stream(
new ByteArrayInputStream(new byte[]{}), 0, -1)
.build());
return WotDir;
}
/**
* 删除一个文件
*
* @param bucketName
* @param objectName
*/
@SneakyThrows
public boolean removeObject(String bucketName, String objectName) {
if (!bucketExists(bucketName)) {
return false;
}
client.removeObject(
RemoveObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.build());
return true;
}
/**
* @param bucketName
* @param objectNames
* @return java.util.List<java.lang.String>
* @Description 删除指定桶的多个文件对象, 返回删除错误的对象列表,全部删除成功,返回空列表
* @author exe.wangtaotao
* @date 2020/10/21 16:43
*/
@SneakyThrows
public List<String> removeObjects(String bucketName, List<String> objectNames) {
if (!bucketExists(bucketName)) {
return new ArrayList<>();
}
List<DeleteObject> deleteObjects = new ArrayList<>(objectNames.size());
for (String objectName : objectNames) {
deleteObjects.add(new DeleteObject(objectName));
}
List<String> deleteErrorNames = new ArrayList<>();
Iterable<io.minio.Result<DeleteError>> results = client.removeObjects(
RemoveObjectsArgs.builder()
.bucket(bucketName)
.objects(deleteObjects)
.build());
for (io.minio.Result<DeleteError> result : results) {
DeleteError error = result.get();
deleteErrorNames.add(error.objectName());
}
return deleteErrorNames;
}
/**
* 获取访问对象的外链地址
* 获取文件的下载url
*
* @param bucketName 存储桶名称
* @param objectName 对象名称
* @param expiry 过期时间(分钟) 最大为7天 超过7天则默认最大值
* @return viewUrl
*/
@SneakyThrows
public String getObjectUrl(String bucketName, String objectName, Integer expiry) {
expiry = expiryHandle(expiry);
return client.getPresignedObjectUrl(
GetPresignedObjectUrlArgs.builder()
.method(Method.GET)
.bucket(bucketName)
.object(objectName)
.expiry(expiry)
.build()
);
}
/**
* 创建上传文件对象的外链
*
* @param bucketName 存储桶名称
* @param objectName 欲上传文件对象的名称
* @return uploadUrl
*/
public String createUploadUrl(String bucketName, String objectName) {
return createUploadUrl(bucketName, objectName, DEFAULT_EXPIRY);
}
/**
* 创建上传文件对象的外链
*
* @param bucketName 存储桶名称
* @param objectName 欲上传文件对象的名称
* @param expiry 过期时间(分钟) 最大为7天 超过7天则默认最大值
* @return uploadUrl
*/
@SneakyThrows
public String createUploadUrl(String bucketName, String objectName, Integer expiry) {
expiry = expiryHandle(expiry);
return client.getPresignedObjectUrl(
GetPresignedObjectUrlArgs.builder()
.method(Method.PUT)
.bucket(bucketName)
.object(objectName)
.expiry(expiry)
.build()
);
}
/**
* 文件下载
*
* @param fileName 文件名
* @return InputStream
*/
public void downLoadFile(HttpServletResponse response,String fileName) {
/* InputStream inputStream = null;
try {
StatObjectResponse statObjectResponse = client.statObject(StatObjectArgs.builder().bucket(chunkBucKet).object(fileName).build());
if (statObjectResponse.size() > 0) {
inputStream = client.getObject(GetObjectArgs.builder().bucket(chunkBucKet).object(fileName).build());
}
} catch (Exception e) {
e.printStackTrace();
}
return inputStream;*/
try(InputStream ism = new BufferedInputStream(client.getObject(GetObjectArgs.builder()
.bucket(chunkBucKet)
.object(fileName).build()))) {
// 调用statObject()来判断对象是否存在。
// 如果不存在, statObject()抛出异常,
// 否则则代表对象存在
client.statObject(StatObjectArgs.builder()
.bucket(chunkBucKet)
.object(fileName).build());
byte buf[] = new byte[1024];
int length = 0;
response.reset();
//Content-disposition 是 MIME 协议的扩展,MIME 协议指示 MIME 用户代理如何显示附加的文件。
// Content-disposition其实可以控制用户请求所得的内容存为一个文件的时候提供一个默认的文件名,
// 文件直接在浏览器上显示或者在访问时弹出文件下载对话框。
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
response.setContentType("application/x-msdownload");
response.setCharacterEncoding("utf-8");
OutputStream osm = new BufferedOutputStream(response.getOutputStream());
while ((length = ism.read(buf))>0) {
osm.write(buf,0, length);
}
osm.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
/**
* 批量下载
*
* @param directory
* @return
*/
@SneakyThrows
public List<String> downLoadMore(String bucket, String directory) {
Iterable<io.minio.Result<Item>> objs = client.listObjects(ListObjectsArgs.builder().bucket(bucket).prefix(directory).useUrlEncodingType(false).build());
List<String> list = new ArrayList<>();
for (io.minio.Result<Item> result : objs) {
String objectName = null;
objectName = result.get().objectName();
StatObjectResponse statObject = client.statObject(StatObjectArgs.builder().bucket(bucket).object(objectName).build());
if (statObject != null && statObject.size() > 0) {
String fileurl = client.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder().bucket(bucket).object(statObject.object()).method(Method.GET).build());
list.add(fileurl);
}
}
return list;
}
/**
* @param multipartFile
* @param bucketName
* @param directory image/
* @return java.lang.String
* @Description 文件上传
* @author exe.wangtaotao
* @date 2020/10/21 13:45
*/
public MinIoUploadResDTO upload(MultipartFile multipartFile, String bucketName, String directory) throws Exception {
if (!this.bucketExists(bucketName)) {
this.makeBucket(bucketName);
}
InputStream inputStream = multipartFile.getInputStream();
directory = Optional.ofNullable(directory).orElse("");
String minFileName = directory + minFileName(multipartFile.getOriginalFilename());
System.out.println(minFileName);
System.out.println(multipartFile.getContentType());
//上传文件到指定目录
client.putObject(PutObjectArgs.builder()
.bucket(bucketName)
.object(minFileName)
.contentType(multipartFile.getContentType())
.stream(inputStream, inputStream.available(), -1)
.build());
inputStream.close();
// 返回生成文件名、访问路径
return new MinIoUploadResDTO(minFileName, getObjectUrl(bucketName, minFileName, DEFAULT_EXPIRY));
}
public MinIoUploadResDTO uploadByBytes(byte[] bytes,String fileName, String bucketName, String directory) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
fileName =directory+"/"+fileName;
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
ObjectWriteResponse objectWriteResponse = client.putObject(PutObjectArgs.builder().bucket(bucketName)
.object(fileName)
.contentType("image/jpeg")
.stream(byteArrayInputStream, byteArrayInputStream.available(), -1)
.build());
return new MinIoUploadResDTO(fileName,getObjectUrl(bucketName,fileName,DEFAULT_EXPIRY));
}
/**
* @param response
* @return java.lang.String
* @Description 下载文件
* @author exe.wangtaotao
* @date 2020/10/21 15:18
*/
public void download(HttpServletResponse response, String bucketName, String minFileName) {
InputStream fileInputStream = null;
try {
fileInputStream = client.getObject(GetObjectArgs.builder()
.bucket(bucketName)
.object(minFileName).build());
// response.setHeader("Content-Disposition", "attachment;filename=" + minFileName);
// response.setContentType("application/force-download");
response.setContentType("image/jpeg");
response.setCharacterEncoding("UTF-8");
IOUtils.copy(fileInputStream, response.getOutputStream());
} catch (ErrorResponseException | InternalException | InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException | XmlParserException | InsufficientDataException e) {
e.printStackTrace();
} finally {
//关闭流
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* @param originalFileName
* @return java.lang.String
* @Description 生成上传文件名
* @author exe.wangtaotao
* @date 2020/10/21 15:07
*/
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;
}
/**
* 将分钟数转换为秒数
*
* @param expiry 过期时间(分钟数)
* @return expiry
*/
private static int expiryHandle(Integer expiry) {
expiry = expiry * 60;
if (expiry > 604800) {
return 604800;
}
return expiry;
}
static class Str2IntComparator implements Comparator<String> {
private final boolean reverseOrder; // 是否倒序
public Str2IntComparator(boolean reverseOrder) {
this.reverseOrder = reverseOrder;
}
@Override
public int compare(String arg0, String arg1) {
Integer intArg0 = Integer.parseInt(arg0.substring(arg0.indexOf("/") + 1, arg0.lastIndexOf(".")));
Integer intArg1 = Integer.parseInt(arg1.substring(arg1.indexOf("/") + 1, arg1.lastIndexOf(".")));
if (reverseOrder) {
return intArg1 - intArg0;
} else {
return intArg0 - intArg1;
}
}
}
}
4、测试类
@GetMapping("/upload2")
public void test() throws Exception {
File file = new File("D:\\浏览器下载\\boy1.jpg");
InputStream inputStream = new FileInputStream(file);
MultipartFile multipartFile = getMultipartFile(file);
MinIoUploadResDTO test = minIoUtils.upload(multipartFile, "test", "");
System.out.println(test.getMinFileUrl());
}
public static MultipartFile getMultipartFile(File file) {
FileItem item = new DiskFileItemFactory().createItem("file"
, MediaType.MULTIPART_FORM_DATA_VALUE
, true
, file.getName());
try (InputStream input = new FileInputStream(file);
OutputStream os = item.getOutputStream()) {
// 流转移
IOUtils.copy(input, os);
} catch (Exception e) {
throw new IllegalArgumentException("Invalid file: " + e, e);
}
return new CommonsMultipartFile(item);
}