小伙伴们好,欢迎关注,一起学习,无限进步
minio 是对象存储服务。它基于 Apache License 开源协议,兼容 Amazon S3 云存储接口。适合存储非结构化数据,如图片,音频,视频,日志等。对象文件最大可以达到 5TB。
优点有高性能,可扩展,操作简单,有图形化操作界面,读写性能优异等。官网
minio 部署可参考这篇:Minio 详细安装部署步骤
SpringBoot 快速整合 minio
1、添加 Maven 依赖
在
pom.xml
文件中添加MinIO客户端依赖项
<dependency><groupId>io.minio</groupId><artifactId>minio</artifactId><version>8.2.2</version></dependency><!-- 如果 minio 版本是8.3.4以上 okhttp 一定要大于 4.8.1 版本 --><!-- <dependency>--><!-- <groupId>com.squareup.okhttp3</groupId>--><!-- <artifactId>okhttp</artifactId>--><!-- <version>4.8.2</version>--><!-- </dependency>--><!-- 还用到了 fastjson --><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.78</version></dependency>
2、配置MinIO连接信息
在
application.properties
或
application.yml
文件中配置MinIO的连接信息,包括服务器地址、端口、凭据等信息
# Minio 配置
minio.endpoint=127.0.0.1:9000 #对象存储服务的URL
minio.accessKey=admin #Access key账户 写账号也可以
minio.secretKey=admin #Secret key密码
minio.bucketName=test # 桶名称
# 过期时间
minio.expire=7200
# Minio 配置minio:endpoint: 127.0.0.1:9000#对象存储服务的URLaccessKey: admin #Access key账户 写账号也可以secretKey: admin #Secret key密码bucketName: test # 桶名称expire:7200# 过期时间
3、创建 MinIO 客户端 Bean
在 SpringBoot 应用的配置类中创建一个 MinIO客户端的 Bean,以便在应用中使用 MinIO 服务
importlombok.Data;importorg.springframework.boot.context.properties.ConfigurationProperties;importorg.springframework.stereotype.Component;/**
* minio 配置属性
*/@Data@Component@ConfigurationProperties(prefix ="minio")publicclassMinioProperties{/**
* Minio 连接地址
*/privateString endpoint;/**
* accessKey 或 账号
*/privateString accessKey;/**
* secretKey 或 密码
*/privateString secretKey;/**
* 桶名称
*/privateString bucketName;/**
* 默认是秒 地址过期时间,设置默认值7200秒
*/privateint expire =7200;}
4、异常枚举类
/**
* 异常枚举类
*/publicenumExceptionEnums{FILE_NAME_NOT_NULL("0001","文件名不能为空"),BUCKET_NAME_NOT_NULL("0002","桶名称不能为空"),FILE_NOT_EXIST("0003","文件不存在"),BUCKET_NOT_EXIST("0004","桶不存在"),BUCKET_NAME_NOT_EXIST("0005","桶不存在,需要先创建桶在创建文件夹");//枚举类如果写方法的话,此处需要写分号privateString code;privateString msg;ExceptionEnums(String ecode,String emsg){this.code = ecode;this.msg = emsg;}publicStringgetCode(){return code;}publicStringgetMsg(){return msg;}publicstaticExceptionEnumsstatOf(String code){for(ExceptionEnums state :values())if(state.getCode().equals(code))return state;returnnull;}}
5、全局异常
importorg.springframework.http.HttpStatus;/**
* 异常
*/publicclassGeneralExceptionextendsRuntimeException{privateInteger errorCode;publicGeneralException(){}publicGeneralException(Throwable throwable){super(throwable);}publicGeneralException(String msg){super(msg);this.errorCode =HttpStatus.INTERNAL_SERVER_ERROR.value();}publicGeneralException(Integer errorCode,String msg){super(msg);this.errorCode = errorCode;}publicIntegergetErrorCode(){returnthis.errorCode;}publicvoidsetErrorCode(Integer errorCode){this.errorCode = errorCode;}}
6、minio 工具类
工具类包含创建 bucket,获取全部 bucket,获取 bucket 文件名和大小列表,文件上传,获取上传文件的完整路径,创建文件夹或目录,判断 bucket 是否存在,判断文件是否存在,文件下载,删除文件,批量删除文件方法
importcom.alibaba.fastjson.JSON;importio.minio.*;importio.minio.errors.*;importio.minio.http.Method;importio.minio.messages.Bucket;importio.minio.messages.DeleteError;importio.minio.messages.DeleteObject;importio.minio.messages.Item;importlombok.extern.slf4j.Slf4j;importorg.apache.tomcat.util.http.fileupload.IOUtils;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Component;importorg.springframework.util.CollectionUtils;importorg.springframework.util.StringUtils;importorg.springframework.web.multipart.MultipartFile;importjavax.servlet.http.HttpServletResponse;importjava.io.ByteArrayInputStream;importjava.io.IOException;importjava.io.InputStream;importjava.net.URLEncoder;importjava.security.InvalidKeyException;importjava.security.NoSuchAlgorithmException;importjava.text.DecimalFormat;importjava.util.*;/**
* Minio 工具类
*/@Component@Slf4jpublicclassMinioUtils{@AutowiredprivateMinioClient minioClient;@AutowiredprivateMinioProperties minioProperties;/**
* 初始化Bucket
*/privatevoidcreateBucket(String bucketName){try{// 判断 BucketName 是否存在if(bucketExists(bucketName)){
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());}}catch(Exception e){
e.printStackTrace();}}/**
* 验证bucketName是否存在
*
* @return boolean true:存在
*/publicbooleanbucketExists(String bucketName){if(StringUtils.isEmpty(bucketName)){thrownewGeneralException(ExceptionEnums.BUCKET_NAME_NOT_NULL.getMsg());}boolean flag =true;try{
flag = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());}catch(Exception e){
e.printStackTrace();}return flag;}/**
* 获取全部bucket
* <p>
*/publicList<String>getAllBuckets(){List<String> list =null;try{finalList<Bucket> buckets = minioClient.listBuckets();
list =newArrayList<>(buckets.size());for(Bucket bucket : buckets){
list.add(bucket.name());}}catch(Exception e){
e.printStackTrace();}return list;}/**
* 根据bucketName获取信息
*
* @param bucketName bucket名称
* @return
*/publicStringgetBucket(String bucketName)throwsException{finalOptional<Bucket> first = minioClient.listBuckets().stream().filter(b -> b.name().equals(bucketName)).findFirst();String name =null;if(first.isPresent()){
name = first.get().name();}return name;}/**
* 获取桶中文件名和大小列表
*
* @param bucketName bucket名称
* @param recursive 查询是否递归
* @return
*/publicList<Object>getFileList(String bucketName,boolean recursive){if(StringUtils.isEmpty(bucketName)){thrownewGeneralException(ExceptionEnums.BUCKET_NAME_NOT_NULL.getMsg());}List<Object> items =newArrayList<>();try{Iterable<Result<Item>> myObjects = minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).prefix("/2022-08-03/4674a894-abaf-48cb-9ea9-40a4e8560af9/Desktop").recursive(recursive).build());Iterator<Result<Item>> iterator = myObjects.iterator();String format ="{'fileName':'%s','fileSize':'%s'}";for(Result<Item> myObject : myObjects){System.out.println(myObject.get().objectName());}while(iterator.hasNext()){Item item = iterator.next().get();
items.add(JSON.parse(String.format(format, item.objectName(),formatFileSize(item.size()))));// items.add(JSON.parse(String.format(format, "/".concat("test").concat("/").concat(item.objectName()), formatFileSize(item.size()))));}}catch(Exception e){
e.printStackTrace();
log.info(e.getMessage());}
items.remove(0);return items;}/**
* 文件上传
*
* @param file
* @return
*/publicMap<String,Object>uploadFile(String bucketName,MultipartFile[] file){if(file ==null|| file.length ==0){thrownewGeneralException(ExceptionEnums.FILE_NAME_NOT_NULL.getMsg());}if(StringUtils.isEmpty(bucketName)){thrownewGeneralException(ExceptionEnums.BUCKET_NAME_NOT_NULL.getMsg());}List<String> orgfileNameList =newArrayList<>(file.length);for(MultipartFile multipartFile : file){String orgfileName = multipartFile.getOriginalFilename();
orgfileNameList.add(orgfileName);try{//文件上传InputStream in = multipartFile.getInputStream();
minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(orgfileName).stream(in, multipartFile.getSize(),-1).contentType(multipartFile.getContentType()).build());
in.close();}catch(Exception e){
e.printStackTrace();
log.error(e.getMessage());}}Map<String,Object> data =newHashMap<>();
data.put("bucketName", bucketName);
data.put("fileName", orgfileNameList);return data;}/**
* 获取上传文件的完整路径
*
* @param bucketName 桶名称
* @param fileName 文件名
* @param expire 地址过期时间
* @return
*/publicStringgetPresignedObjectUrl(String bucketName,String fileName,Integer expire){if(StringUtils.isEmpty(bucketName)){thrownewGeneralException(ExceptionEnums.BUCKET_NAME_NOT_NULL.getMsg());}if(StringUtils.isEmpty(fileName)){thrownewGeneralException(ExceptionEnums.FILE_NAME_NOT_NULL.getMsg());}
expire =Objects.isNull(expire)? minioProperties.getExpire(): expire;// 验证桶是否存在在finalboolean validationBucket =bucketExists(bucketName);if(!validationBucket){thrownewGeneralException(ExceptionEnums.BUCKET_NOT_EXIST.getMsg());}// 验证文件是否存在finalboolean validationFileName =doFileNameExist(bucketName, fileName);if(!validationFileName){thrownewGeneralException(ExceptionEnums.FILE_NOT_EXIST.getMsg());}String url =null;try{// 获取桶和文件的完整路径
url = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder().bucket(bucketName).object(fileName).method(Method.GET).expiry(expire).build());}catch(MinioException e){
log.error("Error occurred: "+ e);}catch(Exception e){
e.printStackTrace();}return url;}/**
* 创建文件夹或目录
*
* @param bucketName 存储桶
* @param objectName 目录路径
*/publicMap<String,String>putDirObject(String bucketName,String objectName)throwsIOException,InvalidKeyException,InvalidResponseException,InsufficientDataException,NoSuchAlgorithmException,ServerException,InternalException,XmlParserException,ErrorResponseException{// 判断桶是否存在if(!bucketExists(bucketName)){thrownewGeneralException(ExceptionEnums.BUCKET_NAME_NOT_EXIST.getMsg());}finalObjectWriteResponse response = minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectName).stream(newByteArrayInputStream(newbyte[]{}),0,-1).build());Map<String,String> map =newHashMap<>();
map.put("etag", response.etag());
map.put("versionId", response.versionId());return map;}/**
* 判断桶是否存在
*
* @param bucketName 存储桶
* @param objectName 文件夹名称(去掉/)
* @return true:存在
*/publicbooleandoFolderExist(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()){
exist =true;}}}catch(Exception e){
exist =false;}return exist;}/**
* 判断文件是否存在
*
* @param fileName 对象
* @return true:存在
*/publicbooleandoFileNameExist(String bucketName,String fileName){if(StringUtils.isEmpty(bucketName)){thrownewGeneralException(ExceptionEnums.BUCKET_NAME_NOT_NULL.getMsg());}if(StringUtils.isEmpty(fileName)){thrownewGeneralException(ExceptionEnums.FILE_NAME_NOT_NULL.getMsg());}boolean exist =true;try{
minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(fileName).build());}catch(Exception e){
exist =false;}return exist;}/**
* 文件下载
*
* @param response
* @param fileName
*/publicvoiddownloadFile(HttpServletResponse response,String bucketName,String fileName){if(StringUtils.isEmpty(bucketName)){thrownewGeneralException(ExceptionEnums.BUCKET_NAME_NOT_NULL.getMsg());}if(StringUtils.isEmpty(fileName)){thrownewGeneralException(ExceptionEnums.FILE_NAME_NOT_NULL.getMsg());}// 判断文件是否存在finalboolean flag =doFileNameExist(bucketName, fileName);if(!flag){thrownewGeneralException(ExceptionEnums.FILE_NOT_EXIST.getMsg());}InputStream in =null;try{// 获取对象信息StatObjectResponse stat = minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(fileName).build());
response.setContentType(stat.contentType());
response.setHeader("Content-Disposition","attachment;filename="+URLEncoder.encode(fileName,"UTF-8"));//文件下载
in = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(fileName).build());IOUtils.copy(in, response.getOutputStream());}catch(Exception e){
log.error(e.getMessage());}finally{if(in !=null){try{
in.close();}catch(IOException e){
log.error(e.getMessage());}}}}/**
* 删除文件
*
* @param bucketName bucket名称
* @param fileName 文件名称
* 说明:当前方法不能真正删除,需要验证
*/publicvoiddeleteFile(String bucketName,String fileName){if(StringUtils.isEmpty(bucketName)){thrownewGeneralException(ExceptionEnums.BUCKET_NAME_NOT_NULL.getMsg());}if(StringUtils.isEmpty(fileName)){thrownewGeneralException(ExceptionEnums.FILE_NAME_NOT_NULL.getMsg());}try{
minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(fileName).build());}catch(Exception e){
log.error(e.getMessage());
e.printStackTrace();}}/**
* 批量文件删除
*
* @param bucketName bucket名称
* @param fileNames 文件名
*/publicvoiddeleteBatchFile(String bucketName,List<String> fileNames){if(StringUtils.isEmpty(bucketName)){thrownewGeneralException(ExceptionEnums.BUCKET_NAME_NOT_NULL.getMsg());}if(CollectionUtils.isEmpty(fileNames)){thrownewGeneralException(ExceptionEnums.FILE_NAME_NOT_NULL.getMsg());}try{List<DeleteObject> objects =newLinkedList<>();for(String fileName : fileNames){
objects.add(newDeleteObject(fileName));}Iterable<Result<DeleteError>> results =
minioClient.removeObjects(RemoveObjectsArgs.builder().bucket(bucketName).objects(objects).build());for(Result<DeleteError> result : results){DeleteError error = result.get();
log.error("Error occurred: "+ error);}}catch(Exception e){
e.printStackTrace();
log.error("批量删除失败!error:{}", e);}}/**
* 文件大小
*
* @param fileS
* @return
*/privatestaticStringformatFileSize(long fileS){DecimalFormat df =newDecimalFormat("#.00");String fileSizeString ="";String wrongSize ="0B";if(fileS ==0){return wrongSize;}if(fileS <1024){
fileSizeString = df.format((double) fileS)+" B";}elseif(fileS <1048576){
fileSizeString = df.format((double) fileS /1024)+" KB";}elseif(fileS <1073741824){
fileSizeString = df.format((double) fileS /1048576)+" MB";}else{
fileSizeString = df.format((double) fileS /1073741824)+" GB";}return fileSizeString;}}
7、文件调用接口
上传文件,获取上传文件完整路径,文件下载,文件删除,批量删除
importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.util.StringUtils;importorg.springframework.web.bind.annotation.*;importorg.springframework.web.multipart.MultipartFile;importjavax.servlet.http.HttpServletResponse;importjava.util.List;importjava.util.Map;@RestControllerpublicclassMinioController{@AutowiredprivateMinioUtils minioUtils;/**
* 获取桶中文件名和大小列表
*
* @return
*/@GetMapping("/getFileList")publicList<Object>getFileList(){return minioUtils.getFileList("test",true);}/**
* 判断文件是否存在
*
* @param bucketName
* @param fileName
* @return
*/@GetMapping("/doFileNameExist")publicbooleandoFileNameExist(String bucketName,String fileName){return minioUtils.doFolderExist(bucketName, fileName);}/**
* 上传文件
*
* @param file
* @return
*/@PostMapping("/uploadFiles")publicMap<String,Object>uploadFiles(String bucketName,@RequestParam(name ="file", required =false)MultipartFile[] file){if(file ==null|| file.length ==0){thrownewGeneralException(ExceptionEnums.FILE_NAME_NOT_NULL.getMsg());}if(StringUtils.isEmpty(bucketName)){thrownewGeneralException(ExceptionEnums.BUCKET_NAME_NOT_NULL.getMsg());}return minioUtils.uploadFile(bucketName, file);}/**
* 获取上传文件的完整浏览路径
*
* @param filename
* @return
*/@GetMapping("/getPresignedObjectUrl")publicStringgetPresignedObjectUrl(@RequestParam(name ="filename")String filename){return minioUtils.getPresignedObjectUrl("test", filename,null);}/**
* 文件下载
*
* @param response
* @param fileName
*/@GetMapping("/downloadFile")publicvoiddownloadFile(HttpServletResponse response,@RequestParam("fileName")String fileName){
minioUtils.downloadFile(response,"test", fileName);}/**
* 删除单个文件
*
* @param fileName 完整路径(不包含bucket)
*/@DeleteMapping("/deleteFile")publicvoiddeleteFile(String bucketName,String fileName){
minioUtils.deleteFile(bucketName, fileName);}/**
* 批量删除文件
*
* @param fileNames 完整路径(不包含bucket)
*/@DeleteMapping("/deleteBatchFile")publicvoiddeleteBatchFile(String bucketName,@RequestParam("fileNames")List<String> fileNames){
minioUtils.deleteBatchFile(bucketName, fileNames);}}
版权归原作者 师小师 所有, 如有侵权,请联系我们删除。