删除模块 取消打开紫鸟
This commit is contained in:
BIN
backend-java/aiimage-backend.log.2026-03-30.0.gz
Normal file
BIN
backend-java/aiimage-backend.log.2026-03-30.0.gz
Normal file
Binary file not shown.
@@ -7,6 +7,7 @@ import com.nanri.aiimage.modules.deletebrand.model.dto.DeleteBrandTaskBatchReque
|
||||
import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandHistoryVo;
|
||||
import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandRunVo;
|
||||
import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandTaskBatchVo;
|
||||
import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandTaskDeletionStatusVo;
|
||||
import com.nanri.aiimage.modules.deletebrand.service.DeleteBrandRunService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
@@ -83,6 +84,15 @@ public class DeleteBrandRunController {
|
||||
return ApiResponse.success(deleteBrandRunService.getTaskDetails(request.getTaskIds()));
|
||||
}
|
||||
|
||||
@GetMapping("/tasks/{taskId}/deletion-status")
|
||||
@Operation(summary = "获取删除品牌任务删除状态", description = "供 Python/插件按 taskId 查询该删除品牌任务是否已被前端删空。")
|
||||
public ApiResponse<DeleteBrandTaskDeletionStatusVo> getTaskDeletionStatus(
|
||||
@PathVariable Long taskId,
|
||||
@Parameter(name = "user_id", description = "当前登录用户 ID", required = true, in = ParameterIn.QUERY)
|
||||
@RequestParam("user_id") Long userId) {
|
||||
return ApiResponse.success(deleteBrandRunService.getTaskDeletionStatus(taskId, userId));
|
||||
}
|
||||
|
||||
@PostMapping("/tasks/{taskId}/result")
|
||||
@Operation(summary = "提交删除品牌处理结果", description = "插件/前端分片回传处理结果;后端累计分片并推进进度,全部完成后再执行最终组装与落库。")
|
||||
public ApiResponse<Void> submitResult(
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.nanri.aiimage.modules.deletebrand.model.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Schema(description = "删除品牌任务删除状态响应")
|
||||
public class DeleteBrandTaskDeletionStatusVo {
|
||||
@Schema(description = "任务ID")
|
||||
private Long taskId;
|
||||
|
||||
@Schema(description = "该任务的结果记录是否已被全部删除")
|
||||
private boolean deleted;
|
||||
|
||||
@Schema(description = "该任务当前剩余的结果记录数量")
|
||||
private long remainingResultCount;
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandLineProgressVo;
|
||||
import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandPreviewRowVo;
|
||||
import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandResultItemVo;
|
||||
import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandRunVo;
|
||||
import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandTaskDeletionStatusVo;
|
||||
import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandTaskDetailVo;
|
||||
import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandTaskItemVo;
|
||||
import com.nanri.aiimage.modules.file.service.LocalFileStorageService;
|
||||
@@ -31,6 +32,8 @@ import com.nanri.aiimage.modules.task.mapper.FileResultMapper;
|
||||
import com.nanri.aiimage.modules.task.mapper.FileTaskMapper;
|
||||
import com.nanri.aiimage.modules.task.model.entity.FileResultEntity;
|
||||
import com.nanri.aiimage.modules.task.model.entity.FileTaskEntity;
|
||||
import com.nanri.aiimage.modules.ziniao.model.vo.ZiniaoShopMatchResultVo;
|
||||
import com.nanri.aiimage.modules.ziniao.service.ZiniaoShopSwitchService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.poi.ss.usermodel.Cell;
|
||||
@@ -68,7 +71,7 @@ public class DeleteBrandRunService {
|
||||
private final FileResultMapper fileResultMapper;
|
||||
private final LocalFileStorageService localFileStorageService;
|
||||
private final DeleteBrandTaskCacheService deleteBrandTaskCacheService;
|
||||
private final com.nanri.aiimage.modules.ziniao.service.ZiniaoAuthService ziniaoAuthService;
|
||||
private final ZiniaoShopSwitchService ziniaoShopSwitchService;
|
||||
private final OssStorageService ossStorageService;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final DeleteBrandProgressProperties deleteBrandProgressProperties;
|
||||
@@ -96,7 +99,7 @@ public class DeleteBrandRunService {
|
||||
int parsedSuccessCount = 0;
|
||||
int parsedFailedCount = 0;
|
||||
Map<String, DeleteBrandParsedFileCacheDto> parsedPayloadByFileIdentity = new LinkedHashMap<>();
|
||||
Map<String, com.nanri.aiimage.modules.ziniao.service.ZiniaoAuthService.StoreMatchResult> storeMatchByShopName = new LinkedHashMap<>();
|
||||
Map<String, ZiniaoShopMatchResultVo> storeMatchByShopName = new LinkedHashMap<>();
|
||||
|
||||
for (DeleteBrandSourceFileDto sourceFile : request.getFiles()) {
|
||||
DeleteBrandResultItemVo item = new DeleteBrandResultItemVo();
|
||||
@@ -117,25 +120,22 @@ public class DeleteBrandRunService {
|
||||
item.setPreviewRows(parsed.previewRows());
|
||||
|
||||
try {
|
||||
String normalizedShopName = normalizeShopName(item.getShopName());
|
||||
com.nanri.aiimage.modules.ziniao.service.ZiniaoAuthService.StoreMatchResult matchResult = normalizedShopName.isBlank()
|
||||
? new com.nanri.aiimage.modules.ziniao.service.ZiniaoAuthService.StoreMatchResult(false, null, null, null, null, null)
|
||||
String normalizedShopName = ziniaoShopSwitchService.normalizeShopName(item.getShopName());
|
||||
ZiniaoShopMatchResultVo matchResult = normalizedShopName.isBlank()
|
||||
? ziniaoShopSwitchService.emptyMatchResult()
|
||||
: storeMatchByShopName.computeIfAbsent(normalizedShopName,
|
||||
ignored -> ziniaoAuthService.matchStoreByNameAcrossStaff(item.getShopName(), null));
|
||||
item.setMatched(matchResult.matched());
|
||||
if (matchResult.matched()) {
|
||||
item.setShopId(matchResult.shopId());
|
||||
item.setPlatform(matchResult.platform());
|
||||
item.setOpenStoreUrl(matchResult.openStoreUrl());
|
||||
ignored -> ziniaoShopSwitchService.matchStoreByNameAcrossStaff(item.getShopName(), null));
|
||||
item.setMatched(matchResult.isMatched());
|
||||
if (matchResult.isMatched()) {
|
||||
item.setShopId(matchResult.getShopId());
|
||||
item.setPlatform(matchResult.getPlatform());
|
||||
item.setOpenStoreUrl(matchResult.getOpenStoreUrl());
|
||||
} else {
|
||||
throw new BusinessException("未匹配到紫鸟店铺,跳过处理");
|
||||
}
|
||||
} catch (BusinessException ex) {
|
||||
String message = ex.getMessage();
|
||||
if (message != null && (message.contains("code=40004")
|
||||
|| message.contains("userId存在无效的参数值")
|
||||
|| message.contains("无权限")
|
||||
|| message.contains("没有权限")) && !message.contains("白名单")) {
|
||||
if (ziniaoShopSwitchService.isSkippableMatchError(ex)) {
|
||||
log.warn("[ziniao-match] suppressed upstream permission error for {}, msg: {}", item.getShopName(), message);
|
||||
item.setMatched(false);
|
||||
} else {
|
||||
@@ -301,6 +301,24 @@ public class DeleteBrandRunService {
|
||||
return vo;
|
||||
}
|
||||
|
||||
public DeleteBrandTaskDeletionStatusVo getTaskDeletionStatus(Long taskId, Long userId) {
|
||||
FileTaskEntity task = fileTaskMapper.selectById(taskId);
|
||||
if (task == null || !MODULE_TYPE.equals(task.getModuleType()) || !userId.equals(task.getUserId())) {
|
||||
throw new BusinessException("任务不存在");
|
||||
}
|
||||
|
||||
Long remainingResultCount = fileResultMapper.selectCount(new LambdaQueryWrapper<FileResultEntity>()
|
||||
.eq(FileResultEntity::getTaskId, taskId)
|
||||
.eq(FileResultEntity::getModuleType, MODULE_TYPE)
|
||||
.eq(FileResultEntity::getUserId, userId));
|
||||
|
||||
DeleteBrandTaskDeletionStatusVo vo = new DeleteBrandTaskDeletionStatusVo();
|
||||
vo.setTaskId(taskId);
|
||||
vo.setRemainingResultCount(remainingResultCount == null ? 0L : remainingResultCount);
|
||||
vo.setDeleted(vo.getRemainingResultCount() == 0);
|
||||
return vo;
|
||||
}
|
||||
|
||||
public void deleteHistory(Long resultId, Long userId) {
|
||||
FileResultEntity entity = fileResultMapper.selectById(resultId);
|
||||
if (entity == null || !MODULE_TYPE.equals(entity.getModuleType()) || !userId.equals(entity.getUserId())) {
|
||||
@@ -400,13 +418,6 @@ public class DeleteBrandRunService {
|
||||
return value.replace(" ", "").trim().toUpperCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
private String normalizeShopName(String value) {
|
||||
if (value == null) {
|
||||
return "";
|
||||
}
|
||||
return value.replace("\u3000", " ").trim();
|
||||
}
|
||||
|
||||
private List<CountryColumnPair> resolveCountryPairs(Row titleRow, Row headerRow, DataFormatter formatter) {
|
||||
List<CountryColumnPair> pairs = new ArrayList<>();
|
||||
int lastCellNum = Math.max(titleRow.getLastCellNum(), headerRow.getLastCellNum());
|
||||
@@ -876,7 +887,7 @@ public class DeleteBrandRunService {
|
||||
throw new BusinessException("存在未完成的结果分片: " + parsedFile.getSourceFilename());
|
||||
}
|
||||
MergedDeleteBrandFile mergedFile = mergeChunks(parsedFile, chunks);
|
||||
File outputFile = buildResultWorkbook(task.getId(), parsedFile, mergedFile);
|
||||
File outputFile = buildResultWorkbookPreserveLayout(task.getId(), parsedFile, mergedFile);
|
||||
|
||||
deleteBrandTaskCacheService.saveProgress(task.getId(), Map.of(
|
||||
"phase", DeleteBrandTaskCacheService.PHASE_UPLOADING,
|
||||
@@ -1032,6 +1043,52 @@ public class DeleteBrandRunService {
|
||||
}
|
||||
}
|
||||
|
||||
private File buildResultWorkbookPreserveLayout(Long taskId,
|
||||
DeleteBrandParsedFileCacheDto parsedFile,
|
||||
MergedDeleteBrandFile mergedFile) {
|
||||
File outputDir = FileUtil.mkdir(FileUtil.file(System.getProperty("java.io.tmpdir"), "delete-brand-result", String.valueOf(taskId)));
|
||||
File outputFile = buildNamedOutputFile(outputDir, blankToDefault(parsedFile.getSourceFilename(), "delete-brand-result.xlsx"));
|
||||
|
||||
try (Workbook workbook = new XSSFWorkbook(); FileOutputStream outputStream = new FileOutputStream(outputFile)) {
|
||||
Sheet sheet = workbook.createSheet("删除品牌结果");
|
||||
Row countryRow = sheet.createRow(0);
|
||||
Row headerRow = sheet.createRow(1);
|
||||
|
||||
for (int countryIndex = 0; countryIndex < mergedFile.countries().size(); countryIndex++) {
|
||||
DeleteBrandProcessedCountryDto country = mergedFile.countries().get(countryIndex);
|
||||
int asinColumnIndex = countryIndex * 2;
|
||||
int statusColumnIndex = asinColumnIndex + 1;
|
||||
|
||||
createTextCell(countryRow, asinColumnIndex, country.getCountry());
|
||||
createTextCell(headerRow, asinColumnIndex, "删除ASIN");
|
||||
createTextCell(headerRow, statusColumnIndex, "状态");
|
||||
|
||||
List<DeleteBrandCountryResultItemDto> items = country.getItems();
|
||||
if (items == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int itemIndex = 0; itemIndex < items.size(); itemIndex++) {
|
||||
DeleteBrandCountryResultItemDto item = items.get(itemIndex);
|
||||
Row row = sheet.getRow(itemIndex + 2);
|
||||
if (row == null) {
|
||||
row = sheet.createRow(itemIndex + 2);
|
||||
}
|
||||
createTextCell(row, asinColumnIndex, item.getAsin());
|
||||
createTextCell(row, statusColumnIndex, item.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < mergedFile.countries().size() * 2; i++) {
|
||||
sheet.autoSizeColumn(i);
|
||||
}
|
||||
workbook.write(outputStream);
|
||||
return outputFile;
|
||||
} catch (Exception ex) {
|
||||
throw new BusinessException("生成删除品牌结果文件失败: " + parsedFile.getSourceFilename());
|
||||
}
|
||||
}
|
||||
|
||||
private void createTextCell(Row row, int index, String value) {
|
||||
Cell cell = row.createCell(index);
|
||||
cell.setCellValue(value == null ? "" : value);
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.nanri.aiimage.modules.ziniao.model.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "紫鸟店铺匹配结果")
|
||||
public class ZiniaoShopMatchResultVo {
|
||||
@Schema(description = "是否匹配成功")
|
||||
private boolean matched;
|
||||
|
||||
@Schema(description = "店铺ID")
|
||||
private String shopId;
|
||||
|
||||
@Schema(description = "店铺名称")
|
||||
private String shopName;
|
||||
|
||||
@Schema(description = "平台")
|
||||
private String platform;
|
||||
|
||||
@Schema(description = "匹配到的员工 userId")
|
||||
private Long matchedUserId;
|
||||
|
||||
@Schema(description = "打开店铺链接")
|
||||
private String openStoreUrl;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.nanri.aiimage.modules.ziniao.service;
|
||||
|
||||
import com.nanri.aiimage.common.exception.BusinessException;
|
||||
import com.nanri.aiimage.modules.ziniao.model.vo.ZiniaoShopMatchResultVo;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ZiniaoShopSwitchService {
|
||||
|
||||
private final ZiniaoAuthService ziniaoAuthService;
|
||||
|
||||
public ZiniaoShopMatchResultVo matchStoreByNameAcrossStaff(String targetShopName, Long preferUserId) {
|
||||
String normalizedShopName = normalizeShopName(targetShopName);
|
||||
if (normalizedShopName.isBlank()) {
|
||||
return emptyMatchResult();
|
||||
}
|
||||
|
||||
ZiniaoAuthService.StoreMatchResult result = ziniaoAuthService.matchStoreByNameAcrossStaff(normalizedShopName, preferUserId);
|
||||
return new ZiniaoShopMatchResultVo(
|
||||
result.matched(),
|
||||
result.shopId(),
|
||||
result.shopName(),
|
||||
result.platform(),
|
||||
result.matchedUserId(),
|
||||
result.openStoreUrl()
|
||||
);
|
||||
}
|
||||
|
||||
public ZiniaoShopMatchResultVo emptyMatchResult() {
|
||||
return new ZiniaoShopMatchResultVo(false, null, null, null, null, null);
|
||||
}
|
||||
|
||||
public String normalizeShopName(String value) {
|
||||
if (value == null) {
|
||||
return "";
|
||||
}
|
||||
return value.replace("\u3000", " ").trim();
|
||||
}
|
||||
|
||||
public boolean isSkippableMatchError(BusinessException ex) {
|
||||
String message = ex == null ? null : ex.getMessage();
|
||||
if (message == null || message.isBlank()) {
|
||||
return false;
|
||||
}
|
||||
return (message.contains("code=40004")
|
||||
|| message.contains("userId\u5b58\u5728\u65e0\u6548\u7684\u53c2\u6570\u503c")
|
||||
|| message.contains("\u65e0\u6743\u9650")
|
||||
|| message.contains("\u6ca1\u6709\u6743\u9650"))
|
||||
&& !message.contains("\u767d\u540d\u5355");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user