springboot集成Minio
1、Minio单击安装
mkdir /data/minio cd /data/minio chmod +x minio mkdir data mkdir logs wget https://dl.min.io/server/minio/release/linux-amd64/minio
2、将Minio的账密写入环境变量中
vim /etc/profile export MINIO_ROOT_USER=username export MINIO_ROOT_PASSWORD=password source /etc/profile
3、启动Minio(9002-server端口,9001-web端口)
nohup ./minio server --address :9002 --console-address :9001 /data/minio/data > /data/minio/logs/minio.log 2>&1 &
4、Springboot依赖
<dependency> <groupId>io.minio</groupId> <artifactId>minio</artifactId> <version>7.1.0</version> </dependency>
5、yml配置
server: port: 8084 servlet: context-path: /minio minio: endpoint: http://ip:9002 domain: ${minio.endpoint}/${minio.bucketName} bucketName: zjk-minio accessKey: root secretKey: password secure: false spring: servlet: multipart: max-file-size: 100MB max-request-size: 150MB
6、MinioConfig
package com.zjk.config; import io.minio.MinioClient; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Data @Configuration @ConfigurationProperties(prefix = "minio") public class MinioConfig { private String endpoint; private String domain; private int port; private String accessKey; private String secretKey; private String bucketName; @Bean public MinioClient getMinioClient(){ MinioClient minioClient = MinioClient.builder().endpoint(endpoint) .credentials(accessKey, secretKey) .build(); return minioClient; } }
7、实现类
package com.zjk.common.utils.file; import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.StrUtil; import com.zjk.common.config.MinioConfig; 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.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletResponse; import java.io.*; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.List; import java.util.Optional; @Slf4j @Component public class MinioUtils { /** * 默认大小 500M */ public static final long DEFAULT_MAX_SIZE = 512 * 1024 * 1024; private static final int DEFAULT_EXPIRY_TIME = 7 * 24 * 3600; /** * 默认的文件名最大长度 100 */ public static final int DEFAULT_FILE_NAME_LENGTH = 100; @Autowired private MinioConfig minioConfig; @Autowired private MinioClient minioClient; /** * 本地文件上传接口 * * @param bucketName * @param file 上传的文件 * @return 访问地址 */ public String uploadFile(String bucketName, MultipartFile file) throws Exception { String fileName = file.getOriginalFilename(); PutObjectArgs args = PutObjectArgs.builder() .bucket(bucketName) .object(fileName) .stream(file.getInputStream(), file.getInputStream().available(), -1) //.contentType(file.getContentType()) //不这样写超过5M会报错 .contentType("application/octet-strem") .build(); minioClient.putObject(args); return minioConfig.getDomain() + "/" + fileName; } /** * 本地文件上传接口(上传到指定的目录) * * @param bucketName * @param file 上传的文件 * @return 访问地址 */ public String uploadFileWithPrefix(String bucketName, String directory, MultipartFile file) throws Exception { String fileName = file.getOriginalFilename(); InputStream inputStream = file.getInputStream(); PutObjectArgs args = PutObjectArgs.builder() .bucket(bucketName) .object(directory + "/" + fileName) .stream(inputStream, inputStream.available(), -1) .contentType("application/octet-strem") .build(); minioClient.putObject(args); return minioConfig.getDomain() + "/" + directory + "/" + fileName; } public List<String> modulesMultiUpload(String bucketName, MultipartFile[] files) throws Exception { List<String> urlList = new ArrayList<>(files.length); for (MultipartFile file : files) { String url = uploadFile(bucketName, file); urlList.add(url); } return urlList; } public void download(String bucketName, String originName, String fileName, HttpServletResponse response) { GetObjectArgs build = GetObjectArgs .builder() .bucket(bucketName) .object(fileName.replaceFirst("/" + bucketName + "/", "")) .build(); try (InputStream inputStream = minioClient.getObject(build); OutputStream outputStream = response.getOutputStream()) { String outputName = Optional .ofNullable(originName) .filter(StrUtil::isNotBlank) .orElse("项目文件." + fileName.substring(fileName.lastIndexOf(".") + 1)); // 获取文件对象 response.reset(); response.setCharacterEncoding("UTF-8"); response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(outputName, "UTF-8")); // 输出文件 // todo zys: 流直接转换 IoUtil.copy(inputStream, outputStream); } catch (Exception ex) { response.setHeader("Content-type", "text/html;charset=UTF-8"); String data = "文件下载失败"; try { OutputStream ps = response.getOutputStream(); ps.write(data.getBytes(StandardCharsets.UTF_8)); } catch (IOException e) { log.error("minio文件下载失败", e); } } } // public void multiDownload(String bucketName, List<Map<String, String>> mapList, HttpServletResponse response) { // // InputStream inputStream = null; // OutputStream outputStream = null; // try { // for (int i = 0; i < mapList.size(); i++) { // String originName = mapList.get(i).get("originName"); // String fileName = mapList.get(i).get("fileName"); // if(StringUtils.isEmpty(originName)){ // originName = URLEncoder.encode(fileName.substring(fileName.lastIndexOf("/") + 1), "UTF-8"); // } // outputStream = response.getOutputStream(); // // 获取文件对象 // inputStream = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(fileName.replaceFirst("/" + bucketName + "/", "")).build()); // byte[] buf = new byte[1024]; // int length = 0; // response.setHeader("Content-Disposition", "attachment;filename=" + // URLEncoder.encode(fileName.substring(fileName.lastIndexOf("/") + 1), "UTF-8")); // response.setContentType("application/octet-stream"); // response.setCharacterEncoding("UTF-8"); // // 输出文件 // while ((length = inputStream.read(buf)) > 0) { // outputStream.write(buf, 0, length); // } // } // // } catch (Throwable ex) { // response.reset(); // response.setHeader("Content-type", "text/html;charset=UTF-8"); // String data = "文件下载失败"; // try { // OutputStream ps = response.getOutputStream(); // ps.write(data.getBytes(StandardCharsets.UTF_8)); // } catch (IOException e) { // log.error("minio文件下载失败", e); // } // } finally { // try { // if (outputStream != null) { // outputStream.close(); // } // if (inputStream != null) { // inputStream.close(); // } // } catch (IOException e) { // log.error("minio文件流关闭失败", e); // } // } // } /** * 检查存储桶是否存在 * * @param bucketName 存储桶名称 * @return boolean */ public boolean bucketExists(String bucketName) throws ServerException, InvalidBucketNameException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException { return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build()); } /** * 创建存储桶 * * @param bucketName 存储桶名称 */ public boolean makeBucket(String bucketName) throws InvalidBucketNameException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException, RegionConflictException, ServerException { boolean flag = bucketExists(bucketName); if (!flag) { minioClient.makeBucket( MakeBucketArgs.builder() .bucket(bucketName) .build()); return true; } else { return false; } } /** * 列出所有存储桶名称 * * @return List<String> */ public List<String> listBucketNames() throws io.minio.errors.ServerException, InvalidBucketNameException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException { List<Bucket> bucketList = minioClient.listBuckets(); List<String> bucketListName = new ArrayList<>(); for (Bucket bucket : bucketList) { bucketListName.add(bucket.name()); } return bucketListName; } /** * 删除存储桶 * * @param bucketName 存储桶名称 * @return boolean */ public boolean removeBucket(String bucketName) throws InvalidBucketNameException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException, io.minio.errors.ServerException { 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; } } // 删除存储桶,注意,只有存储桶为空时才能删除成功。 minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build()); flag = bucketExists(bucketName); if (!flag) { return true; } } return false; } /** * 列出存储桶中的所有对象 * * @param bucketName 存储桶名称 * @return Iterable<Result<Item>> */ public Iterable<Result<Item>> listObjects(String bucketName) throws XmlParserException, IOException, InvalidResponseException, InvalidKeyException, NoSuchAlgorithmException, ServerException, ErrorResponseException, InvalidBucketNameException, InsufficientDataException, InternalException { boolean flag = bucketExists(bucketName); if (flag) { return minioClient.listObjects( ListObjectsArgs.builder().bucket(bucketName).build()); } return null; } /** * 删除一个对象 * * @param bucketName 存储桶名称 * @param objectName 存储桶里的对象名称 */ public boolean removeObject(String bucketName, String objectName) throws InvalidBucketNameException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException, io.minio.errors.ServerException { boolean flag = bucketExists(bucketName); if (flag) { minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build()); return true; } return false; } /** * 删除一个对象 * * @param bucketName 存储桶名称 * @param objectName 存储桶里的对象名称 */ public boolean removeObjectWithPrefix(String bucketName, String prefix, String objectName) throws InvalidBucketNameException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException, io.minio.errors.ServerException { boolean flag = bucketExists(bucketName); if (flag) { String objectPath = prefix + "/" + objectName; minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectPath).build()); return true; } return false; } /** * 删除指定桶的多个文件对象,返回删除错误的对象列表,全部删除成功,返回空列表 * * @param bucketName 存储桶名称 * @param objectNames 含有要删除的多个object名称的迭代器对象 * @return * eg: * List<DeleteObject> objects = new LinkedList<>(); * objects.add(new DeleteObject("my-objectname1")); * objects.add(new DeleteObject("my-objectname2")); * objects.add(new DeleteObject("my-objectname3")); */ public List<String> removeObjects(String bucketName, List<DeleteObject> objectNames) throws InvalidBucketNameException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException, io.minio.errors.ServerException { List<String> deleteErrorNames = new ArrayList<>(); boolean flag = bucketExists(bucketName); if (flag) { Iterable<Result<DeleteError>> results = minioClient.removeObjects(RemoveObjectsArgs.builder().bucket(bucketName).objects(objectNames).build()); for (Result<DeleteError> result : results) { DeleteError error = result.get(); deleteErrorNames.add(error.objectName()); } } return deleteErrorNames; } /** * 生成一个给HTTP GET请求用的presigned URL。 * 浏览器/移动端的客户端可以用这个URL进行下载,即使其所在的存储桶是私有的。这个presigned URL可以设置一个失效时间,默认值是7天。 * * @param bucketName 存储桶名称 * @param objectName 存储桶里的对象名称 * @param expires 失效时间(以秒为单位),默认是7天,不得大于七天 * @return */ public String getPresignedObjectUrl(String bucketName, String objectName, Integer expires) throws ServerException, InvalidBucketNameException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException, InvalidExpiresRangeException { boolean flag = bucketExists(bucketName); String url = ""; if (flag) { if (expires < 1 || expires > DEFAULT_EXPIRY_TIME) { throw new InvalidExpiresRangeException(expires, "expires must be in range of 1 to " + DEFAULT_EXPIRY_TIME); } try { url = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder() .method(Method.GET) .bucket(bucketName) .object(objectName) .expiry(expires)//动态参数 // .expiry(24 * 60 * 60)//用秒来计算一天时间有效期 // .expiry(1, TimeUnit.DAYS)//按天传参 // .expiry(1, TimeUnit.HOURS)//按小时传参数 .build()); } catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidBucketNameException | InvalidExpiresRangeException | InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException | XmlParserException e) { log.info(e.getMessage()); } } return url; } /** * 文件访问路径,这里指没有nginx代理,直接从minio自带9002端口访问的情况下 * 一般会使用nginx代理一遍,以保证和web服务的端口一致 * @param bucketName 存储桶名称 * @param objectName 存储桶里的对象名称 * @return String */ public String getObjectUrl(String bucketName, String objectName) throws InvalidBucketNameException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException, ServerException { boolean flag = bucketExists(bucketName); String url = ""; if (flag) { try { url = minioClient.getObjectUrl(bucketName, objectName); } catch (ErrorResponseException e) { log.error("XmlParserException",e); } catch (InsufficientDataException e) { log.error("InsufficientDataException",e); } catch (InternalException e) { log.error("InternalException",e); } catch (InvalidBucketNameException e) { log.error("InvalidBucketNameException",e); } catch (InvalidKeyException e) { log.error("InvalidKeyException",e); } catch (InvalidResponseException e) { log.error("InvalidResponseException",e); } catch (IOException e) { log.error("IOException",e); } catch (NoSuchAlgorithmException e) { log.error("NoSuchAlgorithmException",e); } catch (XmlParserException e) { log.error("XmlParserException",e); } catch (io.minio.errors.ServerException e) { throw new RuntimeException(e); } } return url; } public byte[] getObject(String bucketName, String objectName) throws ServerException, InvalidBucketNameException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException { try (InputStream stream = minioClient.getObject( GetObjectArgs.builder() .bucket(bucketName) .object(objectName) .build()); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) { byte[] buffer = new byte[1024]; int length; while ((length = stream.read(buffer)) != -1) { byteArrayOutputStream.write(buffer, 0, length); } return byteArrayOutputStream.toByteArray(); } } public File getFileFromMinioAsStream(String bucketName, String fileName, String originName) throws IOException { GetObjectArgs build = GetObjectArgs .builder() .bucket(bucketName) .object(fileName.replaceFirst("/" + bucketName + "/", "")) .build(); try (InputStream inputStream = minioClient.getObject(build)) { // 创建一个临时文件 int lastIndex = originName.lastIndexOf("."); // 截取最后一个分隔符前的部分 String beforeLastDelimiter = originName.substring(0, lastIndex); // 截取最后一个分隔符后的部分 String afterLastDelimiter = originName.substring(lastIndex + ".".length()); Path tempFilePath = Files.createTempFile(beforeLastDelimiter, "." + afterLastDelimiter); File tempFile = tempFilePath.toFile(); // 将输入流写入临时文件 try (FileOutputStream outputStream = new FileOutputStream(tempFile)) { byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); } } // 返回临时文件的File对象(注意:这里返回的File对象指向的是临时文件,你可能需要在不再需要时删除它) return tempFile; } catch (MinioException e) { // 处理MinIO相关的异常 e.printStackTrace(); throw new IOException("Failed to download file from MinIO", e); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } catch (InvalidKeyException e) { throw new RuntimeException(e); } } }