task-78: 为所有外部调用增加耗时、重试、失败率和 payload 字节指标
新增 ExternalCallMetricsRecorder:通过 RestClient 拦截器统一记录 aiimage.external-call.duration(耗时)、aiimage.external-call.total (失败率)、aiimage.external-call.payload.bytes(请求字节)与 aiimage.external-call.retry.total(重试次数);Coze/品牌检查/紫鸟 三个外部客户端全部接入,指标注册表缺失时静默降级,不改变调用语义。
This commit is contained in:
+7
-7
@@ -87,7 +87,7 @@ class HttpClientConnectionReuseTest {
|
||||
void test_task_077_brand_normal_default_path() throws Exception {
|
||||
// 默认路径:品牌检查客户端通过共享池创建单例 RestClient,
|
||||
// 工厂为带 keep-alive 连接池的 JdkClientHttpRequestFactory。
|
||||
BrandCheckClient client = new BrandCheckClient(new BrandCheckProperties());
|
||||
BrandCheckClient client = new BrandCheckClient(new BrandCheckProperties(), null);
|
||||
BrandCheckBatchResult result = client.checkTitleText(" ");
|
||||
|
||||
assertTrue(result.brands().isEmpty(), "空标题安全跳过,不创建无效资源");
|
||||
@@ -98,8 +98,8 @@ class HttpClientConnectionReuseTest {
|
||||
void test_task_077_brand_normal_multiple_items() throws Exception {
|
||||
// 批量场景:Coze/品牌/紫鸟三个客户端各自持有独立 RestClient,
|
||||
// 但底层连接池共用同一 HttpClient 实例,不重复创建。
|
||||
SimilarAsinCozeClient coze = new SimilarAsinCozeClient(new SimilarAsinProperties(), new ObjectMapper(), null);
|
||||
BrandCheckClient brand = new BrandCheckClient(new BrandCheckProperties());
|
||||
SimilarAsinCozeClient coze = new SimilarAsinCozeClient(new SimilarAsinProperties(), new ObjectMapper(), null, null);
|
||||
BrandCheckClient brand = new BrandCheckClient(new BrandCheckProperties(), null);
|
||||
ZiniaoClientImpl ziniao = new ZiniaoClientImpl(new ZiniaoProperties(), new ObjectMapper());
|
||||
|
||||
HttpClient cozeClient = clientOf(factoryOf(restClientOf(coze)));
|
||||
@@ -115,7 +115,7 @@ class HttpClientConnectionReuseTest {
|
||||
void test_task_077_brand_normal_repeated_operation_is_idempotent() throws Exception {
|
||||
// 幂等:同一客户端重复触发请求创建逻辑只产生一个 RestClient,
|
||||
// 重复调用返回同一实例,不重复创建客户端对象。
|
||||
BrandCheckClient brand = new BrandCheckClient(new BrandCheckProperties());
|
||||
BrandCheckClient brand = new BrandCheckClient(new BrandCheckProperties(), null);
|
||||
assertSame(restClientOf(brand), restClientOf(brand), "品牌客户端复用同一 RestClient");
|
||||
|
||||
ZiniaoClientImpl ziniao = new ZiniaoClientImpl(new ZiniaoProperties(), new ObjectMapper());
|
||||
@@ -125,7 +125,7 @@ class HttpClientConnectionReuseTest {
|
||||
@Test
|
||||
void test_task_077_brand_boundary_empty_input() throws Exception {
|
||||
// 空输入:空品牌列表不发起任何 HTTP 请求、不创建客户端资源。
|
||||
BrandCheckClient brand = new BrandCheckClient(new BrandCheckProperties());
|
||||
BrandCheckClient brand = new BrandCheckClient(new BrandCheckProperties(), null);
|
||||
BrandCheckBatchResult result = brand.checkAll(List.of(), "Terms");
|
||||
|
||||
assertTrue(result.brands().isEmpty());
|
||||
@@ -136,7 +136,7 @@ class HttpClientConnectionReuseTest {
|
||||
@Test
|
||||
void test_task_077_brand_boundary_single_item() throws Exception {
|
||||
// 单元素:单客户端单请求走共享池,工厂带连接池,行为与批量一致。
|
||||
BrandCheckClient brand = new BrandCheckClient(new BrandCheckProperties());
|
||||
BrandCheckClient brand = new BrandCheckClient(new BrandCheckProperties(), null);
|
||||
assertPooled(factoryOf(restClientOf(brand)));
|
||||
}
|
||||
|
||||
@@ -146,7 +146,7 @@ class HttpClientConnectionReuseTest {
|
||||
// 不随实例数量线性增长连接资源。
|
||||
int instances = 8;
|
||||
for (int i = 0; i < instances; i++) {
|
||||
restClientOf(new BrandCheckClient(new BrandCheckProperties()));
|
||||
restClientOf(new BrandCheckClient(new BrandCheckProperties(), null));
|
||||
restClientOf(new ZiniaoClientImpl(new ZiniaoProperties(), new ObjectMapper()));
|
||||
}
|
||||
assertSame(HttpClientPool.sharedHttpClient(), HttpClientPool.sharedHttpClient(),
|
||||
|
||||
+388
@@ -0,0 +1,388 @@
|
||||
package com.nanri.aiimage.metrics;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.nanri.aiimage.config.BrandCheckProperties;
|
||||
import com.nanri.aiimage.config.SimilarAsinProperties;
|
||||
import com.nanri.aiimage.config.ZiniaoProperties;
|
||||
import com.nanri.aiimage.metrics.ExternalCallMetricsRecorder;
|
||||
import com.nanri.aiimage.modules.brand.client.BrandCheckClient;
|
||||
import com.nanri.aiimage.modules.coze.service.CozeCredentialPoolService;
|
||||
import com.nanri.aiimage.modules.similarasin.client.SimilarAsinCozeClient;
|
||||
import com.nanri.aiimage.modules.similarasin.model.dto.SimilarAsinResultRowDto;
|
||||
import com.nanri.aiimage.modules.ziniao.client.ZiniaoClientImpl;
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import com.sun.net.httpserver.HttpServer;
|
||||
import io.micrometer.core.instrument.Counter;
|
||||
import io.micrometer.core.instrument.DistributionSummary;
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import io.micrometer.core.instrument.Timer;
|
||||
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Task 78:为所有外部调用(Coze / 品牌检查 / 紫鸟)统一增加耗时、重试、
|
||||
* 失败率和 payload 字节指标。全部用例通过本地 HttpServer 发起真实 HTTP 调用,
|
||||
* 在 SimpleMeterRegistry 上断言指标语义(无 mock 外部客户端)。
|
||||
*/
|
||||
class ExternalCallMetricsRecorderTest {
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
private final SimpleMeterRegistry registry = new SimpleMeterRegistry();
|
||||
private HttpServer server;
|
||||
private int port;
|
||||
private final ExecutorService serverExecutor = Executors.newCachedThreadPool();
|
||||
private final AtomicInteger cozeSubmitCount = new AtomicInteger();
|
||||
private final AtomicInteger ziniaoCount = new AtomicInteger();
|
||||
private final AtomicInteger brandCount = new AtomicInteger();
|
||||
private final AtomicBoolean cozeFailNext = new AtomicBoolean();
|
||||
|
||||
/** 可复用 Coze 凭据池:返回一个固定凭据,避免真实 HTTP 调用被凭据检查拦截。 */
|
||||
private static CozeCredentialPoolService credentialPool() {
|
||||
CozeCredentialPoolService pool = mock(CozeCredentialPoolService.class);
|
||||
when(pool.listEnabled(anyString())).thenReturn(List.of(
|
||||
new CozeCredentialPoolService.CozeCredential("test", "wf-1", "token", Integer.MAX_VALUE)));
|
||||
return pool;
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws IOException {
|
||||
server = HttpServer.create(new InetSocketAddress(0), 0);
|
||||
server.setExecutor(serverExecutor);
|
||||
server.createContext("/brand_check", this::handleBrandCheck);
|
||||
server.createContext("/app/builtin/company", this::handleZiniaoCompany);
|
||||
server.createContext("/v1/workflow/run", this::handleCozeSubmit);
|
||||
server.start();
|
||||
port = server.getAddress().getPort();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
server.stop(0);
|
||||
serverExecutor.shutdownNow();
|
||||
}
|
||||
|
||||
private void handleBrandCheck(HttpExchange exchange) throws IOException {
|
||||
brandCount.incrementAndGet();
|
||||
sendJson(exchange, 200,
|
||||
"{\"faild_data\":[],\"query_faild_data\":[]}".getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
private void handleZiniaoCompany(HttpExchange exchange) throws IOException {
|
||||
ziniaoCount.incrementAndGet();
|
||||
sendJson(exchange, 200,
|
||||
"{\"code\":\"0\",\"data\":{\"companyId\":1001}}".getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
private void handleCozeSubmit(HttpExchange exchange) throws IOException {
|
||||
int count = cozeSubmitCount.incrementAndGet();
|
||||
if (cozeFailNext.getAndSet(false)) {
|
||||
exchange.sendResponseHeaders(500, 0);
|
||||
exchange.close();
|
||||
return;
|
||||
}
|
||||
String executeId = "exec-" + count;
|
||||
String payload = "{\"data\":[{\"asin\":\"B0TEST78\",\"country\":\"US\",\"result\":\"ok\",\"conclusion\":\"ok\"}]}";
|
||||
String response = "{\"code\":0,\"data\":{\"execute_id\":\"" + executeId
|
||||
+ "\",\"status\":\"Success\",\"data\":" + payload + "}}";
|
||||
sendJson(exchange, 200, response.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
private String readBody(HttpExchange exchange) throws IOException {
|
||||
return new String(exchange.getRequestBody().readAllBytes(), StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private void sendJson(HttpExchange exchange, int status, byte[] body) throws IOException {
|
||||
exchange.getResponseHeaders().set("Content-Type", "application/json; charset=utf-8");
|
||||
exchange.sendResponseHeaders(status, body.length);
|
||||
exchange.getResponseBody().write(body);
|
||||
exchange.close();
|
||||
}
|
||||
|
||||
private void awaitMetric(String name, String... tags) throws InterruptedException {
|
||||
for (int i = 0; i < 500; i++) {
|
||||
if (name.endsWith(".duration")) {
|
||||
if (findTimer(name, tags) != null) return;
|
||||
} else if (name.endsWith(".bytes")) {
|
||||
if (findSummary(name, tags) != null) return;
|
||||
} else {
|
||||
if (findCounter(name, tags) != null && findCounter(name, tags).count() > 0) return;
|
||||
}
|
||||
Thread.sleep(10);
|
||||
}
|
||||
throw new AssertionError("metric not recorded: " + name);
|
||||
}
|
||||
|
||||
private Timer findTimer(String name, String... tags) {
|
||||
for (Timer timer : registry.find(name).timers()) {
|
||||
if (matchesTags(timer.getId().getTags(), tags)) return timer;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private DistributionSummary findSummary(String name, String... tags) {
|
||||
for (DistributionSummary summary : registry.find(name).summaries()) {
|
||||
if (matchesTags(summary.getId().getTags(), tags)) return summary;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Counter findCounter(String name, String... tags) {
|
||||
for (Counter counter : registry.find(name).counters()) {
|
||||
if (matchesTags(counter.getId().getTags(), tags)) return counter;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private double counterCount(String name, String... tags) {
|
||||
Counter counter = findCounter(name, tags);
|
||||
return counter == null ? 0.0 : counter.count();
|
||||
}
|
||||
|
||||
private boolean matchesTags(Iterable<io.micrometer.core.instrument.Tag> tags, String... expected) {
|
||||
Map<String, String> map = new java.util.HashMap<>();
|
||||
tags.forEach(tag -> map.put(tag.getKey(), tag.getValue()));
|
||||
for (int i = 0; i + 1 < expected.length; i += 2) {
|
||||
if (!expected[i + 1].equals(map.get(expected[i]))) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 1. 正常默认路径:Coze 批量检查走本地服务,耗时/payload 字节全部记录 ----
|
||||
|
||||
@Test
|
||||
void test_task_078_payload_metrics_normal_default_path() throws Exception {
|
||||
SimilarAsinProperties props = cozeProps();
|
||||
SimilarAsinCozeClient client =
|
||||
new SimilarAsinCozeClient(props, objectMapper, credentialPool(), new ExternalCallMetricsRecorder(registry));
|
||||
SimilarAsinResultRowDto row = new SimilarAsinResultRowDto();
|
||||
row.setAsin("B0TEST78");
|
||||
row.setTitle("Test");
|
||||
row.setSku("SKU-1");
|
||||
row.setCountry("US");
|
||||
|
||||
List<SimilarAsinResultRowDto> result = client.inspect(List.of(row), "", "test-key");
|
||||
|
||||
assertEquals(1, result.size(), "默认成功路径必须返回完整结果");
|
||||
assertEquals("ok", result.getFirst().getConclusion(), "主输出必须解析到 Coze 结果");
|
||||
assertNotNull(findTimer("aiimage.external-call.duration", "client", "coze"),
|
||||
"必须记录 Coze 调用耗时");
|
||||
assertTrue(findSummary("aiimage.external-call.payload.bytes", "client", "coze").totalAmount() > 0,
|
||||
"必须记录 payload 字节指标");
|
||||
}
|
||||
|
||||
// ---- 2. 批量:多行顺序稳定、无丢失 ----
|
||||
|
||||
@Test
|
||||
void test_task_078_payload_metrics_normal_multiple_items() throws Exception {
|
||||
SimilarAsinProperties props = cozeProps();
|
||||
SimilarAsinCozeClient client =
|
||||
new SimilarAsinCozeClient(props, objectMapper, credentialPool(), new ExternalCallMetricsRecorder(registry));
|
||||
List<SimilarAsinResultRowDto> rows = new ArrayList<>();
|
||||
for (int i = 0; i < 3; i++) {
|
||||
SimilarAsinResultRowDto row = new SimilarAsinResultRowDto();
|
||||
row.setAsin("B0BATCH" + i);
|
||||
row.setCountry("US");
|
||||
row.setTitle("Batch " + i);
|
||||
row.setSku("SKU-" + i);
|
||||
rows.add(row);
|
||||
}
|
||||
|
||||
List<SimilarAsinResultRowDto> result = client.inspect(rows, "", "test-key");
|
||||
|
||||
assertEquals(3, result.size(), "批量结果不丢失");
|
||||
assertEquals("B0BATCH0", result.get(0).getAsin(), "顺序稳定");
|
||||
assertEquals("B0BATCH2", result.get(2).getAsin(), "顺序稳定");
|
||||
awaitMetric("aiimage.external-call.duration", "client", "coze");
|
||||
|
||||
// 全部外部客户端(Coze / 品牌检查 / 紫鸟)在同一次批量中各自记录指标
|
||||
BrandCheckClient brand = new BrandCheckClient(brandProps(), new ExternalCallMetricsRecorder(registry));
|
||||
brand.checkAll(List.of("Nintendo", "LEGO", "Sony"), "Terms");
|
||||
ZiniaoClientImpl ziniao = new ZiniaoClientImpl(ziniaoProps(), objectMapper, new ExternalCallMetricsRecorder(registry));
|
||||
assertEquals(1001L, ziniao.getCompanyIdByApiKey("test-api-key"));
|
||||
|
||||
awaitMetric("aiimage.external-call.duration", "client", "brand");
|
||||
awaitMetric("aiimage.external-call.duration", "client", "ziniao");
|
||||
assertEquals(3.0, counterCount("aiimage.external-call.total",
|
||||
"client", "brand", "result", "success"), "品牌批量 3 次成功全部记录");
|
||||
assertEquals(1.0, counterCount("aiimage.external-call.total",
|
||||
"client", "ziniao", "result", "success"), "紫鸟单次成功记录");
|
||||
}
|
||||
|
||||
// ---- 3. 重复操作幂等:指标按请求精确累加,不重复 ----
|
||||
|
||||
@Test
|
||||
void test_task_078_payload_metrics_normal_repeated_operation_is_idempotent() throws Exception {
|
||||
BrandCheckProperties props = brandProps();
|
||||
BrandCheckClient client = new BrandCheckClient(props, new ExternalCallMetricsRecorder(registry));
|
||||
|
||||
client.check("Nintendo");
|
||||
client.check("Nintendo");
|
||||
|
||||
assertEquals(2, brandCount.get(), "两次请求真实发出");
|
||||
assertEquals(2.0, counterCount("aiimage.external-call.total",
|
||||
"client", "brand", "result", "success"), "指标按请求次数精确累加,不重复");
|
||||
assertNotNull(findTimer("aiimage.external-call.duration", "client", "brand"),
|
||||
"重复调用均记录耗时");
|
||||
}
|
||||
|
||||
// ---- 4. 空输入:无请求、无资源创建、无指标 ----
|
||||
|
||||
@Test
|
||||
void test_task_078_payload_metrics_boundary_empty_input() throws Exception {
|
||||
BrandCheckProperties props = brandProps();
|
||||
BrandCheckClient client = new BrandCheckClient(props, new ExternalCallMetricsRecorder(registry));
|
||||
|
||||
BrandCheckClient.BrandCheckBatchResult result = client.checkAll(List.of(), "Terms");
|
||||
|
||||
assertEquals(0, brandCount.get(), "空输入不发起外部调用");
|
||||
assertTrue(result.brands().isEmpty());
|
||||
assertEquals(0.0, counterCount("aiimage.external-call.total",
|
||||
"client", "brand", "result", "success"), "无调用无指标");
|
||||
}
|
||||
|
||||
// ---- 5. 单元素:走独立路径,指标正确 ----
|
||||
|
||||
@Test
|
||||
void test_task_078_payload_metrics_boundary_single_item() throws Exception {
|
||||
BrandCheckProperties props = brandProps();
|
||||
BrandCheckClient client = new BrandCheckClient(props, new ExternalCallMetricsRecorder(registry));
|
||||
|
||||
BrandCheckClient.BrandCheckResponse response = client.check("LEGO");
|
||||
|
||||
assertNotNull(response);
|
||||
awaitMetric("aiimage.external-call.duration", "client", "brand");
|
||||
assertEquals(1.0, counterCount("aiimage.external-call.total",
|
||||
"client", "brand", "result", "success"));
|
||||
}
|
||||
|
||||
// ---- 6. 上限/超限:固定线程池并发 20 个请求,指标按请求精确累加 ----
|
||||
|
||||
@Test
|
||||
void test_task_078_payload_metrics_boundary_limit_and_overflow() throws Exception {
|
||||
SimilarAsinProperties props = cozeProps();
|
||||
props.setCozeReadTimeoutMillis(5000);
|
||||
ExternalCallMetricsRecorder recorder = new ExternalCallMetricsRecorder(registry);
|
||||
ExecutorService pool = Executors.newFixedThreadPool(4);
|
||||
try {
|
||||
for (int i = 0; i < 20; i++) {
|
||||
int index = i;
|
||||
pool.submit(() -> {
|
||||
SimilarAsinCozeClient client = new SimilarAsinCozeClient(props, objectMapper, credentialPool(), recorder);
|
||||
SimilarAsinResultRowDto row = new SimilarAsinResultRowDto();
|
||||
row.setAsin("B0LIMIT" + index);
|
||||
row.setCountry("US");
|
||||
row.setTitle("Limit " + index);
|
||||
row.setSku("SKU-" + index);
|
||||
try {
|
||||
client.inspect(List.of(row), "", "test-key");
|
||||
} catch (Exception ignored) {
|
||||
// 并发下结果失败也视为已处理
|
||||
}
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
pool.shutdown();
|
||||
}
|
||||
|
||||
awaitMetric("aiimage.external-call.total", "client", "coze", "result", "success");
|
||||
for (int i = 0; i < 2000 && cozeSubmitCount.get() < 20; i++) {
|
||||
Thread.sleep(10);
|
||||
}
|
||||
assertEquals(20, cozeSubmitCount.get(), "并发 20 请求全部真实发出");
|
||||
assertEquals(20.0, counterCount("aiimage.external-call.total",
|
||||
"client", "coze", "result", "success"), "20 次成功全部记录,无重复");
|
||||
}
|
||||
|
||||
// ---- 7. 非法参数:空列表拒绝,不发起请求,无指标 ----
|
||||
|
||||
@Test
|
||||
void test_task_078_payload_metrics_invalid_input_rejected() throws Exception {
|
||||
BrandCheckProperties props = brandProps();
|
||||
BrandCheckClient client = new BrandCheckClient(props, new ExternalCallMetricsRecorder(registry));
|
||||
|
||||
BrandCheckClient.BrandCheckBatchResult result = client.checkAll(null, "Terms");
|
||||
|
||||
assertTrue(result.brands().isEmpty(), "null 列表安全跳过");
|
||||
assertEquals(0, brandCount.get(), "非法输入不发起外部调用");
|
||||
assertEquals(0.0, counterCount("aiimage.external-call.total",
|
||||
"client", "brand", "result", "success"), "非法输入无指标");
|
||||
}
|
||||
|
||||
// ---- 8. 依赖失败:先 500 后成功,错误可恢复;重试与失败率指标被记录 ----
|
||||
|
||||
@Test
|
||||
void test_task_078_payload_metrics_dependency_failure_releases_resources() throws Exception {
|
||||
ExternalCallMetricsRecorder recorder = new ExternalCallMetricsRecorder(registry);
|
||||
SimilarAsinProperties props = cozeProps();
|
||||
props.setCozeReadTimeoutMillis(5000);
|
||||
SimilarAsinCozeClient client =
|
||||
new SimilarAsinCozeClient(props, objectMapper, credentialPool(), recorder);
|
||||
List<SimilarAsinResultRowDto> rows = new ArrayList<>();
|
||||
SimilarAsinResultRowDto row = new SimilarAsinResultRowDto();
|
||||
row.setAsin("B0FAIL78");
|
||||
row.setCountry("US");
|
||||
row.setTitle("Fail");
|
||||
row.setSku("SKU-FAIL");
|
||||
rows.add(row);
|
||||
|
||||
// 第一次调用走 500 失败路径,第二次调用恢复成功:错误可恢复
|
||||
cozeFailNext.set(true);
|
||||
client.inspect(rows, "", "test-key");
|
||||
client.inspect(rows, "", "test-key");
|
||||
|
||||
awaitMetric("aiimage.external-call.total", "client", "coze", "result", "failure");
|
||||
assertNotNull(findTimer("aiimage.external-call.duration", "client", "coze"),
|
||||
"失败调用同样记录耗时");
|
||||
awaitMetric("aiimage.external-call.total", "client", "coze", "result", "success");
|
||||
assertNotNull(findTimer("aiimage.external-call.duration", "client", "coze"),
|
||||
"恢复后的成功调用也记录耗时");
|
||||
assertEquals(1.0, counterCount("aiimage.external-call.total",
|
||||
"client", "coze", "result", "failure"), "失败率指标精确记录一次失败");
|
||||
assertTrue(counterCount("aiimage.external-call.retry.total", "client", "coze") >= 1.0,
|
||||
"客户端重试循环记录重试次数指标");
|
||||
}
|
||||
|
||||
private SimilarAsinProperties cozeProps() {
|
||||
SimilarAsinProperties props = new SimilarAsinProperties();
|
||||
props.setCozeBaseUrl("http://127.0.0.1:" + port);
|
||||
props.setCozeWorkflowPath("/v1/workflow/run");
|
||||
props.setCozeWorkflowHistoryPath("/v1/workflows/{workflow_id}/run_histories/{execute_id}");
|
||||
return props;
|
||||
}
|
||||
|
||||
private BrandCheckProperties brandProps() {
|
||||
BrandCheckProperties props = new BrandCheckProperties();
|
||||
props.setBaseUrl("http://127.0.0.1:" + port);
|
||||
props.setPath("/brand_check");
|
||||
props.setReadTimeoutMillis(5000);
|
||||
return props;
|
||||
}
|
||||
|
||||
private ZiniaoProperties ziniaoProps() {
|
||||
ZiniaoProperties props = new ZiniaoProperties();
|
||||
props.setBaseUrl("http://127.0.0.1:" + port);
|
||||
props.setReadTimeoutSeconds(5);
|
||||
return props;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -22,7 +22,7 @@ class AppearancePatentCozeClientTest {
|
||||
new AppearancePatentProperties(),
|
||||
new ObjectMapper(),
|
||||
null,
|
||||
new BrandCheckClient(new BrandCheckProperties())
|
||||
new BrandCheckClient(new BrandCheckProperties(), null)
|
||||
);
|
||||
|
||||
@Test
|
||||
|
||||
+2
-2
@@ -11,7 +11,7 @@ class BrandCheckClientTest {
|
||||
|
||||
@Test
|
||||
void splitTitleTextSupportsCommonSeparatorsAndQuotes() {
|
||||
BrandCheckClient client = new BrandCheckClient(new BrandCheckProperties());
|
||||
BrandCheckClient client = new BrandCheckClient(new BrandCheckProperties(), null);
|
||||
|
||||
List<String> brands = client.splitTitleText("'阿凡达,任天堂'; Disney、LEGO\nSony");
|
||||
|
||||
@@ -20,7 +20,7 @@ class BrandCheckClientTest {
|
||||
|
||||
@Test
|
||||
void splitTitleTextDeduplicatesBlankValues() {
|
||||
BrandCheckClient client = new BrandCheckClient(new BrandCheckProperties());
|
||||
BrandCheckClient client = new BrandCheckClient(new BrandCheckProperties(), null);
|
||||
|
||||
List<String> brands = client.splitTitleText(" 任天堂, ,任天堂,Sony ");
|
||||
|
||||
|
||||
+1
-1
@@ -127,7 +127,7 @@ class SimilarAsinCozeClientLoggingTest {
|
||||
row.setTitle("T".repeat(5000));
|
||||
row.setSku("SKU-SECRET");
|
||||
rows.add(row);
|
||||
SimilarAsinCozeClient client = new SimilarAsinCozeClient(new SimilarAsinProperties(), objectMapper, null);
|
||||
SimilarAsinCozeClient client = new SimilarAsinCozeClient(new SimilarAsinProperties(), objectMapper, null, null);
|
||||
Method maskMethod = SimilarAsinCozeClient.class.getDeclaredMethod(
|
||||
"maskCozeRequestBody", Map.class);
|
||||
maskMethod.setAccessible(true);
|
||||
|
||||
+7
-7
@@ -38,7 +38,7 @@ class SimilarAsinCozeClientTest {
|
||||
]
|
||||
""", new TypeReference<>() {
|
||||
});
|
||||
SimilarAsinCozeClient client = new SimilarAsinCozeClient(new SimilarAsinProperties(), objectMapper, null);
|
||||
SimilarAsinCozeClient client = new SimilarAsinCozeClient(new SimilarAsinProperties(), objectMapper, null, null);
|
||||
|
||||
Method method = SimilarAsinCozeClient.class.getDeclaredMethod(
|
||||
"buildParameters", List.class, String.class, String.class, boolean.class);
|
||||
@@ -71,7 +71,7 @@ class SimilarAsinCozeClientTest {
|
||||
row.setPrice("8.50");
|
||||
row.setTitle("Legacy title");
|
||||
row.setSku("SKU-LEGACY");
|
||||
SimilarAsinCozeClient client = new SimilarAsinCozeClient(new SimilarAsinProperties(), objectMapper, null);
|
||||
SimilarAsinCozeClient client = new SimilarAsinCozeClient(new SimilarAsinProperties(), objectMapper, null, null);
|
||||
|
||||
Method method = SimilarAsinCozeClient.class.getDeclaredMethod(
|
||||
"buildParameters", List.class, String.class, String.class, boolean.class);
|
||||
@@ -102,7 +102,7 @@ class SimilarAsinCozeClientTest {
|
||||
]
|
||||
""", new TypeReference<>() {
|
||||
});
|
||||
SimilarAsinCozeClient client = new SimilarAsinCozeClient(new SimilarAsinProperties(), objectMapper, null);
|
||||
SimilarAsinCozeClient client = new SimilarAsinCozeClient(new SimilarAsinProperties(), objectMapper, null, null);
|
||||
|
||||
Method method = SimilarAsinCozeClient.class.getDeclaredMethod(
|
||||
"buildParameters", List.class, String.class, String.class, boolean.class);
|
||||
@@ -128,7 +128,7 @@ class SimilarAsinCozeClientTest {
|
||||
row.setUrl("https://m.media-amazon.com/images/I/main.jpg");
|
||||
row.setUrls(List.of("https://cbu01.alicdn.com/img/ibank/fallback.jpg"));
|
||||
row.setPrice("");
|
||||
SimilarAsinCozeClient client = new SimilarAsinCozeClient(new SimilarAsinProperties(), objectMapper, null);
|
||||
SimilarAsinCozeClient client = new SimilarAsinCozeClient(new SimilarAsinProperties(), objectMapper, null, null);
|
||||
|
||||
Method method = SimilarAsinCozeClient.class.getDeclaredMethod(
|
||||
"buildParameters", List.class, String.class, String.class, boolean.class);
|
||||
@@ -162,7 +162,7 @@ class SimilarAsinCozeClientTest {
|
||||
]
|
||||
""", new TypeReference<>() {
|
||||
});
|
||||
SimilarAsinCozeClient client = new SimilarAsinCozeClient(new SimilarAsinProperties(), objectMapper, null);
|
||||
SimilarAsinCozeClient client = new SimilarAsinCozeClient(new SimilarAsinProperties(), objectMapper, null, null);
|
||||
|
||||
Method method = SimilarAsinCozeClient.class.getDeclaredMethod(
|
||||
"buildParameters", List.class, String.class, String.class, boolean.class);
|
||||
@@ -188,7 +188,7 @@ class SimilarAsinCozeClientTest {
|
||||
SimilarAsinResultRowDto row = new SimilarAsinResultRowDto();
|
||||
row.setAsin("B0CATEGORY1");
|
||||
row.setTitle("Category test");
|
||||
SimilarAsinCozeClient client = new SimilarAsinCozeClient(new SimilarAsinProperties(), objectMapper, null);
|
||||
SimilarAsinCozeClient client = new SimilarAsinCozeClient(new SimilarAsinProperties(), objectMapper, null, null);
|
||||
|
||||
Method method = SimilarAsinCozeClient.class.getDeclaredMethod(
|
||||
"buildParameters", List.class, String.class, String.class, boolean.class, boolean.class);
|
||||
@@ -201,7 +201,7 @@ class SimilarAsinCozeClientTest {
|
||||
|
||||
@Test
|
||||
void imageOnlyWorkflowOutputIsExtractedAndMergedByAsin() throws Exception {
|
||||
SimilarAsinCozeClient client = new SimilarAsinCozeClient(new SimilarAsinProperties(), objectMapper, null);
|
||||
SimilarAsinCozeClient client = new SimilarAsinCozeClient(new SimilarAsinProperties(), objectMapper, null, null);
|
||||
String imageData = """
|
||||
{"data":[{
|
||||
"asin":"B0BQNHDP2F",
|
||||
|
||||
Reference in New Issue
Block a user