MinIO介绍以及结合SpringBoot的应用场景详解
目录
什么是MinIO?
MinIO的核心特性
MinIO与传统存储的对比
1. 与传统文件系统对比
2. 与关系型数据库对比
MinIO安装和配置
1. Docker安装
2. 单机部署
3. 分布式部署
Spring Boot集成MinIO
1. Maven依赖
2. 配置文件
3. MinIO配置类
4. MinIO服务类
实际应用场景
1. 文件上传服务
2. 图片处理服务
什么是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 {@Idprivate 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:latestcontainer_name: minioports:- "9000:9000" # API端口- "9001:9001" # 控制台端口environment:MINIO_ROOT_USER: adminMINIO_ROOT_PASSWORD: admin123volumes:- minio_data:/datacommand: server /data --console-address ":9001"healthcheck:test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]interval: 30stimeout: 20sretries: 3volumes: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:9000access-key: adminsecret-key: admin123bucket: default-bucketregion: us-east-1secure: 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;@Beanpublic MinioClient minioClient() {return MinioClient.builder().endpoint(endpoint).credentials(accessKey, secretKey).region(region).build();}@Beanpublic 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 {@Autowiredprivate MinioClient minioClient;@Autowiredprivate 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 {@Autowiredprivate 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小时有效的预签名URLString 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 {@Autowiredprivate 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;}
}