同步平台业务模块能力
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user