diff --git a/pom.xml b/pom.xml
index 9c3288c..b151597 100644
--- a/pom.xml
+++ b/pom.xml
@@ -160,6 +160,20 @@
aspectjweaver
+
+
+ io.minio
+ minio
+ 8.5.7
+
+
+
+
+ commons-io
+ commons-io
+ 2.11.0
+
+
diff --git a/src/main/java/com/guwan/backend/config/MinioConfig.java b/src/main/java/com/guwan/backend/config/MinioConfig.java
new file mode 100644
index 0000000..fd6cbf7
--- /dev/null
+++ b/src/main/java/com/guwan/backend/config/MinioConfig.java
@@ -0,0 +1,32 @@
+package com.guwan.backend.config;
+
+import io.minio.MinioClient;
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Data
+@Configuration
+@ConfigurationProperties(prefix = "minio")
+public class MinioConfig {
+
+ private String endpoint;
+ private String accessKey;
+ private String secretKey;
+ private Bucket bucket;
+
+ @Data
+ public static class Bucket {
+ private String files;
+ private String images;
+ }
+
+ @Bean
+ public MinioClient minioClient() {
+ return MinioClient.builder()
+ .endpoint(endpoint)
+ .credentials(accessKey, secretKey)
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/guwan/backend/util/MinioUtil.java b/src/main/java/com/guwan/backend/util/MinioUtil.java
new file mode 100644
index 0000000..d327681
--- /dev/null
+++ b/src/main/java/com/guwan/backend/util/MinioUtil.java
@@ -0,0 +1,159 @@
+package com.guwan.backend.util;
+
+import io.minio.*;
+import io.minio.http.Method;
+import io.minio.messages.Bucket;
+import io.minio.messages.DeleteObject;
+import io.minio.messages.Item;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.io.FilenameUtils;
+import org.springframework.stereotype.Component;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class MinioUtil {
+
+ private final MinioClient minioClient;
+
+ /**
+ * 创建存储桶
+ */
+ public void createBucket(String bucketName) {
+ try {
+ boolean found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
+ if (!found) {
+ minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
+ }
+ } catch (Exception e) {
+ log.error("创建存储桶失败", e);
+ throw new RuntimeException("创建存储桶失败", e);
+ }
+ }
+
+ /**
+ * 上传文件
+ */
+ public String uploadFile(String bucketName, MultipartFile file) {
+ try {
+ String fileName = generateFileName(file.getOriginalFilename());
+ InputStream inputStream = file.getInputStream();
+ minioClient.putObject(
+ PutObjectArgs.builder()
+ .bucket(bucketName)
+ .object(fileName)
+ .stream(inputStream, file.getSize(), -1)
+ .contentType(file.getContentType())
+ .build()
+ );
+ return fileName;
+ } catch (Exception e) {
+ log.error("上传文件失败", e);
+ throw new RuntimeException("上传文件失败", e);
+ }
+ }
+
+ /**
+ * 上传Base64图片
+ */
+ public String uploadBase64Image(String bucketName, String base64Image, String folder) {
+ try {
+ String[] parts = base64Image.split(",");
+ String imageData = parts[1];
+ byte[] bytes = Base64.getDecoder().decode(imageData);
+
+ String fileName = folder + "/" + UUID.randomUUID() + ".jpg";
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
+
+ minioClient.putObject(
+ PutObjectArgs.builder()
+ .bucket(bucketName)
+ .object(fileName)
+ .stream(inputStream, bytes.length, -1)
+ .contentType("image/jpeg")
+ .build()
+ );
+ return fileName;
+ } catch (Exception e) {
+ log.error("上传Base64图片失败", e);
+ throw new RuntimeException("上传Base64图片失败", e);
+ }
+ }
+
+ /**
+ * 删除文件
+ */
+ public void deleteFile(String bucketName, String fileName) {
+ try {
+ minioClient.removeObject(
+ RemoveObjectArgs.builder()
+ .bucket(bucketName)
+ .object(fileName)
+ .build()
+ );
+ } catch (Exception e) {
+ log.error("删除文件失败", e);
+ throw new RuntimeException("删除文件失败", e);
+ }
+ }
+
+ /**
+ * 批量删除文件
+ */
+ public void deleteFiles(String bucketName, List fileNames) {
+ try {
+ List objects = fileNames.stream()
+ .map(DeleteObject::new)
+ .toList();
+
+ Iterable> results = minioClient.removeObjects(
+ RemoveObjectsArgs.builder()
+ .bucket(bucketName)
+ .objects(objects)
+ .build()
+ );
+
+ for (Result result : results) {
+ DeleteError error = result.get();
+ log.error("删除文件失败: {}", error.message());
+ }
+ } catch (Exception e) {
+ log.error("批量删除文件失败", e);
+ throw new RuntimeException("批量删除文件失败", e);
+ }
+ }
+
+ /**
+ * 获取文件访问URL
+ */
+ public String getFileUrl(String bucketName, String fileName) {
+ try {
+ return minioClient.getPresignedObjectUrl(
+ GetPresignedObjectUrlArgs.builder()
+ .method(Method.GET)
+ .bucket(bucketName)
+ .object(fileName)
+ .expiry(7, TimeUnit.DAYS)
+ .build()
+ );
+ } catch (Exception e) {
+ log.error("获取文件访问URL失败", e);
+ throw new RuntimeException("获取文件访问URL失败", e);
+ }
+ }
+
+ /**
+ * 生成文件名
+ */
+ private String generateFileName(String originalFilename) {
+ String extension = FilenameUtils.getExtension(originalFilename);
+ return UUID.randomUUID() + "." + extension;
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 1fa9b0e..f4f61f3 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -74,9 +74,14 @@ jwt:
aliyun:
sms:
-
-
-
+# MinIO配置
+minio:
+ endpoint: http://localhost:9000
+ accessKey: minioadmin
+ secretKey: minioadmin
+ bucket:
+ files: files # 文件桶
+ images: images # 图片桶
# 文件上传配置
file:
diff --git a/src/test/java/com/guwan/backend/util/MinioUtilTest.java b/src/test/java/com/guwan/backend/util/MinioUtilTest.java
new file mode 100644
index 0000000..9e1cecc
--- /dev/null
+++ b/src/test/java/com/guwan/backend/util/MinioUtilTest.java
@@ -0,0 +1,67 @@
+package com.guwan.backend.util;
+
+import com.guwan.backend.config.MinioConfig;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.mock.web.MockMultipartFile;
+
+import java.nio.charset.StandardCharsets;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+@SpringBootTest
+class MinioUtilTest {
+
+ @Autowired
+ private MinioUtil minioUtil;
+
+ @Autowired
+ private MinioConfig minioConfig;
+
+ @Test
+ void testUploadAndDeleteFile() {
+ // 创建测试文件
+ String content = "test content";
+ MockMultipartFile file = new MockMultipartFile(
+ "test.txt",
+ "test.txt",
+ "text/plain",
+ content.getBytes(StandardCharsets.UTF_8)
+ );
+
+ // 上传文件
+ String fileName = minioUtil.uploadFile(minioConfig.getBucket().getFiles(), file);
+ assertNotNull(fileName);
+
+ // 获取文件URL
+ String url = minioUtil.getFileUrl(minioConfig.getBucket().getFiles(), fileName);
+ assertNotNull(url);
+ assertTrue(url.contains(fileName));
+
+ // 删除文件
+ minioUtil.deleteFile(minioConfig.getBucket().getFiles(), fileName);
+ }
+
+ @Test
+ void testUploadBase64Image() {
+ // 创建Base64图片
+ String base64Image = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAb/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAX/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCdABmX/9k=";
+
+ // 上传图片
+ String fileName = minioUtil.uploadBase64Image(
+ minioConfig.getBucket().getImages(),
+ base64Image,
+ "test"
+ );
+ assertNotNull(fileName);
+
+ // 获取图片URL
+ String url = minioUtil.getFileUrl(minioConfig.getBucket().getImages(), fileName);
+ assertNotNull(url);
+ assertTrue(url.contains(fileName));
+
+ // 删除图片
+ minioUtil.deleteFile(minioConfig.getBucket().getImages(), fileName);
+ }
+}
\ No newline at end of file