这年头不会还有谁没碰过minio的吧?这可太...🤡

🏆本文收录于「滚雪球学Spring Boot」专栏,专业攻坚指数级提升持续更新中,up!up!up!!

🥝 前言:文件存储那些"坑",你踩过几个?

想象一下,你正在开发一个新项目,老板突然拍着桌子跟你说:"咱这个项目得支持海量文件存储,用户随时上传随时下载,成本要低,性能要高,安全也不能落下!"你抓了抓头发,盯着屏幕陷入沉思,传统文件系统?太笨重。云存储?预算超标。就在你一筹莫展时,MinIO横空出世,仿佛一道曙光,照亮了你前行的路。

MinIO,这款开源的对象存储系统,以其高性能、易扩展、S3兼容性等优点,迅速成为开发者圈中的"香饽饽"。如果你用Spring Boot开发项目,想要高效管理文件存储,那么接下来的内容会让你大呼过瘾。

🍇 MinIO是什么?

MinIO,是一款以高性能、轻量级著称的对象存储服务。它完全兼容Amazon S3 API,支持大规模非结构化数据的存储,适合图片、视频、日志、备份等海量数据的管理需求。

简单点说,它就是你的"私人云存储",但没有昂贵的费用和复杂的运维。不论是几百GB还是上百TB的数据,MinIO都能轻松搞定。

🍒 MinIO的"秘密武器"

  • 开源免费:没有隐藏费用,企业也能无压力使用。
  • S3 API兼容:现有的S3工具可以无缝衔接。
  • 性能炸裂:每秒高达数十GB的吞吐量,轻松应对高并发。
  • 易部署,易维护:几行命令搞定,开发小白也能轻松上手。

🍅 为什么选择MinIO?

有人可能会问:"为啥不用传统的文件系统?" 传统文件系统确实在小规模存储中还算凑合,但当你面对动辄几百GB甚至TB级的数据时,传统方案的缺点就暴露无遗了。管理难、性能低、扩展性差......而MinIO正是为了解决这些痛点而生。

🥝 MinIO能给你什么?

  1. 超高性价比:无需支付昂贵的存储服务费用,MinIO让你拥有"云存储"的体验,却不需要"云存储"的钱包。
  2. 弹性扩展:无论是初创团队还是大型企业,MinIO都能根据业务规模灵活扩展,绝不让存储成为发展瓶颈。
  3. 高可用性:MinIO支持分布式部署,即使某个节点故障,数据依然安全无忧。

选择MinIO,就是选择一种面向未来的存储方式。

🥑 MinIO核心概念

● 对象(Object):对象是实际的数据单元,例如:上传的图片。

● 存储桶(Bucket):存储桶是用于组织对象的名称空间,类似于文件夹。每个存储桶可以包含多个对象(文件)。

● 端点(Endpoint):MinIO服务器的网络地址,用于访问存储桶和对象。例如:http://192.168.10.100:9000 , 注意:9000为 MinIO的API默认端口。

● AccessKey 和Secret Key:

  • AccessKey:用于标识和验证访问者身份的唯一标识符,相当于用户名。

  • Secret Key:与AccessKey关联的密码,用于验证访问者的身份。

🌽 MinIO客户端实操

🥬 创建bucket

这里的bucket存储桶是用于组织对象的名称空间,类似于我们所说的文件夹。

🥜 测试文件上传

然后来测试一下,文件上传。

上传文件,点击"upload",选择上传的文件即可。

🥖 设置匿名用户的访问权限

将匿名用户权限设置为只读。

🧆 创建 Access Key

这里的Access Key用于标识和验证访问者身份的唯一标识符,相当于用户名。

如上操作完后,我们便来进行此期的真正的干货了,直接上手实操。

🌯 Spring Boot集成MinIO的实操指南

🫔 环境准备

首先,确保你的开发环境已经配置好以下工具:

  • JDK 1.8
  • Spring Boot 2.6+
  • MinIO服务(可使用Docker快速部署)
