更新处理这个外观部分
This commit is contained in:
@@ -288,7 +288,7 @@ class SpiderTask:
|
|||||||
print(f"[{timestamp}] [PriceTask] [{level}] {message}")
|
print(f"[{timestamp}] [PriceTask] [{level}] {message}")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def group_by_id_prefix(data):
|
def group_by_id_prefix(data):
|
||||||
"""
|
"""
|
||||||
根据每条数据的 id 字段进行分组:
|
根据每条数据的 id 字段进行分组:
|
||||||
- 如果 id 为 "2_1",则取 "_" 前面的 "2" 作为分组 key
|
- 如果 id 为 "2_1",则取 "_" 前面的 "2" 作为分组 key
|
||||||
@@ -304,10 +304,35 @@ class SpiderTask:
|
|||||||
item_id = str(item.get("id", ""))
|
item_id = str(item.get("id", ""))
|
||||||
group_key = item_id.split("_")[0]
|
group_key = item_id.split("_")[0]
|
||||||
grouped[group_key].append(item)
|
grouped[group_key].append(item)
|
||||||
|
|
||||||
return list(grouped.values())
|
return list(grouped.values())
|
||||||
|
|
||||||
def process_task(self, task_data: dict):
|
@staticmethod
|
||||||
|
def normalize_groups(data):
|
||||||
|
groups = data.get("groups")
|
||||||
|
if isinstance(groups, list) and len(groups) > 0:
|
||||||
|
return groups
|
||||||
|
|
||||||
|
rows = data.get("rows") or data.get("items") or []
|
||||||
|
if not isinstance(rows, list) or len(rows) == 0:
|
||||||
|
return []
|
||||||
|
|
||||||
|
normalized = []
|
||||||
|
for items in SpiderTask.group_by_id_prefix(rows):
|
||||||
|
first = items[0] if items else {}
|
||||||
|
item_id = str(first.get("id") or first.get("displayId") or "")
|
||||||
|
base_id = item_id.split("_")[0] if item_id else ""
|
||||||
|
normalized.append({
|
||||||
|
"sourceFileKey": first.get("sourceFileKey", ""),
|
||||||
|
"sourceFilename": first.get("sourceFilename", ""),
|
||||||
|
"groupKey": first.get("groupKey") or base_id,
|
||||||
|
"baseId": first.get("baseId") or base_id,
|
||||||
|
"displayId": first.get("displayId") or item_id,
|
||||||
|
"items": items,
|
||||||
|
})
|
||||||
|
return normalized
|
||||||
|
|
||||||
|
def process_task(self, task_data: dict):
|
||||||
"""处理审批任务主入口
|
"""处理审批任务主入口
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -316,7 +341,7 @@ class SpiderTask:
|
|||||||
try:
|
try:
|
||||||
data = task_data.get("data", {})
|
data = task_data.get("data", {})
|
||||||
task_id = data.get("taskId")
|
task_id = data.get("taskId")
|
||||||
groups = data.get("groups")
|
groups = self.normalize_groups(data)
|
||||||
|
|
||||||
# 用于测试
|
# 用于测试
|
||||||
limit = data.get("limit", None)
|
limit = data.get("limit", None)
|
||||||
@@ -328,7 +353,11 @@ class SpiderTask:
|
|||||||
|
|
||||||
self.log(f"开始处理爬取任务 {task_id},{len(groups)} 个任务")
|
self.log(f"开始处理爬取任务 {task_id},{len(groups)} 个任务")
|
||||||
|
|
||||||
from config import runing_task
|
if not groups:
|
||||||
|
self.log("appearance-patent groups/rows is empty, skip", "WARNING")
|
||||||
|
return
|
||||||
|
|
||||||
|
from config import runing_task
|
||||||
runing_task[task_id] = {
|
runing_task[task_id] = {
|
||||||
"status": "running",
|
"status": "running",
|
||||||
"start_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
"start_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
@@ -354,11 +383,12 @@ class SpiderTask:
|
|||||||
try:
|
try:
|
||||||
# 数据整理
|
# 数据整理
|
||||||
# new_items = self.group_by_id_prefix(items)
|
# new_items = self.group_by_id_prefix(items)
|
||||||
result = []
|
result = []
|
||||||
for gp_index,gp in enumerate(groups):
|
for gp_index,gp in enumerate(groups):
|
||||||
items = gp.get("items", [])
|
items = gp.get("items", [])
|
||||||
|
return_data = {}
|
||||||
for index,value in enumerate(items):
|
|
||||||
|
for index,value in enumerate(items):
|
||||||
|
|
||||||
return_data = {
|
return_data = {
|
||||||
'image_url': "",
|
'image_url': "",
|
||||||
@@ -369,13 +399,15 @@ class SpiderTask:
|
|||||||
return_data = None
|
return_data = None
|
||||||
for _ in range(max_retry):
|
for _ in range(max_retry):
|
||||||
try:
|
try:
|
||||||
return_data = chrome.run(country, asin)
|
return_data = chrome.run(country, asin) or {}
|
||||||
self.log(f"抓取结果->{return_data}")
|
self.log(f"抓取结果->{return_data}")
|
||||||
break
|
break
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if "与页面的连接已断开" in str(e):
|
if "与页面的连接已断开" in str(e):
|
||||||
chrome = ChromeAmzone()
|
chrome = ChromeAmzone()
|
||||||
if return_data.get("image_url"):
|
if not isinstance(return_data, dict):
|
||||||
|
return_data = {}
|
||||||
|
if return_data.get("image_url"):
|
||||||
break
|
break
|
||||||
|
|
||||||
group_item = [
|
group_item = [
|
||||||
@@ -8381,4 +8413,4 @@ if __name__ == '__main__':
|
|||||||
]
|
]
|
||||||
# spide.process_task(task_data)
|
# spide.process_task(task_data)
|
||||||
res = SpiderTask.group_by_id_prefix(task_data)
|
res = SpiderTask.group_by_id_prefix(task_data)
|
||||||
print(res)
|
print(res)
|
||||||
|
|||||||
@@ -60,14 +60,24 @@ public class AppearancePatentCozeClient {
|
|||||||
log.warn("[appearance-patent] coze batch fallback split size={} left={} right={} err={}",
|
log.warn("[appearance-patent] coze batch fallback split size={} left={} right={} err={}",
|
||||||
rows.size(), middle, rows.size() - middle, failureMessage(ex));
|
rows.size(), middle, rows.size() - middle, failureMessage(ex));
|
||||||
List<AppearancePatentResultRowDto> merged = new ArrayList<>(rows.size());
|
List<AppearancePatentResultRowDto> merged = new ArrayList<>(rows.size());
|
||||||
merged.addAll(inspectWithFallback(rows.subList(0, middle), prompt));
|
merged.addAll(inspectPartitionWithFailureFallback(rows.subList(0, middle), prompt));
|
||||||
merged.addAll(inspectWithFallback(rows.subList(middle, rows.size()), prompt));
|
merged.addAll(inspectPartitionWithFailureFallback(rows.subList(middle, rows.size()), prompt));
|
||||||
return merged;
|
return merged;
|
||||||
}
|
}
|
||||||
throw propagate(ex);
|
throw propagate(ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<AppearancePatentResultRowDto> inspectPartitionWithFailureFallback(List<AppearancePatentResultRowDto> rows, String prompt) {
|
||||||
|
try {
|
||||||
|
return inspectWithFallback(rows, prompt);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
String failureMessage = failureMessage(ex);
|
||||||
|
log.warn("[appearance-patent] coze partition failed size={} err={}", rows.size(), failureMessage);
|
||||||
|
return rows.stream().map(this::copy).map(row -> markFailed(row, failureMessage)).toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private List<AppearancePatentResultRowDto> inspectSingleRowWithRetry(List<AppearancePatentResultRowDto> rows, String prompt) throws Exception {
|
private List<AppearancePatentResultRowDto> inspectSingleRowWithRetry(List<AppearancePatentResultRowDto> rows, String prompt) throws Exception {
|
||||||
AppearancePatentResultRowDto row = rows.getFirst();
|
AppearancePatentResultRowDto row = rows.getFirst();
|
||||||
PartialCozeResultException lastFailure = null;
|
PartialCozeResultException lastFailure = null;
|
||||||
@@ -154,6 +164,9 @@ public class AppearancePatentCozeClient {
|
|||||||
body.put("workflow_id", properties.getCozeWorkflowId());
|
body.put("workflow_id", properties.getCozeWorkflowId());
|
||||||
body.put("parameters", parameters);
|
body.put("parameters", parameters);
|
||||||
body.put("is_async", Boolean.TRUE);
|
body.put("is_async", Boolean.TRUE);
|
||||||
|
log.info("[appearance-patent] coze request url={} body={}",
|
||||||
|
joinUrl(properties.getCozeBaseUrl(), properties.getCozeWorkflowPath()),
|
||||||
|
writeJson(body));
|
||||||
|
|
||||||
RestClient.RequestBodySpec request = restClient().post()
|
RestClient.RequestBodySpec request = restClient().post()
|
||||||
.uri(joinUrl(properties.getCozeBaseUrl(), properties.getCozeWorkflowPath()))
|
.uri(joinUrl(properties.getCozeBaseUrl(), properties.getCozeWorkflowPath()))
|
||||||
@@ -164,7 +177,11 @@ public class AppearancePatentCozeClient {
|
|||||||
request.body(body);
|
request.body(body);
|
||||||
return request.exchange((clientRequest, clientResponse) -> {
|
return request.exchange((clientRequest, clientResponse) -> {
|
||||||
byte[] responseBytes = StreamUtils.copyToByteArray(clientResponse.getBody());
|
byte[] responseBytes = StreamUtils.copyToByteArray(clientResponse.getBody());
|
||||||
return responseBytes.length == 0 ? "" : new String(responseBytes, StandardCharsets.UTF_8);
|
String responseText = responseBytes.length == 0 ? "" : new String(responseBytes, StandardCharsets.UTF_8);
|
||||||
|
log.info("[appearance-patent] coze submit response status={} body={}",
|
||||||
|
clientResponse.getStatusCode(),
|
||||||
|
responseText);
|
||||||
|
return responseText;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,7 +197,12 @@ public class AppearancePatentCozeClient {
|
|||||||
})
|
})
|
||||||
.exchange((clientRequest, clientResponse) -> {
|
.exchange((clientRequest, clientResponse) -> {
|
||||||
byte[] responseBytes = StreamUtils.copyToByteArray(clientResponse.getBody());
|
byte[] responseBytes = StreamUtils.copyToByteArray(clientResponse.getBody());
|
||||||
return responseBytes.length == 0 ? "" : new String(responseBytes, StandardCharsets.UTF_8);
|
String responseText = responseBytes.length == 0 ? "" : new String(responseBytes, StandardCharsets.UTF_8);
|
||||||
|
log.info("[appearance-patent] coze history response executeId={} status={} body={}",
|
||||||
|
executeId,
|
||||||
|
clientResponse.getStatusCode(),
|
||||||
|
responseText);
|
||||||
|
return responseText;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,10 +267,26 @@ public class AppearancePatentCozeClient {
|
|||||||
text(firstNonNull(
|
text(firstNonNull(
|
||||||
firstNonNull(node.get("country"), node.get("site")),
|
firstNonNull(node.get("country"), node.get("site")),
|
||||||
firstNonNull(itemNode.get("country"), itemNode.get("site")))),
|
firstNonNull(itemNode.get("country"), itemNode.get("site")))),
|
||||||
text(node.get("title")),
|
text(firstNonNull(node.get("title"), firstNonNull(itemNode.get("title"), itemNode.get("title_risk")))),
|
||||||
text(node.get("appearance")),
|
text(firstNonNull(node.get("appearance"),
|
||||||
text(firstNonNull(node.get("patent"), node.get("patent "))),
|
firstNonNull(node.get("appearance_risk"),
|
||||||
text(node.get("result"))
|
firstNonNull(itemNode.get("appearance"), itemNode.get("appearance_risk"))))),
|
||||||
|
text(firstNonNull(firstNonNull(node.get("patent"), node.get("patent ")),
|
||||||
|
firstNonNull(node.get("patent_risk"),
|
||||||
|
firstNonNull(firstNonNull(itemNode.get("patent"), itemNode.get("patent ")), itemNode.get("patent_risk"))))),
|
||||||
|
text(firstNonNull(node.get("result"),
|
||||||
|
firstNonNull(node.get("conclusion"),
|
||||||
|
firstNonNull(itemNode.get("result"), itemNode.get("conclusion"))))),
|
||||||
|
text(firstNonNull(node.get("title_reason"),
|
||||||
|
firstNonNull(node.get("titleReason"),
|
||||||
|
firstNonNull(itemNode.get("title_reason"), itemNode.get("titleReason"))))),
|
||||||
|
text(firstNonNull(node.get("appearance_reason"),
|
||||||
|
firstNonNull(node.get("appearanceReason"),
|
||||||
|
firstNonNull(itemNode.get("appearance_reason"), itemNode.get("appearanceReason"))))),
|
||||||
|
text(firstNonNull(node.get("patent_reason"),
|
||||||
|
firstNonNull(firstNonNull(node.get("patentReason"), node.get("patent reason")),
|
||||||
|
firstNonNull(itemNode.get("patent_reason"),
|
||||||
|
firstNonNull(itemNode.get("patentReason"), itemNode.get("patent reason"))))))
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -349,6 +387,9 @@ public class AppearancePatentCozeClient {
|
|||||||
row.setAppearanceRisk(result.appearance());
|
row.setAppearanceRisk(result.appearance());
|
||||||
row.setPatentRisk(result.patent());
|
row.setPatentRisk(result.patent());
|
||||||
row.setConclusion(result.result());
|
row.setConclusion(result.result());
|
||||||
|
row.setTitleReason(result.titleReason());
|
||||||
|
row.setAppearanceReason(result.appearanceReason());
|
||||||
|
row.setPatentReason(result.patentReason());
|
||||||
}
|
}
|
||||||
|
|
||||||
private RestClient restClient() {
|
private RestClient restClient() {
|
||||||
@@ -375,6 +416,9 @@ public class AppearancePatentCozeClient {
|
|||||||
row.setAppearanceRisk(source.getAppearanceRisk());
|
row.setAppearanceRisk(source.getAppearanceRisk());
|
||||||
row.setPatentRisk(source.getPatentRisk());
|
row.setPatentRisk(source.getPatentRisk());
|
||||||
row.setConclusion(source.getConclusion());
|
row.setConclusion(source.getConclusion());
|
||||||
|
row.setTitleReason(source.getTitleReason());
|
||||||
|
row.setAppearanceReason(source.getAppearanceReason());
|
||||||
|
row.setPatentReason(source.getPatentReason());
|
||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -507,13 +551,32 @@ public class AppearancePatentCozeClient {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for (JsonNode item : array) {
|
for (JsonNode item : array) {
|
||||||
if (item.has("appearance") || item.has("patent") || item.has("patent ") || item.has("result")) {
|
JsonNode itemNode = resultItemNode(item);
|
||||||
|
if (hasResultFields(item) || hasResultFields(itemNode)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean hasResultFields(JsonNode item) {
|
||||||
|
return item != null
|
||||||
|
&& (item.has("appearance")
|
||||||
|
|| item.has("appearance_risk")
|
||||||
|
|| item.has("patent")
|
||||||
|
|| item.has("patent ")
|
||||||
|
|| item.has("patent_risk")
|
||||||
|
|| item.has("result")
|
||||||
|
|| item.has("conclusion")
|
||||||
|
|| item.has("title_reason")
|
||||||
|
|| item.has("titleReason")
|
||||||
|
|| item.has("appearance_reason")
|
||||||
|
|| item.has("appearanceReason")
|
||||||
|
|| item.has("patent_reason")
|
||||||
|
|| item.has("patentReason")
|
||||||
|
|| item.has("patent reason"));
|
||||||
|
}
|
||||||
|
|
||||||
private JsonNode parseJsonOrMissing(String value) {
|
private JsonNode parseJsonOrMissing(String value) {
|
||||||
String normalized = normalize(value);
|
String normalized = normalize(value);
|
||||||
if (!(normalized.startsWith("{") || normalized.startsWith("["))) {
|
if (!(normalized.startsWith("{") || normalized.startsWith("["))) {
|
||||||
@@ -705,7 +768,10 @@ public class AppearancePatentCozeClient {
|
|||||||
String title,
|
String title,
|
||||||
String appearance,
|
String appearance,
|
||||||
String patent,
|
String patent,
|
||||||
String result
|
String result,
|
||||||
|
String titleReason,
|
||||||
|
String appearanceReason,
|
||||||
|
String patentReason
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -57,8 +57,10 @@ public class AppearancePatentController {
|
|||||||
@Operation(summary = "查询外观专利检测历史", description = "查询当前用户最近的外观专利检测历史记录,包含源文件名、任务状态、行数、错误信息和最终 xlsx 下载地址。")
|
@Operation(summary = "查询外观专利检测历史", description = "查询当前用户最近的外观专利检测历史记录,包含源文件名、任务状态、行数、错误信息和最终 xlsx 下载地址。")
|
||||||
public ApiResponse<AppearancePatentHistoryVo> history(
|
public ApiResponse<AppearancePatentHistoryVo> history(
|
||||||
@Parameter(description = "当前用户 ID,用于查询该用户自己的历史记录。", required = true, example = "1")
|
@Parameter(description = "当前用户 ID,用于查询该用户自己的历史记录。", required = true, example = "1")
|
||||||
@RequestParam("user_id") Long userId) {
|
@RequestParam("user_id") Long userId,
|
||||||
return ApiResponse.success(service.history(userId));
|
@Parameter(description = "history limit, default 50, max 100", example = "50")
|
||||||
|
@RequestParam(value = "limit", required = false, defaultValue = "50") Integer limit) {
|
||||||
|
return ApiResponse.success(service.history(userId, limit));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/tasks/progress/batch")
|
@PostMapping("/tasks/progress/batch")
|
||||||
|
|||||||
@@ -52,4 +52,16 @@ public class AppearancePatentResultRowDto {
|
|||||||
|
|
||||||
@Schema(description = "Java 调用 Coze 后生成的最终结论,对应最终 xlsx 的“结论”列。Python 回传请求中不要传该字段。", example = "未发现明显侵权风险", accessMode = Schema.AccessMode.READ_ONLY)
|
@Schema(description = "Java 调用 Coze 后生成的最终结论,对应最终 xlsx 的“结论”列。Python 回传请求中不要传该字段。", example = "未发现明显侵权风险", accessMode = Schema.AccessMode.READ_ONLY)
|
||||||
private String conclusion;
|
private String conclusion;
|
||||||
|
|
||||||
|
@JsonAlias({"title_reason", "titleReason"})
|
||||||
|
@Schema(description = "Coze title reason", accessMode = Schema.AccessMode.READ_ONLY)
|
||||||
|
private String titleReason;
|
||||||
|
|
||||||
|
@JsonAlias({"appearance_reason", "appearanceReason"})
|
||||||
|
@Schema(description = "Coze appearance reason", accessMode = Schema.AccessMode.READ_ONLY)
|
||||||
|
private String appearanceReason;
|
||||||
|
|
||||||
|
@JsonAlias({"patent_reason", "patentReason", "patent reason"})
|
||||||
|
@Schema(description = "Coze patent reason", accessMode = Schema.AccessMode.READ_ONLY)
|
||||||
|
private String patentReason;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -238,22 +238,42 @@ public class AppearancePatentTaskService {
|
|||||||
return vo;
|
return vo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AppearancePatentHistoryVo history(Long userId) {
|
public AppearancePatentHistoryVo history(Long userId, Integer limit) {
|
||||||
AppearancePatentHistoryVo vo = new AppearancePatentHistoryVo();
|
AppearancePatentHistoryVo vo = new AppearancePatentHistoryVo();
|
||||||
|
int safeLimit = Math.max(1, Math.min(limit == null ? 50 : limit, 100));
|
||||||
List<FileResultEntity> rows = fileResultMapper.selectList(new LambdaQueryWrapper<FileResultEntity>()
|
List<FileResultEntity> rows = fileResultMapper.selectList(new LambdaQueryWrapper<FileResultEntity>()
|
||||||
|
.select(FileResultEntity::getId,
|
||||||
|
FileResultEntity::getTaskId,
|
||||||
|
FileResultEntity::getSourceFilename,
|
||||||
|
FileResultEntity::getResultFilename,
|
||||||
|
FileResultEntity::getResultFileUrl,
|
||||||
|
FileResultEntity::getRowCount,
|
||||||
|
FileResultEntity::getSuccess,
|
||||||
|
FileResultEntity::getErrorMessage,
|
||||||
|
FileResultEntity::getCreatedAt)
|
||||||
.eq(FileResultEntity::getModuleType, MODULE_TYPE)
|
.eq(FileResultEntity::getModuleType, MODULE_TYPE)
|
||||||
.eq(FileResultEntity::getUserId, userId)
|
.eq(FileResultEntity::getUserId, userId)
|
||||||
.orderByDesc(FileResultEntity::getCreatedAt)
|
.orderByDesc(FileResultEntity::getCreatedAt)
|
||||||
.last("limit 100"));
|
.last("limit " + safeLimit));
|
||||||
Map<Long, String> statusMap = new LinkedHashMap<>();
|
Map<Long, String> statusMap = new LinkedHashMap<>();
|
||||||
List<Long> taskIds = rows.stream().map(FileResultEntity::getTaskId).filter(Objects::nonNull).distinct().toList();
|
List<Long> taskIds = rows.stream().map(FileResultEntity::getTaskId).filter(Objects::nonNull).distinct().toList();
|
||||||
if (!taskIds.isEmpty()) {
|
if (!taskIds.isEmpty()) {
|
||||||
for (FileTaskEntity task : fileTaskMapper.selectBatchIds(taskIds)) {
|
for (FileTaskEntity task : fileTaskMapper.selectList(new LambdaQueryWrapper<FileTaskEntity>()
|
||||||
|
.select(FileTaskEntity::getId, FileTaskEntity::getStatus)
|
||||||
|
.in(FileTaskEntity::getId, taskIds))) {
|
||||||
statusMap.put(task.getId(), task.getStatus());
|
statusMap.put(task.getId(), task.getStatus());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Map<Long, TaskFileJobEntity> jobMap = taskFileJobService.findAssembleJobsByResultIds(MODULE_TYPE, rows.stream()
|
||||||
|
.map(FileResultEntity::getId)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.toList());
|
||||||
for (FileResultEntity row : rows) {
|
for (FileResultEntity row : rows) {
|
||||||
vo.getItems().add(toHistoryItem(row, statusMap.get(row.getTaskId())));
|
String taskStatus = statusMap.get(row.getTaskId());
|
||||||
|
if (STATUS_PENDING.equals(taskStatus)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
vo.getItems().add(toHistoryItem(row, taskStatus, jobMap.get(row.getId())));
|
||||||
}
|
}
|
||||||
return vo;
|
return vo;
|
||||||
}
|
}
|
||||||
@@ -268,11 +288,43 @@ public class AppearancePatentTaskService {
|
|||||||
return vo;
|
return vo;
|
||||||
}
|
}
|
||||||
Map<Long, FileTaskEntity> taskMap = new LinkedHashMap<>();
|
Map<Long, FileTaskEntity> taskMap = new LinkedHashMap<>();
|
||||||
for (FileTaskEntity task : fileTaskMapper.selectBatchIds(normalizedIds)) {
|
for (FileTaskEntity task : fileTaskMapper.selectList(new LambdaQueryWrapper<FileTaskEntity>()
|
||||||
|
.select(FileTaskEntity::getId,
|
||||||
|
FileTaskEntity::getTaskNo,
|
||||||
|
FileTaskEntity::getModuleType,
|
||||||
|
FileTaskEntity::getStatus,
|
||||||
|
FileTaskEntity::getErrorMessage,
|
||||||
|
FileTaskEntity::getCreatedAt,
|
||||||
|
FileTaskEntity::getUpdatedAt,
|
||||||
|
FileTaskEntity::getFinishedAt)
|
||||||
|
.in(FileTaskEntity::getId, normalizedIds))) {
|
||||||
if (task != null && MODULE_TYPE.equals(task.getModuleType())) {
|
if (task != null && MODULE_TYPE.equals(task.getModuleType())) {
|
||||||
taskMap.put(task.getId(), task);
|
taskMap.put(task.getId(), task);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
List<FileResultEntity> resultRows = fileResultMapper.selectList(new LambdaQueryWrapper<FileResultEntity>()
|
||||||
|
.select(FileResultEntity::getId,
|
||||||
|
FileResultEntity::getTaskId,
|
||||||
|
FileResultEntity::getSourceFilename,
|
||||||
|
FileResultEntity::getResultFilename,
|
||||||
|
FileResultEntity::getResultFileUrl,
|
||||||
|
FileResultEntity::getRowCount,
|
||||||
|
FileResultEntity::getSuccess,
|
||||||
|
FileResultEntity::getErrorMessage,
|
||||||
|
FileResultEntity::getCreatedAt)
|
||||||
|
.eq(FileResultEntity::getModuleType, MODULE_TYPE)
|
||||||
|
.in(FileResultEntity::getTaskId, normalizedIds)
|
||||||
|
.orderByDesc(FileResultEntity::getCreatedAt));
|
||||||
|
Map<Long, FileResultEntity> resultByTaskId = new LinkedHashMap<>();
|
||||||
|
for (FileResultEntity row : resultRows) {
|
||||||
|
if (row.getTaskId() != null) {
|
||||||
|
resultByTaskId.putIfAbsent(row.getTaskId(), row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Map<Long, TaskFileJobEntity> jobMap = taskFileJobService.findAssembleJobsByResultIds(MODULE_TYPE, resultRows.stream()
|
||||||
|
.map(FileResultEntity::getId)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.toList());
|
||||||
for (Long taskId : normalizedIds) {
|
for (Long taskId : normalizedIds) {
|
||||||
FileTaskEntity task = taskMap.get(taskId);
|
FileTaskEntity task = taskMap.get(taskId);
|
||||||
if (task == null) {
|
if (task == null) {
|
||||||
@@ -281,6 +333,10 @@ public class AppearancePatentTaskService {
|
|||||||
}
|
}
|
||||||
AppearancePatentTaskDetailVo detail = new AppearancePatentTaskDetailVo();
|
AppearancePatentTaskDetailVo detail = new AppearancePatentTaskDetailVo();
|
||||||
detail.setTask(toTaskItem(task));
|
detail.setTask(toTaskItem(task));
|
||||||
|
FileResultEntity resultRow = resultByTaskId.get(taskId);
|
||||||
|
if (resultRow != null) {
|
||||||
|
detail.getItems().add(toHistoryItem(resultRow, task.getStatus(), jobMap.get(resultRow.getId())));
|
||||||
|
}
|
||||||
vo.getItems().add(detail);
|
vo.getItems().add(detail);
|
||||||
}
|
}
|
||||||
return vo;
|
return vo;
|
||||||
@@ -890,6 +946,9 @@ public class AppearancePatentTaskService {
|
|||||||
row.setAppearanceRisk(representative.getAppearanceRisk());
|
row.setAppearanceRisk(representative.getAppearanceRisk());
|
||||||
row.setPatentRisk(representative.getPatentRisk());
|
row.setPatentRisk(representative.getPatentRisk());
|
||||||
row.setConclusion(representative.getConclusion());
|
row.setConclusion(representative.getConclusion());
|
||||||
|
row.setTitleReason(representative.getTitleReason());
|
||||||
|
row.setAppearanceReason(representative.getAppearanceReason());
|
||||||
|
row.setPatentReason(representative.getPatentReason());
|
||||||
row.setDone(representative.getDone());
|
row.setDone(representative.getDone());
|
||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
@@ -1057,6 +1116,7 @@ public class AppearancePatentTaskService {
|
|||||||
row.createCell(col++).setCellValue(resultRow == null ? missingReason : userFacingCozeCellValue(resultRow, resultRow.getPatentRisk()));
|
row.createCell(col++).setCellValue(resultRow == null ? missingReason : userFacingCozeCellValue(resultRow, resultRow.getPatentRisk()));
|
||||||
row.createCell(col).setCellValue(resultRow == null ? "未送检" : userFacingConclusion(resultRow));
|
row.createCell(col).setCellValue(resultRow == null ? "未送检" : userFacingConclusion(resultRow));
|
||||||
}
|
}
|
||||||
|
writeReasonSheet(workbook, headerStyle, parsed, resultMap);
|
||||||
workbook.write(fos);
|
workbook.write(fos);
|
||||||
workbook.dispose();
|
workbook.dispose();
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
@@ -1064,6 +1124,52 @@ public class AppearancePatentTaskService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void writeReasonSheet(SXSSFWorkbook workbook,
|
||||||
|
CellStyle headerStyle,
|
||||||
|
AppearancePatentParsedPayloadDto parsed,
|
||||||
|
Map<String, AppearancePatentResultRowDto> resultMap) {
|
||||||
|
Sheet sheet = workbook.createSheet("原因");
|
||||||
|
Row header = sheet.createRow(0);
|
||||||
|
List<String> headers = List.of("ASIN", "外观原因", "专利原因", "标题原因");
|
||||||
|
for (int i = 0; i < headers.size(); i++) {
|
||||||
|
Cell cell = header.createCell(i);
|
||||||
|
cell.setCellValue(headers.get(i));
|
||||||
|
cell.setCellStyle(headerStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<String> writtenAsins = new LinkedHashSet<>();
|
||||||
|
int rowIndex = 1;
|
||||||
|
for (AppearancePatentParsedRowVo parsedRow : parsed.getAllItems()) {
|
||||||
|
String asin = normalize(parsedRow.getAsin()).toUpperCase(Locale.ROOT);
|
||||||
|
if (asin.isBlank() || !writtenAsins.add(asin)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
AppearancePatentResultRowDto resultRow = findResultRow(parsedRow, resultMap);
|
||||||
|
if (resultRow == null) {
|
||||||
|
resultRow = findResultRowByAsin(asin, resultMap);
|
||||||
|
}
|
||||||
|
Row row = sheet.createRow(rowIndex++);
|
||||||
|
row.createCell(0).setCellValue(asin);
|
||||||
|
row.createCell(1).setCellValue(resultRow == null ? "" : firstNonBlank(resultRow.getAppearanceReason(), ""));
|
||||||
|
row.createCell(2).setCellValue(resultRow == null ? "" : firstNonBlank(resultRow.getPatentReason(), ""));
|
||||||
|
row.createCell(3).setCellValue(resultRow == null ? "" : firstNonBlank(resultRow.getTitleReason(), ""));
|
||||||
|
rowIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private AppearancePatentResultRowDto findResultRowByAsin(String asin, Map<String, AppearancePatentResultRowDto> resultMap) {
|
||||||
|
String normalizedAsin = normalize(asin).toUpperCase(Locale.ROOT);
|
||||||
|
if (normalizedAsin.isBlank() || resultMap == null || resultMap.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (AppearancePatentResultRowDto row : resultMap.values()) {
|
||||||
|
if (row != null && normalizedAsin.equals(normalize(row.getAsin()).toUpperCase(Locale.ROOT))) {
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private ParsedWorkbook parseWorkbook(File input, AppearancePatentSourceFileDto source) {
|
private ParsedWorkbook parseWorkbook(File input, AppearancePatentSourceFileDto source) {
|
||||||
DataFormatter formatter = new DataFormatter();
|
DataFormatter formatter = new DataFormatter();
|
||||||
try (FileInputStream fis = new FileInputStream(input); Workbook workbook = WorkbookFactory.create(fis)) {
|
try (FileInputStream fis = new FileInputStream(input); Workbook workbook = WorkbookFactory.create(fis)) {
|
||||||
@@ -1342,14 +1448,14 @@ public class AppearancePatentTaskService {
|
|||||||
return vo;
|
return vo;
|
||||||
}
|
}
|
||||||
|
|
||||||
private AppearancePatentHistoryItemVo toHistoryItem(FileResultEntity row, String taskStatus) {
|
private AppearancePatentHistoryItemVo toHistoryItem(FileResultEntity row, String taskStatus, TaskFileJobEntity job) {
|
||||||
AppearancePatentHistoryItemVo vo = new AppearancePatentHistoryItemVo();
|
AppearancePatentHistoryItemVo vo = new AppearancePatentHistoryItemVo();
|
||||||
vo.setResultId(row.getId());
|
vo.setResultId(row.getId());
|
||||||
vo.setTaskId(row.getTaskId());
|
vo.setTaskId(row.getTaskId());
|
||||||
vo.setSourceFilename(row.getSourceFilename());
|
vo.setSourceFilename(row.getSourceFilename());
|
||||||
vo.setResultFilename(row.getResultFilename());
|
vo.setResultFilename(row.getResultFilename());
|
||||||
vo.setDownloadUrl(null);
|
vo.setDownloadUrl(null);
|
||||||
attachFileJobState(vo, row);
|
attachFileJobState(vo, row, job);
|
||||||
vo.setTaskStatus(taskStatus);
|
vo.setTaskStatus(taskStatus);
|
||||||
vo.setSuccess(row.getSuccess() != null && row.getSuccess() == 1);
|
vo.setSuccess(row.getSuccess() != null && row.getSuccess() == 1);
|
||||||
vo.setError(row.getErrorMessage());
|
vo.setError(row.getErrorMessage());
|
||||||
@@ -1358,8 +1464,7 @@ public class AppearancePatentTaskService {
|
|||||||
return vo;
|
return vo;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void attachFileJobState(AppearancePatentHistoryItemVo vo, FileResultEntity row) {
|
private void attachFileJobState(AppearancePatentHistoryItemVo vo, FileResultEntity row, TaskFileJobEntity job) {
|
||||||
TaskFileJobEntity job = taskFileJobService.findAssembleJob(row.getTaskId(), MODULE_TYPE, row.getId());
|
|
||||||
vo.setFileReady(row.getResultFileUrl() != null && !row.getResultFileUrl().isBlank());
|
vo.setFileReady(row.getResultFileUrl() != null && !row.getResultFileUrl().isBlank());
|
||||||
if (job == null) {
|
if (job == null) {
|
||||||
vo.setFileStatus(Boolean.TRUE.equals(vo.getFileReady()) ? "SUCCESS" : null);
|
vo.setFileStatus(Boolean.TRUE.equals(vo.getFileReady()) ? "SUCCESS" : null);
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ import org.springframework.stereotype.Service;
|
|||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@@ -190,6 +192,27 @@ public class TaskFileJobService {
|
|||||||
return findJob(taskId, moduleType, resultId, JOB_TYPE_ASSEMBLE_RESULT);
|
return findJob(taskId, moduleType, resultId, JOB_TYPE_ASSEMBLE_RESULT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<Long, TaskFileJobEntity> findAssembleJobsByResultIds(String moduleType, List<Long> resultIds) {
|
||||||
|
List<Long> normalizedIds = resultIds == null ? List.of() : resultIds.stream()
|
||||||
|
.filter(id -> id != null && id > 0)
|
||||||
|
.distinct()
|
||||||
|
.toList();
|
||||||
|
if (normalizedIds.isEmpty() || moduleType == null || moduleType.isBlank()) {
|
||||||
|
return Map.of();
|
||||||
|
}
|
||||||
|
List<TaskFileJobEntity> jobs = taskFileJobMapper.selectList(new LambdaQueryWrapper<TaskFileJobEntity>()
|
||||||
|
.eq(TaskFileJobEntity::getModuleType, moduleType)
|
||||||
|
.eq(TaskFileJobEntity::getJobType, JOB_TYPE_ASSEMBLE_RESULT)
|
||||||
|
.in(TaskFileJobEntity::getResultId, normalizedIds));
|
||||||
|
Map<Long, TaskFileJobEntity> jobMap = new LinkedHashMap<>();
|
||||||
|
for (TaskFileJobEntity job : jobs) {
|
||||||
|
if (job != null && job.getResultId() != null) {
|
||||||
|
jobMap.putIfAbsent(job.getResultId(), job);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return jobMap;
|
||||||
|
}
|
||||||
|
|
||||||
public long countUnfinishedAssembleJobs(Long taskId, String moduleType) {
|
public long countUnfinishedAssembleJobs(Long taskId, String moduleType) {
|
||||||
if (taskId == null || taskId <= 0 || moduleType == null || moduleType.isBlank()) {
|
if (taskId == null || taskId <= 0 || moduleType == null || moduleType.isBlank()) {
|
||||||
return 0L;
|
return 0L;
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
SET @db_name = DATABASE();
|
||||||
|
|
||||||
|
SET @idx_exists := (
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM information_schema.STATISTICS
|
||||||
|
WHERE TABLE_SCHEMA = @db_name
|
||||||
|
AND TABLE_NAME = 'biz_task_file_job'
|
||||||
|
AND INDEX_NAME = 'idx_file_job_module_type_result'
|
||||||
|
);
|
||||||
|
SET @sql := IF(@idx_exists = 0,
|
||||||
|
'ALTER TABLE biz_task_file_job ADD INDEX idx_file_job_module_type_result (module_type, job_type, result_id)',
|
||||||
|
'SELECT 1'
|
||||||
|
);
|
||||||
|
PREPARE stmt FROM @sql;
|
||||||
|
EXECUTE stmt;
|
||||||
|
DEALLOCATE PREPARE stmt;
|
||||||
@@ -173,6 +173,9 @@ const pollingTaskIds = ref<number[]>([])
|
|||||||
const pendingFileTaskIds = ref<number[]>([])
|
const pendingFileTaskIds = ref<number[]>([])
|
||||||
const pollTimer = ref<number | null>(null)
|
const pollTimer = ref<number | null>(null)
|
||||||
const pollingInFlight = ref(false)
|
const pollingInFlight = ref(false)
|
||||||
|
const HISTORY_CACHE_TTL_MS = 3000
|
||||||
|
let historyInFlight: Promise<void> | null = null
|
||||||
|
let lastHistoryLoadedAt = 0
|
||||||
let disposed = false
|
let disposed = false
|
||||||
const timers = createCategorizedTimers('appearance-patent')
|
const timers = createCategorizedTimers('appearance-patent')
|
||||||
|
|
||||||
@@ -189,7 +192,12 @@ const parsedRows = computed<AppearancePatentParsedRow[]>(() =>
|
|||||||
)
|
)
|
||||||
const previewRows = computed(() => parsedGroups.value[0]?.items || [])
|
const previewRows = computed(() => parsedGroups.value[0]?.items || [])
|
||||||
const currentItems = computed(() => historyItems.value.filter((i) => i.taskId != null && pollingTaskIds.value.includes(i.taskId)))
|
const currentItems = computed(() => historyItems.value.filter((i) => i.taskId != null && pollingTaskIds.value.includes(i.taskId)))
|
||||||
const historyOnlyItems = computed(() => historyItems.value.filter((i) => (i.taskStatus || '') !== 'RUNNING'))
|
const historyOnlyItems = computed(() =>
|
||||||
|
historyItems.value.filter((i) => {
|
||||||
|
const status = normalizeTaskStatus(i)
|
||||||
|
return status !== 'RUNNING' && status !== 'PENDING'
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
function effectiveAiPrompt() {
|
function effectiveAiPrompt() {
|
||||||
return aiPrompt.value.trim() || defaultAiPrompt
|
return aiPrompt.value.trim() || defaultAiPrompt
|
||||||
@@ -210,11 +218,17 @@ function savePollingIds() {
|
|||||||
else window.localStorage.setItem(pollingKey(), JSON.stringify(ids))
|
else window.localStorage.setItem(pollingKey(), JSON.stringify(ids))
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearPollingIds() {
|
function loadPollingIds() {
|
||||||
pollingTaskIds.value = []
|
if (typeof window === 'undefined') return
|
||||||
pendingFileTaskIds.value = []
|
try {
|
||||||
savePollingIds()
|
const raw = window.localStorage.getItem(pollingKey())
|
||||||
stopPolling()
|
const ids = raw ? JSON.parse(raw) : []
|
||||||
|
pollingTaskIds.value = Array.isArray(ids)
|
||||||
|
? ids.filter((id): id is number => typeof id === 'number' && id > 0)
|
||||||
|
: []
|
||||||
|
} catch {
|
||||||
|
pollingTaskIds.value = []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function uploadAppearancePathsToJava(paths: Array<string | BrandExpandFolderItem>) {
|
async function uploadAppearancePathsToJava(paths: Array<string | BrandExpandFolderItem>) {
|
||||||
@@ -306,7 +320,7 @@ async function parseFiles() {
|
|||||||
: [])
|
: [])
|
||||||
queuePayloadText.value = ''
|
queuePayloadText.value = ''
|
||||||
await loadDashboard()
|
await loadDashboard()
|
||||||
await loadHistory()
|
await loadHistory({ force: true })
|
||||||
ElMessage.success(`解析完成,共 ${res.groupCount || 0} 组 / ${res.acceptedRows} 条`)
|
ElMessage.success(`解析完成,共 ${res.groupCount || 0} 组 / ${res.acceptedRows} 条`)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ElMessage.error(e instanceof Error ? e.message : '解析失败')
|
ElMessage.error(e instanceof Error ? e.message : '解析失败')
|
||||||
@@ -366,21 +380,28 @@ async function pushToPythonQueue() {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
queuePayloadText.value = JSON.stringify(payload, null, 2)
|
queuePayloadText.value = JSON.stringify(payload, null, 2)
|
||||||
|
await activateAppearancePatentTask(taskId)
|
||||||
const result = await api.enqueue_json(payload)
|
const result = await api.enqueue_json(payload)
|
||||||
if (!result?.success) {
|
if (!result?.success) {
|
||||||
ElMessage.error(result?.error || '推送失败')
|
ElMessage.error(result?.error || '推送失败')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
await activateAppearancePatentTask(taskId)
|
|
||||||
addPollingTask(taskId)
|
addPollingTask(taskId)
|
||||||
|
clearParsedTask()
|
||||||
await loadDashboard()
|
await loadDashboard()
|
||||||
await loadHistory()
|
await loadHistory({ force: true })
|
||||||
ElMessage.success('已推送到 Python 队列')
|
ElMessage.success('已推送到 Python 队列')
|
||||||
} finally {
|
} finally {
|
||||||
pushing.value = false
|
pushing.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clearParsedTask() {
|
||||||
|
parseResult.value = null
|
||||||
|
parsedGroups.value = []
|
||||||
|
queuePayloadText.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
function addPollingTask(taskId: number) {
|
function addPollingTask(taskId: number) {
|
||||||
if (!pollingTaskIds.value.includes(taskId)) {
|
if (!pollingTaskIds.value.includes(taskId)) {
|
||||||
pollingTaskIds.value = [...pollingTaskIds.value, taskId]
|
pollingTaskIds.value = [...pollingTaskIds.value, taskId]
|
||||||
@@ -399,6 +420,7 @@ function addPendingFileTask(taskId: number) {
|
|||||||
pendingFileTaskIds.value = [...pendingFileTaskIds.value, taskId]
|
pendingFileTaskIds.value = [...pendingFileTaskIds.value, taskId]
|
||||||
savePollingIds()
|
savePollingIds()
|
||||||
}
|
}
|
||||||
|
ensurePolling()
|
||||||
}
|
}
|
||||||
|
|
||||||
function removePendingFileTask(taskId: number) {
|
function removePendingFileTask(taskId: number) {
|
||||||
@@ -446,16 +468,29 @@ async function refreshTaskProgress() {
|
|||||||
}
|
}
|
||||||
pollingInFlight.value = true
|
pollingInFlight.value = true
|
||||||
try {
|
try {
|
||||||
let shouldRefreshHistory = pendingFileTaskIds.value.length > 0
|
let shouldRefreshDashboard = false
|
||||||
if (pollingTaskIds.value.length) {
|
let shouldRefreshHistory = false
|
||||||
const batch = await getAppearancePatentTaskProgressBatch(pollingTaskIds.value)
|
const taskIds = Array.from(new Set([...pollingTaskIds.value, ...pendingFileTaskIds.value]))
|
||||||
|
if (taskIds.length) {
|
||||||
|
const batch = await getAppearancePatentTaskProgressBatch(taskIds, { force: pendingFileTaskIds.value.length > 0 })
|
||||||
for (const detail of batch.items || []) {
|
for (const detail of batch.items || []) {
|
||||||
const task = detail.task
|
const task = detail.task
|
||||||
if (!task?.id) continue
|
if (!task?.id) continue
|
||||||
|
const item = detail.items?.[0]
|
||||||
|
if (item) mergeHistoryItem(item)
|
||||||
if (task.status === 'SUCCESS' || task.status === 'FAILED') {
|
if (task.status === 'SUCCESS' || task.status === 'FAILED') {
|
||||||
removePollingTask(task.id)
|
removePollingTask(task.id)
|
||||||
shouldRefreshHistory = true
|
shouldRefreshDashboard = true
|
||||||
if (task.status === 'SUCCESS') addPendingFileTask(task.id)
|
if (task.status === 'SUCCESS') addPendingFileTask(task.id)
|
||||||
|
else removePendingFileTask(task.id)
|
||||||
|
}
|
||||||
|
if (item && task.status === 'SUCCESS' && pendingFileTaskIds.value.includes(task.id)) {
|
||||||
|
const fileStatus = (item.fileStatus || '').toUpperCase()
|
||||||
|
if (item.fileReady || fileStatus === 'SUCCESS' || fileStatus === 'FAILED') {
|
||||||
|
removePendingFileTask(task.id)
|
||||||
|
shouldRefreshDashboard = true
|
||||||
|
shouldRefreshHistory = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (batch.missingTaskIds?.length) {
|
if (batch.missingTaskIds?.length) {
|
||||||
@@ -464,33 +499,55 @@ async function refreshTaskProgress() {
|
|||||||
removePendingFileTask(taskId)
|
removePendingFileTask(taskId)
|
||||||
})
|
})
|
||||||
shouldRefreshHistory = true
|
shouldRefreshHistory = true
|
||||||
|
shouldRefreshDashboard = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (shouldRefreshHistory) {
|
if (shouldRefreshDashboard) {
|
||||||
await loadDashboard()
|
await loadDashboard()
|
||||||
await loadHistory()
|
|
||||||
settlePendingFileTasks()
|
|
||||||
}
|
}
|
||||||
|
if (shouldRefreshHistory) {
|
||||||
|
await loadHistory()
|
||||||
|
}
|
||||||
|
settlePendingFileTasks()
|
||||||
} finally {
|
} finally {
|
||||||
pollingInFlight.value = false
|
pollingInFlight.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mergeHistoryItem(item: AppearancePatentHistoryItem) {
|
||||||
|
if (!item.taskId && !item.resultId) return
|
||||||
|
const index = historyItems.value.findIndex((row) =>
|
||||||
|
(item.resultId != null && row.resultId === item.resultId)
|
||||||
|
|| (item.taskId != null && row.taskId === item.taskId),
|
||||||
|
)
|
||||||
|
if (index >= 0) {
|
||||||
|
historyItems.value[index] = { ...historyItems.value[index], ...item }
|
||||||
|
} else {
|
||||||
|
historyItems.value = [item, ...historyItems.value]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function settlePendingFileTasks() {
|
function settlePendingFileTasks() {
|
||||||
if (!pendingFileTaskIds.value.length) return
|
if (!pendingFileTaskIds.value.length) return
|
||||||
const remaining: number[] = []
|
const remaining: number[] = []
|
||||||
for (const taskId of pendingFileTaskIds.value) {
|
for (const taskId of pendingFileTaskIds.value) {
|
||||||
const item = historyItems.value.find((row) => row.taskId === taskId)
|
const item = historyItems.value.find((row) => row.taskId === taskId)
|
||||||
if (!item) continue
|
if (!item) {
|
||||||
|
remaining.push(taskId)
|
||||||
|
continue
|
||||||
|
}
|
||||||
const taskStatus = (item.taskStatus || '').toUpperCase()
|
const taskStatus = (item.taskStatus || '').toUpperCase()
|
||||||
const fileStatus = (item.fileStatus || '').toUpperCase()
|
const fileStatus = (item.fileStatus || '').toUpperCase()
|
||||||
if (taskStatus === 'FAILED' || item.fileReady || fileStatus === 'SUCCESS' || fileStatus === 'FAILED') {
|
if (taskStatus === 'FAILED' || item.fileReady || fileStatus === 'SUCCESS' || fileStatus === 'FAILED') {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
remaining.push(taskId)
|
if (taskStatus === 'SUCCESS') {
|
||||||
|
remaining.push(taskId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pendingFileTaskIds.value = remaining
|
pendingFileTaskIds.value = remaining
|
||||||
savePollingIds()
|
savePollingIds()
|
||||||
|
if (pendingFileTaskIds.value.length) ensurePolling()
|
||||||
}
|
}
|
||||||
|
|
||||||
function seedPendingFileTasksFromHistory() {
|
function seedPendingFileTasksFromHistory() {
|
||||||
@@ -506,14 +563,25 @@ async function loadDashboard() {
|
|||||||
dashboard.value = await getAppearancePatentDashboard()
|
dashboard.value = await getAppearancePatentDashboard()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadHistory() {
|
async function loadHistory(options: { force?: boolean } = {}) {
|
||||||
const res = await getAppearancePatentHistory()
|
const now = Date.now()
|
||||||
historyItems.value = res.items || []
|
if (!options.force && historyItems.value.length && now - lastHistoryLoadedAt < HISTORY_CACHE_TTL_MS) return
|
||||||
|
if (historyInFlight) return historyInFlight
|
||||||
|
historyInFlight = getAppearancePatentHistory()
|
||||||
|
.then((res) => {
|
||||||
|
historyItems.value = res.items || []
|
||||||
|
lastHistoryLoadedAt = Date.now()
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
historyInFlight = null
|
||||||
|
})
|
||||||
|
return historyInFlight
|
||||||
}
|
}
|
||||||
|
|
||||||
function statusText(item: AppearancePatentHistoryItem) {
|
function statusText(item: AppearancePatentHistoryItem) {
|
||||||
const status = item.taskStatus || ''
|
const status = normalizeTaskStatus(item)
|
||||||
if (status === 'RUNNING') return '执行中'
|
if (status === 'RUNNING') return '执行中'
|
||||||
|
if (status === 'PENDING') return '已解析待推送'
|
||||||
if (isResultPreparing(item)) return '结果生成中'
|
if (isResultPreparing(item)) return '结果生成中'
|
||||||
if (isResultBuildFailed(item)) return '结果生成失败'
|
if (isResultBuildFailed(item)) return '结果生成失败'
|
||||||
if (status === 'SUCCESS' || item.success) return '已完成'
|
if (status === 'SUCCESS' || item.success) return '已完成'
|
||||||
@@ -522,22 +590,26 @@ function statusText(item: AppearancePatentHistoryItem) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function statusClass(item: AppearancePatentHistoryItem) {
|
function statusClass(item: AppearancePatentHistoryItem) {
|
||||||
const status = item.taskStatus || ''
|
const status = normalizeTaskStatus(item)
|
||||||
if (status === 'RUNNING' || isResultPreparing(item)) return 'running'
|
if (status === 'RUNNING' || isResultPreparing(item)) return 'running'
|
||||||
if (isResultBuildFailed(item) || status === 'FAILED') return 'failed'
|
if (isResultBuildFailed(item) || status === 'FAILED') return 'failed'
|
||||||
if (status === 'SUCCESS' || item.success) return 'success'
|
if (status === 'SUCCESS' || item.success) return 'success'
|
||||||
return 'running'
|
return 'pending'
|
||||||
}
|
}
|
||||||
|
|
||||||
function isResultPreparing(item: AppearancePatentHistoryItem) {
|
function isResultPreparing(item: AppearancePatentHistoryItem) {
|
||||||
const status = item.taskStatus || ''
|
const status = normalizeTaskStatus(item)
|
||||||
const fileStatus = (item.fileStatus || '').toUpperCase()
|
const fileStatus = (item.fileStatus || '').toUpperCase()
|
||||||
if (!(status === 'SUCCESS' || item.success)) return false
|
if (status !== 'SUCCESS') return false
|
||||||
if (canDownload(item)) return false
|
if (canDownload(item)) return false
|
||||||
if (fileStatus === 'FAILED' || fileStatus === 'SUCCESS') return false
|
if (fileStatus === 'FAILED' || fileStatus === 'SUCCESS') return false
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeTaskStatus(item: AppearancePatentHistoryItem) {
|
||||||
|
return (item.taskStatus || '').toUpperCase()
|
||||||
|
}
|
||||||
|
|
||||||
function isResultBuildFailed(item: AppearancePatentHistoryItem) {
|
function isResultBuildFailed(item: AppearancePatentHistoryItem) {
|
||||||
return (item.fileStatus || '').toUpperCase() === 'FAILED'
|
return (item.fileStatus || '').toUpperCase() === 'FAILED'
|
||||||
}
|
}
|
||||||
@@ -587,7 +659,7 @@ async function deleteTaskRecord(item: AppearancePatentHistoryItem) {
|
|||||||
await deleteAppearancePatentHistory(item.resultId)
|
await deleteAppearancePatentHistory(item.resultId)
|
||||||
}
|
}
|
||||||
await loadDashboard()
|
await loadDashboard()
|
||||||
await loadHistory()
|
await loadHistory({ force: true })
|
||||||
ElMessage.success('已删除')
|
ElMessage.success('已删除')
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ElMessage.error(e instanceof Error ? e.message : '删除失败')
|
ElMessage.error(e instanceof Error ? e.message : '删除失败')
|
||||||
@@ -595,12 +667,13 @@ async function deleteTaskRecord(item: AppearancePatentHistoryItem) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
clearPollingIds()
|
loadPollingIds()
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
loadDashboard().catch(() => undefined),
|
loadDashboard().catch(() => undefined),
|
||||||
loadHistory().catch(() => undefined),
|
loadHistory().catch(() => undefined),
|
||||||
])
|
])
|
||||||
seedPendingFileTasksFromHistory()
|
seedPendingFileTasksFromHistory()
|
||||||
|
if (pollingTaskIds.value.length || pendingFileTaskIds.value.length) ensurePolling()
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
@@ -654,6 +727,7 @@ onUnmounted(() => {
|
|||||||
.status.success { background: rgba(46, 204, 113, .18); color: #2ecc71; }
|
.status.success { background: rgba(46, 204, 113, .18); color: #2ecc71; }
|
||||||
.status.failed { background: rgba(231, 76, 60, .18); color: #ff6b6b; }
|
.status.failed { background: rgba(231, 76, 60, .18); color: #ff6b6b; }
|
||||||
.status.running { background: rgba(52, 152, 219, .18); color: #3498db; }
|
.status.running { background: rgba(52, 152, 219, .18); color: #3498db; }
|
||||||
|
.status.pending { background: rgba(149, 165, 166, .18); color: #bdc3c7; }
|
||||||
.result-hint { margin-top: 6px; color: #e0b96d; }
|
.result-hint { margin-top: 6px; color: #e0b96d; }
|
||||||
.download { padding: 6px 10px; color: #d6ecff; background: rgba(52, 152, 219, .18); }
|
.download { padding: 6px 10px; color: #d6ecff; background: rgba(52, 152, 219, .18); }
|
||||||
.btn-delete { padding: 6px 10px; color: #ff8f8f; background: rgba(231, 76, 60, .12); }
|
.btn-delete { padding: 6px 10px; color: #ff8f8f; background: rgba(231, 76, 60, .12); }
|
||||||
|
|||||||
@@ -15,6 +15,10 @@ type TaskProgressCacheEntry = {
|
|||||||
data: unknown;
|
data: unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface TaskProgressBatchOptions {
|
||||||
|
force?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
const taskProgressResponseCache = new Map<string, TaskProgressCacheEntry>();
|
const taskProgressResponseCache = new Map<string, TaskProgressCacheEntry>();
|
||||||
const taskProgressInflightRequests = new Map<string, Promise<unknown>>();
|
const taskProgressInflightRequests = new Map<string, Promise<unknown>>();
|
||||||
|
|
||||||
@@ -44,7 +48,11 @@ function buildTaskProgressRequestKey(path: string, taskIds: number[]) {
|
|||||||
return `${path}::${taskIds.join(",")}`;
|
return `${path}::${taskIds.join(",")}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function postTaskProgressBatch<T>(path: string, taskIds: number[]) {
|
async function postTaskProgressBatch<T>(
|
||||||
|
path: string,
|
||||||
|
taskIds: number[],
|
||||||
|
options: TaskProgressBatchOptions = {},
|
||||||
|
) {
|
||||||
const normalizedTaskIds = normalizeTaskIds(taskIds);
|
const normalizedTaskIds = normalizeTaskIds(taskIds);
|
||||||
if (!normalizedTaskIds.length) {
|
if (!normalizedTaskIds.length) {
|
||||||
return { items: [], missingTaskIds: [] } as T;
|
return { items: [], missingTaskIds: [] } as T;
|
||||||
@@ -53,12 +61,12 @@ async function postTaskProgressBatch<T>(path: string, taskIds: number[]) {
|
|||||||
const cacheKey = buildTaskProgressRequestKey(path, normalizedTaskIds);
|
const cacheKey = buildTaskProgressRequestKey(path, normalizedTaskIds);
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const cached = taskProgressResponseCache.get(cacheKey);
|
const cached = taskProgressResponseCache.get(cacheKey);
|
||||||
if (cached && cached.expiresAt > now) {
|
if (!options.force && cached && cached.expiresAt > now) {
|
||||||
return cached.data as T;
|
return cached.data as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
const inflight = taskProgressInflightRequests.get(cacheKey);
|
const inflight = taskProgressInflightRequests.get(cacheKey);
|
||||||
if (inflight) {
|
if (!options.force && inflight) {
|
||||||
return (await inflight) as T;
|
return (await inflight) as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1500,15 +1508,19 @@ export function getAppearancePatentHistory() {
|
|||||||
return unwrapJavaResponse(
|
return unwrapJavaResponse(
|
||||||
get<JavaApiResponse<AppearancePatentHistoryVo>>(
|
get<JavaApiResponse<AppearancePatentHistoryVo>>(
|
||||||
`${JAVA_API_PREFIX}/appearance-patent/history`,
|
`${JAVA_API_PREFIX}/appearance-patent/history`,
|
||||||
{ params: { user_id: getCurrentUserId() } },
|
{ params: { user_id: getCurrentUserId(), limit: 50 } },
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAppearancePatentTaskProgressBatch(taskIds: number[]) {
|
export function getAppearancePatentTaskProgressBatch(
|
||||||
|
taskIds: number[],
|
||||||
|
options: TaskProgressBatchOptions = {},
|
||||||
|
) {
|
||||||
return postTaskProgressBatch<AppearancePatentTaskBatchVo>(
|
return postTaskProgressBatch<AppearancePatentTaskBatchVo>(
|
||||||
`${JAVA_API_PREFIX}/appearance-patent/tasks/progress/batch`,
|
`${JAVA_API_PREFIX}/appearance-patent/tasks/progress/batch`,
|
||||||
taskIds,
|
taskIds,
|
||||||
|
options,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user