处理修复BUG
This commit is contained in:
+21
@@ -20,9 +20,11 @@ import com.nanri.aiimage.modules.pricetrack.model.vo.PriceTrackLoopRunVo;
|
||||
import com.nanri.aiimage.modules.pricetrack.model.vo.PriceTrackMatchShopsVo;
|
||||
import com.nanri.aiimage.modules.pricetrack.model.vo.PriceTrackPendingDeleteVo;
|
||||
import com.nanri.aiimage.modules.pricetrack.model.vo.PriceTrackTaskBatchVo;
|
||||
import com.nanri.aiimage.modules.pricetrack.model.vo.SkipPriceAsinPageVo;
|
||||
import com.nanri.aiimage.modules.pricetrack.service.PriceTrackLoopRunService;
|
||||
import com.nanri.aiimage.modules.pricetrack.service.PriceTrackService;
|
||||
import com.nanri.aiimage.modules.pricetrack.service.PriceTrackTaskService;
|
||||
import com.nanri.aiimage.modules.shopkey.service.SkipPriceAsinService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||
@@ -58,6 +60,7 @@ public class PriceTrackController {
|
||||
private final PriceTrackService priceTrackService;
|
||||
private final PriceTrackTaskService priceTrackTaskService;
|
||||
private final PriceTrackLoopRunService priceTrackLoopRunService;
|
||||
private final SkipPriceAsinService skipPriceAsinService;
|
||||
|
||||
@GetMapping("/candidates")
|
||||
@Operation(summary = "查询备选店铺列表", description = "返回当前用户在跟价模块中保存的备选店铺。")
|
||||
@@ -104,6 +107,24 @@ public class PriceTrackController {
|
||||
return ApiResponse.success(priceTrackService.matchShops(request));
|
||||
}
|
||||
|
||||
@GetMapping("/tasks/{taskId}/skip-asins/paginated")
|
||||
@Operation(
|
||||
summary = "分页查询任务的跳过 ASIN 数据",
|
||||
description = "根据任务 ID 分页拉取该任务关联的 ASIN 数据。"
|
||||
+ "全量模式:从 biz_skip_price_asin 表查询;"
|
||||
+ "文件模式:从任务 requestJson 中存储的上传文件数据查询。"
|
||||
+ "国家代码从任务的 requestJson.countryCodes 中获取。"
|
||||
)
|
||||
public ApiResponse<SkipPriceAsinPageVo> getTaskSkipAsinsPaginated(
|
||||
@Parameter(description = "任务 ID", required = true, example = "200")
|
||||
@PathVariable Long taskId,
|
||||
@Parameter(description = "页码,从 1 开始", example = "1")
|
||||
@RequestParam(value = "page", defaultValue = "1") Integer page,
|
||||
@Parameter(description = "每页条数,默认 1000,最大 2000", example = "1000")
|
||||
@RequestParam(value = "page_size", defaultValue = "1000") Integer pageSize) {
|
||||
return ApiResponse.success(priceTrackTaskService.getTaskSkipAsinsPaginated(taskId, page, pageSize));
|
||||
}
|
||||
|
||||
@GetMapping("/dashboard")
|
||||
@Operation(summary = "统计看板", description = "返回备选店铺数、已结束任务数、成功任务数和失败任务数。")
|
||||
public ApiResponse<PriceTrackDashboardVo> dashboard(
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package com.nanri.aiimage.modules.pricetrack.model.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 跟价跳过 ASIN 分页返回
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "跟价跳过 ASIN 分页返回")
|
||||
public class SkipPriceAsinPageVo {
|
||||
|
||||
@Schema(description = "当前页码,从 1 开始")
|
||||
private Integer page;
|
||||
|
||||
@Schema(description = "每页条数")
|
||||
private Integer pageSize;
|
||||
|
||||
@Schema(description = "总记录数")
|
||||
private Long total;
|
||||
|
||||
@Schema(description = "总页数")
|
||||
private Integer totalPages;
|
||||
|
||||
@Schema(description = "按国家分组的 ASIN 列表(简化版,仅 ASIN)")
|
||||
private Map<String, List<String>> skipAsinsByCountry = new LinkedHashMap<>();
|
||||
|
||||
@Schema(description = "按国家分组的 ASIN 详情列表(包含 asin 和 minimumPrice)")
|
||||
private Map<String, List<Map<String, String>>> skipAsinDetailsByCountry = new LinkedHashMap<>();
|
||||
}
|
||||
+5
-13
@@ -145,24 +145,16 @@ public class PriceTrackService {
|
||||
}
|
||||
|
||||
boolean asinMode = request.getAsinFiles() != null && !request.getAsinFiles().isEmpty();
|
||||
Map<String, List<String>> skipAsinsByCountry = asinMode
|
||||
? new LinkedHashMap<>()
|
||||
: skipPriceAsinService.listAllSkipAsinsByCountry();
|
||||
Map<String, List<Map<String, String>>> skipAsinDetailsByCountry = asinMode
|
||||
? new LinkedHashMap<>()
|
||||
: skipPriceAsinService.listAllSkipAsinDetailsByCountry();
|
||||
Map<String, List<Map<String, String>>> asinRowsByCountry =
|
||||
!asinMode
|
||||
? new LinkedHashMap<>()
|
||||
: priceTrackTaskService.parseMatchAsinRowsByCountry(
|
||||
request.getAsinFiles(),
|
||||
request.getCountryCodes());
|
||||
// 不再在 matchShops 时加载全量数据,改由 Python 端分页拉取
|
||||
Map<String, List<String>> skipAsinsByCountry = new LinkedHashMap<>();
|
||||
Map<String, List<Map<String, String>>> skipAsinDetailsByCountry = new LinkedHashMap<>();
|
||||
Map<String, List<Map<String, String>>> asinRowsByCountry = new LinkedHashMap<>();
|
||||
|
||||
PriceTrackMatchShopsVo vo = new PriceTrackMatchShopsVo();
|
||||
vo.setSkipAsinsByCountry(skipAsinsByCountry);
|
||||
vo.setSkipAsinDetailsByCountry(skipAsinDetailsByCountry);
|
||||
vo.setAsinRowsByCountry(asinRowsByCountry);
|
||||
vo.setMinimumPriceByCountryAndAsin(priceTrackTaskService.buildMinimumPriceLookupForMatch(asinRowsByCountry));
|
||||
vo.setMinimumPriceByCountryAndAsin(new LinkedHashMap<>());
|
||||
for (String shopName : ordered) {
|
||||
vo.getItems().add(matchOneShop(shopName, skipAsinsByCountry));
|
||||
}
|
||||
|
||||
+147
-4
@@ -428,10 +428,15 @@ public class PriceTrackTaskService {
|
||||
ctx.put("statusMode", request.isStatusMode());
|
||||
ctx.put("asinMode", request.isAsinMode());
|
||||
ctx.put("countryCodes", request.getCountryCodes());
|
||||
ctx.put("skipAsinsByCountry", skipAsinsByCountry);
|
||||
ctx.put("skipAsinDetailsByCountry", skipAsinDetailsByCountry);
|
||||
ctx.put("asinRowsByCountry", asinRowsByCountry);
|
||||
ctx.put("minimumPriceByCountryAndAsin", minimumPriceByCountryAndAsin);
|
||||
// 不再保存全量 ASIN 数据到 requestJson
|
||||
// ctx.put("skipAsinsByCountry", skipAsinsByCountry);
|
||||
// ctx.put("skipAsinDetailsByCountry", skipAsinDetailsByCountry);
|
||||
// ctx.put("asinRowsByCountry", asinRowsByCountry);
|
||||
// ctx.put("minimumPriceByCountryAndAsin", minimumPriceByCountryAndAsin);
|
||||
// 文件模式:保存文件路径,供后续分页读取
|
||||
if (request.isAsinMode()) {
|
||||
ctx.put("asinFiles", request.getAsinFiles());
|
||||
}
|
||||
ctx.put("items", uniqueItems);
|
||||
ctx.put("loopRunId", request.getLoopRunId());
|
||||
ctx.put("roundIndex", request.getRoundIndex());
|
||||
@@ -764,6 +769,144 @@ public class PriceTrackTaskService {
|
||||
return buildMinimumPriceByCountryAndAsin(asinRowsByCountry);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询任务关联的跳过 ASIN 数据
|
||||
*
|
||||
* @param taskId 任务 ID
|
||||
* @param page 页码,从 1 开始
|
||||
* @param pageSize 每页条数
|
||||
* @return 分页结果
|
||||
*/
|
||||
public com.nanri.aiimage.modules.pricetrack.model.vo.SkipPriceAsinPageVo getTaskSkipAsinsPaginated(
|
||||
Long taskId, int page, int pageSize) {
|
||||
if (taskId == null || taskId <= 0) {
|
||||
throw new BusinessException("taskId 不合法");
|
||||
}
|
||||
|
||||
FileTaskEntity task = loadTaskForExecution(taskId);
|
||||
if (task == null || !MODULE_TYPE.equals(task.getModuleType())) {
|
||||
throw new BusinessException("任务不存在");
|
||||
}
|
||||
|
||||
// 解析任务请求参数
|
||||
PriceTrackCreateTaskRequest request;
|
||||
try {
|
||||
if (task.getRequestJson() == null || task.getRequestJson().isBlank()) {
|
||||
throw new BusinessException("任务请求参数为空");
|
||||
}
|
||||
request = objectMapper.readValue(task.getRequestJson(), PriceTrackCreateTaskRequest.class);
|
||||
} catch (Exception e) {
|
||||
throw new BusinessException("任务请求参数解析失败: " + e.getMessage());
|
||||
}
|
||||
|
||||
// 从任务中获取国家代码
|
||||
List<String> countryCodes = request.getCountryCodes();
|
||||
|
||||
// 判断是全量模式还是文件模式
|
||||
boolean isAsinMode = request.isAsinMode();
|
||||
|
||||
if (isAsinMode) {
|
||||
// 文件模式:从任务 requestJson 中的上传文件解析数据并分页返回
|
||||
return getTaskAsinFilePaginated(request, countryCodes, page, pageSize);
|
||||
} else {
|
||||
// 全量模式:从 biz_skip_price_asin 表查询
|
||||
return skipPriceAsinService.listSkipAsinsPaginated(null, countryCodes, page, pageSize);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件模式:从任务上传的文件中分页返回 ASIN 数据
|
||||
*/
|
||||
private com.nanri.aiimage.modules.pricetrack.model.vo.SkipPriceAsinPageVo getTaskAsinFilePaginated(
|
||||
PriceTrackCreateTaskRequest request, List<String> countryCodes, int page, int pageSize) {
|
||||
if (page < 1) {
|
||||
page = 1;
|
||||
}
|
||||
if (pageSize < 1 || pageSize > 2000) {
|
||||
pageSize = 1000;
|
||||
}
|
||||
|
||||
// 解析上传文件中的所有 ASIN 数据
|
||||
Map<String, List<Map<String, String>>> allAsinRows = parseAsinRowsByCountry(
|
||||
request.getAsinFiles(),
|
||||
request.getCountryCodes()
|
||||
);
|
||||
|
||||
// 按国家分组并分页
|
||||
List<String> targetCountries = (countryCodes == null || countryCodes.isEmpty())
|
||||
? new ArrayList<>(allAsinRows.keySet())
|
||||
: countryCodes;
|
||||
|
||||
// 计算总记录数(所有国家的 ASIN 行数总和)
|
||||
long totalRows = 0;
|
||||
for (String country : targetCountries) {
|
||||
List<Map<String, String>> rows = allAsinRows.getOrDefault(country, List.of());
|
||||
totalRows += rows.size();
|
||||
}
|
||||
|
||||
// 计算分页
|
||||
int totalPages = (int) ((totalRows + pageSize - 1) / pageSize);
|
||||
int startIndex = (page - 1) * pageSize;
|
||||
int endIndex = Math.min(startIndex + pageSize, (int) totalRows);
|
||||
|
||||
// 提取当前页的数据(跨国家顺序提取)
|
||||
Map<String, List<String>> skipAsinsByCountry = new LinkedHashMap<>();
|
||||
Map<String, List<Map<String, String>>> skipDetailsByCountry = new LinkedHashMap<>();
|
||||
|
||||
// 初始化所有目标国家
|
||||
for (String country : targetCountries) {
|
||||
skipAsinsByCountry.put(country, new ArrayList<>());
|
||||
skipDetailsByCountry.put(country, new ArrayList<>());
|
||||
}
|
||||
|
||||
int currentIndex = 0;
|
||||
boolean shouldBreak = false;
|
||||
|
||||
for (String country : targetCountries) {
|
||||
if (shouldBreak) {
|
||||
break;
|
||||
}
|
||||
|
||||
List<Map<String, String>> countryRows = allAsinRows.getOrDefault(country, List.of());
|
||||
for (Map<String, String> row : countryRows) {
|
||||
// 判断当前行是否在分页范围内
|
||||
if (currentIndex >= startIndex && currentIndex < endIndex) {
|
||||
String asin = row.get("asin");
|
||||
String minimumPrice = row.get("minimumPrice");
|
||||
|
||||
if (asin != null && !asin.isBlank()) {
|
||||
skipAsinsByCountry.get(country).add(asin);
|
||||
|
||||
Map<String, String> detail = new LinkedHashMap<>();
|
||||
detail.put("asin", asin);
|
||||
detail.put("minimumPrice", minimumPrice == null ? "" : minimumPrice);
|
||||
skipDetailsByCountry.get(country).add(detail);
|
||||
}
|
||||
}
|
||||
|
||||
currentIndex++;
|
||||
|
||||
// 已达到本页结束位置
|
||||
if (currentIndex >= endIndex) {
|
||||
shouldBreak = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 构造返回 VO
|
||||
com.nanri.aiimage.modules.pricetrack.model.vo.SkipPriceAsinPageVo vo =
|
||||
new com.nanri.aiimage.modules.pricetrack.model.vo.SkipPriceAsinPageVo();
|
||||
vo.setPage(page);
|
||||
vo.setPageSize(pageSize);
|
||||
vo.setTotal(totalRows);
|
||||
vo.setTotalPages(totalPages);
|
||||
vo.setSkipAsinsByCountry(skipAsinsByCountry);
|
||||
vo.setSkipAsinDetailsByCountry(skipDetailsByCountry);
|
||||
|
||||
return vo;
|
||||
}
|
||||
|
||||
private Map<String, List<Map<String, String>>> parseAsinRowsByCountry(List<String> asinFiles, List<String> countryCodes) {
|
||||
Map<String, List<Map<String, String>>> merged = new LinkedHashMap<>();
|
||||
if (asinFiles == null || asinFiles.isEmpty()) {
|
||||
|
||||
Reference in New Issue
Block a user