SpringBoot 整合 MinIO 实现对象存储文件上传下载实战
【摘要】 传统服务器本地存储文件存在扩容难、备份麻烦、单点故障等问题,MinIO 是一款轻量、高性能的对象存储服务,兼容 S3 协议。本文基于 SpringBoot 整合 MinIO,封装文件上传、下载、预览、删除接口,包含桶策略配置、文件类型校验、文件名防重处理,附带完整可运行代码,适合图片、附件、视频等文件存储场景快速落地。
前言
在业务系统开发中,图片、合同附件、视频等资源存储是刚需。如果直接把文件存到应用服务器磁盘,集群部署时文件无法共享,扩容和迁移都很麻烦。MinIO 兼容 AWS S3 标准接口,部署简单、资源占用低,私有化部署也很方便,是中小型项目非常合适的对象存储方案。
本文搭建一套通用 MinIO 文件工具类,统一处理文件上传、临时访问链接生成、文件删除,同时梳理开发中高频踩坑点。
一、环境准备
- 启动 MinIO 服务(docker 快速启动参考)
docker run -d -p 9000:9000 -p 9001:9001 \
--name minio \
-e MINIO_ROOT_USER=minioadmin \
-e MINIO_ROOT_PASSWORD=minioadmin123 \
-v /data/minio:/data \
minio/minio server /data --console-address ":9001"
- 9000:API端口
- 9001:可视化控制台
- Maven 依赖
<!-- MinIO SDK -->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.5.7</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
二、yml 配置文件
minio:
endpoint: [http://127.0.0.1:9000](http://127.0.0.1:9000)
access-key: minioadmin
secret-key: minioadmin123
bucket-name: file-bucket
# 分片大小 5MB
part-size: 5242880
三、配置 MinIO Client
import io.minio.MinioClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MinioConfig {
@Value("${minio.endpoint}")
private String endpoint;
@Value("${minio.access-key}")
private String accessKey;
@Value("${minio.secret-key}")
private String secretKey;
@Bean
public MinioClient minioClient() {
return MinioClient.builder()
.endpoint(endpoint)
.credentials(accessKey, secretKey)
.build();
}
}
四、封装 MinIO 工具类(核心)
import io.minio.*;
import io.minio.http.Method;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@Component
@RequiredArgsConstructor
public class MinioUtil {
private final MinioClient minioClient;
@Value("${minio.bucket-name}")
private String bucketName;
/**
* 检查桶是否存在,不存在则创建
*/
public void checkBucket() throws Exception {
boolean exists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
if (!exists) {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
}
}
/**
* 文件上传
* @param file 文件
* @return 文件访问路径
*/
public String upload(MultipartFile file) throws Exception {
checkBucket();
// 原始文件名
String originalName = file.getOriginalFilename();
String suffix = originalName.substring(originalName.lastIndexOf("."));
// UUID重命名,防止文件名覆盖冲突
String objectName = UUID.randomUUID() + suffix;
try (InputStream is = file.getInputStream()) {
minioClient.putObject(
PutObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.stream(is, file.getSize(), -1)
.contentType(file.getContentType())
.build()
);
}
return objectName;
}
/**
* 获取文件临时预览URL(有效期30分钟)
*/
public String getPreviewUrl(String objectName) throws Exception {
return minioClient.getPresignedObjectUrl(
GetPresignedObjectUrlArgs.builder()
.bucket(bucketName)
.object(objectName)
.method(Method.GET)
.expiry(30, TimeUnit.MINUTES)
.build()
);
}
/**
* 删除文件
*/
public void delete(String objectName) throws Exception {
minioClient.removeObject(
RemoveObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.build()
);
}
}
五、Controller 接口
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
@RestController
@RequestMapping("/file")
@RequiredArgsConstructor
public class FileController {
private final MinioUtil minioUtil;
@PostMapping("/upload")
public String upload(@RequestParam("file") MultipartFile file) throws Exception {
String objectName = minioUtil.upload(file);
return minioUtil.getPreviewUrl(objectName);
}
@DeleteMapping("/delete")
public String delete(@RequestParam("objectName") String objectName) throws Exception {
minioUtil.delete(objectName);
return "删除成功";
}
}
六、业务扩展方案
- 文件分类目录:objectName 可以加上日期前缀,比如
2026/10/27/xxx.jpg,方便归档管理; - 文件白名单校验:限制后缀 jpg/png/pdf,禁止上传可执行文件;
- 大文件分片上传:MinIO 原生支持分片,适合几十MB以上视频;
- 永久公开访问:修改桶策略为 public,不推荐业务使用,优先用带时效签名URL;
- 文件水印、图片压缩:上传成功后搭配 thumbnail 服务处理图片。
七、常见坑点
- 跨域问题:前端直传MinIO需要在控制台配置CORS;
- 时区、签名过期:服务器时间不准会导致签名链接直接失效;
- 内网部署:生成的url默认是minio endpoint内网地址,需要配置反向代理和域名;
- 桶名称规范:不能大写、不能带下划线,只能小写字母、数字、短横线;
- 资源释放:InputStream必须关闭,工具类用try-with-resources自动关闭。
八、总结
MinIO 上手简单,API统一,私有化部署可控性强,对比云厂商OSS,本地开发、测试环境非常友好。这套封装可以直接集成到后台管理系统,统一接管全部文件存储。如果后续要迁移到华为云OBS、阿里云OSS,只需要替换存储实现层,上层业务代码几乎不用改动,符合面向接口编程思想。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)