json 复制代码
docker run -p 9000:9000 -p 9001:9001 --name minio \  
-e "MINIO_ROOT_USER=admin" \  
-e "MINIO_ROOT_PASSWORD=password123" \  
minio/minio server /data --console-address ":9001"  

这段命令会在本地启动MinIO服务,你只需要打开浏览器,输入http://localhost:9001,用设置的账号密码登录,即可看到管理界面。

或者你也可以参考Linux常规搭建,可看这篇《Linux零基础安装Minio,手把手教学,一文搞定它!(超详细)》,妥妥傻瓜式教学。

🫑 引入依赖

接下来,修改pom.xml,引入MinIO的Java SDK依赖:

xml 复制代码
        <!--minio oss服务-->
        <dependency>
            <groupId>io.minio</groupId>
            <artifactId>minio</artifactId>
            <version>8.5.12</version>
        </dependency>

🍌 定义MinIO连接信息

我们需要先将minio的连接信息配置到我们的配置类中,方便修改及动态配置。

故我们需要先去minio的客户端先创建于一个access key,然后将access-key 与 secret-key 填写到 yml 配置文件中。

具体配置如下,你们直接改成你们的即可。

java 复制代码
# minio文件存储
minio:
  access-key: Ro2ypdSShhmqQYgHWyDP
  secret-key: 6XOaQsYXBKflV10KDcjgcwE9lvekcN4KYfE85fBL
  url: http://10.66.66.143:9000
  bucket-name: hpy-files

属性解读:

如上这段代码配置的是MinIO文件存储的连接信息,具体内容如下:

  • access-key : Ro2ypdSShhmqQYgHWyDP --- 这是MinIO的访问密钥(类似于用户名),用于身份验证。
  • secret-key : 6XOaQsYXBKflV10KDcjgcwE9lvekcN4KYfE85fBL --- 这是MinIO的密钥(类似于密码),用于进行身份验证。
  • url : http://10.66.66.143:9000 --- 这是MinIO服务器的地址,表示文件存储服务的主机IP地址和端口。
  • bucket-name : hpy-files --- 这是用于存储文件的桶(bucket)名称。在MinIO中,文件是按桶来存储和组织的。

🍐 配置MinIO客户端

我们需要为Spring Boot项目配置一个MinIO客户端。新建MinioConfig.java

java 复制代码
/**
 * @author: bug菌
 * @date: 2024-10-21 11:59
 */
@Data
@Configuration
@ConfigurationProperties(prefix = "minio")
public class MinioConfig {
    private String accessKey;

    private String secretKey;

    private String url;

    private String bucketName;

    @Bean
    public MinioClient minioClient() {
        return MinioClient.builder()
                .region("cn-north-1")
                .endpoint(url)
                .credentials(accessKey, secretKey)
                .build();
    }
}

配置完成后,MinIO客户端就已经准备好为我们的Spring Boot项目服务了。

🍌 创建文件工具类

接下来,我们需要创建一个MinioUtil类,该类的目的是为了封装和简化与 MinIO 文件存储服务的交互,提供一系列的操作方法,使得我们能够轻松地进行文件上传、下载、删除、获取文件信息等常见的文件存储操作。具体意义如下:

  1. 与 MinIO 交互的封装: 类中封装了与 MinIO 存储服务进行交互的代码,包括检查存储桶是否存在、文件上传、下载、删除等常见的操作。这样,业务逻辑代码无需直接操作 MinIO API,提升了代码的复用性和可维护性。

  2. 自动化存储桶管理 : 在 @PostConstruct 注解的 init() 方法中,会自动检查并创建存储桶(bucket)。确保在程序启动时,指定的存储桶已经存在,避免了在使用过程中因存储桶不存在而导致的错误。

  3. 支持文件的 URL 生成: 提供了生成文件访问 URL 的功能,包括带过期时间的预签名 URL。这是为了允许用户在一定时间内访问文件,避免文件暴露或在外部用户访问时需要额外认证。

  4. 文件下载支持: 类中提供了文件下载的功能,包括标准下载(通过 HTTP ServletResponse)和流式下载(获取文件流)。它可以处理文件的大小、编码等问题,保证文件的正确下载。

  5. 文件操作的错误处理与日志 : 通过 Logger 对操作进行记录,且所有可能抛出异常的操作都进行了捕获和处理,避免了程序因为 MinIO 服务故障等原因而直接崩溃。确保系统的稳定性和错误反馈。

  6. 文件夹与文件的存在性检查: 该类提供了检查文件或文件夹是否存在的方法,有助于在上传或删除文件前进行状态验证,避免重复操作。

  7. 简化 API 调用: 通过抽象出一层高层次的操作接口,开发者不需要直接关注 MinIO 底层的复杂实现,只需调用简洁的方法即可完成文件存储操作。

