From 8ca5b1d53b27cdde0367e7dcd1eae1fa10f32309 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E8=87=AA=E8=BE=BE?= <980324341@qq.com> Date: Wed, 2 Sep 2026 08:00:19 +0800 Subject: [PATCH] =?UTF-8?q?task-167:=20aiimage.http-client.*=20=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E5=91=BD=E5=90=8D=E7=A9=BA=E9=97=B4=EF=BC=88connect/r?= =?UTF-8?q?ead/call=20=E8=B6=85=E6=97=B6=E4=B8=8E=E9=87=8D=E8=AF=95?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E5=80=BC=E5=90=8C=E7=8E=B0=E7=8A=B6=E3=80=81?= =?UTF-8?q?=E9=9D=9E=E6=B3=95=E5=80=BC=E9=92=B3=E5=88=B6=E3=80=81env=20?= =?UTF-8?q?=E8=A6=86=E7=9B=96=EF=BC=89+=208=20=E6=9D=A1=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=EF=BC=9Bsurefire=20=E5=8A=A0=20MaxMetaspaceSize=20=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=20fork=20=E5=86=85=E5=AD=98=E5=B4=A9=E6=BA=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend-java/pom.xml | 2 +- .../aiimage/config/HttpClientProperties.java | 59 +++++++++++++ .../src/main/resources/application.yml | 8 ++ .../config/HttpClientPropertiesTest.java | 85 +++++++++++++++++++ 4 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 backend-java/src/main/java/com/nanri/aiimage/config/HttpClientProperties.java create mode 100644 backend-java/src/test/java/com/nanri/aiimage/config/HttpClientPropertiesTest.java diff --git a/backend-java/pom.xml b/backend-java/pom.xml index a57f4908..a4e1ee38 100644 --- a/backend-java/pom.xml +++ b/backend-java/pom.xml @@ -183,7 +183,7 @@ org.apache.maven.plugins maven-surefire-plugin - -XX:+EnableDynamicAgentLoading -Xshare:off -Xmx1536m + -XX:+EnableDynamicAgentLoading -Xshare:off -Xmx2g -XX:MaxMetaspaceSize=512m diff --git a/backend-java/src/main/java/com/nanri/aiimage/config/HttpClientProperties.java b/backend-java/src/main/java/com/nanri/aiimage/config/HttpClientProperties.java new file mode 100644 index 00000000..7191068e --- /dev/null +++ b/backend-java/src/main/java/com/nanri/aiimage/config/HttpClientProperties.java @@ -0,0 +1,59 @@ +package com.nanri.aiimage.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * 统一 HTTP 客户端配置命名空间(task-167)。 + * + * `aiimage.http-client.*`:connect/read/call 超时与重试默认值与现状一致 + * (RustFS 客户端契约表基线);非法值钳制到合理范围;env 覆盖走宽松绑定。 + */ +@Data +@Component +@ConfigurationProperties(prefix = "aiimage.http-client") +public class HttpClientProperties { + + /** 连接超时(毫秒),默认 10000 与现状一致。 */ + private long connectTimeoutMillis = 10_000; + + /** 读取超时(毫秒),默认 60000 与现状一致。 */ + private long readTimeoutMillis = 60_000; + + /** 调用总超时(毫秒),默认 90000 与现状一致。 */ + private long callTimeoutMillis = 90_000; + + /** 最大重试次数(0-10),默认 3 与现状一致。 */ + private int maxRetries = 3; + + /** 重试基础延迟(毫秒),默认 500 与现状一致。 */ + private long baseRetryDelayMillis = 500; + + /** 钳制后的连接超时:1s-300s。 */ + public long effectiveConnectTimeoutMillis() { + return clamp(connectTimeoutMillis, 1_000, 300_000); + } + + /** 钳制后的读取超时:1s-3600s。 */ + public long effectiveReadTimeoutMillis() { + return clamp(readTimeoutMillis, 1_000, 3_600_000); + } + + /** 钳制后的调用总超时:1s-7200s。 */ + public long effectiveCallTimeoutMillis() { + return clamp(callTimeoutMillis, 1_000, 7_200_000); + } + + /** 钳制后的重试次数:0-10。 */ + public int effectiveMaxRetries() { + return (int) clamp(maxRetries, 0, 10); + } + + private static long clamp(long value, long min, long max) { + if (value < min) { + return min; + } + return Math.min(value, max); + } +} diff --git a/backend-java/src/main/resources/application.yml b/backend-java/src/main/resources/application.yml index 2667c03f..5b105c93 100644 --- a/backend-java/src/main/resources/application.yml +++ b/backend-java/src/main/resources/application.yml @@ -81,6 +81,14 @@ knife4j: language: zh_cn aiimage: + # ===== HTTP 客户端统一配置命名空间(task-167)===== + # 默认值与现状一致(RustFS 客户端契约表基线);非法值由 HttpClientProperties 钳制。 + http-client: + connect-timeout-millis: ${AIIMAGE_HTTP_CLIENT_CONNECT_TIMEOUT_MILLIS:10000} + read-timeout-millis: ${AIIMAGE_HTTP_CLIENT_READ_TIMEOUT_MILLIS:60000} + call-timeout-millis: ${AIIMAGE_HTTP_CLIENT_CALL_TIMEOUT_MILLIS:90000} + max-retries: ${AIIMAGE_HTTP_CLIENT_MAX_RETRIES:3} + base-retry-delay-millis: ${AIIMAGE_HTTP_CLIENT_BASE_RETRY_DELAY_MILLIS:500} # Set a stable server-level ID in 1Panel/Docker with AIIMAGE_INSTANCE_ID. # For IDEA/local startup, set -Daiimage.instance-id= or configure the AIIMAGE_INSTANCE_ID env var. # Avoid relying on container hostname/container ID, otherwise task owner continuity may break after restart/redeploy. diff --git a/backend-java/src/test/java/com/nanri/aiimage/config/HttpClientPropertiesTest.java b/backend-java/src/test/java/com/nanri/aiimage/config/HttpClientPropertiesTest.java new file mode 100644 index 00000000..79efe7cd --- /dev/null +++ b/backend-java/src/test/java/com/nanri/aiimage/config/HttpClientPropertiesTest.java @@ -0,0 +1,85 @@ +package com.nanri.aiimage.config; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * task-167:aiimage.http-client.* 配置命名空间契约(plan 10)。 + * 默认值与现状一致;非法值钳制;配置类绑定字段齐全。 + */ +class HttpClientPropertiesTest { + + private HttpClientProperties properties; + + @BeforeEach + void setUp() { + properties = new HttpClientProperties(); + } + + @Test + void namespaceDefaultsMatchCurrent() { + assertEquals(10_000L, properties.getConnectTimeoutMillis(), "connect 默认 10s(与现状一致)"); + assertEquals(60_000L, properties.getReadTimeoutMillis(), "read 默认 60s"); + assertEquals(90_000L, properties.getCallTimeoutMillis(), "call 默认 90s"); + assertEquals(3, properties.getMaxRetries(), "重试默认 3"); + assertEquals(500L, properties.getBaseRetryDelayMillis(), "退避基础延迟 500ms"); + } + + @Test + void connectTimeoutBounded() { + assertEquals(10_000L, properties.effectiveConnectTimeoutMillis()); + properties.setConnectTimeoutMillis(1_000); + assertEquals(1_000L, properties.effectiveConnectTimeoutMillis()); + } + + @Test + void readTimeoutBounded() { + assertEquals(60_000L, properties.effectiveReadTimeoutMillis()); + properties.setReadTimeoutMillis(3_600_000); + assertEquals(3_600_000L, properties.effectiveReadTimeoutMillis()); + } + + @Test + void retryCountBounded() { + assertEquals(3, properties.effectiveMaxRetries()); + properties.setMaxRetries(0); + assertEquals(0, properties.effectiveMaxRetries()); + } + + @Test + void invalidValuesClamped() { + properties.setConnectTimeoutMillis(-5); + assertEquals(1_000L, properties.effectiveConnectTimeoutMillis(), "负数钳制到下限"); + properties.setReadTimeoutMillis(999_999_999L); + assertEquals(3_600_000L, properties.effectiveReadTimeoutMillis(), "超上限钳制"); + properties.setMaxRetries(99); + assertEquals(10, properties.effectiveMaxRetries(), "重试钳制到 10"); + } + + @Test + void callTimeoutBounded() { + properties.setCallTimeoutMillis(7_200_000); + assertEquals(7_200_000L, properties.effectiveCallTimeoutMillis()); + properties.setCallTimeoutMillis(0); + assertEquals(1_000L, properties.effectiveCallTimeoutMillis()); + } + + @Test + void configClassBindingPrefix() throws Exception { + var annotation = HttpClientProperties.class + .getAnnotation(org.springframework.boot.context.properties.ConfigurationProperties.class); + assertEquals("aiimage.http-client", annotation.prefix(), "绑定 aiimage.http-client 前缀"); + assertTrue(HttpClientProperties.class.isAnnotationPresent( + org.springframework.stereotype.Component.class), "配置类需注册为组件"); + } + + @Test + void envOverrideSemantics() { + // env 覆盖走宽松绑定:设置后 effective 反映新值 + properties.setConnectTimeoutMillis(30_000); + assertEquals(30_000L, properties.effectiveConnectTimeoutMillis()); + } +}