目录
什么是MinIO?
MinIO是一个高性能的分布式对象存储服务,兼容Amazon S3 API。它专为云原生应用设计,具有高性能、可扩展、易部署等特点。MinIO可以用于存储各种类型的数据,如图片、视频、文档、备份文件等。
MinIO的核心特性
1. 高性能:支持高并发读写,单节点可处理数万QPS
2. 兼容S3:完全兼容Amazon S3 API,便于迁移
3. 分布式:支持水平扩展,可扩展到数百个节点
4. 云原生:支持Kubernetes部署,容器化友好
5. 安全性:支持加密、访问控制、版本控制
6. 易部署:单二进制文件,部署简单
7. 开源免费:Apache 2.0许可证
MinIO与传统存储的对比
1. 与传统文件系统对比
// 传统文件系统存储
public class TraditionalFileStorage {
public void saveFile(String fileName, byte[] data) {
// 需要管理文件路径
String filePath = "/data/uploads/" + fileName;
// 需要处理目录创建
File directory = new File("/data/uploads");
if (!directory.exists()) {
directory.mkdirs();
}
// 需要处理文件写入
try (FileOutputStream fos = new FileOutputStream(filePath)) {
fos.write(data);
}
// 需要处理备份、同步等问题
}
}
// MinIO对象存储
public class MinIOStorage {
public void saveFile(String fileName, byte[] data) {
// 直接上传到对象存储
minioClient.putObject(
PutObjectArgs.builder()
.bucket("my-bucket")
.object(fileName)
.stream(new ByteArrayInputStream(data), data.length, -1)
.build()
);
// 自动处理分布式、备份等问题
}
}
2. 与关系型数据库对比
// 数据库存储文件
@Entity
public class FileEntity {
@Id
private Long id;
@Lob
@Column(columnDefinition = "LONGBLOB")
private byte[] fileData; // 大文件存储在数据库中
private String fileName;
private String contentType;
private Long fileSize;
}
// MinIO存储文件
public class MinIOFileService {
public void uploadFile(String fileName, byte[] data) {
// 文件存储在MinIO中
minioClient.putObject(
PutObjectArgs.builder()
.bucket("files")
.object(fileName)
.stream(new ByteArrayInputStream(data), data.length, -1)
.build()
);
// 数据库中只存储元数据
FileMetadata metadata = new FileMetadata();
metadata.setFileName(fileName);
metadata.setFileUrl("https://minio.example.com/files/" + fileName);
metadata.setFileSize(data.length);
fileMetadataRepository.save(metadata);
}
}
MinIO安装和配置
1. Docker安装
# docker-compose.yml
version: '3.8'
services:
minio:
image: minio/minio:latest
container_name: minio
ports:
- "9000:9000" # API端口
- "9001:9001" # 控制台端口
environment:
MINIO_ROOT_USER: admin
MINIO_ROOT_PASSWORD: admin123
volumes:
- minio_data:/data
command: server /data --console-address ":9001"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 20s
retries: 3
volumes:
minio_data:
2. 单机部署
# 下载MinIO
wget https://dl.min.io/server/minio/release/linux-amd64/minio
chmod +x minio
# 启动MinIO
./minio server /data --console-address ":9001"
3. 分布式部署
# 4节点分布式部署
./minio server http://minio{1...4}/data --console-address ":9001"
Spring Boot集成MinIO
1. Maven依赖
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.5.7</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
2. 配置文件
# application.yml
minio:
endpoint: http://localhost:9000
access-key: admin
secret-key: admin123
bucket: default-bucket
region: us-east-1
secure: false
3. MinIO配置类
import io.minio.MinioClient;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "minio")
public class MinIOConfig {
private String endpoint;
private String accessKey;
private String secretKey;
private String bucket;
private String region;
private boolean secure;
@Bean
public MinioClient minioClient() {
return MinioClient.builder()
.endpoint(endpoint)
.credentials(accessKey, secretKey)
.region(region)
.build();
}
@Bean
public String defaultBucket() {
return bucket;
}
// getter和setter方法
public String getEndpoint() { return endpoint; }
public void setEndpoint(String endpoint) { this.endpoint = endpoint; }
public String getAccessKey() { return accessKey; }
public void setAccessKey(String accessKey) { this.accessKey = accessKey; }
public String getSecretKey() { return secretKey; }
public void setSecretKey(String secretKey) { this.secretKey = secretKey; }
public String getBucket() { return bucket; }
public void setBucket(String bucket) { this.bucket = bucket; }
public String getRegion() { return region; }
public void setRegion(String region) { this.region = region; }
public boolean isSecure() { return secure; }
public void setSecure(boolean secure) { this.secure = secure; }
}
4. MinIO服务类
import io.minio.*;
import io.minio.errors.*;
import io.minio.messages.Item;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@Service
public class MinIOService {
@Autowired
private MinioClient minioClient;
@Autowired
private String defaultBucket;
/**
* 上传文件
*/
public String uploadFile(MultipartFile file) throws Exception {
// 生成唯一文件名
String fileName = generateFileName(file.getOriginalFilename());
// 检查bucket是否存在,不存在则创建
ensureBucketExists(defaultBucket);
// 上传文件
minioClient.putObject(
PutObjectArgs.builder()
.bucket(defaultBucket)
.object(fileName)
.stream(file.getInputStream(), file.getSize(), -1)
.contentType(file.getContentType())
.build()
);
return fileName;
}
/**
* 下载文件
*/
public InputStream downloadFile(String fileName) throws Exception {
return minioClient.getObject(
GetObjectArgs.builder()
.bucket(defaultBucket)
.object(fileName)
.build()
);
}
/**
* 删除文件
*/
public void deleteFile(String fileName) throws Exception {
minioClient.removeObject(
RemoveObjectArgs.builder()
.bucket(defaultBucket)
.object(fileName)
.build()
);
}
/**
* 获取文件列表
*/
public List<String> listFiles(String prefix) throws Exception {
Iterable<Result<Item>> results = minioClient.listObjects(
ListObjectsArgs.builder()
.bucket(defaultBucket)
.prefix(prefix)
.build()
);
List<String> fileNames = new ArrayList<>();
for (Result<Item> result : results) {
fileNames.add(result.get().objectName());
}
return fileNames;
}
/**
* 生成预签名URL(用于临时访问)
*/
public String getPresignedUrl(String fileName, int expirySeconds) throws Exception {
return minioClient.getPresignedObjectUrl(
GetPresignedObjectUrlArgs.builder()
.method(Method.GET)
.bucket(defaultBucket)
.object(fileName)
.expiry(expirySeconds)
.build()
);
}
/**
* 检查bucket是否存在
*/
private void ensureBucketExists(String bucketName) throws Exception {
boolean found = minioClient.bucketExists(
BucketExistsArgs.builder()
.bucket(bucketName)
.build()
);
if (!found) {
minioClient.makeBucket(
MakeBucketArgs.builder()
.bucket(bucketName)
.build()
);
}
}
/**
* 生成唯一文件名
*/
private String generateFileName(String originalFileName) {
String extension = "";
if (originalFileName != null && originalFileName.contains(".")) {
extension = originalFileName.substring(originalFileName.lastIndexOf("."));
}
return UUID.randomUUID().toString() + extension;
}
}
实际应用场景
1. 文件上传服务
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.http.ResponseEntity;
@RestController
@RequestMapping("/api/files")
public class FileController {
@Autowired
private MinIOService minIOService;
/**
* 上传文件
*/
@PostMapping("/upload")
public ResponseEntity<?> uploadFile(@RequestParam("file") MultipartFile file) {
try {
// 验证文件
if (file.isEmpty()) {
return ResponseEntity.badRequest().body("文件不能为空");
}
// 验证文件大小(10MB)
if (file.getSize() > 10 * 1024 * 1024) {
return ResponseEntity.badRequest().body("文件大小不能超过10MB");
}
// 验证文件类型
String contentType = file.getContentType();
if (contentType == null || !contentType.startsWith("image/")) {
return ResponseEntity.badRequest().body("只支持图片文件");
}
// 上传文件
String fileName = minIOService.uploadFile(file);
return ResponseEntity.ok(new FileUploadResponse(fileName, "上传成功"));
} catch (Exception e) {
return ResponseEntity.internalServerError().body("上传失败: " + e.getMessage());
}
}
/**
* 下载文件
*/
@GetMapping("/download/{fileName}")
public ResponseEntity<?> downloadFile(@PathVariable String fileName) {
try {
InputStream inputStream = minIOService.downloadFile(fileName);
return ResponseEntity.ok()
.header("Content-Disposition", "attachment; filename=\"" + fileName + "\"")
.body(inputStream);
} catch (Exception e) {
return ResponseEntity.notFound().build();
}
}
/**
* 获取文件访问URL
*/
@GetMapping("/url/{fileName}")
public ResponseEntity<?> getFileUrl(@PathVariable String fileName) {
try {
// 生成1小时有效的预签名URL
String url = minIOService.getPresignedUrl(fileName, 3600);
return ResponseEntity.ok(new FileUrlResponse(url));
} catch (Exception e) {
return ResponseEntity.internalServerError().body("获取URL失败");
}
}
/**
* 删除文件
*/
@DeleteMapping("/{fileName}")
public ResponseEntity<?> deleteFile(@PathVariable String fileName) {
try {
minIOService.deleteFile(fileName);
return ResponseEntity.ok("删除成功");
} catch (Exception e) {
return ResponseEntity.internalServerError().body("删除失败");
}
}
}
// 响应类
class FileUploadResponse {
private String fileName;
private String message;
public FileUploadResponse(String fileName, String message) {
this.fileName = fileName;
this.message = message;
}
// getter方法
public String getFileName() { return fileName; }
public String getMessage() { return message; }
}
class FileUrlResponse {
private String url;
public FileUrlResponse(String url) {
this.url = url;
}
public String getUrl() { return url; }
}
2. 图片处理服务(缩略图、水印)
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@Service
public class ImageProcessingService {
@Autowired
private MinIOService minIOService;
/**
* 生成缩略图
*/
public String generateThumbnail(String originalFileName, int width, int height) throws Exception {
// 下载原图
InputStream originalStream = minIOService.downloadFile(originalFileName);
BufferedImage originalImage = ImageIO.read(originalStream);
// 生成缩略图
BufferedImage thumbnail = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g = thumbnail.createGraphics();
g.drawImage(originalImage, 0, 0, width, height, null);
g.dispose();
// 保存缩略图
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ImageIO.write(thumbnail, "JPEG", outputStream);
String thumbnailFileName = "thumb_" + originalFileName;
minioClient.putObject(
PutObjectArgs.builder()
.bucket(defaultBucket)
.object(thumbnailFileName)
.stream(new ByteArrayInputStream(outputStream.toByteArray()),
outputStream.size(), -1)
.contentType("image/jpeg")
.build()
);
return thumbnailFileName;
}
/**
* 添加水印
*/
public String addWatermark(String originalFileName, String watermarkText) throws Exception {
// 下载原图
InputStream originalStream = minIOService.downloadFile(originalFileName);
BufferedImage originalImage = ImageIO.read(originalStream);
// 添加水印
Graphics2D g = originalImage.createGraphics();
g.setColor(Color.RED);
g.setFont(new Font("Arial", Font.BOLD, 30));
g.drawString(watermarkText, 50, 50);
g.dispose();
// 保存带水印的图片
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ImageIO.write(originalImage, "JPEG", outputStream);
String watermarkedFileName = "watermark_" + originalFileName;
minioClient.putObject(
PutObjectArgs.builder()
.bucket(defaultBucket)
.object(watermarkedFileName)
.stream(new ByteArrayInputStream(outputStream.toByteArray()),
outputStream.size(), -1)
.contentType("image/jpeg")
.build()
);
return watermarkedFileName;
}
}