提交更新
This commit is contained in:
+50
-21
@@ -30,7 +30,7 @@ public class AppearancePatentCozeClient {
|
||||
private final AppearancePatentProperties properties;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public List<AppearancePatentResultRowDto> inspect(List<AppearancePatentResultRowDto> rows, String prompt) {
|
||||
public List<AppearancePatentResultRowDto> inspect(List<AppearancePatentResultRowDto> rows, String prompt, String apiKey) {
|
||||
if (rows == null || rows.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
@@ -39,7 +39,7 @@ public class AppearancePatentCozeClient {
|
||||
return rows.stream().map(this::copy).toList();
|
||||
}
|
||||
try {
|
||||
return inspectWithFallback(rows, prompt);
|
||||
return inspectWithFallback(rows, prompt, apiKey);
|
||||
} catch (Exception ex) {
|
||||
String failureMessage = failureMessage(ex);
|
||||
log.warn("[appearance-patent] coze batch failed size={} err={}", rows.size(), failureMessage);
|
||||
@@ -47,8 +47,8 @@ public class AppearancePatentCozeClient {
|
||||
}
|
||||
}
|
||||
|
||||
public CozeSubmitResponse submitWorkflow(List<AppearancePatentResultRowDto> rows, String prompt) throws Exception {
|
||||
JsonNode submitRoot = objectMapper.readTree(postWorkflow(rows, prompt));
|
||||
public CozeSubmitResponse submitWorkflow(List<AppearancePatentResultRowDto> rows, String prompt, String apiKey) throws Exception {
|
||||
JsonNode submitRoot = objectMapper.readTree(postWorkflow(rows, prompt, apiKey));
|
||||
ensureSuccess(submitRoot);
|
||||
return new CozeSubmitResponse(
|
||||
extractExecuteId(submitRoot),
|
||||
@@ -83,12 +83,12 @@ public class AppearancePatentCozeClient {
|
||||
return rows.stream().map(this::copy).map(row -> markFailed(row, failureMessage)).toList();
|
||||
}
|
||||
|
||||
private List<AppearancePatentResultRowDto> inspectWithFallback(List<AppearancePatentResultRowDto> rows, String prompt) {
|
||||
private List<AppearancePatentResultRowDto> inspectWithFallback(List<AppearancePatentResultRowDto> rows, String prompt, String apiKey) {
|
||||
try {
|
||||
if (rows.size() == 1) {
|
||||
return inspectSingleRowWithRetry(rows, prompt);
|
||||
return inspectSingleRowWithRetry(rows, prompt, apiKey);
|
||||
}
|
||||
InspectAttempt attempt = inspectOnce(rows, prompt);
|
||||
InspectAttempt attempt = inspectOnce(rows, prompt, apiKey);
|
||||
if (attempt.resolvedCount() < rows.size()) {
|
||||
throw new PartialCozeResultException(attempt.resolvedCount(), rows.size(), attempt.rawResultCount());
|
||||
}
|
||||
@@ -99,17 +99,17 @@ public class AppearancePatentCozeClient {
|
||||
log.warn("[appearance-patent] coze batch fallback split size={} left={} right={} err={}",
|
||||
rows.size(), middle, rows.size() - middle, failureMessage(ex));
|
||||
List<AppearancePatentResultRowDto> merged = new ArrayList<>(rows.size());
|
||||
merged.addAll(inspectPartitionWithFailureFallback(rows.subList(0, middle), prompt));
|
||||
merged.addAll(inspectPartitionWithFailureFallback(rows.subList(middle, rows.size()), prompt));
|
||||
merged.addAll(inspectPartitionWithFailureFallback(rows.subList(0, middle), prompt, apiKey));
|
||||
merged.addAll(inspectPartitionWithFailureFallback(rows.subList(middle, rows.size()), prompt, apiKey));
|
||||
return merged;
|
||||
}
|
||||
throw propagate(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private List<AppearancePatentResultRowDto> inspectPartitionWithFailureFallback(List<AppearancePatentResultRowDto> rows, String prompt) {
|
||||
private List<AppearancePatentResultRowDto> inspectPartitionWithFailureFallback(List<AppearancePatentResultRowDto> rows, String prompt, String apiKey) {
|
||||
try {
|
||||
return inspectWithFallback(rows, prompt);
|
||||
return inspectWithFallback(rows, prompt, apiKey);
|
||||
} catch (Exception ex) {
|
||||
String failureMessage = failureMessage(ex);
|
||||
log.warn("[appearance-patent] coze partition failed size={} err={}", rows.size(), failureMessage);
|
||||
@@ -117,12 +117,12 @@ public class AppearancePatentCozeClient {
|
||||
}
|
||||
}
|
||||
|
||||
private List<AppearancePatentResultRowDto> inspectSingleRowWithRetry(List<AppearancePatentResultRowDto> rows, String prompt) throws Exception {
|
||||
private List<AppearancePatentResultRowDto> inspectSingleRowWithRetry(List<AppearancePatentResultRowDto> rows, String prompt, String apiKey) throws Exception {
|
||||
AppearancePatentResultRowDto row = rows.getFirst();
|
||||
PartialCozeResultException lastFailure = null;
|
||||
for (int attemptIndex = 1; attemptIndex <= 3; attemptIndex++) {
|
||||
try {
|
||||
InspectAttempt attempt = inspectOnce(rows, prompt);
|
||||
InspectAttempt attempt = inspectOnce(rows, prompt, apiKey);
|
||||
if (attempt.resolvedCount() == rows.size()) {
|
||||
return attempt.mergedRows();
|
||||
}
|
||||
@@ -153,8 +153,8 @@ public class AppearancePatentCozeClient {
|
||||
throw lastFailure == null ? new PartialCozeResultException(0, rows.size(), 0) : lastFailure;
|
||||
}
|
||||
|
||||
private InspectAttempt inspectOnce(List<AppearancePatentResultRowDto> rows, String prompt) throws Exception {
|
||||
String raw = runWorkflowAsyncAndWait(rows, prompt);
|
||||
private InspectAttempt inspectOnce(List<AppearancePatentResultRowDto> rows, String prompt, String apiKey) throws Exception {
|
||||
String raw = runWorkflowAsyncAndWait(rows, prompt, apiKey);
|
||||
List<CozeResult> results = parseResults(raw);
|
||||
if (rows.size() > 1 && !results.isEmpty() && results.stream().noneMatch(this::hasIdentity)) {
|
||||
throw new PartialCozeResultException(0, rows.size(), results.size());
|
||||
@@ -163,8 +163,8 @@ public class AppearancePatentCozeClient {
|
||||
return new InspectAttempt(raw, merged, resolvedCount(merged), results.size());
|
||||
}
|
||||
|
||||
private String runWorkflowAsyncAndWait(List<AppearancePatentResultRowDto> rows, String prompt) throws Exception {
|
||||
JsonNode submitRoot = objectMapper.readTree(postWorkflow(rows, prompt));
|
||||
private String runWorkflowAsyncAndWait(List<AppearancePatentResultRowDto> rows, String prompt, String apiKey) throws Exception {
|
||||
JsonNode submitRoot = objectMapper.readTree(postWorkflow(rows, prompt, apiKey));
|
||||
ensureSuccess(submitRoot);
|
||||
|
||||
String immediateData = extractResultDataText(submitRoot);
|
||||
@@ -205,15 +205,15 @@ public class AppearancePatentCozeClient {
|
||||
throw new IllegalStateException("Coze async workflow poll timeout");
|
||||
}
|
||||
|
||||
private String postWorkflow(List<AppearancePatentResultRowDto> rows, String prompt) {
|
||||
Map<String, Object> parameters = buildParameters(rows, prompt);
|
||||
private String postWorkflow(List<AppearancePatentResultRowDto> rows, String prompt, String apiKey) {
|
||||
Map<String, Object> parameters = buildParameters(rows, prompt, apiKey);
|
||||
Map<String, Object> body = new LinkedHashMap<>();
|
||||
body.put("workflow_id", properties.getCozeWorkflowId());
|
||||
body.put("parameters", parameters);
|
||||
body.put("is_async", Boolean.TRUE);
|
||||
log.info("[appearance-patent] coze request url={} body={}",
|
||||
joinUrl(properties.getCozeBaseUrl(), properties.getCozeWorkflowPath()),
|
||||
writeJson(body));
|
||||
writeJson(maskCozeRequestBody(body)));
|
||||
|
||||
RestClient.RequestBodySpec request = restClient().post()
|
||||
.uri(joinUrl(properties.getCozeBaseUrl(), properties.getCozeWorkflowPath()))
|
||||
@@ -255,7 +255,7 @@ public class AppearancePatentCozeClient {
|
||||
});
|
||||
}
|
||||
|
||||
private Map<String, Object> buildParameters(List<AppearancePatentResultRowDto> rows, String prompt) {
|
||||
private Map<String, Object> buildParameters(List<AppearancePatentResultRowDto> rows, String prompt, String apiKey) {
|
||||
List<String> groupKeys = rows.stream().map(row -> nonBlank(row.getGroupKey(), rowKey(row))).toList();
|
||||
List<String> rowIds = rows.stream().map(row -> nonBlank(row.getId(), "")).toList();
|
||||
List<String> asins = rows.stream().map(row -> nonBlank(row.getAsin(), "")).toList();
|
||||
@@ -268,9 +268,38 @@ public class AppearancePatentCozeClient {
|
||||
parameters.put("url_list", urls);
|
||||
parameters.put("items", buildItemObjects(rows, groupKeys, rowIds, asins, countries, titles, urls));
|
||||
parameters.put("prompt", prompt == null ? "" : prompt);
|
||||
if (apiKey != null && !apiKey.isBlank()) {
|
||||
parameters.put("api_key", apiKey.trim());
|
||||
}
|
||||
return parameters;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<String, Object> maskCozeRequestBody(Map<String, Object> body) {
|
||||
Map<String, Object> masked = new LinkedHashMap<>(body);
|
||||
Object parametersObj = masked.get("parameters");
|
||||
if (parametersObj instanceof Map<?, ?> parameters) {
|
||||
Map<String, Object> maskedParameters = new LinkedHashMap<>((Map<String, Object>) parameters);
|
||||
Object apiKey = maskedParameters.get("api_key");
|
||||
if (apiKey instanceof String apiKeyText && !apiKeyText.isBlank()) {
|
||||
maskedParameters.put("api_key", maskSecret(apiKeyText));
|
||||
}
|
||||
masked.put("parameters", maskedParameters);
|
||||
}
|
||||
return masked;
|
||||
}
|
||||
|
||||
private String maskSecret(String secret) {
|
||||
String normalized = secret == null ? "" : secret.trim();
|
||||
if (normalized.isBlank()) {
|
||||
return "";
|
||||
}
|
||||
if (normalized.length() <= 10) {
|
||||
return "***";
|
||||
}
|
||||
return normalized.substring(0, 6) + "***" + normalized.substring(normalized.length() - 4);
|
||||
}
|
||||
|
||||
private List<Map<String, Object>> buildItemObjects(List<AppearancePatentResultRowDto> rows,
|
||||
List<String> groupKeys,
|
||||
List<String> rowIds,
|
||||
|
||||
+7
@@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonAlias;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
@@ -25,4 +26,10 @@ public class AppearancePatentParseRequest {
|
||||
@JsonAlias({"aiPrompt", "prompt"})
|
||||
@Schema(description = "AI 提示词。非必填;为空时前端会使用默认提示词。后端会保存该提示词,并在调用 Coze workflow 时作为 prompt 参数传入。", example = "请排查这些亚马逊商品在英国及欧洲地区是否存在知识产权侵权风险。")
|
||||
private String aiPrompt;
|
||||
|
||||
@JsonProperty("api_key")
|
||||
@JsonAlias({"apiKey"})
|
||||
@Schema(description = "传递给 Coze workflow parameters.api_key 的任务级密钥。")
|
||||
@NotBlank(message = "密钥不能为空")
|
||||
private String apiKey;
|
||||
}
|
||||
|
||||
+3
@@ -14,6 +14,9 @@ public class AppearancePatentParsedPayloadDto {
|
||||
@Schema(description = "AI 提示词")
|
||||
private String aiPrompt;
|
||||
|
||||
@Schema(description = "传递给 Coze workflow parameters.api_key 的任务级密钥")
|
||||
private String apiKey;
|
||||
|
||||
@Schema(description = "本次解析的源文件列表")
|
||||
private List<AppearancePatentSourceFileDto> sourceFiles = new ArrayList<>();
|
||||
|
||||
|
||||
+134
-40
@@ -9,6 +9,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.nanri.aiimage.common.exception.BusinessException;
|
||||
import com.nanri.aiimage.common.service.DistributedJobLockService;
|
||||
import com.nanri.aiimage.config.AppearancePatentProperties;
|
||||
import com.nanri.aiimage.config.InstanceMetadata;
|
||||
import com.nanri.aiimage.config.StorageProperties;
|
||||
import com.nanri.aiimage.modules.appearancepatent.client.AppearancePatentCozeClient;
|
||||
import com.nanri.aiimage.modules.appearancepatent.model.dto.AppearancePatentParseRequest;
|
||||
@@ -131,6 +132,7 @@ public class AppearancePatentTaskService {
|
||||
private final PlatformTransactionManager transactionManager;
|
||||
private final DistributedJobLockService distributedJobLockService;
|
||||
private final TaskDistributedLockService taskDistributedLockService;
|
||||
private final InstanceMetadata instanceMetadata;
|
||||
@Autowired
|
||||
@Qualifier("cozeTaskExecutor")
|
||||
private TaskExecutor cozeTaskExecutor;
|
||||
@@ -199,11 +201,11 @@ public class AppearancePatentTaskService {
|
||||
|
||||
String aggregateScopeKey = buildAggregateScopeKey(sourceFiles);
|
||||
String sourceScopeHash = DigestUtil.sha256Hex(aggregateScopeKey);
|
||||
String parsedPayload = buildParsedPayloadJson(request.getAiPrompt(), sourceFiles, mergedHeaders, groups, allRows);
|
||||
String parsedPayload = buildParsedPayloadJson(request.getAiPrompt(), request.getApiKey(), sourceFiles, mergedHeaders, groups, allRows);
|
||||
long payloadBuiltAt = System.nanoTime();
|
||||
String parsedPayloadPointer = storeParsedPayload(task.getId(), sourceScopeHash, parsedPayload);
|
||||
long payloadStoredAt = System.nanoTime();
|
||||
task.setResultJson(buildTaskResultJson(request.getAiPrompt(), sourceFiles, parsedPayloadPointer));
|
||||
task.setResultJson(buildTaskResultJson(request.getAiPrompt(), request.getApiKey(), sourceFiles, parsedPayloadPointer));
|
||||
task.setUpdatedAt(LocalDateTime.now());
|
||||
fileTaskMapper.updateById(task);
|
||||
|
||||
@@ -267,6 +269,7 @@ public class AppearancePatentTaskService {
|
||||
if (task == null || !MODULE_TYPE.equals(task.getModuleType()) || userId != null && !Objects.equals(userId, task.getUserId())) {
|
||||
throw new BusinessException("任务不存在");
|
||||
}
|
||||
ensureTaskOwnedByCurrentInstance(task, "activate");
|
||||
if (STATUS_SUCCESS.equals(task.getStatus()) || STATUS_FAILED.equals(task.getStatus())) {
|
||||
throw new BusinessException("任务已结束");
|
||||
}
|
||||
@@ -429,6 +432,7 @@ public class AppearancePatentTaskService {
|
||||
if (task == null || !MODULE_TYPE.equals(task.getModuleType())) {
|
||||
throw new BusinessException("任务不存在");
|
||||
}
|
||||
ensureTaskOwnedByCurrentInstance(task, "submit result");
|
||||
if (!STATUS_RUNNING.equals(task.getStatus())) {
|
||||
throw new BusinessException("任务不是运行中状态");
|
||||
}
|
||||
@@ -620,6 +624,7 @@ public class AppearancePatentTaskService {
|
||||
throw new BusinessException("任务不是运行中状态");
|
||||
}
|
||||
|
||||
ensureTaskOwnedByCurrentInstance(task, "submit result");
|
||||
int chunkIndex = request.getChunkIndex() == null ? 0 : request.getChunkIndex();
|
||||
int chunkTotal = request.getChunkTotal() == null ? 1 : request.getChunkTotal();
|
||||
boolean done = Boolean.TRUE.equals(request.getDone());
|
||||
@@ -772,10 +777,11 @@ public class AppearancePatentTaskService {
|
||||
return List.of();
|
||||
}
|
||||
String prompt = readAiPrompt(task);
|
||||
String apiKey = readApiKey(task);
|
||||
int batchSize = Math.max(1, properties.getCozeBatchSize());
|
||||
List<AppearancePatentResultRowDto> result = new ArrayList<>();
|
||||
for (int i = 0; i < items.size(); i += batchSize) {
|
||||
result.addAll(cozeClient.inspect(items.subList(i, Math.min(i + batchSize, items.size())), prompt));
|
||||
result.addAll(cozeClient.inspect(items.subList(i, Math.min(i + batchSize, items.size())), prompt, apiKey));
|
||||
if (progressHook != null) {
|
||||
progressHook.run();
|
||||
}
|
||||
@@ -1060,6 +1066,14 @@ public class AppearancePatentTaskService {
|
||||
}
|
||||
}
|
||||
|
||||
private String readApiKey(FileTaskEntity task) {
|
||||
try {
|
||||
return normalize(readParsedPayload(task).getApiKey());
|
||||
} catch (Exception ignored) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private void finalizeTask(FileTaskEntity task, String error, int rowCount, boolean assembleWorkbook) {
|
||||
String finalError = error;
|
||||
FileResultEntity result = null;
|
||||
@@ -1098,7 +1112,7 @@ public class AppearancePatentTaskService {
|
||||
}
|
||||
fileResultMapper.updateById(result);
|
||||
if (!failed && assembleWorkbook) {
|
||||
taskFileJobService.enqueueAssembleResult(task.getId(), MODULE_TYPE, result.getId(), "task:" + task.getId());
|
||||
taskFileJobService.enqueueAssembleResult(task.getId(), MODULE_TYPE, result.getId(), buildTaskOwnerScopeKey(task.getId()));
|
||||
}
|
||||
}
|
||||
taskCacheService.deleteTaskCache(task.getId());
|
||||
@@ -1112,6 +1126,11 @@ public class AppearancePatentTaskService {
|
||||
if (job == null || job.getTaskId() == null || job.getResultId() == null) {
|
||||
throw new BusinessException("result file job arguments are incomplete");
|
||||
}
|
||||
if (!isJobOwnedByCurrentInstance(job)) {
|
||||
log.info("[appearance-patent] skip result file job because owner is another instance jobId={} taskId={} owner={} current={}",
|
||||
job.getId(), job.getTaskId(), ownerFromScopeKey(job.getScopeKey()), currentInstanceId());
|
||||
return false;
|
||||
}
|
||||
TaskDistributedLockService.LockHandle lockHandle = acquireTaskLock(job.getTaskId(), TASK_LOCK_WAIT_MILLIS);
|
||||
if (lockHandle == null) {
|
||||
taskFileJobService.requeue(job.getId(), "Task is busy, waiting for appearance patent result merge");
|
||||
@@ -1159,38 +1178,31 @@ public class AppearancePatentTaskService {
|
||||
|
||||
@Scheduled(fixedDelayString = "${aiimage.appearance-patent.coze-poll-delay-ms:5000}")
|
||||
public void pollPendingCozeJobs() {
|
||||
DistributedJobLockService.LockHandle lockHandle =
|
||||
distributedJobLockService.tryLock("appearance-patent:coze-poll", Duration.ofMinutes(1));
|
||||
if (lockHandle == null) {
|
||||
List<TaskScopeStateEntity> states = taskScopeStateMapper.selectList(new LambdaQueryWrapper<TaskScopeStateEntity>()
|
||||
.eq(TaskScopeStateEntity::getModuleType, MODULE_TYPE)
|
||||
.in(TaskScopeStateEntity::getCozeStatus, List.of(COZE_STATUS_SUBMITTED, COZE_STATUS_RUNNING))
|
||||
.isNotNull(TaskScopeStateEntity::getCozeExecuteId)
|
||||
.orderByAsc(TaskScopeStateEntity::getUpdatedAt)
|
||||
.last("limit 50"));
|
||||
if (states == null || states.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
try (lockHandle) {
|
||||
List<TaskScopeStateEntity> states = taskScopeStateMapper.selectList(new LambdaQueryWrapper<TaskScopeStateEntity>()
|
||||
.eq(TaskScopeStateEntity::getModuleType, MODULE_TYPE)
|
||||
.in(TaskScopeStateEntity::getCozeStatus, List.of(COZE_STATUS_SUBMITTED, COZE_STATUS_RUNNING))
|
||||
.isNotNull(TaskScopeStateEntity::getCozeExecuteId)
|
||||
.orderByAsc(TaskScopeStateEntity::getUpdatedAt)
|
||||
.last("limit 50"));
|
||||
if (states == null || states.isEmpty()) {
|
||||
return;
|
||||
log.info("[appearance-patent] coze poll picked pending states count={}", states.size());
|
||||
for (TaskScopeStateEntity state : states) {
|
||||
if (state == null || state.getId() == null || !isCozeStateOwnedByCurrentInstance(state)) {
|
||||
continue;
|
||||
}
|
||||
log.info("[appearance-patent] coze poll picked pending states count={}", states.size());
|
||||
for (TaskScopeStateEntity state : states) {
|
||||
if (state == null || state.getId() == null) {
|
||||
continue;
|
||||
}
|
||||
Long stateId = state.getId();
|
||||
try {
|
||||
cozeTaskExecutor.execute(() -> {
|
||||
log.info("[appearance-patent] coze poll worker entered stateId={} taskId={} executeId={}",
|
||||
stateId, state.getTaskId(), state.getCozeExecuteId());
|
||||
pollPendingCozeState(stateId);
|
||||
});
|
||||
} catch (Exception ex) {
|
||||
log.warn("[appearance-patent] coze poll dispatch failed stateId={} taskId={} executeId={} err={}",
|
||||
stateId, state.getTaskId(), state.getCozeExecuteId(),
|
||||
firstNonBlank(ex.getMessage(), ex.getClass().getSimpleName()), ex);
|
||||
}
|
||||
Long stateId = state.getId();
|
||||
try {
|
||||
cozeTaskExecutor.execute(() -> {
|
||||
log.info("[appearance-patent] coze poll worker entered stateId={} taskId={} executeId={}",
|
||||
stateId, state.getTaskId(), state.getCozeExecuteId());
|
||||
pollPendingCozeState(stateId);
|
||||
});
|
||||
} catch (Exception ex) {
|
||||
log.warn("[appearance-patent] coze poll dispatch failed stateId={} taskId={} executeId={} err={}",
|
||||
stateId, state.getTaskId(), state.getCozeExecuteId(),
|
||||
firstNonBlank(ex.getMessage(), ex.getClass().getSimpleName()), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1209,6 +1221,7 @@ public class AppearancePatentTaskService {
|
||||
return false;
|
||||
}
|
||||
String prompt = readAiPrompt(task);
|
||||
String apiKey = readApiKey(task);
|
||||
int batchSize = Math.max(1, properties.getCozeBatchSize());
|
||||
boolean pending = false;
|
||||
for (TaskChunkEntity chunk : chunks) {
|
||||
@@ -1225,7 +1238,7 @@ public class AppearancePatentTaskService {
|
||||
for (int i = 0; i < unresolvedRows.size(); i += batchSize) {
|
||||
List<AppearancePatentResultRowDto> batchRows =
|
||||
unresolvedRows.subList(i, Math.min(i + batchSize, unresolvedRows.size()));
|
||||
pending |= submitCozeBatch(task, result, job, chunk, batchRows, batchIndex, batchTotal, prompt, allRowsByBaseId);
|
||||
pending |= submitCozeBatch(task, result, job, chunk, batchRows, batchIndex, batchTotal, prompt, apiKey, allRowsByBaseId);
|
||||
batchIndex++;
|
||||
}
|
||||
}
|
||||
@@ -1240,6 +1253,7 @@ public class AppearancePatentTaskService {
|
||||
int batchIndex,
|
||||
int batchTotal,
|
||||
String prompt,
|
||||
String apiKey,
|
||||
Map<String, List<AppearancePatentParsedRowVo>> allRowsByBaseId) {
|
||||
if (batchRows == null || batchRows.isEmpty()) {
|
||||
return false;
|
||||
@@ -1256,7 +1270,7 @@ public class AppearancePatentTaskService {
|
||||
|| COZE_STATUS_RUNNING.equals(existing.getCozeStatus());
|
||||
}
|
||||
try {
|
||||
AppearancePatentCozeClient.CozeSubmitResponse submit = cozeClient.submitWorkflow(batchRows, prompt);
|
||||
AppearancePatentCozeClient.CozeSubmitResponse submit = cozeClient.submitWorkflow(batchRows, prompt, apiKey);
|
||||
if (submit.immediateData() != null && !submit.immediateData().isBlank()) {
|
||||
List<AppearancePatentResultRowDto> cozeRows = cozeClient.mergeRowsFromDataText(batchRows, submit.immediateData());
|
||||
mergeCozeRowsIntoChunk(task, chunk.getScopeHash(), chunk.getChunkIndex(), cozeRows, allRowsByBaseId);
|
||||
@@ -1305,7 +1319,8 @@ public class AppearancePatentTaskService {
|
||||
chunk.getScopeHash(),
|
||||
chunk.getChunkIndex(),
|
||||
batchIndex,
|
||||
batchTotal
|
||||
batchTotal,
|
||||
currentInstanceId()
|
||||
);
|
||||
String batchPayload = writeJson(batchRows, "serialize coze batch payload failed");
|
||||
String storedBatchPayload = storeSharedCozeBatchPayload(task.getId(), batchScopeHash, batchPayload);
|
||||
@@ -1341,6 +1356,11 @@ public class AppearancePatentTaskService {
|
||||
if (lockState != null) {
|
||||
taskIdForLock = lockState.getTaskId();
|
||||
}
|
||||
if (!isCozeStateOwnedByCurrentInstance(lockState)) {
|
||||
log.info("[appearance-patent] coze poll skipped because owner is another instance taskId={} stateId={} owner={} current={}",
|
||||
taskIdForLock, stateId, ownerFromCozeState(lockState), currentInstanceId());
|
||||
return;
|
||||
}
|
||||
TaskDistributedLockService.LockHandle lockHandle = acquireTaskLock(taskIdForLock, 0L);
|
||||
if (lockHandle == null) {
|
||||
if (taskIdForLock != null) {
|
||||
@@ -1375,6 +1395,11 @@ public class AppearancePatentTaskService {
|
||||
markCozeStateTerminal(state, COZE_STATUS_FAILED, "Coze batch context missing");
|
||||
return;
|
||||
}
|
||||
if (!isOwnerCurrent(context.ownerInstanceId())) {
|
||||
log.info("[appearance-patent] coze poll skipped after context refresh because owner is another instance taskId={} stateId={} owner={} current={}",
|
||||
state.getTaskId(), state.getId(), context.ownerInstanceId(), currentInstanceId());
|
||||
return;
|
||||
}
|
||||
taskFileJobService.touchRunning(context.jobId());
|
||||
log.info("[appearance-patent] coze poll start taskId={} stateId={} executeId={} jobId={} chunk={} batch={}/{}",
|
||||
state.getTaskId(), state.getId(), state.getCozeExecuteId(),
|
||||
@@ -1664,6 +1689,69 @@ public class AppearancePatentTaskService {
|
||||
+ ":batch:" + batchIndex;
|
||||
}
|
||||
|
||||
private String buildTaskOwnerScopeKey(Long taskId) {
|
||||
return "task:" + taskId + ":owner:" + currentInstanceId();
|
||||
}
|
||||
|
||||
private String currentInstanceId() {
|
||||
String instanceId = instanceMetadata == null ? null : instanceMetadata.getInstanceId();
|
||||
return firstNonBlank(instanceId, "unknown-instance");
|
||||
}
|
||||
|
||||
private boolean isJobOwnedByCurrentInstance(TaskFileJobEntity job) {
|
||||
return job == null || isOwnerCurrent(ownerFromScopeKey(job.getScopeKey()));
|
||||
}
|
||||
|
||||
private void ensureTaskOwnedByCurrentInstance(FileTaskEntity task, String operation) {
|
||||
String owner = ownerFromTask(task);
|
||||
if (isOwnerCurrent(owner)) {
|
||||
return;
|
||||
}
|
||||
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, "该任务已绑定到另一台服务实例处理,请通过原实例继续处理");
|
||||
}
|
||||
|
||||
private boolean isCozeStateOwnedByCurrentInstance(TaskScopeStateEntity state) {
|
||||
return state == null || isOwnerCurrent(ownerFromCozeState(state));
|
||||
}
|
||||
|
||||
private boolean isOwnerCurrent(String owner) {
|
||||
return owner == null || owner.isBlank() || Objects.equals(owner, currentInstanceId());
|
||||
}
|
||||
|
||||
private String ownerFromCozeState(TaskScopeStateEntity state) {
|
||||
CozeBatchContext context = readCozeBatchContext(state);
|
||||
return context == null ? null : context.ownerInstanceId();
|
||||
}
|
||||
|
||||
private String ownerFromTask(FileTaskEntity task) {
|
||||
if (task == null || task.getResultJson() == null || task.getResultJson().isBlank()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
String owner = objectMapper.readTree(task.getResultJson()).path("ownerInstanceId").asText("");
|
||||
return owner.isBlank() ? null : owner;
|
||||
} catch (Exception ex) {
|
||||
log.warn("[appearance-patent] read task owner failed taskId={} err={}",
|
||||
task.getId(), ex.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String ownerFromScopeKey(String scopeKey) {
|
||||
if (scopeKey == null || scopeKey.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
String marker = ":owner:";
|
||||
int index = scopeKey.lastIndexOf(marker);
|
||||
if (index < 0) {
|
||||
return null;
|
||||
}
|
||||
String owner = scopeKey.substring(index + marker.length()).trim();
|
||||
return owner.isBlank() ? null : owner;
|
||||
}
|
||||
|
||||
private String abbreviate(String value, int maxLength) {
|
||||
if (value == null) {
|
||||
return "";
|
||||
@@ -2340,7 +2428,9 @@ public class AppearancePatentTaskService {
|
||||
LocalDateTime baseTime = snapshot.getUpdatedAt() != null ? snapshot.getUpdatedAt() : job.getUpdatedAt();
|
||||
long elapsedSeconds = baseTime == null ? 0 : Math.max(0, Duration.between(baseTime, LocalDateTime.now()).getSeconds());
|
||||
if (current <= 0) {
|
||||
percent = Math.max(percent, Math.min(35, 8 + (int) (elapsedSeconds / 6)));
|
||||
int firstRealProgressPercent = Math.max(1, Math.min(99, (int) Math.floor(100.0 / total)));
|
||||
int waitingCap = Math.max(8, Math.min(35, firstRealProgressPercent - 1));
|
||||
percent = Math.max(percent, Math.min(waitingCap, 8 + (int) (elapsedSeconds / 6)));
|
||||
} else if (current < total) {
|
||||
percent = Math.max(percent, Math.min(92, percent + (int) (elapsedSeconds / 10)));
|
||||
}
|
||||
@@ -2379,9 +2469,10 @@ public class AppearancePatentTaskService {
|
||||
return val == null ? "" : val.replace(String.valueOf((char) 0xFEFF), "").replace((char) 0x3000, ' ').trim().replaceAll("\\s+", " ");
|
||||
}
|
||||
|
||||
private String buildParsedPayloadJson(String aiPrompt, List<AppearancePatentSourceFileDto> sourceFiles, List<String> headers, List<AppearancePatentParsedGroupVo> groups, List<AppearancePatentParsedRowVo> allRows) {
|
||||
private String buildParsedPayloadJson(String aiPrompt, String apiKey, List<AppearancePatentSourceFileDto> sourceFiles, List<String> headers, List<AppearancePatentParsedGroupVo> groups, List<AppearancePatentParsedRowVo> allRows) {
|
||||
AppearancePatentParsedPayloadDto payload = new AppearancePatentParsedPayloadDto();
|
||||
payload.setAiPrompt(normalize(aiPrompt));
|
||||
payload.setApiKey(normalize(apiKey));
|
||||
payload.setSourceFiles(sourceFiles == null ? List.of() : sourceFiles);
|
||||
payload.setHeaders(headers == null ? List.of() : headers);
|
||||
payload.setItems(List.of());
|
||||
@@ -2390,14 +2481,16 @@ public class AppearancePatentTaskService {
|
||||
return writeJson(payload, "保存解析结果失败");
|
||||
}
|
||||
|
||||
private String buildTaskResultJson(String aiPrompt, List<AppearancePatentSourceFileDto> sourceFiles, String parsedPayloadPointer) {
|
||||
private String buildTaskResultJson(String aiPrompt, String apiKey, List<AppearancePatentSourceFileDto> sourceFiles, String parsedPayloadPointer) {
|
||||
Map<String, Object> payload = new LinkedHashMap<>();
|
||||
payload.put("aiPrompt", normalize(aiPrompt));
|
||||
payload.put("apiKey", normalize(apiKey));
|
||||
payload.put("sourceFileKeys", sourceFiles == null ? List.of() : sourceFiles.stream()
|
||||
.map(AppearancePatentSourceFileDto::getFileKey)
|
||||
.filter(Objects::nonNull)
|
||||
.toList());
|
||||
payload.put("parsedPayloadRef", parsedPayloadPointer);
|
||||
payload.put("ownerInstanceId", currentInstanceId());
|
||||
return writeJson(payload, "保存任务结果索引失败");
|
||||
}
|
||||
|
||||
@@ -2661,7 +2754,8 @@ public class AppearancePatentTaskService {
|
||||
String chunkScopeHash,
|
||||
Integer chunkIndex,
|
||||
Integer batchIndex,
|
||||
Integer batchTotal) {
|
||||
Integer batchTotal,
|
||||
String ownerInstanceId) {
|
||||
}
|
||||
|
||||
private record ParsedWorkbook(int totalRows, int droppedRows, List<String> headers, List<AppearancePatentParsedRowVo> allRows) {
|
||||
|
||||
Reference in New Issue
Block a user