完善后台接口字段
This commit is contained in:
@@ -229,6 +229,9 @@ public class ProductRiskTaskService {
|
||||
task.setSuccessFileCount(ok);
|
||||
task.setFailedFileCount(fail);
|
||||
task.setUpdatedAt(LocalDateTime.now());
|
||||
|
||||
log.info("[product-risk] reconcile summary taskId={} ok={} fail={} allDone={}",
|
||||
taskId, ok, fail, allDone);
|
||||
if (!allDone) {
|
||||
task.setStatus("RUNNING");
|
||||
task.setErrorMessage(null);
|
||||
@@ -399,10 +402,17 @@ public class ProductRiskTaskService {
|
||||
payloadByShop.put(key, p);
|
||||
}
|
||||
}
|
||||
log.info("[product-risk] submitResult received taskId={} payloadShopCount={} payloadKeys={}",
|
||||
taskId, payloadByShop.size(), payloadByShop.keySet());
|
||||
|
||||
File workRoot = FileUtil.mkdir(FileUtil.file(System.getProperty("java.io.tmpdir"), "product-risk-result", String.valueOf(taskId)));
|
||||
|
||||
List<String> batchErrors = new ArrayList<>();
|
||||
int matchedShopCount = 0;
|
||||
int skippedUnmatchedCount = 0;
|
||||
int waitingCount = 0;
|
||||
int assembledCount = 0;
|
||||
int fallbackMatchedCount = 0;
|
||||
|
||||
for (FileResultEntity fr : resultRows) {
|
||||
String shopKey = fr.getSourceFilename();
|
||||
@@ -411,14 +421,17 @@ public class ProductRiskTaskService {
|
||||
// 兼容单店任务:若规范化店名偶发不一致(空格/符号差异)导致未命中,且任务与回传都只有 1 个店时按唯一店回填。
|
||||
if (payload == null && resultRows.size() == 1 && payloadByShop.size() == 1) {
|
||||
payload = payloadByShop.values().iterator().next();
|
||||
fallbackMatchedCount++;
|
||||
log.warn("[product-risk] single-shop fallback matched taskId={} dbShop={} payloadShop={}",
|
||||
taskId, shopKey, payload.getShopName());
|
||||
}
|
||||
|
||||
if (payload == null) {
|
||||
skippedUnmatchedCount++;
|
||||
// 与删除品牌一致:支持队列逐条处理、分次回传,未在本次 payload 中的店铺保持待处理
|
||||
continue;
|
||||
}
|
||||
matchedShopCount++;
|
||||
if (payload.getError() != null && !payload.getError().isBlank()) {
|
||||
markResultFailed(fr, payload.getError());
|
||||
batchErrors.add(shopKey + ": " + payload.getError());
|
||||
@@ -428,7 +441,16 @@ public class ProductRiskTaskService {
|
||||
|
||||
// 分次上报累积:合并到 Redis 缓存,再按“合并后是否完成”决定是否出包。
|
||||
ProductRiskShopPayloadDto mergedPayload = mergeShopPayload(taskId, shopKey, payload);
|
||||
if (!isShopPayloadCompleted(mergedPayload)) {
|
||||
boolean shopDone = isShopPayloadCompleted(mergedPayload);
|
||||
log.info("[product-risk] shop merged taskId={} shop={} incomingDone={} mergedDone={} mergedRows={} payloadCountries={}",
|
||||
taskId,
|
||||
shopKey,
|
||||
payloadHasAnyDoneTrue(payload),
|
||||
shopDone,
|
||||
countPayloadRows(mergedPayload),
|
||||
mergedPayload.getCountries() == null ? 0 : mergedPayload.getCountries().size());
|
||||
if (!shopDone) {
|
||||
waitingCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -451,6 +473,9 @@ public class ProductRiskTaskService {
|
||||
fr.setSuccess(1);
|
||||
fr.setErrorMessage(null);
|
||||
fileResultMapper.updateById(fr);
|
||||
assembledCount++;
|
||||
log.info("[product-risk] shop assembled taskId={} shop={} rowCount={} file={}",
|
||||
taskId, shopKey, fr.getRowCount(), fr.getResultFilename());
|
||||
FileUtil.del(xlsx);
|
||||
FileUtil.del(zip);
|
||||
productRiskTaskCacheService.removeShopMergedPayload(taskId, shopKey);
|
||||
@@ -708,6 +733,20 @@ public class ProductRiskTaskService {
|
||||
if (row == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 兼容 Python 末次“空行 done=true”心跳:视为当前国家已有行全部完成。
|
||||
if (isDoneValue(row.getDone()) && isEmptyBusinessRow(row) && !byKey.isEmpty()) {
|
||||
for (Map.Entry<String, ProductRiskRowDto> entry : byKey.entrySet()) {
|
||||
ProductRiskRowDto existed = entry.getValue();
|
||||
ProductRiskRowDto doneMerged = mergeRow(existed, row);
|
||||
if (doneMerged.getDone() == null || doneMerged.getDone().isBlank()) {
|
||||
doneMerged.setDone("true");
|
||||
}
|
||||
entry.setValue(doneMerged);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
String key = buildRowKey(row);
|
||||
ProductRiskRowDto prev = byKey.get(key);
|
||||
byKey.put(key, mergeRow(prev, row));
|
||||
@@ -760,6 +799,22 @@ public class ProductRiskTaskService {
|
||||
return out;
|
||||
}
|
||||
|
||||
private boolean isEmptyBusinessRow(ProductRiskRowDto row) {
|
||||
if (row == null) {
|
||||
return true;
|
||||
}
|
||||
// 注意:shopName 只是上下文信息,不作为业务行是否为空的判定条件。
|
||||
// Python 末次心跳行通常只带 shopName + done=true,其他业务字段为空。
|
||||
return isBlank(row.getProductAsinSku())
|
||||
&& isBlank(row.getRemoveAsin())
|
||||
&& isBlank(row.getStatus())
|
||||
&& isBlank(row.getRemoveStatus());
|
||||
}
|
||||
|
||||
private boolean isBlank(String val) {
|
||||
return val == null || val.trim().isEmpty();
|
||||
}
|
||||
|
||||
private String firstNonBlank(String preferred, String fallback) {
|
||||
if (preferred != null && !preferred.isBlank()) {
|
||||
return preferred;
|
||||
@@ -771,22 +826,61 @@ public class ProductRiskTaskService {
|
||||
if (payload == null || payload.getCountries() == null || payload.getCountries().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
boolean hasAnyRow = false;
|
||||
boolean hasAnyBusinessRow = false;
|
||||
for (List<ProductRiskRowDto> rows : payload.getCountries().values()) {
|
||||
if (rows == null || rows.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
for (ProductRiskRowDto row : rows) {
|
||||
if (row == null) {
|
||||
if (row == null || isEmptyBusinessRow(row)) {
|
||||
continue;
|
||||
}
|
||||
hasAnyRow = true;
|
||||
hasAnyBusinessRow = true;
|
||||
if (!isDoneValue(row.getDone())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return hasAnyRow;
|
||||
return hasAnyBusinessRow;
|
||||
}
|
||||
|
||||
private int countPayloadRows(ProductRiskShopPayloadDto payload) {
|
||||
if (payload == null || payload.getCountries() == null || payload.getCountries().isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
int n = 0;
|
||||
for (List<ProductRiskRowDto> rows : payload.getCountries().values()) {
|
||||
if (rows == null) {
|
||||
continue;
|
||||
}
|
||||
for (ProductRiskRowDto row : rows) {
|
||||
if (row == null || isEmptyBusinessRow(row)) {
|
||||
continue;
|
||||
}
|
||||
n++;
|
||||
}
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
private boolean payloadHasAnyDoneTrue(ProductRiskShopPayloadDto payload) {
|
||||
if (payload == null || payload.getCountries() == null) {
|
||||
return false;
|
||||
}
|
||||
for (List<ProductRiskRowDto> rows : payload.getCountries().values()) {
|
||||
if (rows == null) {
|
||||
continue;
|
||||
}
|
||||
for (ProductRiskRowDto row : rows) {
|
||||
if (row == null || isEmptyBusinessRow(row)) {
|
||||
continue;
|
||||
}
|
||||
if (isDoneValue(row.getDone())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isDoneValue(String value) {
|
||||
|
||||
@@ -13,6 +13,9 @@ public class ShopManageCreateRequest {
|
||||
@NotBlank(message = "店铺名不能为空")
|
||||
private String shopName;
|
||||
|
||||
@NotBlank(message = "店铺商城名不能为空")
|
||||
private String mallName;
|
||||
|
||||
@NotBlank(message = "账号不能为空")
|
||||
private String account;
|
||||
|
||||
|
||||
@@ -13,6 +13,9 @@ public class ShopManageUpdateRequest {
|
||||
@NotBlank(message = "店铺名不能为空")
|
||||
private String shopName;
|
||||
|
||||
@NotBlank(message = "店铺商城名不能为空")
|
||||
private String mallName;
|
||||
|
||||
@NotBlank(message = "账号不能为空")
|
||||
private String account;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.nanri.aiimage.modules.shopkey.model.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
@@ -16,6 +17,8 @@ public class ShopManageEntity {
|
||||
private Long groupId;
|
||||
private String groupName;
|
||||
private String shopName;
|
||||
@TableField("mall_name")
|
||||
private String mallName;
|
||||
private String account;
|
||||
private String password;
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@@ -8,6 +8,7 @@ public class ShopManageCredentialVo {
|
||||
private Long groupId;
|
||||
private String groupName;
|
||||
private String shopName;
|
||||
private String mallName;
|
||||
private String account;
|
||||
private String password;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ public class ShopManageItemVo {
|
||||
private Long groupId;
|
||||
private String groupName;
|
||||
private String shopName;
|
||||
private String mallName;
|
||||
private String account;
|
||||
private String password;
|
||||
private String passwordMasked;
|
||||
|
||||
@@ -57,12 +57,14 @@ Map<Long, String> groupNameById = buildGroupNameMap(rows.stream()
|
||||
public ShopManageItemVo create(ShopManageCreateRequest request) {
|
||||
ShopManageGroupEntity group = shopManageGroupService.getById(request.getGroupId());
|
||||
String shopName = normalizeRequired(request.getShopName(), "店铺名不能为空");
|
||||
String mallName = normalizeRequired(request.getMallName(), "店铺商城名不能为空");
|
||||
ensureShopNameUnique(shopName, null);
|
||||
|
||||
ShopManageEntity entity = new ShopManageEntity();
|
||||
entity.setGroupId(group.getId());
|
||||
entity.setGroupName(group.getGroupName());
|
||||
entity.setShopName(shopName);
|
||||
entity.setMallName(mallName);
|
||||
entity.setAccount(normalizeRequired(request.getAccount(), "账号不能为空"));
|
||||
entity.setPassword(shopCredentialCryptoService.encrypt(normalizeRequired(request.getPassword(), "密码不能为空")));
|
||||
shopManageMapper.insert(entity);
|
||||
@@ -75,11 +77,13 @@ Map<Long, String> groupNameById = buildGroupNameMap(rows.stream()
|
||||
ShopManageEntity entity = getById(id);
|
||||
ShopManageGroupEntity group = shopManageGroupService.getById(request.getGroupId());
|
||||
String shopName = normalizeRequired(request.getShopName(), "店铺名不能为空");
|
||||
String mallName = normalizeRequired(request.getMallName(), "店铺商城名不能为空");
|
||||
ensureShopNameUnique(shopName, id);
|
||||
|
||||
entity.setGroupId(group.getId());
|
||||
entity.setGroupName(group.getGroupName());
|
||||
entity.setShopName(shopName);
|
||||
entity.setMallName(mallName);
|
||||
entity.setAccount(normalizeRequired(request.getAccount(), "账号不能为空"));
|
||||
entity.setPassword(shopCredentialCryptoService.encrypt(normalizeRequired(request.getPassword(), "密码不能为空")));
|
||||
shopManageMapper.updateById(entity);
|
||||
@@ -105,6 +109,7 @@ Map<Long, String> groupNameById = buildGroupNameMap(rows.stream()
|
||||
vo.setId(entity.getId());
|
||||
vo.setGroupId(entity.getGroupId());
|
||||
vo.setShopName(entity.getShopName());
|
||||
vo.setMallName(entity.getMallName());
|
||||
vo.setAccount(entity.getAccount());
|
||||
vo.setPassword(shopCredentialCryptoService.decrypt(entity.getPassword()));
|
||||
try {
|
||||
@@ -166,6 +171,7 @@ Map<Long, String> groupNameById = buildGroupNameMap(rows.stream()
|
||||
vo.setGroupId(entity.getGroupId());
|
||||
vo.setGroupName(groupName == null ? "" : groupName);
|
||||
vo.setShopName(entity.getShopName());
|
||||
vo.setMallName(entity.getMallName());
|
||||
vo.setAccount(entity.getAccount());
|
||||
String masked = entity.getPassword() == null || entity.getPassword().isBlank() ? "" : "******";
|
||||
// 兼容旧前端字段:password 继续返回脱敏值
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
SET @db_name = DATABASE();
|
||||
|
||||
SET @mall_name_col_exists := (
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db_name
|
||||
AND TABLE_NAME = 'biz_shop_manage'
|
||||
AND COLUMN_NAME = 'mall_name'
|
||||
);
|
||||
|
||||
SET @sql_add_mall_name_col := IF(@mall_name_col_exists = 0,
|
||||
'ALTER TABLE biz_shop_manage ADD COLUMN mall_name VARCHAR(128) NOT NULL DEFAULT '''' COMMENT ''店铺商城名'' AFTER shop_name',
|
||||
'SELECT 1'
|
||||
);
|
||||
PREPARE stmt_add_mall_name_col FROM @sql_add_mall_name_col;
|
||||
EXECUTE stmt_add_mall_name_col;
|
||||
DEALLOCATE PREPARE stmt_add_mall_name_col;
|
||||
Reference in New Issue
Block a user