• Spring Boot 集成 MinIO 实现文件上传


    Spring Boot 集成 MinIO 实现文件上传

    一、 Minio 服务准备

    MinIO的搭建过程参考 Docker 搭建 MinIO 对象存储

    登录MinIO控制台,新建一个 Bucket,修改 Bucket 权限为公开。

    在这里插入图片描述

    二、MinIO 集成

    1. 添加 MinIO 依赖
    
    <dependency>
        <groupId>io.miniogroupId>
        <artifactId>minioartifactId>
        <version>${minio.version}version>
    dependency>
    
    1. 在项目配置文件application.yml中添加自定义配置。properties 文件自行转换
    minio:
      host: http://【服务器公网ip】:【minio运行端口号,默认9000】/
      access-key: 账号
      secret-key: 密码
    
    1. 创建配置文件类
    @Data
    @Component
    public class MinioConfig {
     
        @Value(value = "${minio.host}")
        private String host;
     
        @Value(value = "${minio.access-key}")
        private String accessKey;
     
        @Value(value = "${minio.secret-key}")
        private String secretKey;
    
        @Bean
        public MinioClient minioClient(){
    
            return MinioClient.builder()
                    .endpoint(host)
                    .credentials(accessKey, secretKey)
                    .build();
        }
    }
    
    1. 创建文件上传工具类
    @Component
    @Slf4j
    @AllArgsConstructor
    public class MinioUtils {
    
        private final MinioClient minioClient;
        private final MinioConfig minioConfig;
    
        /**
         * 初始化Bucket
         */
        private void createBucket(String bucketName) {
            // 设置公开读写
            String POLICY_PATTERN = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:GetObject\"],\"Resource\":[\"arn:aws:s3:::%s/*\"]}]}";
            try {
                // 判断 BucketName 是否存在
                if (!bucketExists(bucketName)) {
                    minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
                }
                minioClient.setBucketPolicy(SetBucketPolicyArgs.builder().bucket(bucketName).config(
                        String.format(POLICY_PATTERN, bucketName)
                ).build());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 验证bucketName是否存在
         *
         * @return boolean true:存在
         */
        public boolean bucketExists(String bucketName) {
            if (StringUtils.isBlank(bucketName)) {
                throw new ServerException(ErrorCode.BUCKET_NAME_NOT_NULL);
            }
            boolean flag = true;
            try {
                flag = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
            } catch (Exception e) {
                e.printStackTrace();
            }
            return flag;
        }
    
    
        /**
         * 获取全部bucket
         * 

    */ public List<String> getAllBuckets() { List<String> list = null; try { final List<Bucket> buckets = minioClient.listBuckets(); list = new ArrayList<>(buckets.size()); for (Bucket bucket : buckets) { list.add(bucket.name()); } } catch (Exception e) { e.printStackTrace(); } return list; } /** * 根据bucketName获取信息 * * @param bucketName bucket名称 * @return */ public String getBucket(String bucketName) throws Exception { final Optional<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 */ public List<Object> getFileList(String bucketName, boolean recursive) { if (StringUtils.isEmpty(bucketName)) { throw new ServerException(ErrorCode.BUCKET_NAME_NOT_NULL); } List<Object> items = new ArrayList<>(); 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 bucketName 存储桶名称 * @param file file * @return map */ public Map<String, Object> uploadFile(String bucketName, MultipartFile[] file) { if (file == null || file.length == 0) { throw new ServerException(ErrorCode.FILE_NAME_NOT_NULL); } createBucket(bucketName); List<String> urlList = new ArrayList<>(file.length); for (MultipartFile multipartFile : file) { String originFileName = multipartFile.getOriginalFilename(); if (StringUtils.isBlank(originFileName)) { throw new ServerException(ErrorCode.FILE_NAME_NOT_NULL); } String[] originFileNameArr = originFileName.split("\\."); String suffix = originFileNameArr[originFileNameArr.length - 1]; String newFileName = UUID.randomUUID().toString().replace("-", "").concat(".").concat(suffix); urlList.add(String.format("%s%s/%s", minioConfig.getHost(), bucketName, newFileName)); try { // 文件上传 InputStream in = multipartFile.getInputStream(); minioClient.putObject(PutObjectArgs.builder() .bucket(bucketName) .object(newFileName) .stream(in, multipartFile.getSize(), -1) .contentType(multipartFile.getContentType()) .build()); in.close(); } catch (Exception e) { log.error(e.getMessage()); } } Map<String, Object> data = new HashMap<>(); data.put("bucketName", bucketName); data.put("urlList", urlList); return data; } /** * 获取上传文件的完整路径 * * @param bucketName 桶名称 * @param fileName 文件名 * @return */ public String getPresignedObjectUrl(String bucketName, String fileName) { if (StringUtils.isEmpty(bucketName)) { throw new ServerException(ErrorCode.BUCKET_NAME_NOT_NULL); } if (StringUtils.isEmpty(fileName)) { throw new ServerException(ErrorCode.FILE_NAME_NOT_NULL); } // 验证桶是否存在在 final boolean validationBucket = bucketExists(bucketName); if (!validationBucket) { throw new ServerException(ErrorCode.BUCKET_NOT_EXIST); } // 验证文件是否存在 final boolean validationFileName = doFileNameExist(bucketName, fileName); if (!validationFileName) { throw new ServerException(ErrorCode.FILE_NOT_EXIST); } String url = null; try { // 获取桶和文件的完整路径 url = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder() .bucket(bucketName) .object(fileName) .method(Method.GET) .build()); } catch (MinioException e) { log.error("Error occurred: " + e); } catch (Exception e) { e.printStackTrace(); } return url; } /** * 创建文件夹或目录 * * @param bucketName 存储桶 * @param objectName 目录路径 */ public Map<String, String> putDirObject(String bucketName, String objectName) throws Exception { // 判断桶是否存在 if (!bucketExists(bucketName)) { throw new ServerException(ErrorCode.BUCKET_NAME_NOT_EXIST); } final ObjectWriteResponse response = minioClient.putObject( PutObjectArgs.builder().bucket(bucketName).object(objectName).stream( new ByteArrayInputStream(new byte[]{}), 0, -1) .build()); Map<String, String> map = new HashMap<>(); map.put("etag", response.etag()); map.put("versionId", response.versionId()); return map; } /** * 判断文件是否存在 * * @param fileName 对象 * @return true:存在 */ public boolean doFileNameExist(String bucketName, String fileName) { if (StringUtils.isEmpty(bucketName)) { throw new ServerException(ErrorCode.BUCKET_NAME_NOT_NULL); } if (StringUtils.isEmpty(fileName)) { throw new ServerException(ErrorCode.FILE_NAME_NOT_NULL); } boolean exist = true; try { minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(fileName).build()); } catch (Exception e) { exist = false; } return exist; } /** * 文件下载 * * @param response * @param fileName */ public void downloadFile(HttpServletResponse response, String bucketName, String fileName) { if (StringUtils.isEmpty(bucketName)) { throw new ServerException(ErrorCode.BUCKET_NAME_NOT_NULL); } if (StringUtils.isEmpty(fileName)) { throw new ServerException(ErrorCode.FILE_NAME_NOT_NULL); } // 判断文件是否存在 final boolean flag = doFileNameExist(bucketName, fileName); if (!flag) { throw new ServerException(ErrorCode.FILE_NOT_EXIST); } 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 文件名称 * 说明:当前方法不能真正删除,需要验证 */ public void deleteFile(String bucketName, String fileName) { if (StringUtils.isEmpty(bucketName)) { throw new ServerException(ErrorCode.BUCKET_NAME_NOT_NULL); } if (StringUtils.isEmpty(fileName)) { throw new ServerException(ErrorCode.FILE_NAME_NOT_NULL); } try { minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(fileName).build()); } catch (Exception e) { log.error(e.getMessage()); e.printStackTrace(); } } /** * 批量文件删除 * * @param bucketName bucket名称 * @param fileNames 文件名 */ public void deleteBatchFile(String bucketName, List<String> fileNames) { if (StringUtils.isEmpty(bucketName)) { throw new ServerException(ErrorCode.BUCKET_NAME_NOT_NULL); } if (CollectionUtils.isEmpty(fileNames)) { throw new ServerException(ErrorCode.FILE_NAME_NOT_NULL); } try { List<DeleteObject> objects = new LinkedList<>(); for (String fileName : fileNames) { objects.add(new DeleteObject(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) { log.error("批量删除失败!error:{}", e); } } /** * 文件大小 * * @param fileS * @return */ private static String formatFileSize(long fileS) { DecimalFormat df = new DecimalFormat("#.00"); String fileSizeString = ""; String wrongSize = "0B"; if (fileS == 0) { return wrongSize; } if (fileS < 1024) { fileSizeString = df.format((double) fileS) + " B"; } else if (fileS < 1048576) { fileSizeString = df.format((double) fileS / 1024) + " KB"; } else if (fileS < 1073741824) { fileSizeString = df.format((double) fileS / 1048576) + " MB"; } else { fileSizeString = df.format((double) fileS / 1073741824) + " GB"; } return fileSizeString; } }

    三、上传文件实战

    新建 UploadController,实现上传文件接口。

    @Tag(name = "基础接口")
    @AllArgsConstructor
    @RestController
    @RequestMapping("/file")
    public class UploadController {
        private final MinioUtils minioUtils;
    
        @PostMapping("upload")
        @Operation(summary = "上传文件")
        public Result<Map<String, Object>> upload(@RequestParam(defaultValue = "common") String bucketName,
                                       @RequestParam(name = "file", required = false) MultipartFile[] file) {
            return Result.ok(minioUtils.uploadFile(bucketName, file));
        }
    }
    

    调用上传文件接口后,系统会根据 bucketName 首先判断 bucket 是否存在,不存在则会开始创建,并且设置成公共读写。然后遍历文件数组,对文件重命名,并且记录下上传后的文件访问 url。最后进行文件上传。

  • 相关阅读:
    HTTPS性能优化方案
    [附源码]JAVA毕业设计律师事务管理系统(系统+LW)
    基于Python的信用评分卡模型建立和分析,万字详述,关注收藏
    在实训云平台上配置云主机
    navicat连接mysql数据库
    SpringBoot2.7+借助Validator帮助进行校验
    ADB 命令大全
    JUC并发编程第三篇,CompletableFuture场景练习,电商比价需求性能提升
    算法总结--ST表
    SpringMvc请求流程源码解析
  • 原文地址:https://blog.csdn.net/weixin_48471044/article/details/139860900