diff --git a/app/.env b/app/.env index ef3e12b..ffc438a 100644 --- a/app/.env +++ b/app/.env @@ -11,9 +11,9 @@ zn_username=%E8%87%AA%E5%8A%A8%E5%8C%96_Robot client_name=ShuFuAI -# java_api_base=http://47.111.163.154:18080 +java_api_base=http://47.111.163.154:18080 # java_api_base=http://127.0.0.1:18080 # java_api_base=http://8.136.19.173:18080 -java_api_base=http://121.196.149.225:18080 +# java_api_base=http://121.196.149.225:18080 diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/service/AppearancePatentTaskService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/service/AppearancePatentTaskService.java index d2ec25c..6bdd3f9 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/service/AppearancePatentTaskService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/service/AppearancePatentTaskService.java @@ -86,6 +86,8 @@ public class AppearancePatentTaskService { private static final String STATUS_SUCCESS = "SUCCESS"; private static final String STATUS_FAILED = "FAILED"; private static final String CONTENT_TYPE_XLSX = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; + private static final int RESULT_ROWS_READ_RETRY_LIMIT = 3; + private static final long RESULT_ROWS_READ_RETRY_DELAY_MS = 500L; private static final List RESULT_HEADERS = List.of( "id", "asin", @@ -759,12 +761,17 @@ public class AppearancePatentTaskService { } String payloadJson = writeJson(rows == null ? List.of() : rows, "结果序列化失败"); payloadJson = writeJson(new ArrayList<>(persistedRows.values()), "appearance patent chunk payload merge failed"); - String storedPayload = transientPayloadStorageService.storeChunkPayload(MODULE_TYPE, taskId, scopeHash, chunkIndex, payloadJson); - transientPayloadStorageService.deleteReplacedPayloadIfNeeded(chunk.getPayloadJson(), storedPayload); + String oldPayload = chunk.getPayloadJson(); + String storedPayload = transientPayloadStorageService.storeChunkPayloadVersioned(MODULE_TYPE, taskId, scopeHash, chunkIndex, payloadJson); chunk.setPayloadJson(storedPayload); chunk.setPayloadHash(DigestUtil.sha256Hex(payloadJson)); chunk.setUpdatedAt(LocalDateTime.now()); - taskChunkMapper.updateById(chunk); + int updated = taskChunkMapper.updateById(chunk); + if (updated <= 0) { + transientPayloadStorageService.deletePayloadIfPresent(storedPayload); + throw new IllegalStateException("appearance patent chunk payload update failed"); + } + transientPayloadStorageService.deleteReplacedPayloadIfNeeded(oldPayload, storedPayload); } private List expandRows(List representatives, @@ -1122,7 +1129,7 @@ public class AppearancePatentTaskService { private void assembleResultWorkbook(FileTaskEntity task, FileResultEntity result) { AppearancePatentParsedPayloadDto parsed = readParsedPayload(task); - Map resultMap = loadPersistedResultRows(task.getId()); + Map resultMap = loadPersistedResultRowsWithRetry(task.getId(), parsed.getAllItems().size()); long resolvedRows = parsed.getAllItems().stream() .filter(row -> findResultRow(row, resultMap) != null || findResultRowByAsin(row.getAsin(), resultMap) != null) .count(); @@ -1131,6 +1138,9 @@ public class AppearancePatentTaskService { .count(); log.info("[appearance-patent] assemble workbook taskId={} parsedRows={} resultRows={} resolvedRows={} reasonRows={}", task.getId(), parsed.getAllItems().size(), resultMap.size(), resolvedRows, reasonRows); + if (!parsed.getAllItems().isEmpty() && resultMap.isEmpty()) { + throw new BusinessException("外观专利检测结果为空,请稍后重试生成结果文件"); + } File outputDir = new File(storageProperties.getLocalTempDir(), "appearance-patent-result"); if (!outputDir.exists() && !outputDir.mkdirs()) { throw new BusinessException("创建结果目录失败"); @@ -1164,6 +1174,33 @@ public class AppearancePatentTaskService { return result; } + private Map loadPersistedResultRowsWithRetry(Long taskId, int expectedParsedRows) { + Map result = loadPersistedResultRows(taskId); + if (expectedParsedRows <= 0 || !result.isEmpty()) { + return result; + } + for (int attempt = 1; attempt <= RESULT_ROWS_READ_RETRY_LIMIT && result.isEmpty(); attempt++) { + sleepBeforeResultRowsRetry(attempt); + result = loadPersistedResultRows(taskId); + if (!result.isEmpty()) { + log.info("[appearance-patent] result rows recovered after retry taskId={} attempt={} rows={}", + taskId, attempt, result.size()); + return result; + } + log.warn("[appearance-patent] result rows still empty after retry taskId={} attempt={}/{}", + taskId, attempt, RESULT_ROWS_READ_RETRY_LIMIT); + } + return result; + } + + private void sleepBeforeResultRowsRetry(int attempt) { + try { + Thread.sleep(RESULT_ROWS_READ_RETRY_DELAY_MS * attempt); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } + } + private void writeResultWorkbook(File xlsx, AppearancePatentParsedPayloadDto parsed, Map resultMap) { try (SXSSFWorkbook workbook = new SXSSFWorkbook(200); FileOutputStream fos = new FileOutputStream(xlsx)) { Sheet sheet = workbook.createSheet("外观专利检测结果"); diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/task/service/TransientPayloadStorageService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/task/service/TransientPayloadStorageService.java index d840320..afb3d32 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/task/service/TransientPayloadStorageService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/task/service/TransientPayloadStorageService.java @@ -55,6 +55,13 @@ public class TransientPayloadStorageService { return store("task-chunk", moduleType, taskId, scopeHash, entryKey, content, true); } + public String storeChunkPayloadVersioned(String moduleType, Long taskId, String scopeHash, Integer chunkIndex, String content) { + String entryKey = chunkIndex == null + ? UUID.randomUUID().toString() + : "chunk-" + chunkIndex + "-" + UUID.randomUUID(); + return store("task-chunk", moduleType, taskId, scopeHash, entryKey, content, true); + } + public String resolvePayload(String value, String errorMessage) { String pointer = extractPointer(value); if (pointer == null) {