旧版MinIO的安装(windows)、Spring Boot 后端集成 MinIO 实现文件存储(超详细,带图文)
一、前言
我翻阅了很多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 12345678REM 启动 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 方法二:使用任务管理器关闭进程(不推荐)
操作步骤:
- 按下Ctrl + Shift + Esc组合键,打开 “任务管理器” 窗口。
- 在 “进程” 选项卡中,找到minio.exe进程。如果不确定哪个是 MinIO 进程,可以查看 “命令行” 列,确认其路径和启动参数与你启动 MinIO 时的一致。
- 右键点击minio.exe进程,选择 “结束任务”,即可关闭 MinIO 服务。
但是该操作本质上也是强制结束进程,同样可能存在数据一致性问题,不适合在对数据完整性要求高的生产环境中使用。
2.6.3 方法三:使用命令行命令关闭(推荐)
操作步骤:
- 打开命令提示符(CMD)窗口。可以通过在 “开始” 菜单中搜索 “cmd”,然后以管理员身份运行。
- 使用tasklist命令查找 MinIO 进程的 PID(进程标识符)。输入tasklist | findstr "minio.exe",回车后会显示 MinIO 进程的相关信息,其中包含 PID。你的 MinIO 可执行文件可能不是 minio.exe,而是其他名称(如 minio.RELEASE.2024-06-13T22-53-53Z)。此时需用实际进程名查询。
- 使用taskkill命令结束 MinIO 进程。输入taskkill /PID <进程PID> /F(<进程PID> 替换为实际查询到的 PID,/F 参数表示强制终止进程)。如果不想强制终止,也可以不加/F参数,让进程正常退出(前提是进程能正常响应退出操作)。
2.6.4 方法四:将 MinIO 配置为服务后进行管理(适合生产环境)
操作步骤:
- 按下Win + R组合键,打开 “运行” 对话框,输入services.msc,回车后打开 “服务” 管理界面。
- 在服务列表中找到你为 MinIO 配置的服务名称(在使用 NSSM 配置服务时可以自定义服务名称)。
- 右键点击该服务,选择 “停止”,即可关闭 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;@PostConstructpublic void init() {System.out.println("MinIO配置加载成功:");System.out.println("URL: " + url);System.out.println("Bucket: " + bucketName);System.out.println("AccessKey: " + accessKey);}@Beanpublic 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 {@Autowiredprivate MinioClient minioClient;@Autowiredprivate 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 {// 如果配置了永久访问地址,直接拼接URLif (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 {@Resourceprivate 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.jpgString[] 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 {@Autowiredprivate 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 {@Resourceprivate FileCleanupUtils fileCleanupUtils;@Resourceprivate 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 {@Resourceprivate 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);}
}