旧版MinIO的安装(windows)、Spring Boot 后端集成 MinIO 实现文件存储(超详细,带图文)

发布于:2025-08-14 ⋅ 阅读:(18) ⋅ 点赞:(0)

一、前言

        我翻阅了很多CSDN里面的MinIO教程,有的写的很好讲解了MinIO很多关键的知识点,大家一搜就有很多,所以就不过多赘述了。

        但是教程里面或多或少也存在一些问题,比如安装描述没缺少图片辅助引导,又比如有的步骤跳度太大导致新手小白难以复现教程里面的步骤,还有教程一步步跟下去最后才发现安装的是新版本,而新版的MinIO少了很多的功能,满足不了我们的开发要求,或者日常练习。

        针对上面几点问题于是有了本篇文章的诞生,本文会配上详细的图片帮助小白理解,下面让我们开始吧!

二、旧版MinIO的安装以及简单配置

2.1 MinIO下载前的准备工作

在电脑中新建好文件夹并且做好命名工作

我在 E 盘新建了 Minio 文件,随后在 Minio 文件下新建了 Minio2024-06-13T22-53-53Z 文件(我是拿Minio的版本号来命名,如果下载了多个 Minio 方便分辨版本号) ,最后得到的路径是E:\Minio\Minio2024-06-13T22-53-53Z

接着我在此路径下新建了两个文件夹分别是data和logs

前面的步骤完成之后就是下图这样的

2.2 MinIO下载到本地

点击链接MinIO对象存储 Kubernetes — MinIO中文文档 | MinIO Kubernetes中文文档

进入下面页面 -> 点击 中国加速镜像

点击server/ 

 点击 minio/

 点击 release/

我的是windows系统选的是windows-amd64

 点击archive/

 挑选没有后缀的文件名,单击选择好路径即可下载,(这里的路径选择我们2.1准备工作中的E:\Minio\Minio2024-06-13T22-53-53Z)

我挑选的是 minio.RELEASE.2024-06-13T22-53-53Z 大家也可以选择其他版本

 下载完成后我们的文件里面就是下面这样的

2.3 MinIO自定义账号密码

下面来设置 MinIO 登录的管理员账号和密码,之后编程调用和网页控制台登录的时候用

(本步骤非必须,若不设置则使用默认账号 minioadmin/ 密码 minioadmin)

1. 首先进入E:\Minio\Minio2024-06-13T22-53-53Z 目录

在此路径下输入cmd后回车

进入此页面

2. 接下来我们来设置管理员用户名

输入 setx MINIO_ROOT_USER  new-admin ,  new-admin 是你的用户名可以更改

下面是我的输入

setx MINIO_ROOT_USER root

 回车后与下图提示信息一致,则说明修改成功

3. 然后我们来设置管理员密码

输入 setx MINIO_ROOT_PASSWORD new-password ,  new-password 是你的密码可以更改

下面是我的输入

setx MINIO_ROOT_PASSWORD 12345678

回车后得到

好到这我们的用户名和密码就设置好了,这个要记住后面会用到 

先不要关闭此界面

2.4 MinIO的运行

还是刚刚的界面,或者cmd重新进入一个也可以

我们输入 minio.RELEASE.2024-06-13T22-53-53Z server ./data --console-address "127.0.0.1:9000" --address "127.0.0.1:9090"

我来简单解释一下这个命令

minio.RELEASE.2024-06-13T22-53-53Z 这是 MinIO 服务端的可执行程序文件名

大家下载之后的文件名字是什么就写什么,下图我圈起来的那个文件

server用于指定以服务端模式启动 MinIO 

 ./data 这是指定 MinIO 存储数据的目录(大家也可以根据需求把这个存储数据的目录放去别的地方)

--console-address "127.0.0.1:9000"  

  • 作用:配置 MinIO Web 控制台的监听地址与端口,用于浏览器访问可视化管理界面(如创建桶、上传文件)。  
  • 地址:`127.0.0.1` 仅允许本机访问,需外部访问时改为服务器 IP(如 `192.168.1.100`);  
  • 端口:`9000` 可自定义(需避免冲突),支持省略 IP 设为 `:9000`(监听所有网卡)。  


--address "127.0.0.1:9090"  

  • 作用:配置 MinIO 服务端 API 的监听地址与端口,供客户端程序(如 Spring Boot + MinIO SDK)调用上传、下载等功能。  
  • 地址:`127.0.0.1` 限制本机访问,需外部调用时改为服务器 IP;  
  •  端口:`9090` 可自定义,冲突时需更换;客户端 SDK 需配置此地址为 `endpoint` 才能连接。  

输入好该命令后回车