总结而言,MinioUtil 类通过封装 MinIO 的常见文件操作,提供便捷的接口,降低与 MinIO 交互的复杂性,并通过统一的错误处理和日志记录,增强了系统的健壮性和可维护性。

代码实操:

java 复制代码
/**
 * 文件工具类
 *
 * @author: bug菌
 * @date: 2024-10-21 12:02
 * @desc:
 */
@Service
public class MinioUtil {
    private static final Logger log = LoggerFactory.getLogger(MinioUtil.class);

    @Autowired
    private MinioClient minioClient;
    @Autowired
    private MinioConfig minioConfig;

    @PostConstruct
    public void init() {
        existBucket(minioConfig.getBucketName());
    }


    /**
     * 判断bucket是否存在,不存在则创建
     */
    public boolean existBucket(String bucketName) {
        boolean exists;
        try {
            exists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
            if (!exists) {
                minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
                exists = true;
            }
        } catch (Exception e) {
            e.printStackTrace();
            exists = false;
        }
        return exists;
    }

    /**
     * 上传文件
     */
    public void upload(MultipartFile file, String fileName) {
        // 使用putObject上传一个文件到存储桶中。
        InputStream inputStream = null;
        try {
            inputStream = file.getInputStream();
            minioClient.putObject(PutObjectArgs.builder()
                    .bucket(minioConfig.getBucketName())
                    .object(fileName)
                    .stream(inputStream, file.getSize(), -1)
                    .contentType(file.getContentType())
                    .build());
            inputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取文件访问地址
     */
    public String getFileUrl(String fileName) {
        try {
            return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
                    .method(Method.GET)
                    .bucket(minioConfig.getBucketName())
                    .object(fileName)
                    .build()
            );
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 下载一个文件(返回文件流)
     */
    public InputStream download(String objectName) throws Exception {
        InputStream stream = minioClient.getObject(
                GetObjectArgs.builder().bucket(minioConfig.getBucketName()).object(objectName).build());
        return stream;
    }


    /**
     * 下载文件
     */
    public void download(HttpServletResponse response, String newFileName, String saveFileName) {
        InputStream in = null;
        try {
            // 获取对象信息
            StatObjectResponse stat = minioClient.statObject(StatObjectArgs.builder()
                    .bucket(minioConfig.getBucketName())
                    .object(saveFileName)
                    .build());

            // 设置请求头Content-Type
            response.setContentType(stat.contentType());

            // 确保使用 UTF-8 编码
//            String encodedFileName = encodeFilename(newFileName);
            String encodedFileName = URLEncoder.encode(newFileName, "UTF-8").replaceAll("\\+", "%20");
            response.setHeader("Content-Disposition", "attachment; filename=\"" + encodedFileName + "\"");

            // 设置禁用缓存
            response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
            response.setHeader("Pragma", "no-cache");
            response.setHeader("Expires", "0");

            // 设置文件大小
            long fileSize = stat.size();
            response.setContentLengthLong(fileSize);

            // 获取文件输入流
            in = minioClient.getObject(GetObjectArgs.builder()
                    .bucket(minioConfig.getBucketName())
                    .object(saveFileName)
                    .build());

            // 文件下载
            IOUtils.copy(in, response.getOutputStream());

        } catch (Exception e) {
            e.printStackTrace();
            try {
                response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "File download failed: " + e.getMessage());
            } catch (IOException ioException) {
                ioException.printStackTrace();
            }
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    /**
     * 删除文件
     */
    public void delete(String fileName) {
        try {
            minioClient.removeObject(RemoveObjectArgs.builder().bucket(minioConfig.getBucketName()).object(fileName).build());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

 
    /**
     * 判断文件是否存在
     *
     * @param objectName
     */
    public boolean isFileExist(String objectName) {
        boolean exist = true;
        try {
            minioClient.statObject(StatObjectArgs.builder().bucket(minioConfig.getBucketName()).object(objectName).build());
        } catch (Exception e) {
            log.error("[Minio工具类]>>>> 判断文件是否存在, 异常:", e);
            exist = false;
        }
        return exist;
    }
}

📝 文件上传/下载/预览/删除实战

🧁 1.文件上传

🍆 示例代码

java 复制代码
/**
 * @author: bug菌
 * @date: 2024-10-21 12:07
 */
@Api(tags = "Minio文件管理")
@RestController
@RequestMapping("/file")
public class UploadFileController extends BaseController {

    @Autowired
    private MinioUtil minioUtil;

    /**
     * 上传文件
     */
    @GetMapping(value = "/upload")
    @ApiOperation("上传文件")
    public R upload(MultipartFile file) {
        // 获取到上传文件的完整名称,包括文件后缀
        String fileName = file.getOriginalFilename();
        // 获取不带后缀的文件名
        String baseName = FilenameUtils.getBaseName(fileName);
        // 获取文件后缀
        String extension = FilenameUtils.getExtension(fileName);
        //创建一个独一的文件名(存于服务器名),格式为 name_时间戳.后缀
        String saveFileName = baseName + "_" + System.currentTimeMillis() + "." + extension;
        minioUtil.upload(file, saveFileName);
        return R.ok("上传成功!存放文件名为:" + saveFileName);
    }
}

🥔 示例测试

Postman接口测试上传接口如下:

校验文件是否真正上传到minio中,我们可以上客户端查验下。根据登录查看确实是我们测试时所上传的文件。

🍓 示例代码解析

在本次的代码演示中,我将会深入剖析每句代码,详细阐述其背后的设计思想和实现逻辑。通过这样的讲解方式,我希望能够引导同学们逐步构建起对代码的深刻理解。我会先从代码的结构开始,逐步拆解每个模块的功能和作用,并指出关键的代码段,并解释它们是如何协同运行的。通过这样的讲解和实践相结合的方式,我相信每位同学都能够对代码有更深入的理解,并能够早日将其掌握,应用到自己的学习和工作中。

如上提供的这段代码是一个用于文件上传的控制器,使用 Spring Boot 构建,负责处理文件的上传操作。以下是代码的详细解析:

  1. 类注解

    • @Api(tags = "Minio文件管理"):使用 Swagger API 文档工具生成接口文档,并为该类提供了一个标签"Minio文件管理",用于描述文件管理相关的接口。
    • @RestController:该注解表示这是一个控制器类,并且返回的内容会被自动序列化为 JSON 格式。它是 @Controller@ResponseBody 的组合。
    • @RequestMapping("/file"):设置该类的基础请求路径为 /file,所有该类中的请求都会以 /file 开头。
  2. 依赖注入

    • @Autowired:自动注入 MinioUtil 类的实例,MinioUtil 是一个封装了 MinIO 操作的工具类,用于处理与 MinIO 存储服务的交互。
  3. 方法注解

    • @GetMapping(value = "/upload"):处理 HTTP GET 请求,路径为 /file/upload。尽管通常文件上传使用 POST 请求,但这里使用 GET 请求可能是简化了请求示例,实际应用中可能使用 POST。
    • @ApiOperation("上传文件"):Swagger 文档生成的描述,表示该接口用于上传文件。
  4. 上传文件操作

    • MultipartFile file:表示前端传递的文件。Spring 会自动将请求中的文件映射到该参数。
    • String fileName = file.getOriginalFilename();:获取上传文件的原始文件名,包括文件扩展名。
    • String baseName = FilenameUtils.getBaseName(fileName);:使用 Apache Commons IO 库的 FilenameUtils 类,获取文件的基本名称(不包含扩展名)。
    • String extension = FilenameUtils.getExtension(fileName);:获取文件的扩展名。
    • String saveFileName = baseName + "_" + System.currentTimeMillis() + "." + extension;:生成一个新的唯一文件名。通过文件的基本名称加上当前的时间戳(毫秒级),确保文件名不重复。
    • minioUtil.upload(file, saveFileName);:调用 MinioUtil 类中的 upload 方法,将文件上传到 MinIO 存储服务,保存为 saveFileName
  5. 返回结果

    • return R.ok("上传成功!存放文件名为:" + saveFileName);:返回上传成功的响应,R.ok() 是一个自定义的响应方法,表示操作成功并返回相应的信息,saveFileName 作为返回信息的一部分,告知客户端上传文件后的存储文件名。

小结:

该控制器类用于处理文件上传请求,接收文件并生成一个唯一的文件名,通过 MinioUtil 工具类将文件上传至 MinIO 存储。它结合了文件名生成、上传及响应返回等功能,实现了简单的文件上传管理。

🍬 2.文件下载

🍆 示例代码

java 复制代码
    /**
     * 根据文件ID下载文件
     */
    @GetMapping("/download")
    @ApiOperation("根据文件ID下载文件")
    public void downloadById(@RequestParam("fileName") String fileName, @RequestParam("saveFileName") String saveFileName, HttpServletResponse response) {
        // 下载文件,传递存储文件名和显示文件名
        minioUtil.download(response, fileName, saveFileName);
        return;
    }

🥔 示例测试

Postman接口测试上传接口如下:

🍓 示例代码解析

在本次的代码演示中,我将会深入剖析每句代码,详细阐述其背后的设计思想和实现逻辑。通过这样的讲解方式,我希望能够引导同学们逐步构建起对代码的深刻理解。我会先从代码的结构开始,逐步拆解每个模块的功能和作用,并指出关键的代码段,并解释它们是如何协同运行的。通过这样的讲解和实践相结合的方式,我相信每位同学都能够对代码有更深入的理解,并能够早日将其掌握,应用到自己的学习和工作中。

如上提供的这段代码是用于根据文件ID下载文件的控制器方法。以下是对代码的详细解析:

  1. 方法注解

    • @GetMapping("/download"):该方法处理 HTTP GET 请求,路径为 /download。该请求用于根据文件ID下载文件。
    • @ApiOperation("根据文件ID下载文件"):Swagger 文档生成的描述,表明该接口用于根据文件ID下载文件。
  2. 方法参数

    • @RequestParam("fileName") String fileName:从请求中获取名为 fileName 的请求参数,并将其绑定到 fileName 变量。这个参数通常表示文件在存储中的实际名称。
    • @RequestParam("fileName") String saveFileName:这个参数也是从请求中获取名为 fileName 的请求参数。由于参数名称重复,可能会导致问题。正确的做法是使用不同的名字,例如 fileNamesaveFileName,用来分别传递存储文件名和显示文件名。
    • HttpServletResponse response:Spring MVC 自动注入的 HttpServletResponse 对象,用于设置响应信息,发送文件内容到客户端。
  3. 下载文件操作

    • minioUtil.download(response, fileName, saveFileName);:调用 MinioUtil 类中的 download 方法。该方法接收 HttpServletResponse 对象、存储文件名(fileName)和显示文件名(saveFileName)作为参数。download 方法将从 MinIO 存储中获取指定的文件并通过 HTTP 响应将其返回给客户端。
  4. 方法结束

    • return;:该方法没有返回任何内容,因为文件内容通过 HttpServletResponse 被直接流式传输到客户端。

小结:

该方法用于处理根据文件ID下载文件的请求。它通过传递文件名参数,调用 MinioUtil 的下载方法,将文件从 MinIO 存储下载并返回给客户端。

🍩 3.文件预览

🍓 示例代码

java 复制代码
    @GetMapping("/preview")
    @ApiOperation("根据文件ID预览文件")
    public String previewFileById(@RequestParam("fileName") String fileName) {
        return minioUtil.getFileUrl(fileName);
    }

🥔 示例测试

Postman接口测试上传接口如下:

通过接口可直接给你返回该文件的预览地址,我们只需要在浏览器输入该地址便可预览。

🍆 示例代码解析

在本次的代码演示中,我将会深入剖析每句代码,详细阐述其背后的设计思想和实现逻辑。通过这样的讲解方式,我希望能够引导同学们逐步构建起对代码的深刻理解。我会先从代码的结构开始,逐步拆解每个模块的功能和作用,并指出关键的代码段,并解释它们是如何协同运行的。通过这样的讲解和实践相结合的方式,我相信每位同学都能够对代码有更深入的理解,并能够早日将其掌握,应用到自己的学习和工作中。

如上提供的这段代码是用于根据文件ID预览文件的控制器方法。以下是详细解析:

  1. 方法注解

    • @GetMapping("/preview"):该方法处理 HTTP GET 请求,路径为 /preview,用于根据文件ID预览文件。
    • @ApiOperation("根据文件ID预览文件"):Swagger 文档生成的描述,表明该接口用于根据文件ID预览文件。
  2. 方法参数

    • @RequestParam("fileName") String fileName:从请求中获取名为 fileName 的请求参数,并将其绑定到 fileName 变量。这个参数通常表示要预览的文件在存储中的文件名。
  3. 文件预览操作

    • minioUtil.getFileUrl(fileName):调用 MinioUtil 类中的 getFileUrl 方法,该方法使用文件名从 MinIO 存储生成文件的预览 URL。返回的 URL 通常是一个可以直接访问该文件的链接,可以在客户端浏览器中打开进行预览。
  4. 返回值

    • 方法返回 String 类型的文件预览 URL,这个 URL 可以直接访问文件并在浏览器中预览。

小结:

该方法用于处理根据文件ID预览文件的请求。它通过文件名生成一个文件的预览 URL,并将该 URL 返回给客户端,客户端可以使用该 URL 访问文件进行预览。

🍭 4.文件删除

🍓 示例代码

java 复制代码
    /**
     * 根据文件ID删除文件
     */
    @GetMapping("/delete")
    @ApiOperation("根据文件ID删除文件")
    public R deleteById(@RequestParam("fileName") String fileName) {
        minioUtil.delete(fileName);
        return R.ok();
    }

🥔 示例测试

Postman接口测试上传接口如下:

接着我们上客户端查验下,该文件是否真被删除了。

根据时间倒序排序,确实该文件被删除了。

🍆 示例代码解析

在本次的代码演示中,我将会深入剖析每句代码,详细阐述其背后的设计思想和实现逻辑。通过这样的讲解方式,我希望能够引导同学们逐步构建起对代码的深刻理解。我会先从代码的结构开始,逐步拆解每个模块的功能和作用,并指出关键的代码段,并解释它们是如何协同运行的。通过这样的讲解和实践相结合的方式,我相信每位同学都能够对代码有更深入的理解,并能够早日将其掌握,应用到自己的学习和工作中。

如上提供的这段代码是用于根据文件ID删除文件的控制器方法。以下是详细解析:

  1. 方法注解

    • @GetMapping("/delete"):该方法处理 HTTP GET 请求,路径为 /delete,用于根据文件ID删除文件。
    • @ApiOperation("根据文件ID删除文件"):Swagger 文档生成的描述,表明该接口用于根据文件ID删除文件。
  2. 方法参数

    • @RequestParam("fileName") String fileName:从请求中获取名为 fileName 的请求参数,并将其绑定到 fileName 变量。这个参数通常表示要删除的文件在存储中的文件名。
  3. 删除文件操作

    • minioUtil.delete(fileName):调用 MinioUtil 类中的 delete 方法,该方法会根据提供的 fileName 删除 MinIO 存储中的对应文件。
  4. 返回值

    • 方法返回 R.ok():表示操作成功,返回一个响应对象,R.ok() 是一种常见的封装返回成功的方式,可能会带有自定义的状态码或消息。

小结:   该方法处理根据文件ID删除文件的请求。它通过文件名调用 MinioUtil 删除对应的文件,并返回一个成功的响应。

🫐 MinIO与云原生架构的完美契合

MinIO不仅是一个存储工具,它更是云原生架构中不可或缺的一部分。与Kubernetes无缝整合,让微服务架构下的数据管理变得轻松自如。不论是CI/CD流水线还是大数据分析,MinIO都能应对自如。

🍐 总结与思考

通过这篇文章,你应该对Spring Boot与MinIO的结合有了一个全面的了解。这种现代化的文件存储方案不仅让开发更高效,也为未来业务的扩展奠定了坚实基础。既然已经Get到这么棒的技能,何不立即尝试一下,让你的项目也能"飞"起来?

🥕 附录相关报错及方案解决

🫛1、okhttp3包冲突

如果你遇到你的项目集成 minio 8.5.4 遇到 okhttp3包冲突,比如报错如下所示,可见我这篇《SpringBoot项目集成 minio 8.5.4 遇到 okhttp3包冲突,如何解决?》带你解决此问题:

🍏2、启动报错

如果你启动后遇到如下问题,比如报错如下所示,可见我这篇《集成minio启动报错:Caused by:java.lang.IllegalArgumentException:invalid hostname 10.66.66.143:9000...| 亲测有效》带你解决此问题:

ok,本期内容我就暂聊到这里,哇,一口气给大家输出完,我我我我...头发又脱落了一撮。

📣 关于我

我是bug菌,CSDN | 掘金 | InfoQ | 51CTO | 华为云 | 阿里云 | 腾讯云 等社区博客专家,C站博客之星Top30,华为云2023年度十佳博主,掘金多年度人气作者Top40,掘金等各大社区平台签约作者,51CTO年度博主Top12,掘金/InfoQ/51CTO等社区优质创作者;全网粉丝合计 30w+;硬核微信公众号「猿圈奇妙屋」,欢迎你的加入!免费白嫖最新BAT互联网公司面试真题、4000G PDF电子书籍、简历模板等海量资料,你想要的我都有,关键是你不来拿哇。
-End-

相关推荐
suweijie7682 小时前
SpringCloudAlibaba | Sentinel从基础到进阶
java·大数据·sentinel
公贵买其鹿3 小时前
List深拷贝后,数据还是被串改
java
xlsw_6 小时前
java全栈day20--Web后端实战(Mybatis基础2)
java·开发语言·mybatis
神仙别闹7 小时前
基于java的改良版超级玛丽小游戏
java
黄油饼卷咖喱鸡就味增汤拌孜然羊肉炒饭7 小时前
SpringBoot如何实现缓存预热?
java·spring boot·spring·缓存·程序员
Data跳动8 小时前
Spark内存都消耗在哪里了?
大数据·分布式·spark
暮湫8 小时前
泛型(2)
java
超爱吃士力架8 小时前
邀请逻辑
java·linux·后端
南宫生8 小时前
力扣-图论-17【算法学习day.67】
java·学习·算法·leetcode·图论
转码的小石8 小时前
12/21java基础
java