这年头不会还有谁没碰过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-

相关推荐
Good Note5 分钟前
Golang的静态强类型、编译型、并发型
java·数据库·redis·后端·mysql·面试·golang
m0_7482365821 分钟前
跟据spring boot版本,查看对应的tomcat,并查看可支持的tomcat的版本范围
spring boot·后端·tomcat
web1511736022327 分钟前
Spring Boot项目中解决跨域问题(四种方式)
spring boot·后端·dubbo
我就是我35241 分钟前
记录一次SpringMVC的406错误
java·后端·springmvc
向哆哆44 分钟前
Java应用程序的跨平台性能优化研究
java·开发语言·性能优化
ekkcole1 小时前
windows使用命令解压jar包,替换里面的文件。并重新打包成jar包,解决Failed to get nested archive for entry
java·windows·jar
翱翔-蓝天1 小时前
Spring Boot 3 集成 RabbitMQ 实践指南
spring boot·rabbitmq·java-rabbitmq
Dolphin_Home1 小时前
搭建 Hadoop 3.3.6 伪分布式
大数据·hadoop·分布式
luckilyil2 小时前
RabbitMQ学习—day6—springboot整合
spring boot·rabbitmq·java-rabbitmq
handsomestWei2 小时前
java实现多图合成mp4和视频附件下载
java·开发语言·音视频·wutool·图片合成视频·视频附件下载