minio.RELEASE.2024-06-13T22-53-53Z server ./data --console-address "127.0.0.1:9000" --address "127.0.0.1:9090"

经过上面的配置我们可以看到上图中会显示前面设置成功的用户名、密码、API地址以及WebUI地址,该窗口我们先不要关闭,关闭后MinIO的运行也会停止

API: http://127.0.0.1:9090
MinIO 提供的编程接口(API)访问地址,后面开发 Spring Boot 等项目时,Java 代码里用 MinIO 的 SDK 连接 MinIO 服务,就要填这个地址(http://127.0.0.1:9090 ),程序通过它来调用 MinIO 的各种功能(上传、下载文件等 )。


WebUI: http://127.0.0.1:9000
MinIO 的网页管理控制台访问地址,你打开浏览器,输入这个地址(http://127.0.0.1:9000 ),就能进入可视化的管理界面,在里面手动创建存储桶、上传下载文件、设置权限啥的,很方便。

2.6 进入MinIO的可视化管理界面

我们将WebUI的地址 http://127.0.0.1:9000 复制粘贴去浏览器即可进入下图

输入我们前面设置好的用户名以及密码,点击Login

进来后就是这样的,右上角可以切换夜间模式

创建秘钥

应用程序需要借助访问密钥和秘密密钥与 MinIO 进行交互,实现对存储对象的各种操作。

创建桶用来组织和管理对象

设置桶(Bucket)的访问权限

  • Public(公共读):所有人可读(下载、查看)桶内对象,适合公开资源。
  • Private(私有,默认):只有通过 Access Key + Secret Key 认证的用户,才能访问 / 操作桶(需结合用户权限策略细化控制)。
  • Custom(自定义):用 JSON 策略语法,精确控制权限(比如:仅允许特定 IP 访问、仅允许读某类文件)。

2.5 批处理脚本简化运行

如果每次运行minio都需要输入命令,实话说是有些麻烦的,所以这里提供了一个小技巧通过脚本来简化运行

我们在 E:\Minio\Minio2024-06-13T22-53-53Z 目录下新建一个文件,命名为start_minio.bat,右键以记事本的方式打开,里面贴上下面的代码保存即可

@echo off
REM 设置 MinIO 环境变量(可选,若需要自定义账号密码)
setx MINIO_ROOT_USER root
setx MINIO_ROOT_PASSWORD 12345678

REM 启动 MinIO 服务(替换为你的实际路径和参数)
minio.RELEASE.2024-06-13T22-53-53Z server ./data --console-address "127.0.0.1:9000" --address "127.0.0.1:9090"

就是这样的

双击start_minio.bat 文件即可运行,得到下图

2.6 如何关闭minio

本篇文章介绍四种方法,前两种适合对数据完整性要求不高,拿来练手的项目。

2.6.1 方法一:直接关闭命令行窗口(简单但不推荐)

        如果你是通过命令行启动的 MinIO,并且没有将其配置为后台服务,最直接的方法就是关闭运行 MinIO 命令的命令提示符(CMD)窗口。

        但是这种方式属于强制关闭,如果MinIO正在为后台服务,强制关闭可能会导致MinIO没有足够的时间来完成正在进行的操作,比如正在写入文件、更新元数据等,可能会导致数据不一致或损坏。在生产环境或对数据完整性要求较高的场景下,不建议使用这种方式。

2.6.2 方法二:使用任务管理器关闭进程(不推荐)

操作步骤:

  1. 按下Ctrl + Shift + Esc组合键,打开 “任务管理器” 窗口。
  2. 在 “进程” 选项卡中,找到minio.exe进程。如果不确定哪个是 MinIO 进程,可以查看 “命令行” 列,确认其路径和启动参数与你启动 MinIO 时的一致。
  3. 右键点击minio.exe进程,选择 “结束任务”,即可关闭 MinIO 服务。

但是该操作本质上也是强制结束进程,同样可能存在数据一致性问题,不适合在对数据完整性要求高的生产环境中使用。

2.6.3 方法三:使用命令行命令关闭(推荐)

操作步骤:

  1. 打开命令提示符(CMD)窗口。可以通过在 “开始” 菜单中搜索 “cmd”,然后以管理员身份运行。
  2. 使用tasklist命令查找 MinIO 进程的 PID(进程标识符)。输入tasklist | findstr "minio.exe",回车后会显示 MinIO 进程的相关信息,其中包含 PID。你的 MinIO 可执行文件可能不是 minio.exe,而是其他名称(如 minio.RELEASE.2024-06-13T22-53-53Z)。此时需用实际进程名查询。
  3. 使用taskkill命令结束 MinIO 进程。输入taskkill /PID <进程PID> /F(<进程PID> 替换为实际查询到的 PID,/F 参数表示强制终止进程)。如果不想强制终止,也可以不加/F参数,让进程正常退出(前提是进程能正常响应退出操作)。

2.6.4 方法四:将 MinIO 配置为服务后进行管理(适合生产环境)

 操作步骤:

  1. 按下Win + R组合键,打开 “运行” 对话框,输入services.msc,回车后打开 “服务” 管理界面。
  2. 在服务列表中找到你为 MinIO 配置的服务名称(在使用 NSSM 配置服务时可以自定义服务名称)。
  3. 右键点击该服务,选择 “停止”,即可关闭 MinIO 服务。

        这种方式是较为安全的关闭方式,MinIO 服务有机会进行必要的清理和收尾工作,有助于保障数据的完整性。并且通过服务管理界面可以方便地对 MinIO 服务进行启动、停止、重启等操作,适合在生产环境中使用。
        但是配置 MinIO 为服务相对复杂一些,需要借助工具(如 NSSM)来完成,并且在配置过程中需要注意一些细节,如服务的启动路径、参数设置等。

三、Spring Boot 后端集成 MinIO 实现文件存储

 3.1. 添加 MinIO 依赖
        <!-- MinIO客户端依赖:用于操作MinIO对象存储服务(文件上传下载等) -->
        <dependency>
            <groupId>io.minio</groupId>
            <artifactId>minio</artifactId>
            <version>8.5.7</version> <!-- 指定MinIO客户端版本 -->
        </dependency>

        <!-- OKHttp 依赖 -->
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>4.9.3</version>
        </dependency>
3.2. 配置 MinIO 连接信息
3.2.1 创建配置文件

在 application.yml 或 application-minio.yml 中添加 MinIO 配置:

minio:
  access-key: JTFudOAovCfybwraLIh7  # MinIO 访问密钥
  secret-key: 794DZK90eK8W9YEc53raCWyEbinPlmfCFQjncOcU  # MinIO 密钥
  url: http://localhost:9005  # MinIO 服务地址
  bucket-name: group-purchase-2025  # 存储桶名称
  # 各种文件存储路径配置
  admin-avatar-base-path: /admin-avatar/
  user-avatar-base-path: /user-avatar/
  business-logo-base-path: /business-logo/
  business-license-base-path: /business-license/
  banner-base-path: /banner/
  goods-img-base-path: /goods/
3.2.2 创建配置类

创建 MinioConfig 类映射配置信息并创建 MinIO 客户端:

package com.example.common.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;
import org.springframework.validation.annotation.Validated;

import javax.annotation.PostConstruct;
import javax.validation.constraints.NotBlank;

/**
 * MinIO配置类
 */
@Data
@Configuration
@Validated
@ConfigurationProperties(prefix = "minio")
public class MinioConfig {

    @NotBlank(message = "MinIO accessKey不能为空")
    private String accessKey;

    @NotBlank(message = "MinIO secretKey不能为空")
    private String secretKey;

    @NotBlank(message = "MinIO url不能为空")
    private String url;

    @NotBlank(message = "MinIO bucketName不能为空")
    private String bucketName;

    @NotBlank(message = "管理员头像基础路径不能为空")
    private String adminAvatarBasePath;

    @NotBlank(message = "用户头像基础路径不能为空")
    private String userAvatarBasePath;

    @NotBlank(message = "商家logo基础路径不能为空")
    private String businessLogoBasePath;

    @NotBlank(message = "商家营业执照基础路径不能为空")
    private String businessLicenseBasePath;

    @NotBlank(message = "轮播图广告基础路径不能为空")
    private String bannerBasePath;

    @NotBlank(message = "商品图片基础路径不能为空")
    private String goodsImgBasePath;

    @PostConstruct
    public void init() {
        System.out.println("MinIO配置加载成功:");
        System.out.println("URL: " + url);
        System.out.println("Bucket: " + bucketName);
        System.out.println("AccessKey: " + accessKey);
    }

    @Bean
    public MinioClient minioClient() {
        return MinioClient.builder()
                .endpoint(url)
                .credentials(accessKey, secretKey)
                .build();
    }
}
3.3. 实现 MinIO 工具类

创建 MinioUtils 封装文件:

package com.example.utils;

import com.example.common.config.MinioConfig;
import io.minio.*;
import io.minio.errors.MinioException;
import io.minio.http.Method;
import io.minio.messages.DeleteObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * MinIO工具类 - 社区团购系统文件存储核心工具
 * 
 * 功能说明:
 * - 文件上传:支持单文件和批量上传,自动生成唯一文件名防止覆盖
 * - 文件删除:支持单文件和批量删除,包含删除验证
 * - 文件访问:生成永久访问URL,支持预签名URL作为备选方案
 * - 文件检查:验证文件是否存在,用于删除前的安全检查
 * 
 * 应用场景:
 * - 商品图片管理:批量上传商品多图,删除商品时清理相关图片
 * - 用户头像管理:上传用户头像,删除用户时清理头像文件
 * - 商家资料管理:上传营业执照、Logo等商家认证文件
 * - 系统资源管理:Banner图、广告图等系统级图片资源
 * 
 * @author zwt
 * @version 3.0 (2025-01-XX)
 */
@Component
public class MinioUtils {

    @Autowired
    private MinioClient minioClient;

    @Autowired
    private MinioConfig configuration;

    /**
     * 检查并创建存储桶(如果不存在)
     * 
     * 功能:确保MinIO存储桶存在,不存在则自动创建
     * 应用场景:系统启动时初始化存储环境,文件上传前的安全检查
     * 
     * @return 存储桶存在或创建成功返回true,失败返回false
     */
    public boolean existBucket() {
        try {
            String bucketName = configuration.getBucketName();
            boolean exists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
            if (!exists) {
                minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
                return true;
            }
            return exists;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 检查MinIO中指定文件是否存在
     * 
     * 功能:通过MinIO API验证文件是否存在
     * 应用场景:
     * - 删除文件前的安全检查,避免删除不存在的文件
     * - 文件操作前的状态验证
     * - 批量操作时的文件存在性校验
     * 
     * @param fileName 要检查的文件名(含路径)
     * @return 文件存在返回true,不存在或发生异常返回false
     */
    public boolean exists(String fileName) {
        try {
            String bucketName = configuration.getBucketName();
            // 使用statObject方法检查对象是否存在
            minioClient.statObject(StatObjectArgs.builder()
                    .bucket(bucketName)
                    .object(fileName)
                    .build());
            return true;
        } catch (MinioException e) {
            // 对于MinIO特定异常,通过错误代码判断文件是否不存在
            if (e.getMessage().contains("No such key")) {
                return false;
            }
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 单文件上传
     * 
     * 功能:将单个文件上传到MinIO存储
     * 应用场景:
     * - 用户头像上传
     * - 商家Logo上传
     * - Banner图上传
     * - 单个商品图片上传
     * 
     * @param file 上传的文件(MultipartFile类型)
     * @param fileName 保存到MinIO的文件名(含路径)
     * @throws RuntimeException 上传失败时抛出运行时异常
     */
    public void upload(MultipartFile file, String fileName) {
        try {
            // 确保存储桶存在
            existBucket();

            InputStream inputStream = file.getInputStream();
            minioClient.putObject(PutObjectArgs.builder()
                    .bucket(configuration.getBucketName())
                    .object(fileName)
                    .stream(inputStream, file.getSize(), -1)
                    .contentType(file.getContentType())
                    .build());
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("文件上传失败: " + e.getMessage());
        }
    }

    /**
     * 批量上传文件
     * 
     * 功能:一次性上传多个文件,自动生成唯一文件名
     * 应用场景:
     * - 商品多图批量上传
     * - 批量导入图片资源
     * - 系统初始化时批量上传默认图片
     * 
     * 优化特性:
     * - 自动生成时间戳文件名,避免文件覆盖
     * - 支持目录结构,便于文件分类管理
     * - 批量处理减少网络开销
     * 
     * @param files 文件数组
     * @param dir 上传目录(如"goods-img/"、"user-avatar/")
     * @return 上传成功的文件名列表(含路径)
     * @throws RuntimeException 批量上传失败时抛出运行时异常
     */
    public List<String> batchUploadFiles(MultipartFile[] files, String dir) {
        List<String> uploadedFileNames = new ArrayList<>();
        if (files == null || files.length == 0) {
            return uploadedFileNames;
        }

        // 处理目录路径(确保格式正确)
        String directory = (dir == null || dir.isEmpty()) ? "" :
                (dir.endsWith("/") ? dir : dir + "/");

        // 确保存储桶存在
        existBucket();

        for (MultipartFile file : files) {
            if (file.isEmpty()) {
                continue;
            }

            try {
                // 生成唯一文件名(避免覆盖)
                String originalFileName = file.getOriginalFilename();
                String suffix = originalFileName.substring(originalFileName.lastIndexOf("."));
                String uniqueFileName = directory + System.currentTimeMillis() + suffix;

                // 上传文件
                minioClient.putObject(PutObjectArgs.builder()
                        .bucket(configuration.getBucketName())
                        .object(uniqueFileName)
                        .stream(file.getInputStream(), file.getSize(), -1)
                        .contentType(file.getContentType())
                        .build());

                uploadedFileNames.add(uniqueFileName);
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException("批量上传失败: " + e.getMessage());
            }
        }
        return uploadedFileNames;
    }

    /**
     * 获取文件访问URL
     * 
     * 功能:生成文件的访问链接,支持永久访问和预签名URL
     * 应用场景:
     * - 前端展示图片
     * - 文件下载链接
     * - 图片预览功能
     * - 外部系统访问文件
     * 
     * URL生成策略:
     * 1. 优先使用配置的永久访问地址(适合内网环境)
     * 2. 备选方案:生成预签名URL(适合外网访问,默认7天有效期)
     * 
     * @param fileName 文件名(含路径)
     * @return 文件访问URL,失败时返回null
     */
    public String getFileUrl(String fileName) {
        try {
            // 如果配置了永久访问地址,直接拼接URL
            if (configuration.getUrl() != null && !configuration.getUrl().isEmpty()) {
                // 确保URL格式正确,移除末尾的斜杠
                String baseUrl = configuration.getUrl();
                if (baseUrl.endsWith("/")) {
                    baseUrl = baseUrl.substring(0, baseUrl.length() - 1);
                }
                return baseUrl + "/" + configuration.getBucketName() + "/" + fileName;
            }

            // 否则生成预签名URL(默认7天有效期)
            return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
                    .method(Method.GET)
                    .bucket(configuration.getBucketName())
                    .object(fileName)
                    .expiry(7, java.util.concurrent.TimeUnit.DAYS)
                    .build());
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 删除单个文件
     * 
     * 功能:从MinIO存储中删除指定文件
     * 应用场景:
     * - 删除用户头像
     * - 删除商品图片
     * - 删除过期文件
     * - 清理无效文件
     * 
     * 注意事项:
     * - 删除前建议先调用exists()方法检查文件是否存在
     * - 删除操作不可逆,请谨慎使用
     * 
     * @param fileName 文件名(含路径)
     * @return 删除成功返回true,失败返回false
     */
    public boolean deleteFile(String fileName) {
        try {
            minioClient.removeObject(RemoveObjectArgs.builder()
                    .bucket(configuration.getBucketName())
                    .object(fileName)
                    .build());
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 批量删除文件
     * 
     * 功能:一次性删除多个文件,提高删除效率
     * 应用场景:
     * - 删除商品时批量清理相关图片
     * - 删除用户时清理用户相关文件
     * - 批量清理过期或无效文件
     * - 系统维护时的批量文件清理
     * 
     * 优化特性:
     * - 批量操作减少网络请求次数
     * - 自动处理删除结果,返回整体操作状态
     * - 支持大量文件的高效删除
     * 
     * @param fileNames 文件名列表(含路径)
     * @return 全部删除成功返回true,任一文件删除失败返回false
     */
    public boolean batchDeleteFiles(List<String> fileNames) {
        if (fileNames == null || fileNames.isEmpty()) {
            return false;
        }

        List<DeleteObject> deleteObjects = fileNames.stream()
                .map(DeleteObject::new)
                .collect(Collectors.toList());

        try {
            Iterable<Result<io.minio.messages.DeleteError>> results = minioClient.removeObjects(
                    RemoveObjectsArgs.builder()
                            .bucket(configuration.getBucketName())
                            .objects(deleteObjects)
                            .build());

            // 检查是否有删除失败的文件
            for (Result<io.minio.messages.DeleteError> result : results) {
                io.minio.messages.DeleteError error = result.get();
                if (error != null) {
                    System.err.println("删除失败: " + error.objectName());
                    return false;
                }
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
}

创建辅助工具类 FileCleanupUtils处理 URL 解析和文件清理:

package com.example.utils;

import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 文件清理工具类 - xxx系统文件清理专用工具
 * 
 * 功能说明:
 * - URL解析:从文件访问URL中提取MinIO存储路径
 * - 文件删除:支持单个和批量文件删除
 * - 资源清理:在删除用户、商品等实体时清理相关文件
 * 
 * 应用场景:
 * - 用户删除:删除用户时清理用户头像、上传的图片等
 * - 商品删除:删除商品时清理商品相关图片
 * - 商家删除:删除商家时清理营业执照、Logo等文件
 * - 系统维护:批量清理过期或无效文件
 * 
 * 技术特性:
 * - 自动URL解析:支持标准MinIO URL格式
 * - 批量操作:支持批量文件删除,提高清理效率
 * - 空值处理:自动过滤无效URL,避免删除错误
 * - 依赖注入:集成MinioUtils,复用文件操作逻辑
 * 
 * @author xxx
 * @version 1.0 (2025-xx-XX)
 */
@Component
public class FileCleanupUtils {
    
    @Resource
    private MinioUtils minioUtils;
    
    /**
     * 从文件访问URL中提取MinIO存储路径
     * 
     * 功能:解析标准MinIO文件访问URL,提取文件在存储中的相对路径
     * 应用场景:
     * - 文件删除:根据URL确定要删除的文件路径
     * - 文件管理:将前端URL转换为MinIO操作路径
     * - 批量操作:批量处理文件URL列表
     * 
     * URL格式说明:
     * 标准格式:http://localhost:9090/group-purchase-2025/user_avatar/xxx.jpg
     * 提取结果:user_avatar/xxx.jpg
     * 
     * 技术特性:
     * - 自动分割:基于"/files/"路径分割符提取
     * - 空值处理:自动过滤无效URL
     * - 路径保持:完整保留文件在MinIO中的存储路径
     * 
     * @param url 文件访问URL
     * @return 文件在MinIO中的存储路径,无效URL返回null
     */
    public String extractFileNameFromUrl(String url) {
        if (url == null || url.isEmpty()) {
            return null;
        }
        // 从 http://localhost:9090/group-purchase-2025/user_avatar/xxx.jpg 提取 user_avatar/xxx.jpg
        String[] parts = url.split("/files/");
        return parts.length > 1 ? parts[1] : null;
    }
    
    /**
     * 根据文件URL删除单个文件
     * 
     * 功能:通过文件访问URL自动删除MinIO中对应的文件
     * 应用场景:
     * - 用户头像删除:用户更换或删除头像时
     * - 商品图片删除:商品下架时删除相关图片
     * - 文件清理:删除无效或过期的文件
     * 
     * 工作流程:
     * 1. 从URL中提取文件存储路径
     * 2. 调用MinIO工具类删除文件
     * 3. 自动处理无效URL,避免删除错误
     * 
     * 技术特性:
     * - 自动URL解析:无需手动指定文件路径
     * - 空值保护:自动过滤无效URL
     * - 错误容错:删除失败时不影响主流程
     * 
     * @param url 文件的访问URL
     */
    public void deleteFileFromUrl(String url) {
        String fileName = extractFileNameFromUrl(url);
        if (fileName != null) {
            minioUtils.deleteFile(fileName);
        }
    }
    
    /**
     * 根据文件URL列表批量删除文件
     * 
     * 功能:批量处理文件URL列表,一次性删除多个文件
     * 应用场景:
     * - 用户删除:删除用户时清理用户上传的所有文件
     * - 商品删除:删除商品时批量清理商品相关图片
     * - 商家删除:删除商家时清理营业执照、Logo等文件
     * - 系统维护:批量清理过期或无效文件
     * 
     * 工作流程:
     * 1. 批量解析URL列表,提取文件存储路径
     * 2. 过滤无效URL,确保操作安全
     * 3. 调用MinIO工具类进行批量删除
     * 4. 提高删除效率,减少网络请求次数
     * 
     * 技术特性:
     * - 批量处理:支持大量URL的高效处理
     * - 自动过滤:自动过滤无效URL,避免删除错误
     * - 流式处理:使用Stream API进行高效的数据转换
     * - 空值保护:自动处理空列表,避免不必要的操作
     * 
     * @param urls 文件访问URL列表
     */
    public void deleteFilesFromUrls(List<String> urls) {
        if (urls == null || urls.isEmpty()) {
            return;
        }
        
        List<String> fileNames = urls.stream()
                .map(this::extractFileNameFromUrl)
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
        
        if (!fileNames.isEmpty()) {
            minioUtils.batchDeleteFiles(fileNames);
        }
    }
} 
3.4. 创建文件操作控制器

创建 MinioFileUploadController 提供文件操作的 REST 接口:

package com.example.controller;

import com.example.common.Result;
import com.example.common.enums.ResultCodeEnum;
import com.example.utils.MinioUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;

import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.stream.Collectors;

/**
 * MinIO文件上传控制器 - xx系统文件管理核心接口
 * 
 * 功能说明:
 * - 文件上传:支持单文件和批量文件上传,自动生成唯一文件名
 * - 文件删除:支持单文件和批量文件删除,包含删除验证
 * - 文件访问:提供文件URL获取接口,支持各种文件类型
 * 
 * 应用场景:
 * - 商品图片管理:批量上传商品多图,删除商品时清理图片
 * - 用户头像管理:上传用户头像,删除用户时清理头像
 * - 商家资料管理:上传营业执照、Logo等认证文件
 * - 系统资源管理:Banner图、广告图等系统级资源
 * 
 * 接口特性:
 * - 跨域支持:支持前端跨域请求
 * - 文件验证:上传前进行文件类型和大小验证
 * - 错误处理:统一的异常处理和错误响应
 * - 批量操作:支持批量上传和删除,提高操作效率
 * 
 * @author xxx
 * @version 1.0 (2025-xx-XX)
 */
@CrossOrigin
@RestController
@RequestMapping("/files")
public class MinioFileUploadController {
    @Autowired
    private MinioUtils minioUtils;

    /**
     * 单文件上传接口
     * 
     * 功能:上传单个文件到MinIO存储,自动生成唯一文件名
     * 应用场景:
     * - 用户头像上传:用户注册或修改头像时使用
     * - Banner图上传:系统管理员上传轮播图广告
     * - 商家Logo上传:商家注册或更新店铺Logo
     * - 单个商品图片上传:商品添加单张图片
     * 
     * 技术特性:
     * - 自动生成时间戳文件名,避免文件覆盖
     * - 支持目录分类,便于文件管理
     * - 文件类型和大小验证
     * - 统一的错误处理和响应格式
     * 
     * @param file 要上传的文件(MultipartFile类型)
     * @param dir 存储目录(可选,如"user-avatar/"、"banner/")
     * @param request HTTP请求对象,用于验证请求类型
     * @return 上传成功返回文件URL,失败返回错误信息
     */
    @PostMapping("/upload")
    public Result uploadFile(@RequestParam("file") MultipartFile file,
                             @RequestParam(required = false) String dir,
                             HttpServletRequest request) {
        // 验证请求类型
        if (!(request instanceof MultipartHttpServletRequest)) {
            return Result.error(ResultCodeEnum.FILE_UPLOAD_ERROR);
        }

        if (file == null || file.isEmpty()) {
            return Result.error(ResultCodeEnum.FILE_EMPTY_ERROR);
        }

        try {
            // 生成唯一文件名(防止覆盖)
            String originalFileName = file.getOriginalFilename();
            String suffix = originalFileName.substring(originalFileName.lastIndexOf("."));
            String fileName = (dir != null ? dir + "/" : "") + System.currentTimeMillis() + suffix;

            minioUtils.upload(file, fileName);
            return Result.success(minioUtils.getFileUrl(fileName));
        } catch (Exception e) {
            e.printStackTrace();
            return Result.error(ResultCodeEnum.FILE_UPLOAD_ERROR);
        }
    }

    /**
     * 获取文件访问URL接口
     * 
     * 功能:根据文件名生成文件的访问链接
     * 应用场景:
     * - 前端展示图片:商品详情页、用户头像等
     * - 文件下载:用户下载上传的文件
     * - 图片预览:文件管理界面的缩略图显示
     * - 外部引用:其他系统或应用访问文件
     * 
     * 技术特性:
     * - 支持永久访问URL(适合内网环境)
     * - 备选预签名URL(适合外网访问,7天有效期)
     * - 自动处理URL格式,确保访问正常
     * 
     * @param fileName 文件名(含路径)
     * @return 文件访问URL,失败时返回错误信息
     */
    @GetMapping("/url")
    public Result getFileUrl(@RequestParam("fileName") String fileName) {
        return Result.success(minioUtils.getFileUrl(fileName));
    }

    /**
     * 删除单个文件接口
     * 
     * 功能:从MinIO存储中删除指定的单个文件
     * 应用场景:
     * - 删除用户头像:用户删除头像或更换头像时
     * - 删除商品图片:商品下架或删除时清理图片
     * - 删除过期文件:系统维护时清理无效文件
     * - 删除错误文件:上传错误或重复文件的清理
     * 
     * 技术特性:
     * - 支持路径参数传递文件名
     * - 统一的删除结果响应格式
     * - 删除失败时返回详细错误信息
     * 
     * @param fileName 要删除的文件名(含路径)
     * @return 删除成功返回成功信息,失败返回错误信息
     */
    @DeleteMapping("/{fileName}")
    public Result deleteFile(@PathVariable String fileName) {
        return minioUtils.deleteFile(fileName)
                ? Result.success("删除成功")
                : Result.error(ResultCodeEnum.FILE_DELETE_ERROR);
    }

    /**
     * 批量删除文件接口
     * 
     * 功能:一次性删除多个文件,提高删除效率
     * 应用场景:
     * - 商品多图删除:商品下架时批量清理所有相关图片
     * - 用户文件清理:删除用户时清理用户上传的所有文件
     * - 批量维护:系统维护时批量清理过期或无效文件
     * - 批量操作:支持前端多选删除功能
     * 
     * 技术特性:
     * - 支持JSON格式的文件名列表
     * - 批量操作减少网络请求次数
     * - 统一的删除结果响应
     * - 任一文件删除失败时返回错误信息
     * 
     * @param fileNames 要删除的文件名列表(含路径)
     * @return 全部删除成功返回成功信息,失败返回错误信息
     */
    @DeleteMapping("/batch")
    public Result batchDeleteFiles(@RequestBody List<String> fileNames) {
        return minioUtils.batchDeleteFiles(fileNames)
                ? Result.success("批量删除成功")
                : Result.error(ResultCodeEnum.FILE_BATCH_DELETE_ERROR);
    }

    /**
     * 批量上传文件接口
     * 
     * 功能:一次性上传多个文件,支持目录分类和自动命名
     * 应用场景:
     * - 商品多图上传:商家添加商品时批量上传多张图片
     * - 批量资源导入:系统管理员批量导入图片资源
     * - 多文件处理:支持前端多文件选择上传
     * - 批量初始化:系统初始化时批量上传默认资源
     * 
     * 技术特性:
     * - 支持多文件同时上传
     * - 自动生成唯一文件名,避免覆盖
     * - 支持目录分类存储
     * - 返回文件名和URL的完整信息
     * - 统一的错误处理和响应格式
     * 
     * @param files 要上传的文件数组
     * @param dir 存储目录(可选,如"goods-img/"、"banner/")
     * @param request HTTP请求对象,用于验证请求类型
     * @return 上传成功返回文件名和URL列表,失败返回错误信息
     */
    @PostMapping("/batchUpload")
    public Result batchUploadFiles(@RequestParam("files") MultipartFile[] files,
                                   @RequestParam(required = false) String dir,
                                   HttpServletRequest request) {
        // 验证请求类型
        if (!(request instanceof MultipartHttpServletRequest)) {
            return Result.error(ResultCodeEnum.FILE_UPLOAD_ERROR);
        }

        if (files == null || files.length == 0) {
            return Result.error(ResultCodeEnum.PARAM_ERROR);
        }

        try {
            // 上传文件并获取文件名列表
            List<String> fileNames = minioUtils.batchUploadFiles(files, dir);
            if (fileNames.isEmpty()) {
                return Result.error(ResultCodeEnum.FILE_UPLOAD_ERROR);
            }

            // 构建包含文件名和URL的结果集(方便前端直接使用)
            List<FileUploadResult> results = fileNames.stream()
                    .map(name -> {
                        String url = minioUtils.getFileUrl(name);
                        return new FileUploadResult(name, url);
                    })
                    .collect(Collectors.toList());
            
            return Result.success(results);
        } catch (Exception e) {
            e.printStackTrace();
            return Result.error(ResultCodeEnum.FILE_UPLOAD_ERROR);
        }
    }

    /**
     * 文件上传结果封装类
     * 
     * 功能:封装批量上传接口的返回结果
     * 应用场景:
     * - 批量上传接口的响应数据封装
     * - 前端获取文件名和URL的完整信息
     * - 支持文件管理和预览功能
     * 
     * 数据字段:
     * - fileName: 文件在MinIO中的存储路径和文件名
     * - url: 文件的访问链接
     */
    public static class FileUploadResult {
        private String fileName;
        private String url;

        public FileUploadResult(String fileName, String url) {
            this.fileName = fileName;
            this.url = url;
        }

        public String getFileName() {
            return fileName;
        }

        public void setFileName(String fileName) {
            this.fileName = fileName;
        }

        public String getUrl() {
            return url;
        }

        public void setUrl(String url) {
            this.url = url;
        }
    }
}
3.5. 业务中使用文件存储功能

在业务服务中注入 MinioUtils 或 FileCleanupUtils 工具类使用文件存储功能。

3.5.1 用户头像处理示例
@Service
public class UserService {
    @Resource
    private FileCleanupUtils fileCleanupUtils;
    
    @Resource
    private MinioConfig minioConfig;

    // 用户删除时清理头像
    public void deleteById(Integer id) {
        User user = userMapper.selectById(id);
        if (user != null && user.getAvatar() != null) {
            // 通过URL删除文件
            fileCleanupUtils.deleteFileFromUrl(user.getAvatar());
        }
        userMapper.deleteById(id);
    }
}
3.5.2 商品图片处理示例
@Service
public class GoodsImagesService {
    @Resource
    private MinioUtils minioUtils;

    // 商品图片删除
    public void removeById(Long id) {
        GoodsImages goodsImages = selectById(id);
        String fileName = extractFileName(goodsImages.getImgUrl());
        
        // 检查文件是否存在
        if (!minioUtils.exists(fileName)) {
            throw new RuntimeException("文件不存在");
        }
        
        // 删除MinIO文件
        boolean deleteSuccess = minioUtils.deleteFile(fileName);
        if (!deleteSuccess) {
            throw new RuntimeException("文件删除失败");
        }
        
        // 删除数据库记录
        goodsImagesMapper.deleteById(id);
    }
}


网站公告

今日签到

点亮在社区的每一天
去签到