From d87266b3e27d63ef233e8e5132fcd07c824b4793 Mon Sep 17 00:00:00 2001 From: luke Date: Mon, 16 Jun 2025 01:38:44 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=AF=B9=E8=B1=A1=E5=AD=98?= =?UTF-8?q?=E5=82=A8=E4=B8=8A=E4=BC=A0=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Readme.md | 1 + pom.xml | 14 +++++ .../config/ObjectStorageProperties.java | 12 ++++ .../north/controller/FileWriteController.java | 58 +++++++++++++++++++ .../south/MinioOSGatewayImpl.java | 49 ++++++++++++++++ .../south/ObjectStorageGateway.java | 16 +++++ src/main/resources/application.properties | 3 + 7 files changed, 153 insertions(+) create mode 100644 src/main/java/com/knowledge/base/infrastructure/config/ObjectStorageProperties.java create mode 100644 src/main/java/com/knowledge/base/infrastructure/south/MinioOSGatewayImpl.java create mode 100644 src/main/java/com/knowledge/base/infrastructure/south/ObjectStorageGateway.java diff --git a/Readme.md b/Readme.md index 384ac95..9a2088e 100644 --- a/Readme.md +++ b/Readme.md @@ -6,6 +6,7 @@ | 1.0.0 | 2025-06-05 | Luke.Ye | 添加修订记录(新增token校验接口) | | 1.0.1 | 2025-06-06 | Luke.Ye | 重构登录Token校验逻辑, | | 1.0.2 | 2025-06-09 | Luke.Ye | 添加用户角色相关逻辑,重构代码 | +| 1.0.3 | 2025-06-16 | Luke.Ye | 新增文档上传至对象存储接口 | diff --git a/pom.xml b/pom.xml index 1f70706..2f2ce0a 100644 --- a/pom.xml +++ b/pom.xml @@ -44,6 +44,8 @@ 3.5.5 1.70 + + 8.5.3 @@ -213,6 +215,13 @@ ${org.bouncycastle.version} + + io.minio + minio + ${minio.version} + + + @@ -357,6 +366,11 @@ bcprov-jdk15to18 + + io.minio + minio + + diff --git a/src/main/java/com/knowledge/base/infrastructure/config/ObjectStorageProperties.java b/src/main/java/com/knowledge/base/infrastructure/config/ObjectStorageProperties.java new file mode 100644 index 0000000..7f7038a --- /dev/null +++ b/src/main/java/com/knowledge/base/infrastructure/config/ObjectStorageProperties.java @@ -0,0 +1,12 @@ +package com.knowledge.base.infrastructure.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConfigurationProperties(prefix = "object.storage") +@Data +public class ObjectStorageProperties { + private boolean useUuidPrefix = true; +} \ No newline at end of file diff --git a/src/main/java/com/knowledge/base/infrastructure/north/controller/FileWriteController.java b/src/main/java/com/knowledge/base/infrastructure/north/controller/FileWriteController.java index 3d14422..c09dd86 100644 --- a/src/main/java/com/knowledge/base/infrastructure/north/controller/FileWriteController.java +++ b/src/main/java/com/knowledge/base/infrastructure/north/controller/FileWriteController.java @@ -1,8 +1,11 @@ package com.knowledge.base.infrastructure.north.controller; +import cn.hutool.core.lang.UUID; import com.knowledge.base.application.service.UserAppService; import com.knowledge.base.domain.doc.service.iface.DocumentImporter; import com.knowledge.base.infrastructure.cache.FileCacheService; +import com.knowledge.base.infrastructure.config.ObjectStorageProperties; +import com.knowledge.base.infrastructure.south.ObjectStorageGateway; import com.knowledge.base.infrastructure.util.SafeIdUtil; import lombok.RequiredArgsConstructor; import org.elasticsearch.action.delete.DeleteRequest; @@ -14,7 +17,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import java.nio.file.Paths; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -32,6 +38,9 @@ public class FileWriteController { private final UserAppService userAppService; + private final ObjectStorageGateway objectStorageGateway; + private final ObjectStorageProperties objectStorageProperties; + private List importers; @PostMapping("/addUserFileAuth") @@ -119,4 +128,53 @@ public class FileWriteController { return ResponseEntity.status(500).body("全量清空失败: " + e.getMessage()); } } + + @PostMapping("/upload-files") + public ResponseEntity uploadFolderToOS( + @RequestParam("files") MultipartFile[] files, + @RequestParam("bucket") String bucket + ) { + try { + objectStorageGateway.ensureBucketExists(bucket); + + Map filePathMap = new LinkedHashMap<>(); + + for (MultipartFile file : files) { + String originalName = file.getOriginalFilename(); + if (originalName == null || originalName.isBlank()) { + originalName = file.getName(); + } + + // 路径前缀 + String folderPrefix = "default/"; + String safeName = originalName.replace("\\", "/"); + + // 是否启用 UUID 前缀 + if (objectStorageProperties.isUseUuidPrefix()) { + String uuid = UUID.randomUUID().toString().replace("-", "").substring(0, 8); + safeName = folderPrefix + uuid + "_" + Paths.get(safeName).getFileName().toString(); + } else { + safeName = folderPrefix + Paths.get(safeName).getFileName().toString(); + } + + // 上传到 MinIO + objectStorageGateway.uploadFile(bucket, safeName, file); + + // 使用原始文件名作为 key,MinIO 对象路径作为 value + String fileKey = Paths.get(originalName).getFileName().toString(); + filePathMap.put(fileKey, String.format("http://s3.wisdompulse.cn/%s/%s", bucket, safeName)); + } + + return ResponseEntity.ok(Map.of( + "msg", "上传成功", + "count", filePathMap.size(), + "files", filePathMap + )); + } catch (Exception e) { + logger.error("上传失败", e); + return ResponseEntity.status(500).body("上传失败: " + e.getMessage()); + } + } + + } diff --git a/src/main/java/com/knowledge/base/infrastructure/south/MinioOSGatewayImpl.java b/src/main/java/com/knowledge/base/infrastructure/south/MinioOSGatewayImpl.java new file mode 100644 index 0000000..be2be35 --- /dev/null +++ b/src/main/java/com/knowledge/base/infrastructure/south/MinioOSGatewayImpl.java @@ -0,0 +1,49 @@ +package com.knowledge.base.infrastructure.south; + +import io.minio.*; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; + +import java.util.Optional; + +@Component +@Slf4j +public class MinioOSGatewayImpl implements ObjectStorageGateway { + + private final MinioClient minioClient; + + public MinioOSGatewayImpl( + @Value("${minio.endpoint:http://s3.wisdompulse.cn}") String endpoint, + @Value("${minio.access-key:lukeye}") String accessKey, + @Value("${minio.secret-key:lukeye@6}") String secretKey + ) { + this.minioClient = MinioClient.builder() + .endpoint(endpoint) + .credentials(accessKey, secretKey) + .build(); + } + + @Override + public void ensureBucketExists(String bucket) throws Exception { + boolean exists = minioClient.bucketExists( + BucketExistsArgs.builder().bucket(bucket).build() + ); + if (!exists) { + minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucket).build()); + log.info("已创建 bucket: {}", bucket); + } + } + + @Override + public void uploadFile(String bucket, String objectPath, MultipartFile file) throws Exception { + minioClient.putObject(PutObjectArgs.builder() + .bucket(bucket) + .object(objectPath) + .stream(file.getInputStream(), file.getSize(), -1) + .contentType(Optional.ofNullable(file.getContentType()).orElse("application/octet-stream")) + .build()); + log.info("上传成功: {}/{}", bucket, objectPath); + } +} diff --git a/src/main/java/com/knowledge/base/infrastructure/south/ObjectStorageGateway.java b/src/main/java/com/knowledge/base/infrastructure/south/ObjectStorageGateway.java new file mode 100644 index 0000000..e8e2cf4 --- /dev/null +++ b/src/main/java/com/knowledge/base/infrastructure/south/ObjectStorageGateway.java @@ -0,0 +1,16 @@ +package com.knowledge.base.infrastructure.south; + +import org.springframework.web.multipart.MultipartFile; + +public interface ObjectStorageGateway { + + /** + * 确保 bucket 存在,不存在则创建 + */ + void ensureBucketExists(String bucket) throws Exception; + + /** + * 上传文件到指定 bucket 的路径 + */ + void uploadFile(String bucket, String objectPath, MultipartFile file) throws Exception; +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 6dffe6c..84adbaf 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -26,3 +26,6 @@ import.schedule.cron=0 0 * * * * # Redis 开关控制 knowledge.base.redis.enable=false +object.storage.use-uuid-prefix=true +spring.servlet.multipart.max-file-size=200MB +spring.servlet.multipart.max-request-size=500MB