MinIO Java 对接试用:从连接、上传到下载的完整示例

MinIO Java 对接试用:从连接、上传到下载的完整示例

适用场景:Spring Boot 服务接入自建 MinIO,用于文件上传、下载、预览和删除。 本文示例不包含真实地址、账号或密钥,生产环境请使用环境变量或密钥管理服务。

一、先明确 MinIO 的对象模型

MinIO 兼容 S3,核心对象是:Endpoint(服务地址)、Bucket(桶)、Object(对象键)和凭证。对象键不是本地文件路径,例如 2026/08/report.pdf 只是一个字符串前缀。

二、准备试用环境

yaml 复制代码
MinIO Endpoint: http://127.0.0.1:9000
控制台地址:    http://127.0.0.1:9001
Bucket:        demo-files
Access Key:    通过环境变量注入
Secret Key:    通过环境变量注入

首次试用建议创建独立测试桶和最小权限账号,不要直接使用管理员账号连接业务系统。

三、引入 Java SDK

在 Maven 中加入依赖,版本由项目统一管理;不要在多个模块写不同版本。

xml 复制代码
<dependency>
  <groupId>io.minio</groupId>
  <artifactId>minio</artifactId>
  <version>${minio.version}</version>
</dependency>

四、Spring Boot 配置

ruby 复制代码
minio:
  endpoint: ${MINIO_ENDPOINT:http://127.0.0.1:9000}
  access-key: ${MINIO_ACCESS_KEY:}
  secret-key: ${MINIO_SECRET_KEY:}
  bucket: ${MINIO_BUCKET:demo-files}

不要把 secret-key 提交到 Git。开发机可以用 PowerShell 临时变量:

ini 复制代码
$env:MINIO_ENDPOINT="http://127.0.0.1:9000"
$env:MINIO_ACCESS_KEY="测试账号"
$env:MINIO_SECRET_KEY="测试密钥"
$env:MINIO_BUCKET="demo-files"

五、创建 MinioClient Bean

less 复制代码
@Configuration
public class MinioConfig {
    @Bean
    MinioClient minioClient(
            @Value("${minio.endpoint}") String endpoint,
            @Value("${minio.access-key}") String accessKey,
            @Value("${minio.secret-key}") String secretKey) {
        return MinioClient.builder()
                .endpoint(endpoint)
                .credentials(accessKey, secretKey)
                .build();
    }
}

六、封装常用操作

scss 复制代码
@Service
public class ObjectStorageService {
    private final MinioClient client;
    private final String bucket;
​
    public ObjectStorageService(MinioClient client,
                                @Value("${minio.bucket}") String bucket) {
        this.client = client;
        this.bucket = bucket;
    }
​
    public void ensureBucket() throws Exception {
        boolean exists = client.bucketExists(
                BucketExistsArgs.builder().bucket(bucket).build());
        if (!exists) {
            client.makeBucket(MakeBucketArgs.builder().bucket(bucket).build());
        }
    }
​
    public void upload(String objectName, InputStream input, long size,
                       String contentType) throws Exception {
        client.putObject(PutObjectArgs.builder()
                .bucket(bucket)
                .object(objectName)
                .stream(input, size, -1)
                .contentType(contentType)
                .build());
    }
​
    public InputStream download(String objectName) throws Exception {
        return client.getObject(GetObjectArgs.builder()
                .bucket(bucket).object(objectName).build());
    }
​
    public String presignedGetUrl(String objectName, int minutes) throws Exception {
        return client.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
                .method(Method.GET).bucket(bucket).object(objectName)
                .expiry(minutes, TimeUnit.MINUTES).build());
    }
​
    public void delete(String objectName) throws Exception {
        client.removeObject(RemoveObjectArgs.builder()
                .bucket(bucket).object(objectName).build());
    }
}

InputStream 用完必须关闭;大文件不要先读入 byte[]size 已知时传实际长度,未知时传 -1 并设置合理的分片大小。

七、提供一个联调接口

less 复制代码
@RestController
@RequestMapping("/api/files")
public class FileController {
    private final ObjectStorageService storage;
​
    @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public Map<String, String> upload(@RequestPart MultipartFile file) throws Exception {
        String objectName = UUID.randomUUID() + "-" +
                StringUtils.cleanPath(file.getOriginalFilename());
        storage.upload(objectName, file.getInputStream(), file.getSize(),
                Optional.ofNullable(file.getContentType())
                        .orElse(MediaType.APPLICATION_OCTET_STREAM_VALUE));
        return Map.of("objectName", objectName);
    }
}

测试:

bash 复制代码
curl -F "file=@./demo.txt" http://localhost:8080/api/files

八、试用验收清单

  1. 启动服务后能自动创建测试桶或检测到已有桶。
  2. 上传后在 MinIO Console 中能看到对象,大小和 Content-Type 正确。
  3. 下载内容与原文件校验和一致。
  4. 预签名 URL 在有效期内可访问,过期后失效。
  5. 删除对象后再次下载返回不存在。
  6. 错误日志不打印 Secret Key、完整预签名 URL 或用户隐私。

九、生产化注意事项

  • 桶默认保持私有,下载使用后端鉴权后生成短时预签名 URL。
  • 对象键使用业务 ID 加随机值,避免用户原文件名造成覆盖和路径穿越。
  • 上传前校验扩展名、MIME、大小和病毒扫描结果。
  • 大文件使用分片上传;失败时记录 uploadId 并提供清理策略。
  • 需要审计时保存对象键、业务单号、上传人、大小、哈希和时间,不保存密钥。
  • 跨服务器迁移前先确认版本控制、对象锁、生命周期和元数据是否需要保留。

本文示例适合首次验证 SDK 与 MinIO 连通性。真正上线前应补充统一异常、权限模型、断点续传、限流和监控。

相关推荐
神经蛙199615 小时前
🌍 别再硬编码中文了!Python Web 项目国际化(i18n)完全指南
后端·python
二月龙15 小时前
Spring 事务失效的 8 种场景,很多老手依然频繁踩雷
后端
掘金酱15 小时前
「TRAE Work 实战帮」征文启动!你沉淀的经验,值得被看见!
前端·人工智能·后端
长大198815 小时前
MyBatis 常见性能陷阱:N+1 查询、一级缓存踩坑解决方案
后端
爱勇宝16 小时前
DeepSeek V4-Flash 更新:代码与 Agent 能力全面增强
前端·后端·deepseek
极客悟道16 小时前
SDKMAN vs jEnv vs JetTUI,JDK 版本管理到底选哪个
后端
长大198816 小时前
Java8 新特性到底要不要吃透?工作中高频使用的 5 个功能总结
后端
二月龙16 小时前
Spring Bean 生命周期 & 循环依赖:90% 开发者只知结论不懂原理
后端