视频任务管理更新
This commit is contained in:
+12
-1
@@ -5,6 +5,7 @@ import com.nanri.aiimage.common.util.DownloadHeaderUtil;
|
||||
import com.nanri.aiimage.modules.pricetrack.model.dto.PriceTrackCandidateAddRequest;
|
||||
import com.nanri.aiimage.modules.pricetrack.model.dto.PriceTrackCountryPreferenceSaveRequest;
|
||||
import com.nanri.aiimage.modules.pricetrack.model.dto.PriceTrackCreateTaskRequest;
|
||||
import com.nanri.aiimage.modules.pricetrack.model.dto.PriceTrackDispatchFailedRequest;
|
||||
import com.nanri.aiimage.modules.pricetrack.model.dto.PriceTrackLoopRunChildFinishedRequest;
|
||||
import com.nanri.aiimage.modules.pricetrack.model.dto.PriceTrackLoopRunCreateRequest;
|
||||
import com.nanri.aiimage.modules.pricetrack.model.dto.PriceTrackMatchShopsRequest;
|
||||
@@ -145,7 +146,7 @@ public class PriceTrackController {
|
||||
@PathVariable Long taskId,
|
||||
@Parameter(description = "国家代码", required = true, example = "DE")
|
||||
@RequestParam("country") String country,
|
||||
@Parameter(description = "ASIN", required = true, example = "B0XXXX")
|
||||
@Parameter(description = "商品 ASIN", required = true, example = "B0XXXX")
|
||||
@RequestParam("asin") String asin) {
|
||||
return ApiResponse.success(priceTrackTaskService.checkTaskSkipAsin(taskId, country, asin));
|
||||
}
|
||||
@@ -182,6 +183,16 @@ public class PriceTrackController {
|
||||
return ApiResponse.success(priceTrackTaskService.createTask(request));
|
||||
}
|
||||
|
||||
@PostMapping("/tasks/{taskId}/dispatch-failed")
|
||||
@Operation(summary = "标记 Python 入队失败", description = "前端创建任务后若 Python 队列拒绝接收,立即将任务及占位店铺标记为失败。")
|
||||
public ApiResponse<Void> markDispatchFailed(
|
||||
@PathVariable Long taskId,
|
||||
@RequestParam("user_id") Long userId,
|
||||
@Valid @RequestBody PriceTrackDispatchFailedRequest request) {
|
||||
priceTrackTaskService.markDispatchFailed(taskId, userId, request.getErrorMessage());
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@PostMapping("/loop-runs")
|
||||
@Operation(summary = "创建跟价循环任务", description = "创建整批店铺的多轮循环主任务。")
|
||||
public ApiResponse<PriceTrackLoopRunVo> createLoopRun(@Valid @RequestBody PriceTrackLoopRunCreateRequest request) {
|
||||
|
||||
+3
-3
@@ -33,12 +33,12 @@ public class PriceTrackCreateTaskRequest {
|
||||
@NotEmpty(message = "国家列表不能为空")
|
||||
@Schema(description = "国家代码列表,顺序即跟价处理顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "[\"DE\",\"UK\",\"FR\"]")
|
||||
private List<String> countryCodes;
|
||||
@Schema(description = "optional loop run id", example = "1")
|
||||
@Schema(description = "可选的循环运行 ID", example = "1")
|
||||
private Long loopRunId;
|
||||
|
||||
@Schema(description = "optional round index, 1-based", example = "2")
|
||||
@Schema(description = "可选的轮次序号,从 1 开始", example = "2")
|
||||
private Integer roundIndex;
|
||||
|
||||
@Schema(description = "optional shop index, 0-based", example = "0")
|
||||
@Schema(description = "可选的店铺序号,从 0 开始", example = "0")
|
||||
private Integer shopIndex;
|
||||
}
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package com.nanri.aiimage.modules.pricetrack.model.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Schema(description = "跟价任务推送 Python 队列失败请求")
|
||||
public class PriceTrackDispatchFailedRequest {
|
||||
|
||||
@NotBlank(message = "errorMessage不能为空")
|
||||
@Schema(description = "Python 队列拒绝或调用失败的原始原因", example = "当前店铺正在执行中")
|
||||
private String errorMessage;
|
||||
}
|
||||
+1
-1
@@ -32,7 +32,7 @@ public class PriceTrackLoopRunCreateRequest {
|
||||
private List<String> countryCodes = new ArrayList<>();
|
||||
|
||||
@NotBlank(message = "executionMode不能为空")
|
||||
@Schema(description = "FINITE or INFINITE", example = "FINITE")
|
||||
@Schema(description = "循环模式:FINITE 表示有限次数,INFINITE 表示无限循环", example = "FINITE")
|
||||
private String executionMode;
|
||||
|
||||
@Schema(description = "固定轮次时必填", example = "3")
|
||||
|
||||
+5
-5
@@ -8,20 +8,20 @@ import lombok.Data;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Schema(description = "Price track match shops request")
|
||||
@Schema(description = "跟价店铺匹配请求")
|
||||
public class PriceTrackMatchShopsRequest {
|
||||
|
||||
@NotNull(message = "userId is required")
|
||||
@Schema(description = "Current user id", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@Schema(description = "当前用户 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Long userId;
|
||||
|
||||
@NotEmpty(message = "shopNames cannot be empty")
|
||||
@Schema(description = "Shop names to match", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@Schema(description = "需要匹配的店铺名称", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private List<String> shopNames;
|
||||
|
||||
@Schema(description = "Selected ASIN file local paths")
|
||||
@Schema(description = "已选择的 ASIN 文件本地路径")
|
||||
private List<String> asinFiles;
|
||||
|
||||
@Schema(description = "Selected country codes")
|
||||
@Schema(description = "已选择的国家代码")
|
||||
private List<String> countryCodes;
|
||||
}
|
||||
|
||||
+1
-1
@@ -65,7 +65,7 @@ public class PriceTrackSubmitResultRequest {
|
||||
@Schema(description = "店铺商城名称", example = "Amazon.de")
|
||||
private String shopMallName;
|
||||
|
||||
@Schema(description = "ASIN", example = "B0ABCDE123")
|
||||
@Schema(description = "商品 ASIN", example = "B0ABCDE123")
|
||||
private String asin;
|
||||
|
||||
@Schema(description = "价格", example = "19.99")
|
||||
|
||||
+7
-7
@@ -7,23 +7,23 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
@Schema(description = "price track create task response")
|
||||
@Schema(description = "跟价任务创建响应")
|
||||
public class PriceTrackCreateTaskVo {
|
||||
@Schema(description = "task id", example = "200")
|
||||
@Schema(description = "任务 ID", example = "200")
|
||||
private Long taskId;
|
||||
|
||||
@Schema(description = "initial task items")
|
||||
@Schema(description = "初始任务项")
|
||||
private List<PriceTrackResultItemVo> items;
|
||||
|
||||
@Schema(description = "all skip asins grouped by country")
|
||||
@Schema(description = "按国家分组的全部跳过跟价 ASIN")
|
||||
private Map<String, List<String>> skipAsinsByCountry;
|
||||
|
||||
@Schema(description = "all skip asin details grouped by country code, each item includes asin and minimumPrice")
|
||||
@Schema(description = "按国家代码分组的全部跳过跟价 ASIN 明细,每项包含 asin 和 minimumPrice")
|
||||
private Map<String, List<Map<String, String>>> skipAsinDetailsByCountry;
|
||||
|
||||
@Schema(description = "parsed asin rows by country")
|
||||
@Schema(description = "按国家分组的已解析 ASIN 行")
|
||||
private Map<String, List<Map<String, String>>> asinRowsByCountry;
|
||||
|
||||
@Schema(description = "minimum price grouped by country code and asin")
|
||||
@Schema(description = "按国家代码和 ASIN 分组的最低价格")
|
||||
private Map<String, Map<String, String>> minimumPriceByCountryAndAsin;
|
||||
}
|
||||
|
||||
+16
-16
@@ -8,53 +8,53 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
@Schema(description = "Price track shop match response")
|
||||
@Schema(description = "跟价店铺匹配响应")
|
||||
public class PriceTrackMatchShopsVo {
|
||||
|
||||
@Schema(description = "Match results in the same order as requested shop names")
|
||||
@Schema(description = "匹配结果,顺序与请求中的店铺名称一致")
|
||||
private List<PriceTrackShopQueueItem> items = new ArrayList<>();
|
||||
|
||||
@Schema(description = "All skip ASINs grouped by country code")
|
||||
@Schema(description = "按国家代码分组的全部跳过跟价 ASIN")
|
||||
private Map<String, List<String>> skipAsinsByCountry;
|
||||
|
||||
@Schema(description = "All skip ASIN details grouped by country code, each item includes asin and minimumPrice")
|
||||
@Schema(description = "按国家代码分组的全部跳过跟价 ASIN 明细,每项包含 asin 和 minimumPrice")
|
||||
private Map<String, List<Map<String, String>>> skipAsinDetailsByCountry;
|
||||
|
||||
@Schema(description = "Parsed asin rows grouped by country code")
|
||||
@Schema(description = "按国家代码分组的已解析 ASIN 行")
|
||||
private Map<String, List<Map<String, String>>> asinRowsByCountry;
|
||||
|
||||
@Schema(description = "Minimum price grouped by country code and asin")
|
||||
@Schema(description = "按国家代码和 ASIN 分组的最低价格")
|
||||
private Map<String, Map<String, String>> minimumPriceByCountryAndAsin;
|
||||
|
||||
@Data
|
||||
@Schema(description = "Single matched shop item")
|
||||
@Schema(description = "单个店铺匹配结果")
|
||||
public static class PriceTrackShopQueueItem {
|
||||
|
||||
@Schema(description = "Shop name", example = "Demo Shop")
|
||||
@Schema(description = "店铺名称", example = "Demo Shop")
|
||||
private String shopName;
|
||||
|
||||
@Schema(description = "Shop mall name", example = "Amazon.de")
|
||||
@Schema(description = "店铺商城名称", example = "Amazon.de")
|
||||
private String shopMallName;
|
||||
|
||||
@Schema(description = "Matched shop id")
|
||||
@Schema(description = "匹配到的店铺 ID")
|
||||
private Object shopId;
|
||||
|
||||
@Schema(description = "Platform", example = "Amazon")
|
||||
@Schema(description = "平台", example = "Amazon")
|
||||
private String platform;
|
||||
|
||||
@Schema(description = "Company name")
|
||||
@Schema(description = "公司名称")
|
||||
private String companyName;
|
||||
|
||||
@Schema(description = "Whether the shop matched")
|
||||
@Schema(description = "店铺是否匹配成功")
|
||||
private Boolean matched;
|
||||
|
||||
@Schema(description = "Match status, such as MATCHED or PENDING")
|
||||
@Schema(description = "匹配状态,例如 MATCHED 或 PENDING")
|
||||
private String matchStatus;
|
||||
|
||||
@Schema(description = "Readable match message")
|
||||
@Schema(description = "可读的匹配结果说明")
|
||||
private String matchMessage;
|
||||
|
||||
@Schema(description = "All skip ASINs grouped by country code")
|
||||
@Schema(description = "按国家代码分组的全部跳过跟价 ASIN")
|
||||
private Map<String, List<String>> skipAsins;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ public class PriceTrackResultItemVo {
|
||||
@Schema(description = "店铺名称,详情页会尽量恢复创建任务时的展示名")
|
||||
private String shopName;
|
||||
|
||||
@Schema(description = "Shop mall name")
|
||||
@Schema(description = "店铺商城名称")
|
||||
private String shopMallName;
|
||||
|
||||
@Schema(description = "店铺 ID(紫鸟)")
|
||||
|
||||
+5
-5
@@ -4,18 +4,18 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Schema(description = "single skip price asin lookup response")
|
||||
@Schema(description = "单个跳过跟价 ASIN 查询响应")
|
||||
public class SkipPriceAsinCheckVo {
|
||||
|
||||
@Schema(description = "country code", example = "DE")
|
||||
@Schema(description = "国家代码", example = "DE")
|
||||
private String country;
|
||||
|
||||
@Schema(description = "normalized asin", example = "B0XXXX")
|
||||
@Schema(description = "规范化后的 ASIN", example = "B0XXXX")
|
||||
private String asin;
|
||||
|
||||
@Schema(description = "whether the asin exists in skip price asin table")
|
||||
@Schema(description = "该 ASIN 是否存在于跳过跟价 ASIN 表中")
|
||||
private boolean exists;
|
||||
|
||||
@Schema(description = "configured minimum price, empty when not configured or not found")
|
||||
@Schema(description = "已配置的最低价格;未配置或未找到时为空")
|
||||
private String minimumPrice;
|
||||
}
|
||||
|
||||
+40
@@ -277,6 +277,46 @@ public class PriceTrackTaskService {
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void markDispatchFailed(Long taskId, Long userId, String errorMessage) {
|
||||
if (userId == null || userId <= 0) throw new BusinessException("user_id 不合法");
|
||||
if (errorMessage == null || errorMessage.isBlank()) throw new BusinessException("errorMessage 不能为空");
|
||||
String normalizedError = errorMessage.trim();
|
||||
try (TaskDistributedLockService.LockHandle ignored = acquireTaskLockOrThrow(taskId)) {
|
||||
FileTaskEntity task = loadTaskForExecution(taskId);
|
||||
if (task == null || !MODULE_TYPE.equals(task.getModuleType()) || !userId.equals(task.getUserId())) {
|
||||
throw new BusinessException("任务不存在");
|
||||
}
|
||||
if ("SUCCESS".equals(task.getStatus()) || "FAILED".equals(task.getStatus())) {
|
||||
return;
|
||||
}
|
||||
List<FileResultEntity> results = fileResultMapper.selectList(
|
||||
new LambdaQueryWrapper<FileResultEntity>()
|
||||
.eq(FileResultEntity::getTaskId, taskId)
|
||||
.eq(FileResultEntity::getModuleType, MODULE_TYPE)
|
||||
.orderByAsc(FileResultEntity::getId));
|
||||
if (results.isEmpty()) {
|
||||
task.setStatus("FAILED");
|
||||
task.setErrorMessage(normalizedError);
|
||||
task.setUpdatedAt(LocalDateTime.now());
|
||||
task.setFinishedAt(LocalDateTime.now());
|
||||
fileTaskMapper.updateById(task);
|
||||
cleanupTaskCacheIfTerminal(taskId, task.getStatus());
|
||||
priceTrackLoopRunService.syncLoopRunAfterChildTerminal(taskId);
|
||||
return;
|
||||
}
|
||||
for (FileResultEntity result : results) {
|
||||
boolean succeeded = result.getSuccess() != null && result.getSuccess() == 1;
|
||||
boolean failed = result.getErrorMessage() != null && !result.getErrorMessage().isBlank();
|
||||
if (!succeeded && !failed) {
|
||||
markResultFailed(result, normalizedError);
|
||||
}
|
||||
}
|
||||
updateTaskStatusFromLatestRows(task, results);
|
||||
log.warn("[price-track] Python dispatch failed taskId={} userId={} error={}", taskId, userId, normalizedError);
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public PriceTrackPendingDeleteVo deletePendingShopResult(Long userId, String shopName) {
|
||||
if (userId == null || userId <= 0) throw new BusinessException("user_id 不合法");
|
||||
|
||||
Reference in New Issue
Block a user