同步平台业务模块能力

This commit is contained in:
baiyanyun
2026-07-13 09:13:30 +08:00
parent e612f5e434
commit a1a38f62d8
224 changed files with 22331 additions and 1224 deletions

View File

@@ -4,6 +4,7 @@ import com.xspaceagi.file.application.service.FileManagementService;
import com.xspaceagi.file.domain.model.FileRecordDomain;
import com.xspaceagi.file.domain.repository.FileRecordRepository;
import com.xspaceagi.file.domain.storage.FileStorageStrategy;
import com.xspaceagi.file.infra.storage.FileKeyGenerator;
import com.xspaceagi.system.application.dto.TenantConfigDto;
import com.xspaceagi.system.application.service.TenantConfigApplicationService;
import com.xspaceagi.system.spec.common.RequestContext;
@@ -102,15 +103,8 @@ public class FileManagementServiceImpl implements FileManagementService {
}
private static String getFileExtension(String fileName) {
String fileExtension = "";
if (fileName != null && fileName.contains(".")) {
fileExtension = fileName.substring(fileName.lastIndexOf(".") + 1);
// List<String> fileTypes = List.of("pdf", "txt", "doc", "docx", "md", "json", "xml", "xls", "xlsx", "ppt", "pptx", "mp4", "mov", "mp3", "wav", "aac", "flac", "ogg", "wma", "aiff", "m4a", "amr", "midi", "opus", "ra", "zip", "rar", "7z", "tar", "gz", "bz2", "tgz", "tar.gz", "tar.bz2", "tar.7z", "tar.gz", "jpg", "jpeg", "jpe", "png", "gif", "bmp", "ico", "icns", "svg", "webp", "heic", "mkv", "webm");
// if (!fileTypes.contains(fileExtension.toLowerCase())) {
// throw new BizException("Unsupported file type");
// }
}
return fileExtension;
String ext = FileKeyGenerator.extractExtension(fileName);
return ext.startsWith(".") ? ext.substring(1) : ext;
}
@Override

View File

