feat: update task routing and coze handling

This commit is contained in:
super
2026-06-03 17:14:50 +08:00
parent ea37d82d73
commit e98a1a1207
37 changed files with 1883 additions and 254 deletions
@@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nanri.aiimage.config.AppearancePatentProperties;
import com.nanri.aiimage.modules.appearancepatent.model.dto.AppearancePatentResultRowDto;
import com.nanri.aiimage.modules.brand.client.BrandCheckClient;
import com.nanri.aiimage.modules.coze.service.CozeCredentialPoolService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -29,10 +30,14 @@ public class AppearancePatentCozeClient {
private static final String MODULE_TYPE = "APPEARANCE_PATENT";
private static final MediaType APPLICATION_JSON_UTF8 = new MediaType("application", "json", StandardCharsets.UTF_8);
private static final String INFRINGEMENT = "侵权";
private static final String NO_INFRINGEMENT = "无侵权";
private static final String BRAND_QUERY_FAILED = "商标查询失败";
private final AppearancePatentProperties properties;
private final ObjectMapper objectMapper;
private final CozeCredentialPoolService cozeCredentialPoolService;
private final BrandCheckClient brandCheckClient;
private final AtomicLong credentialCursor = new AtomicLong();
public List<AppearancePatentResultRowDto> inspect(List<AppearancePatentResultRowDto> rows, String prompt, String apiKey) {
@@ -361,7 +366,7 @@ public class AppearancePatentCozeClient {
List<String> rowIds = rows.stream().map(row -> nonBlank(row.getId(), "")).toList();
List<String> asins = rows.stream().map(row -> nonBlank(row.getAsin(), "")).toList();
List<String> countries = rows.stream().map(row -> nonBlank(row.getCountry(), "")).toList();
List<String> skus = rows.stream().map(row -> nonBlank(row.getSku(), "")).toList();
List<String> skus = rows.stream().map(row -> sanitizeSku(row.getSku())).toList();
List<String> titles = rows.stream().map(row -> nonBlank(row.getTitle(), row.getAsin())).toList();
List<String> urls = rows.stream().map(row -> nonBlank(row.getUrl(), "")).toList();
@@ -580,10 +585,15 @@ public class AppearancePatentCozeClient {
if (row == null || result == null) {
return;
}
row.setTitleRisk(result.title());
if (isNoTitleRisk(result)) {
row.setTitleRisk(NO_INFRINGEMENT);
} else {
BrandCheckClient.BrandCheckBatchResult brandCheck = brandCheckClient.checkTitleText(result.title(), "Terms");
row.setTitleRisk(buildTitleRisk(result.title(), brandCheck));
}
row.setAppearanceRisk(result.appearance());
row.setPatentRisk(result.patent());
row.setConclusion(result.result());
row.setConclusion(buildConclusion(row.getTitleRisk(), row.getAppearanceRisk(), result.result()));
row.setStatus(result.status());
row.setTitleReason(result.titleReason());
row.setAppearanceReason(result.appearanceReason());
@@ -591,6 +601,55 @@ public class AppearancePatentCozeClient {
row.setScore(result.score());
}
private boolean isNoTitleRisk(CozeResult result) {
return result != null
&& "".equals(normalize(result.title()))
&& "".equals(normalize(result.titleReason()));
}
String buildTitleRisk(String cozeTitle, BrandCheckClient.BrandCheckBatchResult brandCheck) {
List<String> brands = brandCheck == null ? List.of() : brandCheck.brands();
if (brands == null || brands.isEmpty()) {
return firstNonBlank(cozeTitle, "");
}
if (brandCheck.hasFailedData()) {
return INFRINGEMENT;
}
if (brandCheck.hasQueryFailedData()) {
return BRAND_QUERY_FAILED;
}
return NO_INFRINGEMENT;
}
String buildConclusion(String titleRisk, String appearanceRisk, String fallbackConclusion) {
if (isInfringement(titleRisk) || isInfringement(appearanceRisk)) {
return INFRINGEMENT;
}
if (isNoInfringement(titleRisk) && isNoInfringement(appearanceRisk)) {
return NO_INFRINGEMENT;
}
if (isBrandQueryFailed(titleRisk)) {
return BRAND_QUERY_FAILED;
}
return firstNonBlank(fallbackConclusion, "");
}
private boolean isInfringement(String value) {
String normalized = normalize(value);
return normalized.contains("侵权")
&& !normalized.contains("无侵权")
&& !normalized.contains("未侵权");
}
private boolean isNoInfringement(String value) {
String normalized = normalize(value);
return normalized.contains("无侵权") || normalized.contains("未侵权");
}
private boolean isBrandQueryFailed(String value) {
return normalize(value).contains(BRAND_QUERY_FAILED);
}
private RestClient restClient() {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setConnectTimeout(properties.getCozeConnectTimeoutMillis());
@@ -1012,6 +1071,32 @@ public class AppearancePatentCozeClient {
return value == null || value.isBlank() ? fallback : value;
}
private String sanitizeSku(String value) {
String normalized = normalize(value);
if (normalized.isBlank() || isProbablyDescriptionSku(normalized)) {
return "";
}
return value;
}
private boolean isProbablyDescriptionSku(String value) {
String normalized = normalize(value);
if (normalized.isBlank()) {
return false;
}
String lower = normalized.toLowerCase(Locale.ROOT);
return normalized.length() > 120
|| normalized.contains("\n")
|| lower.contains(" minimum tensile ")
|| lower.contains(" multipurpose:")
|| lower.contains(" strong, durable")
|| lower.contains(" product includes ")
|| lower.contains(" perfect gift")
|| lower.contains(" high quality ")
|| lower.contains(" easy to ")
|| lower.contains(" capacity:");
}
private String firstNonBlank(String preferred, String fallback) {
return preferred == null || preferred.isBlank() ? fallback : preferred.trim();
}
@@ -7,6 +7,7 @@ import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nanri.aiimage.common.exception.BusinessException;
import com.nanri.aiimage.common.exception.TaskOwnerMismatchException;
import com.nanri.aiimage.common.service.DistributedJobLockService;
import com.nanri.aiimage.common.util.CozeGroupResultPropagator;
import com.nanri.aiimage.common.util.FailedStatusRowFilter;
@@ -108,6 +109,7 @@ public class AppearancePatentTaskService {
private static final String COZE_STATUS_FAILED = "FAILED";
private static final String CONTENT_TYPE_XLSX = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
private static final String CONTENT_TYPE_ZIP = "application/zip";
private static final int CHUNK_PAYLOAD_MERGE_RETRY_LIMIT = 3;
private static final int RESULT_ROWS_READ_RETRY_LIMIT = 3;
private static final long RESULT_ROWS_READ_RETRY_DELAY_MS = 500L;
private static final Duration TASK_LOCK_TTL = Duration.ofMinutes(5);
@@ -572,7 +574,8 @@ public class AppearancePatentTaskService {
public void finalizeStaleTasks() {
if (transactionManager != null) {
LocalDateTime threshold = LocalDateTime.now().minusMinutes(Math.max(1, properties.getStaleTimeoutMinutes()));
LocalDateTime now = LocalDateTime.now();
LocalDateTime threshold = now.minusMinutes(Math.max(1, properties.getStaleTimeoutMinutes()));
long thresholdMillis = threshold.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
List<FileTaskEntity> tasks = listStaleFinalizeCandidates(threshold);
for (FileTaskEntity task : tasks) {
@@ -596,7 +599,8 @@ public class AppearancePatentTaskService {
}
return;
}
LocalDateTime threshold = LocalDateTime.now().minusMinutes(Math.max(1, properties.getStaleTimeoutMinutes()));
LocalDateTime now = LocalDateTime.now();
LocalDateTime threshold = now.minusMinutes(Math.max(1, properties.getStaleTimeoutMinutes()));
long thresholdMillis = threshold.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
List<FileTaskEntity> tasks = listStaleFinalizeCandidates(threshold);
for (FileTaskEntity task : tasks) {
@@ -647,8 +651,8 @@ public class AppearancePatentTaskService {
return fileTaskMapper.selectList(new LambdaQueryWrapper<FileTaskEntity>()
.eq(FileTaskEntity::getModuleType, MODULE_TYPE)
.eq(FileTaskEntity::getStatus, STATUS_RUNNING)
.lt(FileTaskEntity::getCreatedAt, threshold)
.orderByAsc(FileTaskEntity::getCreatedAt)
.lt(FileTaskEntity::getUpdatedAt, threshold)
.orderByAsc(FileTaskEntity::getUpdatedAt)
.last("limit 200"));
}
@@ -811,22 +815,6 @@ public class AppearancePatentTaskService {
long activeAssembleJobs = taskFileJobService.countActiveAssembleJobs(taskId, MODULE_TYPE);
log.info("[appearance-patent] stale recovery probe taskId={} uploadComplete={} pendingCozeStates={} activeAssembleJobs={} persistedRows={}",
taskId, uploadComplete, pendingCozeStates, activeAssembleJobs, hasPersistedResultRows(taskId));
if (isJavaSideProcessing(taskId)) {
// 防止 Coze 永远 pending 时 stale-recovery 永久 defer
// 超过 stale-timeout-minutes × 4 仍未推进的 RUNNING 任务,强制走 finalize 链路。
long deferCeilingMinutes = Math.max(1, properties.getStaleTimeoutMinutes()) * 4L;
LocalDateTime updatedAt = task.getUpdatedAt();
if (updatedAt != null
&& Duration.between(updatedAt, LocalDateTime.now()).toMinutes() >= deferCeilingMinutes) {
log.warn("[appearance-patent] stale recovery defer ceiling exceeded, forcing finalize taskId={} updatedAt={} ceilingMinutes={} pendingCozeStates={} activeAssembleJobs={}",
taskId, updatedAt, deferCeilingMinutes, pendingCozeStates, activeAssembleJobs);
return false;
}
touchJavaSideTaskActivity(taskId);
log.info("[appearance-patent] stale recovery deferred because Java-side processing is still active taskId={} uploadComplete={} pendingCozeStates={} activeAssembleJobs={}",
taskId, uploadComplete, pendingCozeStates, activeAssembleJobs);
return true;
}
if (!hasPersistedResultRows(taskId)) {
log.info("[appearance-patent] stale recovery aborted because no persisted rows taskId={}", taskId);
return false;
@@ -839,10 +827,10 @@ public class AppearancePatentTaskService {
return false;
}
int pendingRows = collectPendingCozeCandidates(task, loadSubmittedChunks(taskId)).size();
log.warn("[appearance-patent] python heartbeat timed out, forcing coze flush taskId={} pendingRows={} pendingCozeStates={} activeAssembleJobs={} forcedScopes={}",
log.warn("[appearance-patent] Python 回传心跳超时,已封口上传并继续 Coze/文件收尾 taskId={} pendingRows={} pendingCozeStates={} activeAssembleJobs={} forcedScopes={}",
taskId, pendingRows, pendingCozeStates, activeAssembleJobs, forcedScopes);
} else {
log.warn("[appearance-patent] stale running task resuming coze/file assembly after python timeout taskId={} pendingCozeStates={} activeAssembleJobs={}",
log.warn("[appearance-patent] Python 超时恢复继续推进 Coze/文件收尾 taskId={} pendingCozeStates={} activeAssembleJobs={}",
taskId, pendingCozeStates, activeAssembleJobs);
}
submitCozeForSubmittedChunk(new SubmitContext(task, null, null, 0, true, null));
@@ -1029,32 +1017,52 @@ public class AppearancePatentTaskService {
}
private void mergeChunkPayload(Long taskId, String scopeHash, Integer chunkIndex, List<AppearancePatentResultRowDto> rows) {
TaskChunkEntity chunk = taskChunkMapper.selectOne(new LambdaQueryWrapper<TaskChunkEntity>()
.eq(TaskChunkEntity::getTaskId, taskId)
.eq(TaskChunkEntity::getModuleType, MODULE_TYPE)
.eq(TaskChunkEntity::getScopeHash, scopeHash)
.eq(TaskChunkEntity::getChunkIndex, chunkIndex)
.last("limit 1"));
if (chunk == null || rows == null || rows.isEmpty()) {
if (rows == null || rows.isEmpty()) {
return;
}
Map<String, AppearancePatentResultRowDto> persistedRows = readChunkRows(chunk);
for (AppearancePatentResultRowDto row : rows) {
persistedRows.put(rowKey(row), row);
}
String payloadJson = writeJson(rows == null ? List.of() : rows, "结果序列化失败");
payloadJson = writeJson(new ArrayList<>(persistedRows.values()), "appearance patent chunk payload merge failed");
String oldPayload = chunk.getPayloadJson();
String storedPayload = storeSharedChunkPayloadVersioned(taskId, scopeHash, chunkIndex, payloadJson);
chunk.setPayloadJson(storedPayload);
chunk.setPayloadHash(DigestUtil.sha256Hex(payloadJson));
chunk.setUpdatedAt(LocalDateTime.now());
int updated = taskChunkMapper.updateById(chunk);
if (updated <= 0) {
for (int attempt = 1; attempt <= CHUNK_PAYLOAD_MERGE_RETRY_LIMIT; attempt++) {
TaskChunkEntity chunk = taskChunkMapper.selectOne(new LambdaQueryWrapper<TaskChunkEntity>()
.eq(TaskChunkEntity::getTaskId, taskId)
.eq(TaskChunkEntity::getModuleType, MODULE_TYPE)
.eq(TaskChunkEntity::getScopeHash, scopeHash)
.eq(TaskChunkEntity::getChunkIndex, chunkIndex)
.last("limit 1"));
if (chunk == null) {
return;
}
Map<String, AppearancePatentResultRowDto> persistedRows = readChunkRows(chunk);
for (AppearancePatentResultRowDto row : rows) {
persistedRows.put(rowKey(row), row);
}
String payloadJson = writeJson(new ArrayList<>(persistedRows.values()), "appearance patent chunk payload merge failed");
String oldPayload = chunk.getPayloadJson();
String oldPayloadHash = chunk.getPayloadHash();
String newPayloadHash = DigestUtil.sha256Hex(payloadJson);
String storedPayload = storeSharedChunkPayloadVersioned(taskId, scopeHash, chunkIndex, payloadJson);
LambdaUpdateWrapper<TaskChunkEntity> updateWrapper = new LambdaUpdateWrapper<TaskChunkEntity>()
.eq(TaskChunkEntity::getId, chunk.getId())
.set(TaskChunkEntity::getPayloadJson, storedPayload)
.set(TaskChunkEntity::getPayloadHash, newPayloadHash)
.set(TaskChunkEntity::getUpdatedAt, LocalDateTime.now());
if (oldPayloadHash == null) {
updateWrapper.isNull(TaskChunkEntity::getPayloadHash);
} else {
updateWrapper.eq(TaskChunkEntity::getPayloadHash, oldPayloadHash);
}
int updated = taskChunkMapper.update(null, updateWrapper);
if (updated > 0) {
log.debug("[appearance-patent] chunk payload replaced taskId={} scopeHash={} chunk={} oldPayload={} newPayload={} attempt={}",
taskId, scopeHash, chunkIndex, oldPayload, storedPayload, attempt);
transientPayloadStorageService.deleteReplacedPayloadIfNeeded(oldPayload, storedPayload);
return;
}
transientPayloadStorageService.deletePayloadIfPresent(storedPayload);
throw new IllegalStateException("appearance patent chunk payload update failed");
if (attempt < CHUNK_PAYLOAD_MERGE_RETRY_LIMIT) {
log.warn("[appearance-patent] chunk payload CAS conflict, retry merge taskId={} scopeHash={} chunk={} attempt={}/{}",
taskId, scopeHash, chunkIndex, attempt, CHUNK_PAYLOAD_MERGE_RETRY_LIMIT);
}
}
transientPayloadStorageService.deleteReplacedPayloadIfNeeded(oldPayload, storedPayload);
throw new IllegalStateException("appearance patent chunk payload update conflict");
}
private List<AppearancePatentResultRowDto> expandRows(List<AppearancePatentResultRowDto> representatives,
@@ -1438,6 +1446,9 @@ public class AppearancePatentTaskService {
int cozeWorkUnits = countCozeWorkUnits(chunks, Math.max(1, properties.getCozeBatchSize()));
int plannedCozeUnits = Math.max(cozeWorkUnits, countAllCozeStates(task.getId()));
int totalProgressUnits = Math.max(3, plannedCozeUnits + 3);
if (countPendingCozeStates(task.getId()) > 0) {
finalizeTimedOutCozeStatesForTask(task.getId());
}
if (countPendingCozeStates(task.getId()) > 0) {
taskFileJobService.touchRunning(job.getId());
touchJavaSideTaskActivity(task.getId());
@@ -1605,6 +1616,16 @@ public class AppearancePatentTaskService {
int batchSize = Math.max(1, properties.getCozeBatchSize());
List<CozeCandidate> candidates = collectPendingCozeCandidates(task, chunks);
boolean flushRemainder = isResultSubmissionComplete(task.getId());
if (!flushRemainder && !candidates.isEmpty()) {
LocalDateTime jobUpdatedAt = job.getUpdatedAt();
long pendingFlushMillis = cozeFlushPendingMillis();
if (jobUpdatedAt != null
&& Duration.between(jobUpdatedAt, LocalDateTime.now()).toMillis() >= pendingFlushMillis) {
flushRemainder = true;
log.warn("[appearance-patent] Coze 零头批次等待超时,强制提交 taskId={} jobId={} pendingRows={} batchSize={} jobUpdatedAt={} flushAfterMillis={}",
task.getId(), job.getId(), candidates.size(), batchSize, jobUpdatedAt, pendingFlushMillis);
}
}
int submitLimit = (candidates.size() / batchSize) * batchSize;
if (flushRemainder && submitLimit < candidates.size()) {
submitLimit = candidates.size();
@@ -1869,7 +1890,7 @@ public class AppearancePatentTaskService {
: "";
if (!poll.hasPayload() && failureMessage.isBlank()) {
failureMessage = isCozeStateTimedOut(state)
? "Coze async workflow poll timeout"
? "Coze 异步工作流轮询超时"
: "Coze async workflow completed without output";
}
List<AppearancePatentResultRowDto> batchRows = readCozeBatchRows(state);
@@ -1940,6 +1961,43 @@ public class AppearancePatentTaskService {
}
}
private void finalizeTimedOutCozeStatesForTask(Long taskId) {
if (taskId == null || taskId <= 0) {
return;
}
List<TaskScopeStateEntity> states = taskScopeStateMapper.selectList(new LambdaQueryWrapper<TaskScopeStateEntity>()
.eq(TaskScopeStateEntity::getTaskId, taskId)
.eq(TaskScopeStateEntity::getModuleType, MODULE_TYPE)
.in(TaskScopeStateEntity::getCozeStatus, List.of(COZE_STATUS_SUBMITTED, COZE_STATUS_RUNNING))
.orderByAsc(TaskScopeStateEntity::getCozeSubmittedAt)
.last("limit 50"));
if (states == null || states.isEmpty()) {
return;
}
for (TaskScopeStateEntity state : states) {
if (state == null || state.getId() == null || !isCozeStateTimedOut(state)) {
continue;
}
CozeBatchContext context = readCozeBatchContext(state);
if (context == null || context.resultId() == null) {
markCozeStateTerminal(state, COZE_STATUS_FAILED, "Coze 批次上下文缺失");
continue;
}
List<AppearancePatentResultRowDto> batchRows = readCozeBatchRows(state);
FileTaskEntity task = fileTaskMapper.selectById(taskId);
if (task != null) {
Map<String, List<AppearancePatentParsedRowVo>> allRowsByBaseId = loadAllRowsByBaseId(task);
mergeCozeRowsIntoSubmittedChunks(task,
cozeClient.markRowsFailed(batchRows, "Coze 异步工作流轮询超时"),
allRowsByBaseId);
}
markCozeStateTerminal(state, COZE_STATUS_FAILED, "Coze 异步工作流轮询超时");
maybeFinalizeCozeJobLocked(taskId, context);
log.warn("[appearance-patent] 文件任务超时兜底已将 Coze pending 批次置为失败 taskId={} stateId={} jobId={}",
taskId, state.getId(), context.jobId());
}
}
private boolean retryFailedCozeBatchState(TaskScopeStateEntity state,
CozeBatchContext context,
List<AppearancePatentResultRowDto> batchRows,
@@ -2499,6 +2557,10 @@ public class AppearancePatentTaskService {
return Duration.between(state.getCozeSubmittedAt(), LocalDateTime.now()).toMillis() >= timeoutMillis;
}
private long cozeFlushPendingMillis() {
return Math.max(1, properties.getCozeFlushPendingMinutes()) * 60_000L;
}
private int cozeAttemptCount(TaskScopeStateEntity state) {
return state == null || state.getCozeAttemptCount() == null ? 0 : state.getCozeAttemptCount();
}
@@ -2549,11 +2611,6 @@ public class AppearancePatentTaskService {
return count != null && count > 0;
}
private boolean isJavaSideProcessing(Long taskId) {
return countPendingCozeStates(taskId) > 0
|| taskFileJobService.countActiveAssembleJobs(taskId, MODULE_TYPE) > 0;
}
private void touchJavaSideTaskActivity(Long taskId) {
if (taskId == null || taskId <= 0) {
return;
@@ -2664,13 +2721,27 @@ public class AppearancePatentTaskService {
log.warn("[appearance-patent] coze pending submit retry failed taskId={} stateId={} jobId={} rows={} err={}",
state.getTaskId(), state.getId(), context.jobId(), batchRows.size(), message);
if (isCozeThrottleLockTimeout(message)) {
if (isCozeStateTimedOut(state)) {
Map<String, List<AppearancePatentParsedRowVo>> allRowsByBaseId = loadAllRowsByBaseId(task);
String finalMessage = "等待 Coze 提交凭证超时:" + message;
mergeCozeRowsIntoSubmittedChunks(task,
cozeClient.markRowsFailed(batchRows, finalMessage),
allRowsByBaseId);
markCozeStateTerminal(state, COZE_STATUS_FAILED, finalMessage);
maybeFinalizeCozeJobLocked(state.getTaskId(), context);
log.warn("[appearance-patent] Coze pending 提交等待超时,已将批次置为失败 taskId={} stateId={} jobId={} rows={}",
state.getTaskId(), state.getId(), context.jobId(), batchRows.size());
return;
}
keepPendingCozeSubmitState(state, message);
return;
}
int nextAttemptCount = cozeAttemptCount(state) + 1;
if (nextAttemptCount >= MAX_COZE_SUBMIT_RETRY_COUNT) {
if (nextAttemptCount >= MAX_COZE_SUBMIT_RETRY_COUNT || isCozeStateTimedOut(state)) {
Map<String, List<AppearancePatentParsedRowVo>> allRowsByBaseId = loadAllRowsByBaseId(task);
String finalMessage = message + " after " + nextAttemptCount + " submit attempts";
String finalMessage = isCozeStateTimedOut(state)
? message + "(超过 " + properties.getCozePollTimeoutMillis() + "ms"
: message + ",已重试提交 " + nextAttemptCount + "";
mergeCozeRowsIntoSubmittedChunks(task,
cozeClient.markRowsFailed(batchRows, finalMessage),
allRowsByBaseId);
@@ -2711,7 +2782,11 @@ public class AppearancePatentTaskService {
}
log.warn("[appearance-patent] reject task operation because owner is another instance taskId={} operation={} owner={} current={}",
task == null ? null : task.getId(), operation, owner, currentInstanceId());
throw new BusinessException(40903, "该任务已绑定到另一台服务实例处理,请通过原实例继续处理");
throw new TaskOwnerMismatchException(
task == null ? null : task.getId(),
operation,
owner,
currentInstanceId());
}
private boolean isCozeStateOwnedByCurrentInstance(TaskScopeStateEntity state) {
@@ -3909,6 +3984,7 @@ public class AppearancePatentTaskService {
if (task == null || !MODULE_TYPE.equals(task.getModuleType()) || userId != null && !Objects.equals(userId, task.getUserId())) {
throw new BusinessException("任务不存在");
}
ensureTaskOwnedByCurrentInstance(task, "parsed payload");
return readParsedPayload(task);
}
@@ -3917,6 +3993,7 @@ public class AppearancePatentTaskService {
if (task == null || !MODULE_TYPE.equals(task.getModuleType()) || userId != null && !Objects.equals(userId, task.getUserId())) {
throw new BusinessException("任务不存在");
}
ensureTaskOwnedByCurrentInstance(task, "queue payload");
AppearancePatentParsedPayloadDto payload = readParsedPayload(task);
AppearancePatentParsedPayloadDto queuePayload = new AppearancePatentParsedPayloadDto();
queuePayload.setAiPrompt(payload.getAiPrompt());
@@ -4004,6 +4081,8 @@ public class AppearancePatentTaskService {
} catch (Exception ex) {
log.warn("[appearance-patent] read chunk payload failed taskId={} chunk={} err={}",
chunk.getTaskId(), chunk.getChunkIndex(), ex.getMessage());
throw new BusinessException("appearance patent chunk payload read failed chunk="
+ chunk.getChunkIndex() + ": " + ex.getMessage(), ex);
}
return rows;
}
@@ -4097,13 +4176,6 @@ public class AppearancePatentTaskService {
return firstNonBlank(row.getConclusion(), "");
}
/**
* 导出最终 xlsx 时的状态展示:
* - 如果 status 是 markFailed 合成的(failureSyntheticStatus=true),
* 且 row 实际并没有任何风险维度结果,导出层展示错误信息(有则放入,无则留空),
* 避免用户原样重新上传时该行被反复识别为失败行重新触发 Coze。
* - 真正业务侧失败 / Python 真实回传 FAILED 仍按原值显示。
*/
private String userFacingStatus(AppearancePatentResultRowDto row) {
if (row == null) {
return "";
@@ -4112,7 +4184,6 @@ public class AppearancePatentTaskService {
if (!row.isFailureSyntheticStatus()) {
return original;
}
// 合成状态:有错误信息则放入错误信息,没有则留空
return firstNonBlank(row.getError(), "");
}