环境搭建,创建一个 maven项目,引入依赖:
<!-- minio依赖-->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.3.3</version>
</dependency>
<!-- 官方 miniodemo需要的依赖-->
<dependency>
<groupId>me.tongfei</groupId>
<artifactId>progressbar</artifactId>
<version>0.7.4</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.9.2</version>
</dependency>
注意:这里我把 官方demo 放到了我的项目中,所以必须引入 progressbar依赖。否则忽略它。
初始化Minio客户端
注意:
新版 MinIO 采用 Builder构建者模式来构造 MinioClient对象。
API端口和 访问控制台端口不要搞混了
// 初始化Minio客户端
MinioClient minioClient = MinioClient.builder()
.endpoint("http://xxx.xxx.xxx.xxx:9000/")
.credentials("admin", "xxx")
.build();
System.out.println(minioClient);
配置MinIO的yml
server:
port: 8089
spring:
servlet:
multipart:
max-request-size: 200MB
max-file-size: 200MB
minio:
url: http://127.0.0.1:19000 #换成自己的minio服务端地址
accessKey: admin# 用户名
secretKey: 12345678# 密码
bucketName: otest # bucketName指的就是之前创建的MinIO桶Bucket
配置类Config
@Data
@Configuration
public class MinioConfig {
/**
* 访问地址
*/
@Value("${minio.url}")
private String endpoint;
/**
* accessKey类似于用户ID,用于唯一标识你的账户
*/
@Value("${minio.accessKey}")
private String accessKey;
/**
* secretKey是你账户的密码
*/
@Value("${minio.secretKey}")
private String secretKey;
/**
* 默认存储桶
*/
@Value("${minio.bucketName}")
private String bucketName;
@Bean
public MinioClient minioClient() {
MinioClient minioClient = MinioClient.builder()
.endpoint(endpoint)
.credentials(accessKey, secretKey)
.build();
return minioClient;
}
}
MinIO工具类
/**
* MinIO工具类
*
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class MinioUtils {
private final MinioClient minioClient;
/****************************** Operate Bucket Start ******************************/
/**
* 启动SpringBoot容器的时候初始化Bucket
* 如果没有Bucket则创建
*
* @param bucketName
*/
@SneakyThrows(Exception.class)
private void createBucket(String bucketName) {
if (!bucketExists(bucketName)) {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
}
}
/**
* 判断Bucket是否存在,true:存在,false:不存在
*
* @param bucketName
* @return
*/
@SneakyThrows(Exception.class)
public boolean bucketExists(String bucketName) {
return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
}
/**
* 获得Bucket的策略
*
* @param bucketName
* @return
*/
@SneakyThrows(Exception.class)
public String getBucketPolicy(String bucketName) {
return minioClient.getBucketPolicy(GetBucketPolicyArgs
.builder()
.bucket(bucketName)
.build());
}
/**
* 获得所有Bucket列表
*
* @return
*/
@SneakyThrows(Exception.class)
public List<Bucket> getAllBuckets() {
return minioClient.listBuckets();
}
/**
* 根据bucketName获取其相关信息
*
* @param bucketName
* @return
*/
@SneakyThrows(Exception.class)
public Optional<Bucket> getBucket(String bucketName) {
return getAllBuckets().stream().filter(b -> b.name().equals(bucketName)).findFirst();
}
/**
* 根据bucketName删除Bucket,true:删除成功; false:删除失败,文件或已不存在
*
* @param bucketName
* @throws Exception
*/
@SneakyThrows(Exception.class)
public void removeBucket(String bucketName) {
minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
}
/****************************** Operate Bucket End ******************************/
/****************************** Operate Files Start ******************************/
/**
* 判断文件是否存在
*
* @param bucketName
* @param objectName
* @return
*/
public boolean isObjectExist(String bucketName, String objectName) {
boolean exist = true;
try {
minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build());
} catch (Exception e) {
log.error("[Minio工具类]>>>> 判断文件是否存在, 异常:", e);
exist = false;
}
return exist;
}
/**
* 判断文件夹是否存在
*
* @param bucketName
* @param objectName
* @return
*/
public boolean isFolderExist(String bucketName, String objectName) {
boolean exist = false;
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())) {
exist = true;
}
}
} catch (Exception e) {
log.error("[Minio工具类]>>>> 判断文件夹是否存在,异常:", e);
exist = false;
}
return exist;
}
/**
* 根据文件前置查询文件
*
* @param bucketName 存储桶
* @param prefix 前缀
* @param recursive 是否使用递归查询
* @return MinioItem 列表
*/
@SneakyThrows(Exception.class)
public List<Item> getAllObjectsByPrefix(String bucketName,
String prefix,
boolean recursive) {
List<Item> list = new ArrayList<>();
Iterable<Result<Item>> objectsIterator = minioClient.listObjects(
ListObjectsArgs.builder().bucket(bucketName).prefix(prefix).recursive(recursive).build());
if (objectsIterator != null) {
for (Result<Item> o : objectsIterator) {
Item item = o.get();
list.add(item);
}
}
return list;
}
/**
* 获取文件流
*
* @param bucketName 存储桶
* @param objectName 文件名
* @return 二进制流
*/
@SneakyThrows(Exception.class)
public InputStream getObject(String bucketName, String objectName) {
return minioClient.getObject(
GetObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.build());
}
/**
* 断点下载
*
* @param bucketName 存储桶
* @param objectName 文件名称
* @param offset 起始字节的位置
* @param length 要读取的长度
* @return 二进制流
*/
@SneakyThrows(Exception.class)
public InputStream getObject(String bucketName, String objectName, long offset, long length) {
return minioClient.getObject(
GetObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.offset(offset)
.length(length)
.build());
}
/**
* 获取路径下文件列表
*
* @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 类型
* @return
*/
@SneakyThrows(Exception.class)
public ObjectWriteResponse uploadFile(String bucketName, MultipartFile file, String objectName, String contentType) {
InputStream inputStream = file.getInputStream();
return minioClient.putObject(
PutObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.contentType(contentType)
.stream(inputStream, inputStream.available(), -1)
.build());
}
/**
* 图片上传
* @param bucketName
* @param imageBase64
* @param imageName
* @return
*/
public ObjectWriteResponse uploadImage(String bucketName, String imageBase64, String imageName) {
if (!StringUtils.isEmpty(imageBase64)) {
InputStream in = base64ToInputStream(imageBase64);
String newName = System.currentTimeMillis() + "_" + imageName + ".jpg";
String year = String.valueOf(new Date().getYear());
String month = String.valueOf(new Date().getMonth());
return uploadFile(bucketName, year + "/" + month + "/" + newName, in);
}
return null;
}
// BASE64Decoder在jdk8以上的版本移除了,报错最简单解决换成jdk8就行了
public static InputStream base64ToInputStream(String base64) {
ByteArrayInputStream stream = null;
try {
byte[] bytes = new BASE64Decoder().decodeBuffer(base64.trim());
stream = new ByteArrayInputStream(bytes);
} catch (Exception e) {
e.printStackTrace();
}
return stream;
}
/**
* 上传本地文件
*
* @param bucketName 存储桶
* @param objectName 对象名称
* @param fileName 本地文件路径
* @return
*/
@SneakyThrows(Exception.class)
public ObjectWriteResponse uploadFile(String bucketName, String objectName, String fileName) {
return minioClient.uploadObject(
UploadObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.filename(fileName)
.build());
}
/**
* 通过流上传文件
*
* @param bucketName 存储桶
* @param objectName 文件对象
* @param inputStream 文件流
* @return
*/
@SneakyThrows(Exception.class)
public ObjectWriteResponse uploadFile(String bucketName, String objectName, InputStream inputStream) {
return minioClient.putObject(
PutObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.stream(inputStream, inputStream.available(), -1)
.build());
}
/**
* 创建文件夹或目录
*
* @param bucketName 存储桶
* @param objectName 目录路径
* @return
*/
@SneakyThrows(Exception.class)
public ObjectWriteResponse createDir(String bucketName, String objectName) {
return minioClient.putObject(
PutObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.stream(new ByteArrayInputStream(new byte[]{}), 0, -1)
.build());
}
/**
* 获取文件信息, 如果抛出异常则说明文件不存在
*
* @param bucketName 存储桶
* @param objectName 文件名称
* @return
*/
@SneakyThrows(Exception.class)
public String getFileStatusInfo(String bucketName, String objectName) {
return minioClient.statObject(
StatObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.build()).toString();
}
/**
* 拷贝文件
*
* @param bucketName 存储桶
* @param objectName 文件名
* @param srcBucketName 目标存储桶
* @param srcObjectName 目标文件名
*/
@SneakyThrows(Exception.class)
public ObjectWriteResponse copyFile(String bucketName, String objectName, String srcBucketName, String srcObjectName) {
return minioClient.copyObject(
CopyObjectArgs.builder()
.source(CopySource.builder().bucket(bucketName).object(objectName).build())
.bucket(srcBucketName)
.object(srcObjectName)
.build());
}
/**
* 删除文件
*
* @param bucketName 存储桶
* @param objectName 文件名称
*/
@SneakyThrows(Exception.class)
public void removeFile(String bucketName, String objectName) {
minioClient.removeObject(
RemoveObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.build());
}
/**
* 批量删除文件
*
* @param bucketName 存储桶
* @param keys 需要删除的文件列表
* @return
*/
public void removeFiles(String bucketName, List<String> keys) {
List<DeleteObject> objects = new LinkedList<>();
keys.forEach(s -> {
objects.add(new DeleteObject(s));
try {
removeFile(bucketName, s);
} catch (Exception e) {
log.error("[Minio工具类]>>>> 批量删除文件,异常:", e);
}
});
}
/**
* 获取文件外链
*
* @param bucketName 存储桶
* @param objectName 文件名
* @param expires 过期时间 <=7 秒 (外链有效时间(单位:秒))
* @return url
*/
@SneakyThrows(Exception.class)
public String getPresignedObjectUrl(String bucketName, String objectName, Integer expires) {
GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder().expiry(expires).bucket(bucketName).object(objectName).build();
return minioClient.getPresignedObjectUrl(args);
}
/**
* 获得文件外链
*
* @param bucketName
* @param objectName
* @return url
*/
@SneakyThrows(Exception.class)
public String getPresignedObjectUrl(String bucketName, String objectName) {
GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder()
.bucket(bucketName)
.object(objectName)
.method(Method.GET).build();
return minioClient.getPresignedObjectUrl(args);
}
/**
* 将URLDecoder编码转成UTF8
*
* @param str
* @return
* @throws UnsupportedEncodingException
*/
public String getUtf8ByURLDecoder(String str) throws UnsupportedEncodingException {
String url = str.replaceAll("%(?![0-9a-fA-F]{2})", "%25");
return URLDecoder.decode(url, "UTF-8");
}
}
创建Controller
@Slf4j
@RestController
@RequestMapping("/oss")
public class OSSController {
@Autowired
private MinioUtils minioUtils;
@Autowired
private MinioConfig minioConfig;
/**
* 文件上传
*
* @param file
*/
@PostMapping("/upload")
public String upload(@RequestParam("file") MultipartFile file) {
try {
//文件名
String fileName = file.getOriginalFilename();
String newFileName = System.currentTimeMillis() + "." + StringUtils.substringAfterLast(fileName, ".");
//类型
String contentType = file.getContentType();
minioUtils.uploadFile(minioConfig.getBucketName(), file, newFileName, contentType);
return "上传成功";
} catch (Exception e) {
log.error("上传失败");
return "上传失败";
}
}
/**
* 删除
*
* @param fileName
*/
@DeleteMapping("/")
public void delete(@RequestParam("fileName") String fileName) {
minioUtils.removeFile(minioConfig.getBucketName(), fileName);
}
/**
* 获取文件信息
*
* @param fileName
* @return
*/
@GetMapping("/info")
public String getFileStatusInfo(@RequestParam("fileName") String fileName) {
return minioUtils.getFileStatusInfo(minioConfig.getBucketName(), fileName);
}
/**
* 获取文件外链
*
* @param fileName
* @return
*/
@GetMapping("/url")
public String getPresignedObjectUrl(@RequestParam("fileName") String fileName) {
return minioUtils.getPresignedObjectUrl(minioConfig.getBucketName(), fileName);
}
/**
* 文件下载
*
* @param fileName
* @param response
*/
@GetMapping("/download")
public void download(@RequestParam("fileName") String fileName, HttpServletResponse response) {
try {
InputStream fileInputStream = minioUtils.getObject(minioConfig.getBucketName(), fileName);
response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
response.setContentType("application/force-download");
response.setCharacterEncoding("UTF-8");
IOUtils.copy(fileInputStream, response.getOutputStream());
} catch (Exception e) {
log.error("下载失败");
}
}
}
获取到 MinioClient对象,就可以进行 MinIO的 API操作使用了。
存储桶基本使用
1、检查存储桶是否存在
boolean bucketExists(BucketExistsArgs args):检查存储桶是否存在。
BucketExistsArgs btest2 = BucketExistsArgs.builder().bucket("btest2").build();
boolean existFlag = minioClient.bucketExists(btest2);
2、创建存储桶
public void makeBucket(MakeBucketArgs args):创建一个启用给定区域和对象锁定功能的存储桶。
示例1:存储桶不存在则创建
String bucketName = "java.minio.demo";
// 存储桶不存在则创建
if (!minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())) {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
System.out.printf("%s,创建成功", bucketName);
}else{
System.out.printf("%s,已存在", bucketName);
}
3、查询存储桶
3.1 查询所有桶的列表信息
public List listBuckets():列出所有桶的桶信息。
示例:
List<Bucket> bucketList = minioClient.listBuckets();
bucketList.forEach(bucket -> {
System.out.printf("存储桶名:%s,创建时间:%s ", bucket.name(), bucket.creationDate());
});
注意:桶的创建时间默认是美国时间,创建桶时我们可以指定桶的时区或者设置 MinIO服务器时区。
3.2查询所有桶的列表信息
Iterable<Result<Item>> otatest = minioClient.listObjects(
ListObjectsArgs.builder().bucket("otatest").build());
otatest.forEach(v->{
try {
//文件名
System.out.println(v.get().objectName());
System.out.println(v.get().userMetadata());
} catch (Exception e) {
e.printStackTrace();
}
});
4、删除存储桶
public void removeBucket(RemoveBucketArgs args):删除一个空桶。
示例:
String bucketName2 = "btest3";
minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName2).build());
对象基本使用
1、上传对象
1.1 PutObject方法
public ObjectWriteResponse putObject(PutObjectArgs args):将给定的流上传为存储桶中的对象。
示例1,InputStream上传:
String bucketName = "java.minio.demo";
// 创建InputStream上传
File file = new File("D:\Files\demo.jpg");
InputStream inputStream = new FileInputStream(file);
long start = System.currentTimeMillis();
// 上传流
minioClient.putObject(
PutObjectArgs.builder()
.bucket(bucketName)
.object("2023/02/25/" + file.getName()).stream(inputStream, inputStream.available(), -1)
.build());
inputStream.close();
System.out.println("uploaded successfully 耗时:" + (System.currentTimeMillis() - start));
注意:
添加的Object大小不能超过 5GB。
默认情况下,如果已存在同名Object且对该Object有访问权限,则新添加的Object将覆盖原有的Object,并返回 200 OK。
OSS没有文件夹的概念,所有资源都是以文件来存储,但您可以通过创建一个以正斜线(/)结尾,大小为 0的Object来创建模拟文件夹(指定 /后,默认会自动创建)
1.2 uploadObject方法
public void uploadObject(UploadObjectArgs args):将文件中的内容作为存储桶中的对象上传。
不太常用,一般适合上传磁盘文件(mc cp命令更方便)。
示例:
String bucketName = "java.minio.demo";
long start = System.currentTimeMillis();
// 上传文件
minioClient.uploadObject(
UploadObjectArgs.builder()
.bucket(bucketName)
.object("2023/02/25/demo2.jpg")
.filename("D:\Files\demo.jpg")
.build());
System.out.println("uploaded successfully 耗时:" + (System.currentTimeMillis() - start));
获取对象
2.1 getObject方法
public GetObjectResponse getObject(GetObjectArgs args):获取对象的数据。
示例:
String bucketName = "java.minio.demo";
// GetObjectResponse 继承了 InputStream类
GetObjectResponse objectResponse = minioClient.getObject(GetObjectArgs.builder()
.bucket(bucketName)
.object("2023/02/25/demo2.jpg")
.build());
System.out.println(objectResponse.bucket());
System.out.println(objectResponse.object());
byte[] allBytes = objectResponse.readAllBytes();
System.out.println(allBytes);
// Close the input stream.
objectResponse.close();
返回数据通过流的方式
@GetMapping("/multipart/test")
public void test(HttpServletResponse response) {
GetObjectResponse objectResponse = minioClient.getObject(GetObjectArgs.builder()
.bucket(bucketName)
.object("订餐.xlsx")
.build());
// return new Result().ok(objectResponse);
// return new ResponseEntity<>(objectResponse, HttpStatus.OK);
ServletOutputStream outputStream = response.getOutputStream();
// PrintWriter writer = response.getWriter();
byte[] buffer = new byte[1024];
int len;
//从输入流中读取一定数量的字节,并将其存储在缓冲区字节数组中,读到末尾返回-1
while ((len = objectResponse.read(buffer)) > 0) {
outputStream.write(buffer, 0, len);
// writer.write(new String(buffer));
}
objectResponse.close();
outputStream.close();
}
}
注意:
返回结果是 GetObjectResponse类,它继承了 InputStream类。
GetObjectResponse使用后返回必须关闭以释放网络资源。
此操作需要对此Object具有读权限
downloadObject方法
public void downloadObject(DownloadObjectArgs args):将对象的数据下载到磁盘
String bucketName = "java.minio.demo";
minioClient.downloadObject(DownloadObjectArgs.builder()
.bucket(bucketName)
.object("2023/02/25/demo2.jpg")
.filename("D:\Files\demo.jpg") // 必须指定文件名
.build());
getPresignedObjectUrl方法
public String getPresignedObjectUrl(GetPresignedObjectUrlArgs args):获取 HTTP 方法、到期时间和自定义请求参数的对象的预签名 URL。
示例:返回获取文件对象的URL GET请求,此 URL过期时间为一分钟。
String bucketName = "java.minio.demo";
String objectUrl = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
.bucket(bucketName)
.object("2023/02/25/demo2.jpg")
.method(Method.GET)
//.expiry(60) // 单位秒
.expiry(30, TimeUnit.SECONDS)
.build());
System.out.println(objectUrl);
删除对象
4.1 removeObject方法
public void removeObject(RemoveObjectArgs args) :移除一个对象。
String bucketName = "java.minio.demo";
minioClient.removeObject(RemoveObjectArgs.builder()
.bucket(bucketName)
.object("2023/02/25/demo2.jpg")
//.versionId("my-versionid") //还可以删除指定版本号的对象
.build());
removeObjects方法
public Iterable removeObjects(RemoveObjectsArgs args):懒惰地删除多个对象。它需要迭代返回的 Iterable 以执行删除。
示例:
String bucketName = "java.minio.demo";
// 构建需要删除的对象
List<DeleteObject> objects = new LinkedList<>();
objects.add(new DeleteObject("2023/02/25/demo1.jpg"));
objects.add(new DeleteObject("2023/02/25/demo2.jpg"));
objects.add(new DeleteObject("2023/02/25/demo3.jpg"));
// 删除
Iterable<Result<DeleteError>> results = minioClient.removeObjects(RemoveObjectsArgs.builder()
.bucket(bucketName)
.objects(objects)
.build());
for (Result<DeleteError> result : results) {
// 删除文件不存在时,不会报错
DeleteError error = result.get();
System.out.println("Error in deleting object " + error.objectName() + "; " + error.message());
}