Minio作为高效存储的系统,提供增删改查的一些方法,工具类如下:
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Maps;
import io.minio.BucketExistsArgs;
import io.minio.GetObjectArgs;
import io.minio.GetPresignedObjectUrlArgs;
import io.minio.ListObjectsArgs;
import io.minio.MakeBucketArgs;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import io.minio.RemoveObjectArgs;
import io.minio.RemoveObjectsArgs;
import io.minio.Result;
import io.minio.SetBucketPolicyArgs;
import io.minio.http.Method;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.web.multipart.MultipartFile;
import javax.imageio.ImageIO;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@Slf4j
@Component
public class MinioUtil {
private static final MinioClient client = SpringUtil.getBean(MinioClient.class);
// @Autowired
// private MinioProperties minioProperties;
private static final String BUCKET_PARAM = "${bucket}";
/**
* bucket权限-读写
*/
private static final String READ_WRITE = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:GetBucketLocation\",\"s3:ListBucket\",\"s3:ListBucketMultipartUploads\"],\"Resource\":[\"arn:aws:s3:::" + BUCKET_PARAM + "\"]},{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:DeleteObject\",\"s3:GetObject\",\"s3:ListMultipartUploadParts\",\"s3:PutObject\",\"s3:AbortMultipartUpload\"],\"Resource\":[\"arn:aws:s3:::" + BUCKET_PARAM + "/*\"]}]}";
/**
* 获取URL
*
* @param objectName
* @param bucketName
* @param time
* @param timeUnit
* @return
*/
public static String getUrl(String objectName, String bucketName, int time, TimeUnit timeUnit) {
if (CharSequenceUtil.isBlank(bucketName)) {
bucketName = SpringUtil.getBean(MinioProperties.class).getBucketName();
}
String url = null;
try {
url = client.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
.method(Method.GET)
.bucket(bucketName)
.object(objectName)
.expiry(time, timeUnit).build());
}
catch (Exception e) {
log.error(e.getMessage(), e);
}
return url;
}
/**
* 创建bucket
*/
public static void createBucket(String bucketName) throws Exception {
if (CharSequenceUtil.isBlank(bucketName)) {
bucketName = SpringUtil.getBean(MinioProperties.class).getBucketName();
}
if (!client.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())) {
client.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
createBucketPolicy(bucketName);
}
}
/**
* 设置桶策略
*
* @param bucketName 桶名称
*/
@SneakyThrows(Exception.class)
public static void createBucketPolicy(String bucketName) {
client.setBucketPolicy(SetBucketPolicyArgs.builder().bucket(bucketName).config(READ_WRITE.replace(BUCKET_PARAM, bucketName)).build());
}
/**
* 上传文件
*/
public static Map uploadFile(MultipartFile file, String bucketName) throws Exception {
if (CharSequenceUtil.isBlank(bucketName)) {
bucketName = SpringUtil.getBean(MinioProperties.class).getBucketName();
}
Map<String, Object> map = Maps.newHashMap();
//判断文件是否为空
if (null == file || 0 == file.getSize()) {
return null;
}
//判断存储桶是否存在 不存在则创建
createBucket(bucketName);
//上传到桶中的文件的路径 使用当前日期作为文件存储路径
String data = DateUtil.format(new Date(), "yyyyMMdd") + StrUtil.SLASH;
//文件名
String originalFilename = file.getOriginalFilename();
assert originalFilename != null;
//开始上传
client.putObject(
PutObjectArgs.builder().bucket(bucketName).object(data + originalFilename).stream(
file.getInputStream(), file.getSize(), -1)
.contentType(file.getContentType())
.build());
String url = SpringUtil.getBean(MinioProperties.class).getUrl() + StrUtil.SLASH + bucketName + StrUtil.SLASH + data + originalFilename;
//fileName /路径/文件名
String fileName = StrUtil.SLASH + data + originalFilename;
log.info("上传文件成功url :[{}],", url);
map.put("url", url);
map.put("fileName", fileName);
return map;
}
/**
* Minio上传文件 通用
*
* @param localFilePath 文件路径
* @param bucketName 桶的名字
* @param contentType 文件类型,image/jpeg,video/mp4
* @return
* @throws Exception
*/
public static MinioUploadResponseDto uploadLocalFile(String localFilePath, String bucketName, String contentType) throws Exception {
if (StrUtil.isBlank(bucketName)) {
bucketName = SpringUtil.getBean(MinioProperties.class).getBucketName();
}
// 判断文件是否为空
Path filePath = Paths.get(localFilePath);
Assert.isTrue(Files.exists(filePath), "File does not exist: " + localFilePath);
// 判断存储桶是否存在,不存在则创建
createBucket(bucketName);
/**
*
* 上传到桶中的文件的路径,使用当前日期作为文件存储路径
* data代表的是一个日期或者其他类型的前缀,从存储桶根目录开始创建这个前缀,不需要在data前面添加斜杠,是虚拟的相对路径
*/
String data = DateUtil.format(new Date(), "yyyyMMdd") + File.separator;
// 文件名
String originalFilename = filePath.getFileName().toString();
// 获取内容类型,如果为 null,则设置默认类型
if (StringUtils.isBlank(contentType)) {
contentType = Files.probeContentType(filePath);
if (null == contentType) {
log.warn("Content type is not detected, using default: application/octet-stream");
contentType = "application/octet-stream";
}
}
// 开始上传
client.putObject(PutObjectArgs.builder()
.bucket(bucketName)
.object(data + originalFilename)
.stream(Files.newInputStream(filePath), Files.size(filePath), -1)
.contentType(contentType)
.build());
String url = SpringUtil.getBean(MinioProperties.class).getUrl() + File.separator + bucketName + File.separator + data + originalFilename;
// fileName :不要加/,否则会多拼接 ,路径/文件名
String fileName = data + originalFilename;
log.info("上传文件成功url :[{}]", url);
MinioUploadResponseDto minioUploadResponseDto = new MinioUploadResponseDto();
minioUploadResponseDto.setUrl(url);
minioUploadResponseDto.setFileName(fileName);
return minioUploadResponseDto;
}
/**
* 上传文件
*
* @param file 文件
* @return {@link String}
* @throws Exception 异常
*/
public static String uploadFile(MultipartFile file) throws Exception {
String bucketName = SpringUtil.getBean(MinioProperties.class).getBucketName();
//判断文件是否为空
if (null == file || 0 == file.getSize()) {
return null;
}
//判断存储桶是否存在 不存在则创建
createBucket(bucketName);
//上传到桶中的文件的路径 使用当前日期作为文件存储路径
String data = DateUtil.format(new Date(), "yyyyMMdd") + StrUtil.SLASH;
//文件名
String originalFilename = file.getOriginalFilename() + IdUtil.fastSimpleUUID();
//开始上传
client.putObject(
PutObjectArgs.builder().bucket(bucketName).object(data + originalFilename).stream(
file.getInputStream(), file.getSize(), -1)
.contentType(file.getContentType())
.build());
String url = SpringUtil.getBean(MinioProperties.class).getUrl() + StrUtil.SLASH + bucketName + StrUtil.SLASH + data + originalFilename;
log.info("上传文件成功url :[{}],", url);
return url;
}
/**
* 删除一个对象
*/
public static void deleteObject(String bucketName, String objectName) throws Exception {
if (CharSequenceUtil.isBlank(bucketName)) {
bucketName = SpringUtil.getBean(MinioProperties.class).getBucketName();
}
client.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build());
}
/**
* 批量删除
*/
public static void deleteObjects(String bucketName, List<String> objectNames) throws Exception {
if (CharSequenceUtil.isBlank(bucketName)) {
bucketName = SpringUtil.getBean(MinioProperties.class).getBucketName();
}
List<DeleteObject> objects = objectNames.stream().map(DeleteObject::new).collect(Collectors.toList());
// 构建RemoveObjectsArgs实例
RemoveObjectsArgs removeObjectsArgs = RemoveObjectsArgs.builder()
.bucket(bucketName)
.objects(objects)
.build();
client.removeObjects(removeObjectsArgs);
}
/**
* 根据桶名称删除截止时间之前的数据
*
* @param bucketName 桶的名字
* @param cutoffDate 截止时间
* @throws Exception
*/
public static void deleteObjectsByBucketName(String bucketName, Instant cutoffDate) throws Exception {
if (CharSequenceUtil.isBlank(bucketName)) {
bucketName = SpringUtil.getBean(MinioProperties.class).getBucketName();
}
//列出桶中的所有对象
//recursive参数设置为true,以递归地列出所有子文件夹中的对象。
Iterable<Result<Item>> results = client.listObjects(ListObjectsArgs.builder().bucket(bucketName).recursive(true).build());
for (Result<Item> result : results) {
Item item = result.get();
// 获取对象的最后修改时间
// ZonedDateTime zonedDateTime = item.lastModified();
// log.info("item name:{}", JSONObject.toJSONString(item));
//item name:{"deleteMarker":false,"dir":true,"latest":false}
if (!item.isDir()) {
Instant lastModified = item.lastModified().toInstant();
if (lastModified.isBefore(cutoffDate)) {
// 如果对象的最后修改时间早于指定日期,则删除
// 构建RemoveObjectsArgs实例
RemoveObjectArgs removeObjectsArgs = RemoveObjectArgs.builder().bucket(bucketName).object(item.objectName()).build();
client.removeObject(removeObjectsArgs);
log.info("Deleted object:{}", item.objectName());
}
}
}
}
/**
* 下载一个文件
*/
public static InputStream download(String bucketName, String objectName) {
if (CharSequenceUtil.isBlank(bucketName)) {
bucketName = SpringUtil.getBean(MinioProperties.class).getBucketName();
}
try {
return client.getObject(
GetObjectArgs.builder().bucket(bucketName).object(objectName).build());
}
catch (Exception e) {
throw new ApiException("下载文件失败");
}
}
/**
* 下载视频
*
* @param bucketName
* @param objectName
* @return
*/
public static InputStream downloadVideo(String bucketName, String objectName) {
if (CharSequenceUtil.isBlank(bucketName)) {
bucketName = SpringUtil.getBean(MinioProperties.class).getBucketName();
}
try {
return client.getObject(
GetObjectArgs.builder().bucket(bucketName).object(objectName).build());
}
catch (Exception e) {
throw new ApiException("下载文件失败");
}
}
/**
* 获取图片;列表
*
* @param bucketName
* @param objectNameList
* @return
*/
public static ImageResponse downloadList(String bucketName, List<String> objectNameList) {
if (CharSequenceUtil.isBlank(bucketName)) {
bucketName = SpringUtil.getBean(MinioProperties.class).getBucketName();
}
ImageResponse response = new ImageResponse();
try {
for (String imageName : objectNameList) {
try (InputStream inputStream = client.getObject(
GetObjectArgs.builder().bucket(bucketName).object(imageName).build())) {
// ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
// byte[] buffer = new byte[4096];
// int n = 0;
// while (-1 != (n = inputStream.read(buffer))) {
// outputStream.write(buffer, 0, n);
// }
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(ImageIO.read(inputStream), "jpg", baos);
response.getImageData().put(imageName, baos.toByteArray());
}
}
}
catch (Exception e) {
throw new ApiException("下载文件失败");
}
return response;
}
/**
* 下载资源
*
* @param bucketName
* @param objectNameList
* @return
*/
public static ResponseEntity<InputStreamResource> download(String bucketName, List<String> objectNameList) {
if (CharSequenceUtil.isBlank(bucketName)) {
bucketName = SpringUtil.getBean(MinioProperties.class).getBucketName();
}
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream)) {
for (String objectName : objectNameList) {
// 从MinIO获取对象
try (InputStream inputStream = client.getObject(
GetObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.build())) {
// 创建ZIP条目
ZipEntry zipEntry = new ZipEntry(Paths.get(objectName).getFileName().toString());
zipEntry.setMethod(ZipEntry.DEFLATED); // 设置压缩方法
zipOutputStream.putNextEntry(zipEntry);
// 将输入流复制到ZIP输出流
// IOUtils.copy(inputStream, zipOutputStream);
byte[] buffer = new byte[4096];
int n = 0;
while (-1 != (n = inputStream.read(buffer))) {
zipOutputStream.write(buffer, 0, n);
}
zipOutputStream.closeEntry();
}
catch (Exception e) {
log.error(e.getMessage(), e);
}
}
// 刷新缓冲区,确保所有数据都被写入
zipOutputStream.finish();
zipOutputStream.flush();
zipOutputStream.close();
// 设置响应头
HttpHeaders headers = new HttpHeaders();
String name = "attachment;filename=" + DateUtils.getYyyyMMddHHmmss() + ".zip";
headers.add(HttpHeaders.CONTENT_DISPOSITION, name);
headers.add(HttpHeaders.CONTENT_LENGTH, String.valueOf(byteArrayOutputStream.size()));
// 返回ZIP文件作为附件
return ResponseEntity.ok()
.headers(headers)
.contentType(MediaType.APPLICATION_OCTET_STREAM)
// .contentType(new MediaType("application", "zip", StandardCharsets.UTF_8))
.body(new InputStreamResource(new ByteArrayInputStream(byteArrayOutputStream.toByteArray())));
}
catch (Exception e) {
log.error("下载附件失败", e);
}
return null;
}
/**
* 文件系统压缩
*
* @param bucketName
* @param objectNameList
* @return
*/
public static ResponseEntity<FileSystemResource> downloadResource(String bucketName, List<String> objectNameList) {
if (CharSequenceUtil.isBlank(bucketName)) {
bucketName = SpringUtil.getBean(MinioProperties.class).getBucketName();
}
File tempFile = null;
try {
// 创建临时文件
tempFile = File.createTempFile("temp", ".zip");
try (FileOutputStream fileOutputStream = new FileOutputStream(tempFile);
ZipOutputStream zipOutputStream = new ZipOutputStream(fileOutputStream, StandardCharsets.UTF_8)) {
for (String objectName : objectNameList) {
// 从MinIO获取对象
try (InputStream inputStream = client.getObject(
GetObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.build())) {
// 创建ZIP条目
ZipEntry zipEntry = new ZipEntry(Paths.get(objectName).getFileName().toString());
zipEntry.setMethod(ZipEntry.DEFLATED); // 设置压缩方法
zipOutputStream.putNextEntry(zipEntry);
// 将输入流复制到ZIP输出流
// IOUtils.copy(inputStream, zipOutputStream);
byte[] buffer = new byte[4096];
int n = 0;
while (-1 != (n = inputStream.read(buffer))) {
zipOutputStream.write(buffer, 0, n);
}
zipOutputStream.closeEntry(); // 结束当前条目
}
catch (Exception e) {
log.error(e.getMessage(), e);
}
}
// 刷新缓冲区,确保所有数据都被写入
zipOutputStream.finish();
zipOutputStream.flush();
zipOutputStream.close();
// 设置响应头
HttpHeaders headers = new HttpHeaders();
String name = "attachment; filename=" + DateUtils.getYyyyMMddHHmmss() + ".zip";
headers.add(HttpHeaders.CONTENT_DISPOSITION, name);
headers.add(HttpHeaders.CONTENT_LENGTH, String.valueOf(tempFile.length()));
// 返回ZIP文件作为附件
return ResponseEntity.ok()
.headers(headers)
// .contentType(MediaType.APPLICATION_OCTET_STREAM)
.contentType(new MediaType("application", "zip", StandardCharsets.UTF_8))
.body(new FileSystemResource(tempFile));
}
}
catch (Exception e) {
log.error("下载附件失败", e);
if (tempFile != null && tempFile.exists()) {
tempFile.delete(); // 清理临时文件
}
}
finally {
// 异步删除临时文件
File finalTempFile = tempFile;
CompletableFuture.runAsync(() -> {
if (finalTempFile != null && finalTempFile.exists()) {
boolean deleted = finalTempFile.delete();
if (!deleted) {
log.error("Failed to delete temporary file:{} ", finalTempFile.getAbsolutePath());
}
else {
log.info("Temporary file deleted:{}", finalTempFile.getAbsolutePath());
}
}
});
}
return null;
}
/**
* 下载图片
*
* @param bucketName
* @param objectNameList
* @return
*/
public static ResponseEntity<InputStreamResource> downloadImage(String bucketName, List<String> objectNameList) {
if (CharSequenceUtil.isBlank(bucketName)) {
bucketName = SpringUtil.getBean(MinioProperties.class).getBucketName();
}
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream)) {
for (String objectName : objectNameList) {
// 从MinIO获取对象
try (InputStream inputStream = client.getObject(
GetObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.build())) {
// 创建ZIP条目
ZipEntry zipEntry = new ZipEntry(Paths.get(objectName).getFileName().toString());
zipEntry.setMethod(ZipEntry.DEFLATED); // 设置压缩方法
zipOutputStream.putNextEntry(zipEntry);
// 将输入流复制到ZIP输出流
IOUtils.copy(inputStream, zipOutputStream);
zipOutputStream.closeEntry();
// 关闭输入流
// inputStream.close();
}
}
// 刷新缓冲区,确保所有数据都被写入
zipOutputStream.finish();
zipOutputStream.flush();
zipOutputStream.close();
// 设置响应头
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=images.zip");
headers.add(HttpHeaders.CONTENT_LENGTH, String.valueOf(byteArrayOutputStream.size()));
// 返回ZIP文件作为附件
return ResponseEntity.ok()
.headers(headers)
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(new InputStreamResource(new ByteArrayInputStream(byteArrayOutputStream.toByteArray())));
}
catch (Exception e) {
throw new ApiException("下载图片失败");
}
}
public static String exportAndZipFiles(String bucketName, String zipFileName, String... fileNames) {
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream)) {
for (String fileName : fileNames) {
try (InputStream objectStream = client.getObject(
GetObjectArgs.builder().bucket(bucketName).object(fileName).build())) {
ZipEntry zipEntry = new ZipEntry(fileName);
zipOutputStream.putNextEntry(zipEntry);
byte[] buffer = new byte[1024];
int length;
while ((length = objectStream.read(buffer)) > 0) {
zipOutputStream.write(buffer, 0, length);
}
zipOutputStream.closeEntry();
}
catch (Exception e) {
e.printStackTrace();
}
}
zipOutputStream.finish();
try (InputStream zipInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray())) {
client.putObject(
PutObjectArgs.builder()
.bucket(bucketName)
.object(zipFileName)
.stream(zipInputStream, byteArrayOutputStream.size(), -1)
.contentType("application/zip")
.build()
);
}
catch (Exception e) {
log.error("exportAndZipFiles error:{}", e.getMessage());
}
}
catch (IOException e) {
log.error("exportAndZipFiles error:{}", e.getMessage());
}
return MinioUtil.getUrl(zipFileName, bucketName, 7, TimeUnit.DAYS);
}
}