MinIO 是一个高性能的分布式对象存储系统,兼容 Amazon S3 API。以下是核心功能详解及 Spring Cloud 集成方案:
功能 | 描述 |
---|---|
数据加密 | 客户端/服务端加密(SSE-C/SSE-S3) |
生命周期管理 | 自动转换存储类型或删除过期对象 |
事件通知 | 通过 Webhook、MQTT、Kafka 等通知对象操作事件 |
数据复制 | 跨数据中心/云平台的对象复制 |
分布式部署 | Erasure Code 纠删码技术实现高可用 |
# 常用管理命令
mc ls minio/mybucket # 列出存储桶内容
mc cp image.jpg minio/mybucket # 上传文件
mc policy set public minio/mybucket # 设置公开访问
mc admin info minio # 查看集群信息
<dependency>
<groupId>io.miniogroupId>
<artifactId>minioartifactId>
<version>8.5.7version>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-bootstrapartifactId>
dependency>
bootstrap.yml
:
minio:
endpoint: http://minio-server:9000
access-key: minioadmin
secret-key: minioadmin
bucket: mybucket
secure: false
region: us-east-1
@Configuration
public class MinioConfig {
@Value("${minio.endpoint}")
private String endpoint;
@Value("${minio.access-key}")
private String accessKey;
@Value("${minio.secret-key}")
private String secretKey;
@Value("${minio.bucket}")
private String bucketName;
@Bean
public MinioClient minioClient() {
return MinioClient.builder()
.endpoint(endpoint)
.credentials(accessKey, secretKey)
.build();
}
@Bean
public void initBucket() throws Exception {
if (!minioClient().bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())) {
minioClient().makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
}
}
}
@Service
public class MinioService {
@Autowired
private MinioClient minioClient;
@Value("${minio.bucket}")
private String bucketName;
// 上传文件
public String uploadFile(MultipartFile file, String objectName) throws Exception {
if (objectName == null) {
objectName = UUID.randomUUID() + "_" + file.getOriginalFilename();
}
minioClient.putObject(
PutObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.stream(file.getInputStream(), file.getSize(), -1)
.contentType(file.getContentType())
.build());
return objectName;
}
// 获取文件URL
public String getFileUrl(String objectName, int expiryDays) throws Exception {
return minioClient.getPresignedObjectUrl(
GetPresignedObjectUrlArgs.builder()
.method(Method.GET)
.bucket(bucketName)
.object(objectName)
.expiry(expiryDays * 24 * 60 * 60)
.build());
}
// 下载文件
public InputStream downloadFile(String objectName) throws Exception {
return minioClient.getObject(
GetObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.build());
}
// 删除文件
public void deleteFile(String objectName) throws Exception {
minioClient.removeObject(
RemoveObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.build());
}
}
@RestController
@RequestMapping("/files")
public class FileController {
@Autowired
private MinioService minioService;
@PostMapping("/upload")
public ResponseEntity<String> uploadFile(@RequestParam("file") MultipartFile file) {
try {
String objectName = minioService.uploadFile(file, null);
return ResponseEntity.ok("文件上传成功: " + objectName);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("上传失败: " + e.getMessage());
}
}
@GetMapping("/download/{objectName}")
public ResponseEntity<Resource> downloadFile(@PathVariable String objectName) {
try {
InputStream stream = minioService.downloadFile(objectName);
ByteArrayResource resource = new ByteArrayResource(stream.readAllBytes());
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + objectName + "\"")
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(resource);
} catch (Exception e) {
return ResponseEntity.notFound().build();
}
}
@GetMapping("/url/{objectName}")
public ResponseEntity<String> getFileUrl(@PathVariable String objectName) {
try {
String url = minioService.getFileUrl(objectName, 7);
return ResponseEntity.ok(url);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("获取URL失败: " + e.getMessage());
}
}
}
@Component
public class MinioEventListener {
@EventListener
public void handleMinioEvent(MinioEvent event) {
switch (event.getEventType()) {
case OBJECT_CREATED_PUT:
System.out.println("文件上传: " + event.getObjectName());
break;
case OBJECT_REMOVED_DELETE:
System.out.println("文件删除: " + event.getObjectName());
break;
default:
// 处理其他事件类型
}
}
}
// 配置事件发布
public class MinioEventPublisher {
@Bean
public ApplicationEventPublisher minioEventPublisher() {
return new SimpleApplicationEventPublisher();
}
// 在MinioService的各个方法中发布事件
public void deleteFile(String objectName) throws Exception {
// ...删除操作...
applicationEventPublisher.publishEvent(
new MinioEvent(EventType.OBJECT_REMOVED_DELETE, objectName));
}
}
public String uploadLargeFile(MultipartFile file, String objectName) throws Exception {
if (objectName == null) {
objectName = UUID.randomUUID() + "_" + file.getOriginalFilename();
}
long partSize = 10 * 1024 * 1024; // 10MB分片
String uploadId = minioClient.createMultipartUpload(bucketName, objectName).result().uploadId();
List<Part> parts = new ArrayList<>();
try (InputStream stream = file.getInputStream()) {
byte[] buffer = new byte[(int) partSize];
int partNumber = 1;
int bytesRead;
while ((bytesRead = stream.read(buffer)) > 0) {
ByteArrayInputStream partStream = new ByteArrayInputStream(buffer, 0, bytesRead);
Part part = minioClient.uploadPart(
bucketName, objectName, uploadId, partNumber,
partStream, bytesRead, null).result();
parts.add(part);
partNumber++;
}
}
minioClient.completeMultipartUpload(
bucketName, objectName, uploadId, parts.toArray(new Part[0]));
return objectName;
}
public void setPublicAccess(String objectName) throws Exception {
minioClient.setObjectPolicy(
SetObjectPolicyArgs.builder()
.bucket(bucketName)
.object(objectName)
.config(new PolicyTypeConfig(PolicyType.READ_ONLY))
.build());
}
public void setPrivateAccess(String objectName) throws Exception {
minioClient.deleteObjectPolicy(
DeleteObjectPolicyArgs.builder()
.bucket(bucketName)
.object(objectName)
.build());
}
public void setLifecycleRule(String prefix, int expirationDays) throws Exception {
LifecycleRule rule = new LifecycleRule(
Status.ENABLED,
null,
new Expiration(ZonedDateTime.now().plusDays(expirationDays)),
new RuleFilter(prefix),
"auto-delete-rule",
null,
null,
null
);
minioClient.setBucketLifecycle(
SetBucketLifecycleArgs.builder()
.bucket(bucketName)
.config(new LifecycleConfiguration(Collections.singletonList(rule)))
.build());
}
public Map<String, String> getFileMetadata(String objectName) throws Exception {
StatObjectResponse stat = minioClient.statObject(
StatObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.build());
return stat.userMetadata();
}
public void updateMetadata(String objectName, Map<String, String> metadata) throws Exception {
minioClient.copyObject(
CopyObjectArgs.builder()
.source(CopySource.builder().bucket(bucketName).object(objectName).build())
.bucket(bucketName)
.object(objectName)
.userMetadata(metadata)
.build());
}
@Bean
public MinioClient secureMinioClient() {
return MinioClient.builder()
.endpoint("https://minio.example.com")
.credentials(accessKey, secretKey)
.region("us-east-1")
.httpClient(HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.sslContext(createSSLContext()) // 自定义SSL
.build())
.build();
}
private SSLContext createSSLContext() throws Exception {
return SSLContextBuilder.create()
.loadTrustMaterial(new TrustSelfSignedStrategy())
.build();
}
public MinioClient createTenantClient(String tenantId) {
String tenantBucket = "tenant-" + tenantId;
return MinioClient.builder()
.endpoint(minioEndpoint)
.credentials(generateAccessKey(tenantId), generateSecretKey(tenantId))
.build();
}
private String generateAccessKey(String tenantId) {
// 基于租户ID生成唯一访问密钥
}
# Prometheus 监控配置
management:
endpoints:
web:
exposure:
include: health,metrics,prometheus
metrics:
export:
prometheus:
enabled: true
tags:
application: ${spring.application.name}
// MinIO 健康检查
@Component
public class MinioHealthIndicator implements HealthIndicator {
@Autowired
private MinioClient minioClient;
@Override
public Health health() {
try {
minioClient.listBuckets();
return Health.up().build();
} catch (Exception e) {
return Health.down(e).build();
}
}
}
性能优化
// 使用并行流处理批量操作
public void batchUpload(List<MultipartFile> files) {
files.parallelStream().forEach(file -> {
try {
uploadFile(file, null);
} catch (Exception e) {
// 错误处理
}
});
}
缓存策略
@Cacheable(value = "fileMetadata", key = "#objectName")
public Map<String, String> getCachedMetadata(String objectName) throws Exception {
return getFileMetadata(objectName);
}
错误处理
@ControllerAdvice
public class MinioExceptionHandler {
@ExceptionHandler(MinioException.class)
public ResponseEntity<String> handleMinioException(MinioException e) {
if (e instanceof ErrorResponseException) {
ErrorResponseException ere = (ErrorResponseException) e;
return ResponseEntity.status(ere.errorResponse().code())
.body("MinIO错误: " + ere.getMessage());
}
return ResponseEntity.status(500).body("MinIO服务异常");
}
}
分布式锁
public boolean acquireLock(String lockKey, int ttlSeconds) {
try {
minioClient.putObject(
PutObjectArgs.builder()
.bucket("locks")
.object(lockKey)
.stream(new ByteArrayInputStream(new byte[0]), 0, -1)
.build());
return true;
} catch (ErrorResponseException e) {
if (e.errorResponse().code() == 409) { // 冲突表示锁已存在
return false;
}
throw new RuntimeException("获取锁失败", e);
}
}
连接超时
MinioClient.builder()
.endpoint(endpoint)
.credentials(accessKey, secretKey)
.connectTimeout(Duration.ofSeconds(30))
.writeTimeout(Duration.ofMinutes(2))
.build();
大文件上传内存溢出
权限不足
// 创建服务专用策略
String policyJson = """
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:*"],
"Resource": ["arn:aws:s3:::app-bucket/*"]
}
]
}""";
minioClient.setPolicy(SetPolicyArgs.builder()
.config(policyJson)
.build());
跨域配置
public void configureCors() throws Exception {
minioClient.setBucketCors(
SetBucketCorsArgs.builder()
.bucket(bucketName)
.config(new CorsConfiguration(
Collections.singletonList(new CORSRule(
Collections.singletonList("*"),
Collections.singletonList("*"),
Collections.singletonList("*"),
Collections.singletonList("*"),
null
)))
).build());
}
生产建议:
- 使用分布式 MinIO 集群(至少4节点)
- 启用 TLS 加密传输
- 定期进行存储桶策略审计
- 设置合理的生命周期规则
- 使用客户端加密存储敏感数据
以上内容涵盖了 MinIO 的核心功能、Spring Cloud 集成方案、高级应用场景以及生产环境最佳实践,为构建高效对象存储服务提供全面指导。