新增对象存储上传接口

This commit is contained in:
luke 2025-06-16 01:38:44 +08:00
parent 6440a1c97e
commit d87266b3e2
7 changed files with 153 additions and 0 deletions

View File

@ -6,6 +6,7 @@
| 1.0.0 | 2025-06-05 | Luke.Ye | 添加修订记录新增token校验接口 | | 1.0.0 | 2025-06-05 | Luke.Ye | 添加修订记录新增token校验接口 |
| 1.0.1 | 2025-06-06 | Luke.Ye | 重构登录Token校验逻辑 | | 1.0.1 | 2025-06-06 | Luke.Ye | 重构登录Token校验逻辑 |
| 1.0.2 | 2025-06-09 | Luke.Ye | 添加用户角色相关逻辑,重构代码 | | 1.0.2 | 2025-06-09 | Luke.Ye | 添加用户角色相关逻辑,重构代码 |
| 1.0.3 | 2025-06-16 | Luke.Ye | 新增文档上传至对象存储接口 |

14
pom.xml
View File

@ -44,6 +44,8 @@
<mybatis-plus-boot-starter.version>3.5.5</mybatis-plus-boot-starter.version> <mybatis-plus-boot-starter.version>3.5.5</mybatis-plus-boot-starter.version>
<org.bouncycastle.version>1.70</org.bouncycastle.version> <org.bouncycastle.version>1.70</org.bouncycastle.version>
<minio.version>8.5.3</minio.version>
</properties> </properties>
<dependencyManagement> <dependencyManagement>
<dependencies> <dependencies>
@ -213,6 +215,13 @@
<version>${org.bouncycastle.version}</version> <version>${org.bouncycastle.version}</version>
</dependency> </dependency>
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>${minio.version}</version>
</dependency>
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>
@ -357,6 +366,11 @@
<artifactId>bcprov-jdk15to18</artifactId> <artifactId>bcprov-jdk15to18</artifactId>
</dependency> </dependency>
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -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;
}

View File

@ -1,8 +1,11 @@
package com.knowledge.base.infrastructure.north.controller; package com.knowledge.base.infrastructure.north.controller;
import cn.hutool.core.lang.UUID;
import com.knowledge.base.application.service.UserAppService; import com.knowledge.base.application.service.UserAppService;
import com.knowledge.base.domain.doc.service.iface.DocumentImporter; import com.knowledge.base.domain.doc.service.iface.DocumentImporter;
import com.knowledge.base.infrastructure.cache.FileCacheService; 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 com.knowledge.base.infrastructure.util.SafeIdUtil;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.elasticsearch.action.delete.DeleteRequest; import org.elasticsearch.action.delete.DeleteRequest;
@ -14,7 +17,10 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*; 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.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
@ -32,6 +38,9 @@ public class FileWriteController {
private final UserAppService userAppService; private final UserAppService userAppService;
private final ObjectStorageGateway objectStorageGateway;
private final ObjectStorageProperties objectStorageProperties;
private List<DocumentImporter> importers; private List<DocumentImporter> importers;
@PostMapping("/addUserFileAuth") @PostMapping("/addUserFileAuth")
@ -119,4 +128,53 @@ public class FileWriteController {
return ResponseEntity.status(500).body("全量清空失败: " + e.getMessage()); 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<String, String> 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);
// 使用原始文件名作为 keyMinIO 对象路径作为 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());
}
}
} }

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -26,3 +26,6 @@ import.schedule.cron=0 0 * * * *
# Redis 开关控制 # Redis 开关控制
knowledge.base.redis.enable=false 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