diff --git a/backend-java/src/main/java/com/nanri/aiimage/config/OssProperties.java b/backend-java/src/main/java/com/nanri/aiimage/config/OssProperties.java index 938c6539..5bc875f8 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/config/OssProperties.java +++ b/backend-java/src/main/java/com/nanri/aiimage/config/OssProperties.java @@ -9,6 +9,7 @@ public class OssProperties { private String region; private String endpoint; private String publicEndpoint; + private String downloadEndpoint; private String bucket; private String imageVideoBucket; private String digitalHumanBucket; diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/file/service/oss/OssStorageService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/file/service/oss/OssStorageService.java index 2c70375f..a3e538a2 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/file/service/oss/OssStorageService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/file/service/oss/OssStorageService.java @@ -302,7 +302,11 @@ public class OssStorageService { } public String generateFreshDownloadUrl(String value) { - return getPublicUrl(value); + StorageLocation location = resolveStorageLocation(value); + if (location == null) { + return getPublicUrl(value); + } + return buildDownloadPublicUrl(location.objectKey(), location.bucket()); } private void uploadFile(File file, String bucket, String objectKey) { @@ -321,6 +325,22 @@ public class OssStorageService { } private String getPublicUrl(String objectKey, String bucket) { + return buildPublicUrl(trimTrailingSlash(publicEndpoint()), objectKey, bucket); + } + + /** + * 结果文件(objectKey 以 {@code result/} 开头)的下载直链。 + * 配置了 download-endpoint 时走下载域名(独立出口/缓存节点),否则回退到公开 endpoint。 + */ + private String buildDownloadPublicUrl(String objectKey, String bucket) { + String base = downloadEndpoint(); + if (base == null || objectKey == null || !objectKey.startsWith("result/")) { + return getPublicUrl(objectKey, bucket); + } + return buildPublicUrl(base, objectKey, bucket); + } + + private String buildPublicUrl(String baseUrl, String objectKey, String bucket) { String encodedPath; try { encodedPath = new URI(null, null, "/" + bucket + "/" + trimLeadingSlash(objectKey), null) @@ -328,7 +348,7 @@ public class OssStorageService { } catch (Exception ex) { encodedPath = "/" + bucket + "/" + trimLeadingSlash(objectKey).replace(" ", "%20"); } - return trimTrailingSlash(publicEndpoint()) + encodedPath; + return trimTrailingSlash(baseUrl) + encodedPath; } private StorageLocation resolveStorageLocation(String value) { @@ -488,6 +508,14 @@ public class OssStorageService { return withScheme(firstNonBlank(ossProperties.getPublicEndpoint(), ossProperties.getEndpoint())); } + private String downloadEndpoint() { + String configured = ossProperties.getDownloadEndpoint(); + if (configured == null || configured.isBlank()) { + return null; + } + return withScheme(configured.trim()); + } + private String resultObjectKey(File file, String moduleType) { return String.format("result/%s/%s/%s", normalizeModuleType(moduleType), UUID.randomUUID(), file.getName()); } diff --git a/backend-java/src/main/resources/application.yml b/backend-java/src/main/resources/application.yml index e81e9833..68efcba3 100644 --- a/backend-java/src/main/resources/application.yml +++ b/backend-java/src/main/resources/application.yml @@ -97,6 +97,7 @@ aiimage: region: ${AIIMAGE_OSS_REGION:us-east-1} endpoint: ${AIIMAGE_OSS_ENDPOINT:https://oss.aishufu.top} public-endpoint: ${AIIMAGE_OSS_PUBLIC_ENDPOINT:https://oss.aishufu.top} + download-endpoint: ${AIIMAGE_OSS_DOWNLOAD_ENDPOINT:} bucket: ${AIIMAGE_OSS_BUCKET:nanri-ai-images} image-video-bucket: ${AIIMAGE_IMAGE_VIDEO_OSS_BUCKET:shufu-video} digital-human-bucket: ${AIIMAGE_DIGITAL_HUMAN_OSS_BUCKET:nanri-ai-digital-human} diff --git a/backend-java/src/test/java/com/nanri/aiimage/modules/file/service/oss/OssStorageServiceTest.java b/backend-java/src/test/java/com/nanri/aiimage/modules/file/service/oss/OssStorageServiceTest.java index a0a3f1f1..b16c6723 100644 --- a/backend-java/src/test/java/com/nanri/aiimage/modules/file/service/oss/OssStorageServiceTest.java +++ b/backend-java/src/test/java/com/nanri/aiimage/modules/file/service/oss/OssStorageServiceTest.java @@ -12,10 +12,11 @@ import static org.junit.jupiter.api.Assertions.assertNull; class OssStorageServiceTest { private OssStorageService storageService; + private OssProperties properties; @BeforeEach void setUp() { - OssProperties properties = new OssProperties(); + properties = new OssProperties(); properties.setEndpoint("https://oss.aishufu.top"); properties.setPublicEndpoint("https://oss.aishufu.top"); properties.setBucket("nanri-ai-images"); @@ -93,4 +94,37 @@ class OssStorageServiceTest { assertNull(storageService.normalizeManagedPublicUrl(null)); assertEquals(" ", storageService.normalizeManagedPublicUrl(" ")); } + + @Test + void resultFilesUseDownloadEndpointWhenConfigured() { + properties().setDownloadEndpoint("https://download.aishufu.top"); + assertEquals( + "https://download.aishufu.top/nanri-ai-images/result/similar_asin/1/%E9%87%8D%E6%96%B0-result.xlsx", + storageService.generateFreshDownloadUrl("result/similar_asin/1/重新-result.xlsx")); + assertEquals( + "https://download.aishufu.top/nanri-ai-images/result/similar_asin/1/re.xlsx", + storageService.generateFreshDownloadUrl("https://oss.aishufu.top/nanri-ai-images/result/similar_asin/1/re.xlsx")); + } + + @Test + void nonResultAndUnmanagedValuesKeepPublicEndpoint() { + properties().setDownloadEndpoint("https://download.aishufu.top"); + assertEquals( + "https://oss.aishufu.top/nanri-ai-images/supply_images/main.jpg", + storageService.generateFreshDownloadUrl("supply_images/main.jpg")); + assertEquals( + "https://download.aishufu.top/shufu-video/result/image_video/demo.mp4", + storageService.generateFreshDownloadUrl("shufu-video/result/image_video/demo.mp4")); + } + + @Test + void downloadEndpointDisabledFallsBackToPublicEndpoint() { + assertEquals( + "https://oss.aishufu.top/nanri-ai-images/result/similar_asin/1/re.xlsx", + storageService.generateFreshDownloadUrl("result/similar_asin/1/re.xlsx")); + } + + private OssProperties properties() { + return properties; + } }