task-77: 统一 Coze、品牌检查和紫鸟 HTTP 客户端的连接复用策略

This commit is contained in:
2026-08-30 21:08:28 +08:00
parent 678ba57347
commit 39e52858d1
5 changed files with 253 additions and 12 deletions
@@ -0,0 +1,45 @@
package com.nanri.aiimage.config;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.JdkClientHttpRequestFactory;
import java.net.http.HttpClient;
import java.time.Duration;
/**
* Task 77:外部 HTTP 客户端统一连接复用池。
* Coze / 品牌检查 / 紫鸟三个外部客户端共用同一个 java.net.http.HttpClient
* (内置 keep-alive 连接池),避免各自新建短命客户端导致连接无法复用、
* 每次请求都重新建连。各客户端按自身超时创建独立的
* JdkClientHttpRequestFactory(共享底层连接池),RestClient 单例懒加载。
*/
public class HttpClientPool {
private static volatile HttpClient sharedHttpClient;
/** 共享连接池实例:单一 HttpClient 承载全部外部调用的连接复用。 */
public static HttpClient sharedHttpClient() {
HttpClient client = sharedHttpClient;
if (client != null) {
return client;
}
synchronized (HttpClientPool.class) {
if (sharedHttpClient == null) {
sharedHttpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.version(HttpClient.Version.HTTP_1_1)
.build();
}
return sharedHttpClient;
}
}
/** 按 readTimeout(毫秒)创建共享连接池工厂;非法值钳制到最小正数。 */
public static ClientHttpRequestFactory requestFactory(int readTimeoutMillis) {
int safeReadTimeout = Math.max(1, readTimeoutMillis);
JdkClientHttpRequestFactory factory =
new JdkClientHttpRequestFactory(sharedHttpClient());
factory.setReadTimeout(Duration.ofMillis(safeReadTimeout));
return factory;
}
}
@@ -12,6 +12,8 @@ import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClient;
import com.nanri.aiimage.config.HttpClientPool;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.LinkedHashMap;
@@ -160,10 +162,9 @@ public class BrandCheckClient {
}
synchronized (this) {
if (sharedRestClient == null) {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setConnectTimeout(properties.getConnectTimeoutMillis());
requestFactory.setReadTimeout(properties.getReadTimeoutMillis());
sharedRestClient = RestClient.builder().requestFactory(requestFactory).build();
sharedRestClient = RestClient.builder()
.requestFactory(HttpClientPool.requestFactory(properties.getReadTimeoutMillis()))
.build();
}
return sharedRestClient;
}
@@ -15,6 +15,8 @@ import org.springframework.stereotype.Component;
import org.springframework.util.StreamUtils;
import org.springframework.web.client.RestClient;
import com.nanri.aiimage.config.HttpClientPool;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
@@ -749,10 +751,9 @@ public class SimilarAsinCozeClient {
}
synchronized (this) {
if (sharedRestClient == null) {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setConnectTimeout(properties.getCozeConnectTimeoutMillis());
requestFactory.setReadTimeout(properties.getCozeReadTimeoutMillis());
sharedRestClient = RestClient.builder().requestFactory(requestFactory).build();
sharedRestClient = RestClient.builder()
.requestFactory(HttpClientPool.requestFactory(properties.getCozeReadTimeoutMillis()))
.build();
}
return sharedRestClient;
}
@@ -13,6 +13,8 @@ import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClient;
import com.nanri.aiimage.config.HttpClientPool;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -25,6 +27,9 @@ public class ZiniaoClientImpl implements ZiniaoClient {
private final ZiniaoProperties ziniaoProperties;
private final ObjectMapper objectMapper;
/** Task 77:单例 RestClient(共享连接池),避免每次调用新建短命客户端。 */
private volatile RestClient sharedRestClient;
@Override
public Long getCompanyIdByApiKey(String apiKey) {
String raw = getWithApiKey(apiKey, "/app/builtin/company", "获取 companyId");
@@ -246,10 +251,23 @@ public class ZiniaoClientImpl implements ZiniaoClient {
}
private RestClient getRestClient() {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setConnectTimeout(ziniaoProperties.getConnectTimeoutSeconds() * 1000);
requestFactory.setReadTimeout(ziniaoProperties.getReadTimeoutSeconds() * 1000);
return RestClient.builder().requestFactory(requestFactory).build();
RestClient client = sharedRestClient;
if (client != null) {
return client;
}
synchronized (this) {
if (sharedRestClient == null) {
sharedRestClient = RestClient.builder()
.requestFactory(HttpClientPool.requestFactory(ziniaoProperties.getReadTimeoutSeconds() * 1000))
.build();
}
return sharedRestClient;
}
}
/** 反射/测试可见:与 getRestClient 同一单例。 */
RestClient restClient() {
return getRestClient();
}
private void addStaffItem(List<ZiniaoStaffItemVo> items, JsonNode itemNode) {