山东大学软件学院项目实训-创新实训-计科智伴(二)——个人博客(MinIO配置)
项目背景
在我们的项目开发过程中,需要一个可靠的文件存储解决方案来管理用户上传的图片和其他文件。传统的本地文件存储方式在扩展性、可靠性和安全性方面存在诸多限制,因此我们决定引入对象存储服务。经过调研,我们选择了MinIO作为我们的对象存储解决方案。
接入过程
1. 环境准备
首先,我们需要在本地或服务器上安装并启动MinIO服务。MinIO提供了多种安装方式,我们选择了Docker方式进行部署:
docker run -p 9000:9000 -p 9090:9090 --name minio \ -v /mnt/data:/data \ -e "MINIO_ROOT_USER=minioadmin" \ -e "MINIO_ROOT_PASSWORD=minioadmin" \ minio/minio server /data --console-address ":9090"启动后,可以通过 http://localhost:9090 访问MinIO控制台进行管理。
2. 项目集成
2.1 添加依赖
在项目的 pom.xml 文件中添加MinIO客户端依赖:
<dependency> <groupId>io.minio</groupId> <artifactId>minio</artifactId> <version>8.5.2</version> </dependency>2.2 配置文件
创建 minio.properties 配置文件,存放MinIO服务的连接信息:
minio.url=http://127.0.0.1:9000 minio.username=minioadmin minio.password=minioadmin minio.bucketName=img2.3 配置类实现
创建 MinioConfig 类,用于读取配置并初始化MinIO客户端:
package com.example.Config; import io.minio.MinioClient; import java.io.IOException; import java.io.InputStream; import java.util.Properties; public class MinioConfig { private String url; private String username; private String password; private String bucketName; { final Properties properties = new Properties(); final InputStream is = MinioConfig.class.getResourceAsStream("/minio.properties"); try { properties.load(is); } catch (IOException e) { e.printStackTrace(); } url = properties.getProperty("minio.url"); username = properties.getProperty("minio.username"); password = properties.getProperty("minio.password"); bucketName = properties.getProperty("minio.bucketName"); } public String getBucketName() { return bucketName; } public MinioClient getMinioClient() { MinioClient minioClient = MinioClient.builder().endpoint(url) .credentials(username, password).build(); return minioClient; } }2.4 工具类实现
创建 MinioUtil 类,封装MinIO的各种操作方法:
package com.example.utils; import com.example.Config.MinioConfig; import com.google.common.io.ByteStreams; import io.minio.*; import io.minio.http.Method; import io.minio.messages.Bucket; import io.minio.messages.DeleteError; import io.minio.messages.DeleteObject; import io.minio.messages.Item; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.springframework.web.multipart.MultipartFile; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.util.*; @Slf4j @Component public class MinioUtil { private MinioClient minioClient; private String bucketName; { final MinioConfig minioConfig = new MinioConfig(); minioClient = minioConfig.getMinioClient(); bucketName = minioConfig.getBucketName(); } // 桶操作 public boolean bucketExists() { try { return minioClient.bucketExists(BucketExistsArgs.builder() .bucket(bucketName) .build()); } catch (Exception e) { log.error("查看bucket是否存在", e); return false; } } public boolean createBucket() { if (bucketExists()) { return true; } try { minioClient.makeBucket(MakeBucketArgs.builder() .bucket(bucketName) .build()); return true; } catch (Exception e) { log.error("创建桶失败", e); return false; } } // 文件操作 public String putObject(MultipartFile multipartFile, String folderName, String aimFileName) { if (!bucketExists()) { createBucket(); } if (!StringUtils.hasText(aimFileName)) { aimFileName = UUID.randomUUID().toString(); } // 获取文件后缀 String originalFilename = multipartFile.getOriginalFilename(); String suffix = originalFilename.substring(originalFilename.lastIndexOf(".")); aimFileName += suffix; // 带路径的文件名 String lastFileName = ""; if (StringUtils.hasText(folderName)) { lastFileName = "/" + folderName + "/" + aimFileName; } else { lastFileName = aimFileName; } try (InputStream inputStream = multipartFile.getInputStream();) { // 上传文件到指定目录,文件名称相同会覆盖 minioClient.putObject(PutObjectArgs.builder() .bucket(bucketName) .object(lastFileName) .stream(inputStream, multipartFile.getSize(), -1) .contentType(multipartFile.getContentType()) .build()); return getObjectUrl(lastFileName); } catch (Exception e) { log.error("文件上传失败", e); return null; } } public void getObject(String fileName, HttpServletResponse response) { if (!bucketExists()) { return; } GetObjectArgs getObjectArgs = GetObjectArgs.builder() .bucket(bucketName) .object(fileName) .build(); try (ServletOutputStream outputStream = response.getOutputStream(); GetObjectResponse objectResponse = minioClient.getObject(getObjectArgs)) { response.setCharacterEncoding("utf-8"); response.addHeader("Content-Disposition", "attachment;filename=" + fileName); ByteStreams.copy(objectResponse, outputStream); outputStream.flush(); } catch (Exception e) { log.error("文件下载失败", e); } } public String getObjectUrl(String fileName) { try { if (fileName.startsWith("/")) { fileName = fileName.substring(1); } GetPresignedObjectUrlArgs build = GetPresignedObjectUrlArgs.builder() .bucket(bucketName) .object(fileName) .method(Method.GET) // 过期时间(分钟数) .expiry(60 * 60) .build(); return minioClient.getPresignedObjectUrl(build); } catch (Exception e) { log.error("获取文件路径失败", e); } return null; } // 其他操作方法... }2.5 控制器集成
在 DispatcherController 中添加文件上传和下载接口:
package com.example.Controller; import com.example.utils.MinioUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletResponse; @Controller public class DispatcherController { @Autowired private MinioUtil minioUtil; @PostMapping("/fileUpload") public void fileUpload(MultipartFile imgFile) { final String s = minioUtil.putObject(imgFile); System.out.println(s); } @ResponseBody @GetMapping("/fileDownload/{filename}") public void download(@PathVariable("filename") String filename, HttpServletResponse response) { minioUtil.getObject(filename, response); } }功能特性
我们实现的MinIO工具类支持以下功能:
1. 桶操作 :创建桶、检查桶是否存在、删除桶、获取桶信息等
2. 文件操作 :上传文件、下载文件、获取文件信息、删除文件等
3. 目录操作 :创建目录、检查目录是否存在等
4. URL生成 :生成带签名的临时访问URL
遇到的问题与解决方案
在接入过程中,我们遇到了几个问题:
1. 依赖冲突 :MinIO客户端依赖与项目中其他依赖存在冲突,通过调整依赖版本解决。
2. 权限配置 :默认情况下,MinIO桶的访问权限可能不够,需要在控制台中设置合适的权限。
3. 文件路径处理 :在处理文件路径时,需要注意斜杠的处理,确保路径格式正确。
总结
通过本次实践,我们成功地将MinIO对象存储服务集成到了项目中,为我们的应用提供了可靠的文件存储解决方案。MinIO的优势在于:
1. 高性能 :MinIO采用Go语言编写,性能优异,适合处理大量文件存储。
2. 易于部署 :可以通过Docker快速部署,也支持各种云平台。
3. API友好 :提供了丰富的SDK,方便与各种语言集成。
4. 兼容S3 :完全兼容Amazon S3 API,方便未来可能的迁移。
5. 开源免费 :作为开源项目,可以免费使用。
通过封装的工具类,我们的应用可以方便地进行文件的上传、下载、管理等操作,为用户提供更好的体验。
