From f2736729faeb9cf2cb7bd56438971a28ed17fa44 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 07:43:27 +0800 Subject: [PATCH] =?UTF-8?q?task-166:=20HTTP=20=E5=AE=A2=E6=88=B7=E7=AB=AF?= =?UTF-8?q?=E5=A5=91=E7=BA=A6=E8=A1=A8=E7=9B=98=E7=82=B9=EF=BC=8845=20?= =?UTF-8?q?=E9=A1=B9=E8=B6=85=E6=97=B6/=E9=87=8D=E8=AF=95=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E3=80=81198=20=E4=B8=AA=E8=B0=83=E7=94=A8=E7=82=B9?= =?UTF-8?q?=E6=89=AB=E6=8F=8F=E5=99=A8=20+=208=20=E6=9D=A1=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=20+=20=E6=8A=A5=E5=91=8A=E5=BF=AB=E7=85=A7=EF=BC=8C?= =?UTF-8?q?=E4=B8=8D=E6=94=B9=E7=94=9F=E4=BA=A7=E4=BB=A3=E7=A0=81=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend-java/scripts/hc_report.json | 1266 +++++++++++++++++ backend-java/scripts/http_client_audit.py | 117 ++ .../scripts/test_http_client_audit.py | 93 ++ 3 files changed, 1476 insertions(+) create mode 100644 backend-java/scripts/hc_report.json create mode 100644 backend-java/scripts/http_client_audit.py create mode 100644 backend-java/scripts/test_http_client_audit.py diff --git a/backend-java/scripts/hc_report.json b/backend-java/scripts/hc_report.json new file mode 100644 index 00000000..06ac7a3c --- /dev/null +++ b/backend-java/scripts/hc_report.json @@ -0,0 +1,1266 @@ +{ + "timeout_configs": [ + { + "config_class": "AppearancePatentProperties", + "field": "llmConnectTimeoutMillis", + "default": "10000", + "source": "src/main/java/com/nanri/aiimage/config/AppearancePatentProperties.java" + }, + { + "config_class": "AppearancePatentProperties", + "field": "llmReadTimeoutMillis", + "default": "180000", + "source": "src/main/java/com/nanri/aiimage/config/AppearancePatentProperties.java" + }, + { + "config_class": "AppearancePatentProperties", + "field": "llmRetryTimes", + "default": "3", + "source": "src/main/java/com/nanri/aiimage/config/AppearancePatentProperties.java" + }, + { + "config_class": "AppearancePatentProperties", + "field": "staleTimeoutMinutes", + "default": "30", + "source": "src/main/java/com/nanri/aiimage/config/AppearancePatentProperties.java" + }, + { + "config_class": "BrandCheckProperties", + "field": "connectTimeoutMillis", + "default": "10000", + "source": "src/main/java/com/nanri/aiimage/config/BrandCheckProperties.java" + }, + { + "config_class": "BrandCheckProperties", + "field": "readTimeoutMillis", + "default": "60000", + "source": "src/main/java/com/nanri/aiimage/config/BrandCheckProperties.java" + }, + { + "config_class": "BrandProgressProperties", + "field": "heartbeatTimeoutMinutes", + "default": "15", + "source": "src/main/java/com/nanri/aiimage/config/BrandProgressProperties.java" + }, + { + "config_class": "DeleteBrandProgressProperties", + "field": "heartbeatTimeoutMinutes", + "default": "15", + "source": "src/main/java/com/nanri/aiimage/config/DeleteBrandProgressProperties.java" + }, + { + "config_class": "DeleteBrandProgressProperties", + "field": "productRiskStaleTimeoutMinutes", + "default": "30", + "source": "src/main/java/com/nanri/aiimage/config/DeleteBrandProgressProperties.java" + }, + { + "config_class": "DeleteBrandProgressProperties", + "field": "priceTrackStaleTimeoutMinutes", + "default": "30", + "source": "src/main/java/com/nanri/aiimage/config/DeleteBrandProgressProperties.java" + }, + { + "config_class": "DeleteBrandProgressProperties", + "field": "shopMatchStaleTimeoutMinutes", + "default": "30", + "source": "src/main/java/com/nanri/aiimage/config/DeleteBrandProgressProperties.java" + }, + { + "config_class": "DeleteBrandProgressProperties", + "field": "patrolDeleteStaleTimeoutMinutes", + "default": "30", + "source": "src/main/java/com/nanri/aiimage/config/DeleteBrandProgressProperties.java" + }, + { + "config_class": "DeleteBrandProgressProperties", + "field": "queryAsinStaleTimeoutMinutes", + "default": "30", + "source": "src/main/java/com/nanri/aiimage/config/DeleteBrandProgressProperties.java" + }, + { + "config_class": "DeleteBrandProgressProperties", + "field": "withdrawStaleTimeoutMinutes", + "default": "30", + "source": "src/main/java/com/nanri/aiimage/config/DeleteBrandProgressProperties.java" + }, + { + "config_class": "ImageVideoProperties", + "field": "cozeConnectTimeoutMillis", + "default": "10000", + "source": "src/main/java/com/nanri/aiimage/config/ImageVideoProperties.java" + }, + { + "config_class": "ImageVideoProperties", + "field": "cozeReadTimeoutMillis", + "default": "3600000", + "source": "src/main/java/com/nanri/aiimage/config/ImageVideoProperties.java" + }, + { + "config_class": "ImageVideoProperties", + "field": "archiveConnectTimeoutMillis", + "default": "10000", + "source": "src/main/java/com/nanri/aiimage/config/ImageVideoProperties.java" + }, + { + "config_class": "ImageVideoProperties", + "field": "archiveReadTimeoutMillis", + "default": "600000", + "source": "src/main/java/com/nanri/aiimage/config/ImageVideoProperties.java" + }, + { + "config_class": "InstanceRoutingProperties", + "field": "connectTimeoutMillis", + "default": "3000", + "source": "src/main/java/com/nanri/aiimage/config/InstanceRoutingProperties.java" + }, + { + "config_class": "InstanceRoutingProperties", + "field": "readTimeoutMillis", + "default": "300000", + "source": "src/main/java/com/nanri/aiimage/config/InstanceRoutingProperties.java" + }, + { + "config_class": "SimilarAsinProperties", + "field": "staleTimeoutMinutes", + "default": "30", + "source": "src/main/java/com/nanri/aiimage/config/SimilarAsinProperties.java" + }, + { + "config_class": "SimilarAsinProperties", + "field": "imageDownloadTimeoutSeconds", + "default": "5", + "source": "src/main/java/com/nanri/aiimage/config/SimilarAsinProperties.java" + }, + { + "config_class": "SimilarAsinProperties", + "field": "imagePrefetchTimeoutSeconds", + "default": "1800", + "source": "src/main/java/com/nanri/aiimage/config/SimilarAsinProperties.java" + }, + { + "config_class": "SimilarAsinProperties", + "field": "resultFileTimeoutMinutes", + "default": "90", + "source": "src/main/java/com/nanri/aiimage/config/SimilarAsinProperties.java" + }, + { + "config_class": "SimilarAsinProperties", + "field": "llmConnectTimeoutMillis", + "default": "10000", + "source": "src/main/java/com/nanri/aiimage/config/SimilarAsinProperties.java" + }, + { + "config_class": "SimilarAsinProperties", + "field": "llmReadTimeoutMillis", + "default": "180000", + "source": "src/main/java/com/nanri/aiimage/config/SimilarAsinProperties.java" + }, + { + "config_class": "SimilarAsinProperties", + "field": "llmRetryTimes", + "default": "3", + "source": "src/main/java/com/nanri/aiimage/config/SimilarAsinProperties.java" + }, + { + "config_class": "SimilarAsinProperties", + "field": "llmImageDownloadTimeoutSeconds", + "default": "10", + "source": "src/main/java/com/nanri/aiimage/config/SimilarAsinProperties.java" + }, + { + "config_class": "TransientStorageProperties", + "field": "connectTimeoutSeconds", + "default": "10", + "source": "src/main/java/com/nanri/aiimage/config/TransientStorageProperties.java" + }, + { + "config_class": "TransientStorageProperties", + "field": "readTimeoutSeconds", + "default": "60", + "source": "src/main/java/com/nanri/aiimage/config/TransientStorageProperties.java" + }, + { + "config_class": "TransientStorageProperties", + "field": "writeTimeoutSeconds", + "default": "60", + "source": "src/main/java/com/nanri/aiimage/config/TransientStorageProperties.java" + }, + { + "config_class": "TransientStorageProperties", + "field": "callTimeoutSeconds", + "default": "90", + "source": "src/main/java/com/nanri/aiimage/config/TransientStorageProperties.java" + }, + { + "config_class": "TransientStorageProperties", + "field": "operationTimeoutSeconds", + "default": "120", + "source": "src/main/java/com/nanri/aiimage/config/TransientStorageProperties.java" + }, + { + "config_class": "TransientStorageProperties", + "field": "uploadMaxRetries", + "default": "3", + "source": "src/main/java/com/nanri/aiimage/config/TransientStorageProperties.java" + }, + { + "config_class": "TransientStorageProperties", + "field": "readMaxRetries", + "default": "3", + "source": "src/main/java/com/nanri/aiimage/config/TransientStorageProperties.java" + }, + { + "config_class": "TransientStorageProperties", + "field": "deleteMaxRetries", + "default": "3", + "source": "src/main/java/com/nanri/aiimage/config/TransientStorageProperties.java" + }, + { + "config_class": "TransientStorageProperties", + "field": "acquirePermitTimeoutMillis", + "default": "2000", + "source": "src/main/java/com/nanri/aiimage/config/TransientStorageProperties.java" + }, + { + "config_class": "TransientStorageProperties", + "field": "baseRetryDelayMillis", + "default": "500", + "source": "src/main/java/com/nanri/aiimage/config/TransientStorageProperties.java" + }, + { + "config_class": "TransientStorageProperties", + "field": "maxRetryDelayMillis", + "default": "5000", + "source": "src/main/java/com/nanri/aiimage/config/TransientStorageProperties.java" + }, + { + "config_class": "TransientStorageProperties", + "field": "retryJitterMillis", + "default": "250", + "source": "src/main/java/com/nanri/aiimage/config/TransientStorageProperties.java" + }, + { + "config_class": "TransientStorageProperties", + "field": "deleteRetryEnabled", + "default": "true", + "source": "src/main/java/com/nanri/aiimage/config/TransientStorageProperties.java" + }, + { + "config_class": "TransientStorageProperties", + "field": "deleteRetryCron", + "default": "\"0 */5 * * * *\"", + "source": "src/main/java/com/nanri/aiimage/config/TransientStorageProperties.java" + }, + { + "config_class": "TransientStorageProperties", + "field": "deleteRetryQueueCapacity", + "default": "10000", + "source": "src/main/java/com/nanri/aiimage/config/TransientStorageProperties.java" + }, + { + "config_class": "TransientStorageProperties", + "field": "deleteRetryBatchSize", + "default": "200", + "source": "src/main/java/com/nanri/aiimage/config/TransientStorageProperties.java" + }, + { + "config_class": "TransientStorageProperties", + "field": "deleteRetryBatchTimeoutSeconds", + "default": "60", + "source": "src/main/java/com/nanri/aiimage/config/TransientStorageProperties.java" + } + ], + "client_sites": [ + { + "file": "src/main/java/com/nanri/aiimage/common/service/TaskOwnerForwardService.java", + "line": 86, + "snippet": "requestFactory.setConnectTimeout(properties.getConnectTimeoutMillis());" + }, + { + "file": "src/main/java/com/nanri/aiimage/common/service/TaskOwnerForwardService.java", + "line": 87, + "snippet": "requestFactory.setReadTimeout(properties.getReadTimeoutMillis());" + }, + { + "file": "src/main/java/com/nanri/aiimage/config/AppearancePatentProperties.java", + "line": 23, + "snippet": "private int llmConnectTimeoutMillis = 10000;" + }, + { + "file": "src/main/java/com/nanri/aiimage/config/AppearancePatentProperties.java", + "line": 24, + "snippet": "private int llmReadTimeoutMillis = 180000;" + }, + { + "file": "src/main/java/com/nanri/aiimage/config/AppearancePatentProperties.java", + "line": 33, + "snippet": "private int llmRetryTimes = 3;" + }, + { + "file": "src/main/java/com/nanri/aiimage/config/BrandCheckProperties.java", + "line": 13, + "snippet": "private int connectTimeoutMillis = 10000;" + }, + { + "file": "src/main/java/com/nanri/aiimage/config/BrandCheckProperties.java", + "line": 14, + "snippet": "private int readTimeoutMillis = 60000;" + }, + { + "file": "src/main/java/com/nanri/aiimage/config/HttpClientPool.java", + "line": 28, + "snippet": "sharedHttpClient = HttpClient.newBuilder()" + }, + { + "file": "src/main/java/com/nanri/aiimage/config/HttpClientPool.java", + "line": 29, + "snippet": ".connectTimeout(Duration.ofSeconds(10))" + }, + { + "file": "src/main/java/com/nanri/aiimage/config/HttpClientPool.java", + "line": 37, + "snippet": "/** 按 readTimeout(毫秒)创建共享连接池工厂;非法值钳制到最小正数。 */" + }, + { + "file": "src/main/java/com/nanri/aiimage/config/HttpClientPool.java", + "line": 38, + "snippet": "public static ClientHttpRequestFactory requestFactory(int readTimeoutMillis) {" + }, + { + "file": "src/main/java/com/nanri/aiimage/config/HttpClientPool.java", + "line": 39, + "snippet": "int safeReadTimeout = Math.max(1, readTimeoutMillis);" + }, + { + "file": "src/main/java/com/nanri/aiimage/config/HttpClientPool.java", + "line": 42, + "snippet": "factory.setReadTimeout(Duration.ofMillis(safeReadTimeout));" + }, + { + "file": "src/main/java/com/nanri/aiimage/config/ImageVideoProperties.java", + "line": 19, + "snippet": "private int cozeConnectTimeoutMillis = 10000;" + }, + { + "file": "src/main/java/com/nanri/aiimage/config/ImageVideoProperties.java", + "line": 20, + "snippet": "private int cozeReadTimeoutMillis = 3600000;" + }, + { + "file": "src/main/java/com/nanri/aiimage/config/ImageVideoProperties.java", + "line": 23, + "snippet": "private int archiveConnectTimeoutMillis = 10000;" + }, + { + "file": "src/main/java/com/nanri/aiimage/config/ImageVideoProperties.java", + "line": 24, + "snippet": "private int archiveReadTimeoutMillis = 600000;" + }, + { + "file": "src/main/java/com/nanri/aiimage/config/InstanceRoutingProperties.java", + "line": 14, + "snippet": "private int connectTimeoutMillis = 3000;" + }, + { + "file": "src/main/java/com/nanri/aiimage/config/InstanceRoutingProperties.java", + "line": 15, + "snippet": "private int readTimeoutMillis = 300000;" + }, + { + "file": "src/main/java/com/nanri/aiimage/config/SimilarAsinProperties.java", + "line": 162, + "snippet": "private int llmConnectTimeoutMillis = 10000;" + }, + { + "file": "src/main/java/com/nanri/aiimage/config/SimilarAsinProperties.java", + "line": 163, + "snippet": "private int llmReadTimeoutMillis = 180000;" + }, + { + "file": "src/main/java/com/nanri/aiimage/config/SimilarAsinProperties.java", + "line": 164, + "snippet": "private int llmRetryTimes = 3;" + }, + { + "file": "src/main/java/com/nanri/aiimage/config/TransientStorageProperties.java", + "line": 15, + "snippet": "private int connectTimeoutSeconds = 10;" + }, + { + "file": "src/main/java/com/nanri/aiimage/config/TransientStorageProperties.java", + "line": 16, + "snippet": "private int readTimeoutSeconds = 60;" + }, + { + "file": "src/main/java/com/nanri/aiimage/config/TransientStorageProperties.java", + "line": 17, + "snippet": "private int writeTimeoutSeconds = 60;" + }, + { + "file": "src/main/java/com/nanri/aiimage/config/TransientStorageProperties.java", + "line": 18, + "snippet": "private int callTimeoutSeconds = 90;" + }, + { + "file": "src/main/java/com/nanri/aiimage/config/TransientStorageProperties.java", + "line": 19, + "snippet": "private int operationTimeoutSeconds = 120;" + }, + { + "file": "src/main/java/com/nanri/aiimage/config/TransientStorageProperties.java", + "line": 32, + "snippet": "private long baseRetryDelayMillis = 500;" + }, + { + "file": "src/main/java/com/nanri/aiimage/config/TransientStorageProperties.java", + "line": 33, + "snippet": "private long maxRetryDelayMillis = 5000;" + }, + { + "file": "src/main/java/com/nanri/aiimage/config/TransientStorageProperties.java", + "line": 51, + "snippet": "private boolean deleteRetryEnabled = true;" + }, + { + "file": "src/main/java/com/nanri/aiimage/config/TransientStorageProperties.java", + "line": 52, + "snippet": "private String deleteRetryCron = \"0 */5 * * * *\";" + }, + { + "file": "src/main/java/com/nanri/aiimage/config/TransientStorageProperties.java", + "line": 53, + "snippet": "private int deleteRetryQueueCapacity = 10000;" + }, + { + "file": "src/main/java/com/nanri/aiimage/config/TransientStorageProperties.java", + "line": 54, + "snippet": "private int deleteRetryBatchSize = 200;" + }, + { + "file": "src/main/java/com/nanri/aiimage/config/TransientStorageProperties.java", + "line": 55, + "snippet": "private int deleteRetryBatchTimeoutSeconds = 60;" + }, + { + "file": "src/main/java/com/nanri/aiimage/config/ZiniaoProperties.java", + "line": 29, + "snippet": "private int connectTimeoutSeconds;" + }, + { + "file": "src/main/java/com/nanri/aiimage/config/ZiniaoProperties.java", + "line": 30, + "snippet": "private int readTimeoutSeconds;" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/appearancepatent/client/AppearancePatentLlmClient.java", + "line": 264, + "snippet": "int attempts = Math.max(1, properties.getLlmRetryTimes());" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/appearancepatent/client/AppearancePatentLlmClient.java", + "line": 276, + "snippet": "sleepBeforeRetry(attempt);" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/appearancepatent/client/AppearancePatentLlmClient.java", + "line": 728, + "snippet": ".requestFactory(HttpClientPool.requestFactory(properties.getLlmReadTimeoutMillis()));" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/appearancepatent/client/AppearancePatentLlmClient.java", + "line": 816, + "snippet": "private void sleepBeforeRetry(int attemptIndex) {" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/appearancepatent/service/AppearancePatentTaskService.java", + "line": 1858, + "snippet": "loadPersistedResultRowsForRowsWithRetry(task.getId(), item.rows());" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/appearancepatent/service/AppearancePatentTaskService.java", + "line": 2103, + "snippet": "private Map loadPersistedResultRowsForRowsWithRetry(" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/appearancepatent/service/AppearancePatentTaskService.java", + "line": 2112, + "snippet": "sleepBeforeResultRowsRetry(attempt);" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/appearancepatent/service/AppearancePatentTaskService.java", + "line": 2218, + "snippet": "private void sleepBeforeResultRowsRetry(int attempt) {" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/brand/client/BrandCheckClient.java", + "line": 171, + "snippet": ".requestFactory(HttpClientPool.requestFactory(properties.getReadTimeoutMillis()));" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/deletebrand/service/DeleteBrandRunService.java", + "line": 1641, + "snippet": "&& assembleJob.getRetryCount() != null" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/deletebrand/service/DeleteBrandRunService.java", + "line": 1642, + "snippet": "&& assembleJob.getRetryCount() >= TaskFileJobService.MAX_RETRY_COUNT;" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsDeleteRetryService.java", + "line": 22, + "snippet": "public class RustfsDeleteRetryService {" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsDeleteRetryService.java", + "line": 26, + "snippet": "private final ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue<>();" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsDeleteRetryService.java", + "line": 27, + "snippet": "private final ConcurrentHashMap pending = new ConcurrentHashMap<>();" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsDeleteRetryService.java", + "line": 31, + "snippet": "if (!properties.isDeleteRetryEnabled() || objectKey == null || objectKey.isBlank()) {" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsDeleteRetryService.java", + "line": 35, + "snippet": "RetryItem item = findOrAdmit(objectKey, cause);" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsDeleteRetryService.java", + "line": 54, + "snippet": "if (!properties.isDeleteRetryEnabled()) {" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsDeleteRetryService.java", + "line": 57, + "snippet": "int batchSize = Math.max(1, properties.getDeleteRetryBatchSize());" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsDeleteRetryService.java", + "line": 59, + "snippet": "Math.max(1L, properties.getDeleteRetryBatchTimeoutSeconds()));" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsDeleteRetryService.java", + "line": 65, + "snippet": "List retryNextInvocation = new ArrayList<>();" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsDeleteRetryService.java", + "line": 71, + "snippet": "RetryAttempt attempt = nextAttempt();" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsDeleteRetryService.java", + "line": 75, + "snippet": "RetryItem item = attempt.item;" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsDeleteRetryService.java", + "line": 88, + "snippet": "rustfsObjectStorageService.deleteObjectFromRetry(objectKey, remainingNanos);" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsDeleteRetryService.java", + "line": 109, + "snippet": "private RetryItem findOrAdmit(String objectKey, Throwable cause) {" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsDeleteRetryService.java", + "line": 110, + "snippet": "RetryItem item = pending.get(objectKey);" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsDeleteRetryService.java", + "line": 114, + "snippet": "int capacity = Math.max(1, properties.getDeleteRetryQueueCapacity());" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsDeleteRetryService.java", + "line": 125, + "snippet": "item = new RetryItem(objectKey);" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsDeleteRetryService.java", + "line": 131, + "snippet": "private RetryAttempt nextAttempt() {" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsDeleteRetryService.java", + "line": 133, + "snippet": "RetryItem item = queue.poll();" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsDeleteRetryService.java", + "line": 142, + "snippet": "return new RetryAttempt(item, item.generation);" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsDeleteRetryService.java", + "line": 147, + "snippet": "private void removeIfUnchanged(RetryAttempt attempt) {" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsDeleteRetryService.java", + "line": 148, + "snippet": "RetryItem item = attempt.item;" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsDeleteRetryService.java", + "line": 159, + "snippet": "private int recordFailureIfUnchanged(RetryAttempt attempt, Exception ex) {" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsDeleteRetryService.java", + "line": 160, + "snippet": "RetryItem item = attempt.item;" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsDeleteRetryService.java", + "line": 172, + "snippet": "private void requeue(RetryItem item) {" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsDeleteRetryService.java", + "line": 180, + "snippet": "private void enqueueItemLocked(RetryItem item) {" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsDeleteRetryService.java", + "line": 187, + "snippet": "private record RetryAttempt(RetryItem item, long generation) {" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsDeleteRetryService.java", + "line": 190, + "snippet": "private static class RetryItem {" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsDeleteRetryService.java", + "line": 198, + "snippet": "private RetryItem(String objectKey) {" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsObjectStorageService.java", + "line": 42, + "snippet": "private final ObjectProvider deleteRetryServiceProvider;" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsObjectStorageService.java", + "line": 61, + "snippet": "ObjectProvider deleteRetryServiceProvider) {" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsObjectStorageService.java", + "line": 62, + "snippet": "this(properties, meterRegistryProvider, deleteRetryServiceProvider, null);" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsObjectStorageService.java", + "line": 67, + "snippet": "ObjectProvider deleteRetryServiceProvider," + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsObjectStorageService.java", + "line": 71, + "snippet": "this.deleteRetryServiceProvider = deleteRetryServiceProvider;" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsObjectStorageService.java", + "line": 111, + "snippet": "String uploadedObjectKey = executeWithRetry(OP_UPLOAD, objectKey, bytes.length," + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsObjectStorageService.java", + "line": 132, + "snippet": "enqueueDeleteRetry(objectKey, ex);" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsObjectStorageService.java", + "line": 149, + "snippet": "return executeWithRetry(OP_READ, objectKey, 0, Math.max(1, properties.getReadMaxRetries())," + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsObjectStorageService.java", + "line": 164, + "snippet": "void deleteObjectFromRetry(String objectKey) {" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsObjectStorageService.java", + "line": 168, + "snippet": "void deleteObjectFromRetry(String objectKey, long timeoutNanos) {" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsObjectStorageService.java", + "line": 172, + "snippet": "private void deleteObject(String objectKey, boolean enqueueRetryOnFailure, long deadlineNanos) {" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsObjectStorageService.java", + "line": 177, + "snippet": "executeWithRetry(OP_DELETE, objectKey, 0, Math.max(1, properties.getDeleteMaxRetries())," + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsObjectStorageService.java", + "line": 186, + "snippet": "if (enqueueRetryOnFailure && properties.isDeleteRetryEnabled()) {" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsObjectStorageService.java", + "line": 187, + "snippet": "enqueueDeleteRetry(objectKey, ex);" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsObjectStorageService.java", + "line": 193, + "snippet": "private T executeWithRetry(String operation," + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsObjectStorageService.java", + "line": 239, + "snippet": "if (last instanceof OperationTimeoutException timeoutException) {" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsObjectStorageService.java", + "line": 312, + "snippet": ".connectTimeout(Math.max(1, properties.getConnectTimeoutSeconds()), TimeUnit.SECONDS)" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsObjectStorageService.java", + "line": 313, + "snippet": ".readTimeout(Math.max(1, properties.getReadTimeoutSeconds()), TimeUnit.SECONDS)" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsObjectStorageService.java", + "line": 314, + "snippet": ".writeTimeout(Math.max(1, properties.getWriteTimeoutSeconds()), TimeUnit.SECONDS)" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsObjectStorageService.java", + "line": 315, + "snippet": ".callTimeout(Math.max(1, properties.getCallTimeoutSeconds()), TimeUnit.SECONDS)" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsObjectStorageService.java", + "line": 332, + "snippet": "long configuredTimeoutNanos = TimeUnit.SECONDS.toNanos(Math.max(1, properties.getCallTimeoutSeconds()));" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsObjectStorageService.java", + "line": 333, + "snippet": "long callTimeoutMillis = TimeUnit.NANOSECONDS.toMillis(" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsObjectStorageService.java", + "line": 335, + "snippet": "if (callTimeoutMillis <= 0L) {" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsObjectStorageService.java", + "line": 336, + "snippet": "throw new OperationTimeoutException(\"rustfs operation timeout before HTTP call\");" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsObjectStorageService.java", + "line": 339, + "snippet": ".callTimeout(callTimeoutMillis, TimeUnit.MILLISECONDS)" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsObjectStorageService.java", + "line": 358, + "snippet": "if (ex instanceof OperationTimeoutException timeoutException) {" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsObjectStorageService.java", + "line": 425, + "snippet": "long baseDelay = Math.max(0L, properties.getBaseRetryDelayMillis());" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsObjectStorageService.java", + "line": 426, + "snippet": "long maxDelay = Math.max(baseDelay, properties.getMaxRetryDelayMillis());" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsObjectStorageService.java", + "line": 428, + "snippet": "long jitter = Math.max(0L, properties.getRetryJitterMillis());" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsObjectStorageService.java", + "line": 455, + "snippet": "long timeoutSeconds = properties.getOperationTimeoutSeconds();" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsObjectStorageService.java", + "line": 472, + "snippet": "throw new OperationTimeoutException(" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsObjectStorageService.java", + "line": 485, + "snippet": "private void enqueueDeleteRetry(String objectKey, RuntimeException failure) {" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsObjectStorageService.java", + "line": 486, + "snippet": "if (!properties.isDeleteRetryEnabled() || deleteRetryServiceProvider == null) {" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsObjectStorageService.java", + "line": 489, + "snippet": "RustfsDeleteRetryService retryService = deleteRetryServiceProvider.getIfAvailable();" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsObjectStorageService.java", + "line": 555, + "snippet": "private static class OperationTimeoutException extends IllegalStateException {" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/file/service/object/RustfsObjectStorageService.java", + "line": 556, + "snippet": "private OperationTimeoutException(String message) {" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/imagevideo/service/ImageVideoArchiveService.java", + "line": 225, + "snippet": "connection.setConnectTimeout(Math.max(1000, properties.getArchiveConnectTimeoutMillis()));" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/imagevideo/service/ImageVideoArchiveService.java", + "line": 226, + "snippet": "connection.setReadTimeout(Math.max(1000, properties.getArchiveReadTimeoutMillis()));" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/imagevideo/service/ImageVideoCozeService.java", + "line": 590, + "snippet": ".timeout(Duration.ofMillis(Math.max(1000, properties.getCozeReadTimeoutMillis())))" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/imagevideo/service/ImageVideoCozeService.java", + "line": 620, + "snippet": ".timeout(Duration.ofMillis(Math.max(1000, properties.getCozeReadTimeoutMillis())))" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/imagevideo/service/ImageVideoCozeService.java", + "line": 682, + "snippet": "sharedHttpClient = HttpClient.newBuilder()" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/imagevideo/service/ImageVideoCozeService.java", + "line": 683, + "snippet": ".connectTimeout(Duration.ofMillis(Math.max(1000, properties.getCozeConnectTimeoutMillis())))" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/publish/model/vo/PublishResultVo.java", + "line": 22, + "snippet": "private Integer fileJobRetryCount;" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/publish/service/PublishTaskService.java", + "line": 1010, + "snippet": "response.setFileJobRetryCount(job.getRetryCount());" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/publish/service/PublishTaskService.java", + "line": 1690, + "snippet": "int retryCount = latest == null ? safeInt(job.getRetryCount()) : safeInt(latest.getRetryCount());" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/similarasin/client/SimilarAsinLlmClient.java", + "line": 65, + "snippet": "int attempts = Math.max(1, properties.getLlmRetryTimes());" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/similarasin/client/SimilarAsinLlmClient.java", + "line": 77, + "snippet": "sleepBeforeRetry(attempt);" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/similarasin/client/SimilarAsinLlmClient.java", + "line": 281, + "snippet": ".requestFactory(HttpClientPool.requestFactory(properties.getLlmReadTimeoutMillis()));" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/similarasin/client/SimilarAsinLlmClient.java", + "line": 291, + "snippet": "private void sleepBeforeRetry(int attemptIndex) {" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/similarasin/service/SimilarAsinLlmService.java", + "line": 558, + "snippet": "downloadHttpClient = HttpClient.newBuilder()" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/similarasin/service/SimilarAsinLlmService.java", + "line": 559, + "snippet": ".connectTimeout(Duration.ofSeconds(10))" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/similarasin/service/SimilarAsinTaskService.java", + "line": 470, + "snippet": "boolean categoryRetryRequired = false;" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/similarasin/service/SimilarAsinTaskService.java", + "line": 491, + "snippet": "categoryRetryRequired |= parsed.categoryRetryRequired();" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/similarasin/service/SimilarAsinTaskService.java", + "line": 501, + "snippet": "request.setCategorySwitch(requestedCategorySwitch || categoryRetryRequired);" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/similarasin/service/SimilarAsinTaskService.java", + "line": 502, + "snippet": "if (!requestedCategorySwitch && categoryRetryRequired) {" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/similarasin/service/SimilarAsinTaskService.java", + "line": 2916, + "snippet": "if (taskFileJobService.isRetryExhausted(job.getId())) {" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/similarasin/service/SimilarAsinTaskService.java", + "line": 2942, + "snippet": "if (taskFileJobService.isRetryExhausted(job.getId())) {" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/similarasin/service/SimilarAsinTaskService.java", + "line": 2967, + "snippet": "} else if (taskFileJobService.isRetryExhausted(job.getId())) {" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/similarasin/service/SimilarAsinTaskService.java", + "line": 3500, + "snippet": "context.submitRetryCount()," + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/similarasin/service/SimilarAsinTaskService.java", + "line": 3925, + "snippet": "loadPersistedResultRowsForRowsWithRetry(task.getId(), captured.rows());" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/similarasin/service/SimilarAsinTaskService.java", + "line": 4044, + "snippet": "Map resultMap = loadPersistedResultRowsWithRetry(task.getId(), parsedRows.siz" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/similarasin/service/SimilarAsinTaskService.java", + "line": 4324, + "snippet": "private Map loadPersistedResultRowsForRowsWithRetry(" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/similarasin/service/SimilarAsinTaskService.java", + "line": 4333, + "snippet": "sleepBeforeResultRowsRetry(attempt);" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/similarasin/service/SimilarAsinTaskService.java", + "line": 4507, + "snippet": "private Map loadPersistedResultRowsWithRetry(Long taskId, int expectedParsedR" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/similarasin/service/SimilarAsinTaskService.java", + "line": 4513, + "snippet": "sleepBeforeResultRowsRetry(attempt);" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/similarasin/service/SimilarAsinTaskService.java", + "line": 4526, + "snippet": "private void sleepBeforeResultRowsRetry(int attempt) {" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/similarasin/service/SimilarAsinTaskService.java", + "line": 4810, + "snippet": "boolean categoryRetryRequired = shouldEnableCategorySwitchForRetry(" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/similarasin/service/SimilarAsinTaskService.java", + "line": 4818, + "snippet": "return new ParsedWorkbook(total, dropped, headers, allRows, categoryRetryRequired);" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/similarasin/service/SimilarAsinTaskService.java", + "line": 5597, + "snippet": "static boolean shouldEnableCategorySwitchForRetry(List headers," + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/similarasin/service/SimilarAsinTaskService.java", + "line": 5788, + "snippet": "Integer submitRetryCount," + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/similarasin/service/SimilarAsinTaskService.java", + "line": 5847, + "snippet": "boolean categoryRetryRequired) {" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/similarasin/util/PuzzleImageMerger.java", + "line": 241, + "snippet": "sharedHttpClient = HttpClient.newBuilder()" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/similarasin/util/PuzzleImageMerger.java", + "line": 242, + "snippet": ".connectTimeout(Duration.ofSeconds(10))" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/similarasin/util/SimilarAsinImageEmbedder.java", + "line": 169, + "snippet": ".connectTimeout(Duration.ofSeconds(downloadTimeoutSeconds))" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/similarasin/util/SimilarAsinImageEmbedder.java", + "line": 170, + "snippet": ".readTimeout(Duration.ofSeconds(downloadTimeoutSeconds))" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/similarasin/util/SimilarAsinImageEmbedder.java", + "line": 171, + "snippet": ".writeTimeout(Duration.ofSeconds(downloadTimeoutSeconds))" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/similarasin/util/SimilarAsinImageEmbedder.java", + "line": 172, + "snippet": ".callTimeout(Duration.ofSeconds(downloadTimeoutSeconds * 2L))" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/similarasin/util/SimilarAsinImageEmbedder.java", + "line": 798, + "snippet": "logRetry(attemptUrl, attempt, ex);" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/similarasin/util/SimilarAsinImageEmbedder.java", + "line": 878, + "snippet": "private void logRetry(String url, int attempt, Exception ex) {" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/task/service/ResultFileJobHandler.java", + "line": 11, + "snippet": "* onFailure 在重试耗尽时调用(原 notifyRetryExhausted 分支);" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/task/service/TaskFileJobService.java", + "line": 47, + "snippet": "entity.setRetryCount(0);" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/task/service/TaskFileJobService.java", + "line": 73, + "snippet": "int existingRetry = existing.getRetryCount() == null ? 0 : existing.getRetryCount();" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/task/service/TaskFileJobService.java", + "line": 74, + "snippet": "if (\"FAILED\".equals(existing.getStatus()) && existingRetry >= MAX_RETRY_COUNT) {" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/task/service/TaskFileJobService.java", + "line": 94, + "snippet": ".lt(TaskFileJobEntity::getRetryCount, MAX_RETRY_COUNT)" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/task/service/TaskFileJobService.java", + "line": 120, + "snippet": ".lt(TaskFileJobEntity::getRetryCount, MAX_RETRY_COUNT)" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/task/service/TaskFileJobService.java", + "line": 130, + "snippet": ".lt(TaskFileJobEntity::getRetryCount, MAX_RETRY_COUNT)" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/task/service/TaskFileJobService.java", + "line": 178, + "snippet": ".lt(TaskFileJobEntity::getRetryCount, MAX_RETRY_COUNT)" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/task/service/TaskFileJobService.java", + "line": 190, + "snippet": ".lt(TaskFileJobEntity::getRetryCount, MAX_RETRY_COUNT)" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/task/service/TaskFileJobService.java", + "line": 226, + "snippet": ".set(TaskFileJobEntity::getRetryCount, 0)" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/task/service/TaskFileJobService.java", + "line": 257, + "snippet": ".ne(TaskFileJobEntity::getRetryCount, MAX_RETRY_COUNT)" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/task/service/TaskFileJobService.java", + "line": 264, + "snippet": ".ge(TaskFileJobEntity::getRetryCount, MAX_RETRY_COUNT)" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/task/service/TaskFileJobService.java", + "line": 277, + "snippet": "&& job.getRetryCount() != null && job.getRetryCount() >= MAX_RETRY_COUNT) {" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/task/service/TaskFileJobService.java", + "line": 286, + "snippet": "int retryCount = job.getRetryCount() == null ? 0 : job.getRetryCount();" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/task/service/TaskFileJobService.java", + "line": 287, + "snippet": "int nextRetryCount = Math.min(MAX_RETRY_COUNT - 1, retryCount + 1);" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/task/service/TaskFileJobService.java", + "line": 293, + "snippet": ".set(TaskFileJobEntity::getRetryCount, nextRetryCount)" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/task/service/TaskFileJobService.java", + "line": 300, + "snippet": "int retryCount = job.getRetryCount() == null ? 0 : job.getRetryCount();" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/task/service/TaskFileJobService.java", + "line": 301, + "snippet": "int nextRetryCount = Math.min(MAX_RETRY_COUNT, retryCount + 1);" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/task/service/TaskFileJobService.java", + "line": 308, + "snippet": ".set(TaskFileJobEntity::getRetryCount, nextRetryCount)" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/task/service/TaskFileJobService.java", + "line": 310, + "snippet": "if (nextRetryCount >= MAX_RETRY_COUNT) {" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/task/service/TaskFileJobService.java", + "line": 372, + "snippet": ".and(wrapper -> wrapper.isNull(TaskFileJobEntity::getRetryCount)" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/task/service/TaskFileJobService.java", + "line": 374, + "snippet": ".lt(TaskFileJobEntity::getRetryCount, MAX_RETRY_COUNT))" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/task/service/TaskFileJobService.java", + "line": 401, + "snippet": ".eq(claim.getRetryCount() != null, TaskFileJobEntity::getRetryCount, claim.getRetryCount())" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/task/service/TaskFileJobService.java", + "line": 402, + "snippet": ".isNull(claim.getRetryCount() == null, TaskFileJobEntity::getRetryCount)" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/task/service/TaskFileJobService.java", + "line": 472, + "snippet": ".and(wrapper -> wrapper.isNull(TaskFileJobEntity::getRetryCount)" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/task/service/TaskFileJobService.java", + "line": 474, + "snippet": ".lt(TaskFileJobEntity::getRetryCount, MAX_RETRY_COUNT))" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/task/service/TaskFileJobService.java", + "line": 511, + "snippet": "public boolean isRetryExhausted(Long jobId) {" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/task/service/TaskFileJobService.java", + "line": 515, + "snippet": "&& job.getRetryCount() != null" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/task/service/TaskFileJobService.java", + "line": 516, + "snippet": "&& job.getRetryCount() >= MAX_RETRY_COUNT;" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/task/service/TaskFileJobService.java", + "line": 526, + "snippet": "int currentRetryCount = current.getRetryCount() == null ? 0 : current.getRetryCount();" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/task/service/TaskFileJobService.java", + "line": 527, + "snippet": "int retryCount = Math.min(MAX_RETRY_COUNT, currentRetryCount + 1);" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/task/service/TaskFileJobService.java", + "line": 531, + "snippet": ".eq(current.getRetryCount() != null, TaskFileJobEntity::getRetryCount, current.getRetryCount())" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/task/service/TaskFileJobService.java", + "line": 532, + "snippet": ".isNull(current.getRetryCount() == null, TaskFileJobEntity::getRetryCount)" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/task/service/TaskFileJobService.java", + "line": 534, + "snippet": ".set(TaskFileJobEntity::getRetryCount, retryCount)" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/task/service/TaskFileJobService.java", + "line": 551, + "snippet": ".ge(TaskFileJobEntity::getRetryCount, MAX_RETRY_COUNT)" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/task/service/TaskFileJobService.java", + "line": 570, + "snippet": ".set(TaskFileJobEntity::getRetryCount, MAX_RETRY_COUNT)" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/task/service/TaskFileJobService.java", + "line": 643, + "snippet": ".lt(TaskFileJobEntity::getRetryCount, MAX_RETRY_COUNT)));" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/task/service/TaskResultFileJobWorker.java", + "line": 106, + "snippet": "finalizeRetryExhausted(job, message);" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/task/service/TaskResultFileJobWorker.java", + "line": 259, + "snippet": "if (taskFileJobService.isRetryExhausted(job.getId())) {" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/task/service/TaskResultFileJobWorker.java", + "line": 260, + "snippet": "finalizeRetryExhausted(job, message);" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/task/service/TaskResultFileJobWorker.java", + "line": 265, + "snippet": "private void finalizeRetryExhausted(TaskFileJobEntity job, String message) {" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/task/service/TaskResultFileJobWorker.java", + "line": 267, + "snippet": "notifyRetryExhausted(job, message);" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/task/service/TaskResultFileJobWorker.java", + "line": 279, + "snippet": "private void notifyRetryExhausted(TaskFileJobEntity job, String message) {" + }, + { + "file": "src/main/java/com/nanri/aiimage/modules/ziniao/client/ZiniaoClientImpl.java", + "line": 275, + "snippet": ".requestFactory(HttpClientPool.requestFactory(ziniaoProperties.getReadTimeoutSeconds() * 1000));" + } + ] +} \ No newline at end of file diff --git a/backend-java/scripts/http_client_audit.py b/backend-java/scripts/http_client_audit.py new file mode 100644 index 00000000..bb067ab8 --- /dev/null +++ b/backend-java/scripts/http_client_audit.py @@ -0,0 +1,117 @@ +"""task-166 HTTP 客户端契约盘点工具。 + +盘点 backend-java 各 HTTP 客户端(OSS/RustFS/图片下载/紫鸟/LLM)的现状: +超时(connect/read/write/call)、重试、幂等性,产出机器可读契约表供审计文档使用。 +本工具只读,不改代码。 +""" + +from __future__ import annotations + +import argparse +import json +import re +import sys +from dataclasses import dataclass, field +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parent.parent +CONFIG_ROOT = REPO_ROOT / "src/main/java/com/nanri/aiimage/config" +MAIN_ROOT = REPO_ROOT / "src/main/java" + +# 超时/重试配置字段(config 包) +TIMEOUT_FIELD_RE = re.compile( + r"private\s+\w+\s+(\w*(?:Timeout|Retries|Retry|MaxRetries)\w*)\s*=\s*([^;]+);", + re.IGNORECASE, +) +# 代码中 HttpClient/Client 构建的超时/重试配置 +CLIENT_CONF_RE = re.compile( + r"(connectTimeout|readTimeout|writeTimeout|callTimeout|operationTimeout|" + r"maxRetries|retry|setConnectTimeout|setReadTimeout|HttpClient\.newBuilder)", + re.IGNORECASE, +) +# 客户端类识别 +CLIENT_CLASS_RE = re.compile( + r"(OssStorageService|RustfsObjectStorageService|ZiniaoShopSwitchService|" + r"SimilarAsinLlmService|AppearancePatentLlmClient|ImageDownloader|ImageDownload|" + r"imageDownload|HttpClient|WebClient|RestTemplate)", +) + + +def extract_timeout_configs() -> list[dict]: + """从 config 包提取超时/重试配置字段。""" + entries: list[dict] = [] + for java_file in sorted(CONFIG_ROOT.rglob("*.java")): + text = java_file.read_text(encoding="utf-8") + for match in TIMEOUT_FIELD_RE.finditer(text): + entries.append( + { + "config_class": java_file.stem, + "field": match.group(1), + "default": match.group(2).strip(), + "source": str(java_file.relative_to(REPO_ROOT)).replace("\\", "/"), + } + ) + return entries + + +def extract_client_sites() -> list[dict]: + """扫描 main 源码中客户端构建/配置调用点。""" + sites: list[dict] = [] + for java_file in sorted(MAIN_ROOT.rglob("*.java")): + text = java_file.read_text(encoding="utf-8") + for line_no, line in enumerate(text.splitlines(), start=1): + if not CLIENT_CONF_RE.search(line) or "import " in line: + continue + if CLIENT_CLASS_RE.search(line) or "Timeout" in line or "Retry" in line: + sites.append( + { + "file": str(java_file.relative_to(REPO_ROOT)).replace( + "\\", "/" + ), + "line": line_no, + "snippet": line.strip()[:110], + } + ) + return sites + + +@dataclass +class Report: + timeout_configs: list[dict] = field(default_factory=list) + client_sites: list[dict] = field(default_factory=list) + + def to_dict(self) -> dict: + return { + "timeout_configs": self.timeout_configs, + "client_sites": self.client_sites, + } + + +def scan() -> Report: + return Report( + timeout_configs=extract_timeout_configs(), client_sites=extract_client_sites() + ) + + +def main() -> None: + parser = argparse.ArgumentParser(description="HTTP 客户端契约盘点") + parser.add_argument("--json", help="输出 JSON 报告路径(默认 stdout)") + args = parser.parse_args() + + report = scan() + payload = report.to_dict() + if args.json: + Path(args.json).write_text( + json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8" + ) + else: + print(json.dumps(payload, ensure_ascii=False, indent=2)) + print( + f"timeout configs: {len(payload['timeout_configs'])}, " + f"client sites: {len(payload['client_sites'])}", + file=sys.stderr, + ) + + +if __name__ == "__main__": + main() diff --git a/backend-java/scripts/test_http_client_audit.py b/backend-java/scripts/test_http_client_audit.py new file mode 100644 index 00000000..08b880ec --- /dev/null +++ b/backend-java/scripts/test_http_client_audit.py @@ -0,0 +1,93 @@ +"""task-166 HTTP 客户端契约盘点测试。 + +对应 plan 10 任务 166 的 8 条用例: +1. test_contract_table_all_clients 全客户端覆盖 +2. test_timeouts_listed 超时现状 +3. test_retry_listed 重试现状 +4. test_idempotency_listed 幂等性标注 +5. test_jdk_limits_noted 平台限制 +6. test_doc_committed 文档存在 +7. test_no_code_change 零代码变更 +8. test_repeatable 可复查 +""" + +from __future__ import annotations + +import json +import subprocess +import unittest + +from http_client_audit import ( + REPO_ROOT, + extract_client_sites, + extract_timeout_configs, + scan, +) + +DOC_PATH = REPO_ROOT / "docs" / "http-client-contract-audit.md" + + +class HttpClientAuditTest(unittest.TestCase): + def test_contract_table_all_clients(self): + configs = extract_timeout_configs() + self.assertGreaterEqual(len(configs), 4, "超时/重试配置字段必须齐全") + classes = {c["config_class"] for c in configs} + self.assertTrue( + any("Transient" in name or "Storage" in name for name in classes), + "存储客户端配置存在", + ) + self.assertTrue( + any("SimilarAsin" in name or "ImageVideo" in name for name in classes), + "LLM/图片客户端配置存在", + ) + + def test_timeouts_listed(self): + fields = {c["field"] for c in extract_timeout_configs()} + self.assertTrue(any("connectTimeout" in f for f in fields), "connect 超时缺失") + self.assertTrue(any("readTimeout" in f for f in fields), "read 超时缺失") + self.assertTrue(any("writeTimeout" in f for f in fields), "write 超时缺失") + self.assertTrue(any("callTimeout" in f for f in fields), "call 超时缺失") + + def test_retry_listed(self): + fields = {c["field"] for c in extract_timeout_configs()} + self.assertTrue(any("Retry" in f for f in fields), "重试配置缺失") + + def test_idempotency_listed(self): + sites = extract_client_sites() + self.assertGreater(len(sites), 0, "客户端配置调用点必须记录") + + def test_jdk_limits_noted(self): + sites = extract_client_sites() + self.assertTrue( + any( + "HttpClient" in s["snippet"] or "newBuilder" in s["snippet"] + for s in sites + ), + "JDK HttpClient 使用点必须记录", + ) + + def test_doc_committed(self): + self.assertTrue(DOC_PATH.is_file(), f"契约表文档缺失: {DOC_PATH}") + text = DOC_PATH.read_text(encoding="utf-8") + self.assertIn("超时", text) + self.assertIn("重试", text) + self.assertIn("幂等", text) + + def test_no_code_change(self): + changed = subprocess.run( + ["git", "diff", "--name-only", "HEAD", "--", "backend-java/src"], + cwd=REPO_ROOT, + capture_output=True, + text=True, + check=True, + ).stdout.strip() + self.assertEqual(changed, "", f"生产代码被改动: {changed}") + + def test_repeatable(self): + first = json.dumps(scan().to_dict(), sort_keys=True) + second = json.dumps(scan().to_dict(), sort_keys=True) + self.assertEqual(first, second, "盘点可复查") + + +if __name__ == "__main__": + unittest.main()