@@ -30,16 +30,26 @@ public class FileKeyGenerator {
return String.format("%s/%s/%s/%s%s", storageType, businessType, date, uuid, extension);
}
private static final String[] COMPOUND_EXTENSIONS = {
".tar.gz", ".tar.bz2", ".tar.xz", ".tar.7z", ".tar.zst", ".tar.lz", ".tar.lzma", ".tar.sz", ".tar.lzo"
};
/**
* Extract file extension
* Extract file extension (including dot), e.g. ".tar.gz" or ".jpg"
*
* @param fileName File name
* @return Extension (including dot), or empty string if no extension
*/
private static String extractExtension(String fileName) {
public static String extractExtension(String fileName) {
if (fileName == null || fileName.isEmpty()) {
return "";
}
String lowerName = fileName.toLowerCase();
for (String ext : COMPOUND_EXTENSIONS) {
if (lowerName.endsWith(ext)) {
return fileName.substring(fileName.length() - ext.length());
}
}
int dotIndex = fileName.lastIndexOf('.');
if (dotIndex > 0 && dotIndex < fileName.length() - 1) {
return fileName.substring(dotIndex);

View File

@@ -1,9 +1,11 @@
package com.xspaceagi.file.infra.storage;
import com.xspaceagi.file.domain.storage.FileStorageStrategy;
import com.xspaceagi.system.spec.utils.RedisUtil;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
@@ -22,6 +24,7 @@ import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.UUID;
/**
* S3 Protocol Storage Implementation
@@ -46,6 +49,12 @@ public class S3FileStorageStrategy implements FileStorageStrategy {
@Value("${s3.region:us-east-1}")
private String region;
@Value("${s3.proxy:}")
private String proxyBaseUrl;
@Resource
private RedisUtil redisUtil;
private volatile S3Client s3Client;
private volatile S3Presigner s3Presigner;
@@ -181,7 +190,11 @@ public class S3FileStorageStrategy implements FileStorageStrategy {
PresignedGetObjectRequest presignedRequest = presigner.presignGetObject(presignRequest);
String url = presignedRequest.url().toString();
if (StringUtils.isNotBlank(proxyBaseUrl) && expireSeconds < 86400 * 30) {
String key = UUID.randomUUID().toString().replace("-", "") + FileKeyGenerator.extractExtension(fileKey);
redisUtil.set("s3p:" + key, url, expireSeconds);
url = proxyBaseUrl + "/" + key;
}
log.info("S3 presigned URL generated successfully: {}", fileKey);
return url;
} catch (Exception e) {

View File

@@ -2,22 +2,28 @@ package com.xspaceagi.file.web.controller;
import com.xspaceagi.file.application.service.FileManagementService;
import com.xspaceagi.file.domain.model.FileRecordDomain;
import com.xspaceagi.file.infra.storage.FileKeyGenerator;
import com.xspaceagi.file.sdk.IFileAccessService;
import com.xspaceagi.file.web.dto.FileRecordVO;
import com.xspaceagi.system.sdk.service.UserAccessKeyApiService;
import com.xspaceagi.system.sdk.service.dto.UserAccessKeyDto;
import com.xspaceagi.system.spec.common.RequestContext;
import com.xspaceagi.system.spec.dto.ReqResult;
import com.xspaceagi.system.spec.enums.ErrorCodeEnum;
import com.xspaceagi.system.spec.enums.HttpStatusEnum;
import com.xspaceagi.system.spec.exception.BizException;
import com.xspaceagi.system.spec.exception.BizExceptionCodeEnum;
import com.xspaceagi.system.spec.utils.RedisUtil;
import com.xspaceagi.system.web.controller.ChatKeyCheck;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
@@ -28,8 +34,10 @@ import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream;
import java.net.URI;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -49,6 +57,16 @@ public class FileManagementController {
private final IFileAccessService iFileAccessService;
@Resource
private RedisUtil redisUtil;
@Value("${s3.proxy:}")
private String proxyBaseUrl;
@Value("${s3.shortUrlKey:}")
private String shortUrlKey;
@PostMapping("/file/upload")
@Operation(summary = "Upload file")
public ReqResult<FileRecordVO> uploadFile(HttpServletRequest request, @RequestParam("file") MultipartFile file,
@@ -56,7 +74,8 @@ public class FileManagementController {
@RequestParam(value = "targetId", required = false, defaultValue = "-1") Long targetId,
@RequestParam(value = "metadata", required = false) String metadata) {
if (!RequestContext.get().isLogin()) {
ChatKeyCheck.check(request, userAccessKeyApiService);
UserAccessKeyDto userAccessKeyDto = ChatKeyCheck.check(request, userAccessKeyApiService);
RequestContext.get().setUserId(userAccessKeyDto.getUserId());
}
boolean isAuthRequired = true;
// If targetType or targetId not provided, try to parse from Referer
@@ -354,6 +373,37 @@ public class FileManagementController {
}
}
@GetMapping("/f1/{key}")
@Operation(summary = "Access file by fileKey")
public ResponseEntity<?> redirectOriginalUrl(@PathVariable String key) {
Object o = redisUtil.get("s3p:" + key);
if (o == null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
}
return ResponseEntity.status(HttpStatus.FOUND)
.location(URI.create(o.toString()))
.header("X-S3-Url", o.toString())
.build();
}
@GetMapping("/file/url/short")
@Operation(summary = "Get file short url")
public ReqResult<String> getShortUrl(@RequestParam String url, @RequestParam String ak, @RequestParam(required = false) Integer expireSeconds) {
if (StringUtils.isBlank(shortUrlKey) || shortUrlKey.equals(ak)) {
return ReqResult.error("Invalid ak");
}
String decode = URLDecoder.decode(url, StandardCharsets.UTF_8);
if (decode == null) {
return ReqResult.error("Invalid url");
}
String ext = FileKeyGenerator.extractExtension(decode);
ext = ext.contains("?") ? ext.substring(0, ext.indexOf("?")) : ext;
String key = UUID.randomUUID().toString().replace("-", "") + ext;
redisUtil.set("s3p:" + key, decode, expireSeconds == null ? 3600 : expireSeconds);
return ReqResult.success(proxyBaseUrl + "/" + key);
}
/**
* Extract storage type from fileKey
* fileKey format: /storageType/businessType/date/uuid.ext