修复店铺任务数据覆盖bug
Build Backend JAR / build (push) Has been cancelled

This commit is contained in:
2026-08-29 00:16:47 +08:00
parent 79596a970c
commit 322905607a
5 changed files with 120 additions and 88 deletions
@@ -14,7 +14,9 @@ import org.apache.poi.ss.usermodel.Drawing;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFDrawing;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTTwoCellAnchor;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
@@ -66,10 +68,12 @@ public class ShopDataCrawlExcelAssemblyService {
}
/**
* Appends the supplied task rows to an already assembled daily workbook.
* Existing rows, styles and drawings are deliberately left untouched.
* Replaces the supplied countries' rows inside an already assembled daily
* workbook. Sheets for countries that carry no new rows are left untouched
* (rows, styles and drawings preserved). Returns the total data-row count
* across all sheets after replacement.
*/
public void appendWorkbook(File baseXlsx, File outputXlsx, List<ShopDataCrawlResultItemVo> items) {
public int replaceCountriesWorkbook(File baseXlsx, File outputXlsx, List<ShopDataCrawlResultItemVo> items) {
if (baseXlsx == null || !baseXlsx.isFile()) {
throw new BusinessException("当天累计文件不存在");
}
@@ -84,14 +88,17 @@ public class ShopDataCrawlExcelAssemblyService {
for (int i = 0; i < COUNTRIES.size(); i++) {
List<ShopDataCrawlRowDto> rows = rowsByCountry.get(COUNTRIES.get(i));
if (rows != null && !rows.isEmpty()) {
appendSheet(workbook, workbook.getSheetAt(i), rows, imageCache, pictureIndexes);
Sheet sheet = workbook.getSheetAt(i);
clearSheetPictures(sheet, pictureIndexes);
writeSheet(workbook, sheet, rows, imageCache, pictureIndexes);
}
}
workbook.write(output);
return totalDataRows(workbook);
} catch (BusinessException ex) {
throw ex;
} catch (Exception ex) {
throw new BusinessException("店铺数据抓取累计 Excel 追加失败: " + ex.getMessage());
throw new BusinessException("店铺数据抓取累计 Excel 按国家替换失败: " + ex.getMessage());
}
}
@@ -155,54 +162,21 @@ public class ShopDataCrawlExcelAssemblyService {
sheet.removeRow(row);
}
}
clearSheetPictures(sheet, pictureIndexes);
int rowIndex = 1;
for (ShopDataCrawlRowDto value : rows == null ? List.<ShopDataCrawlRowDto>of() : rows) {
Row row = sheet.createRow(rowIndex++);
String[] values = {value.getDate(), value.getAsin(), "", value.getInventorySales(), value.getSalesRank(),
value.getPageViews(), value.getUnitsSold(), value.getPrice(), value.getRecommendedOffer(), value.getBrand()};
for (int column = 0; column < values.length; column++) {
Cell cell = row.createCell(column);
if (styles[column] != null) cell.setCellStyle(styles[column]);
cell.setCellValue(values[column] == null ? "" : values[column]);
}
if (!blank(value.getCommodityImage())) {
row.setHeightInPoints(IMAGE_ROW_HEIGHT_POINTS);
embedImage(workbook, sheet, row, value.getCommodityImage(), imageCache, pictureIndexes);
}
writeDataRow(workbook, sheet, row, value, styles, imageCache, pictureIndexes);
}
}
private void appendSheet(XSSFWorkbook workbook,
Sheet sheet,
List<ShopDataCrawlRowDto> rows,
Map<String, SimilarAsinImageEmbedder.ResizedImage> imageCache,
Map<String, Integer> pictureIndexes) {
Row header = sheet.getRow(0);
Row styleRow = sheet.getRow(1);
boolean currentTemplate = header != null && "商品图片".equals(cellText(header, IMAGE_COLUMN));
boolean templateHasBrand = header != null && "品牌".equals(cellText(header, BRAND_COLUMN));
CellStyle[] styles = new CellStyle[HEADERS.size()];
for (int column = 0; column < styles.length; column++) {
int sourceColumn = templateColumnForOutput(column, currentTemplate, templateHasBrand);
Cell cell = styleRow == null ? null : styleRow.getCell(sourceColumn);
styles[column] = cell == null ? null : cell.getCellStyle();
}
writeHeaders(sheet, currentTemplate, templateHasBrand);
sheet.setColumnWidth(IMAGE_COLUMN, IMAGE_COLUMN_WIDTH);
int rowIndex = Math.max(1, sheet.getLastRowNum() + 1);
for (ShopDataCrawlRowDto value : rows) {
Row row = sheet.createRow(rowIndex++);
writeRow(workbook, sheet, row, value, styles, imageCache, pictureIndexes);
}
}
private void writeRow(XSSFWorkbook workbook,
Sheet sheet,
Row row,
ShopDataCrawlRowDto value,
CellStyle[] styles,
Map<String, SimilarAsinImageEmbedder.ResizedImage> imageCache,
Map<String, Integer> pictureIndexes) {
private void writeDataRow(XSSFWorkbook workbook,
Sheet sheet,
Row row,
ShopDataCrawlRowDto value,
CellStyle[] styles,
Map<String, SimilarAsinImageEmbedder.ResizedImage> imageCache,
Map<String, Integer> pictureIndexes) {
String[] values = {value.getDate(), value.getAsin(), "", value.getInventorySales(), value.getSalesRank(),
value.getPageViews(), value.getUnitsSold(), value.getPrice(), value.getRecommendedOffer(), value.getBrand()};
for (int column = 0; column < values.length; column++) {
@@ -306,4 +280,25 @@ public class ShopDataCrawlExcelAssemblyService {
}
return result;
}
private void clearSheetPictures(Sheet sheet, Map<String, Integer> pictureIndexes) {
Drawing<?> patriarch = sheet.getDrawingPatriarch();
if (!(patriarch instanceof XSSFDrawing drawing)) {
return;
}
List<CTTwoCellAnchor> anchors = drawing.getCTDrawing().getTwoCellAnchorList();
for (int i = anchors.size() - 1; i >= 0; i--) {
if (anchors.get(i).getPic() != null) {
drawing.getCTDrawing().removeTwoCellAnchor(i);
}
}
}
private int totalDataRows(XSSFWorkbook workbook) {
int total = 0;
for (int i = 0; i < workbook.getNumberOfSheets(); i++) {
total += Math.max(0, workbook.getSheetAt(i).getLastRowNum());
}
return total;
}
}
@@ -1704,12 +1704,14 @@ public class ShopDataCrawlTaskService {
}
int addedRowCount = excelAssemblyService.countRows(List.of(snapshot));
ShopDataCrawlDailyFileEntity baseDailyFile = resolveBaseDailyFile(
preparation.dailyFile(), userId, shopKeyHash, businessDate);
DailyWorkbookArtifact artifact = assembleDailyWorkbook(
task, snapshot, preparation.dailyFile(), addedRowCount);
task, snapshot, baseDailyFile, addedRowCount);
try {
persistedResult = executeShortTransaction(() -> persistDailyAggregation(
task, row, userId, shopKey, shopKeyHash, businessDate,
preparation, artifact, addedRowCount));
preparation, artifact));
if (persistedResult.discardUploadedObject() && artifact.uploaded()) {
deleteObjectQuietly(artifact.objectKey());
}
@@ -1743,23 +1745,35 @@ public class ShopDataCrawlTaskService {
return new DailyAggregationPreparation(dailyFile, false);
}
private ShopDataCrawlDailyFileEntity resolveBaseDailyFile(ShopDataCrawlDailyFileEntity currentDailyFile,
Long userId,
String shopKeyHash,
LocalDate businessDate) {
if (currentDailyFile != null) {
return currentDailyFile;
}
List<ShopDataCrawlDailyFileEntity> older = dailyFileService.findOlder(userId, shopKeyHash, businessDate);
return older == null || older.isEmpty() ? null : older.get(0);
}
private DailyWorkbookArtifact assembleDailyWorkbook(FileTaskEntity task,
ShopDataCrawlResultItemVo snapshot,
ShopDataCrawlDailyFileEntity dailyFile,
ShopDataCrawlDailyFileEntity baseDailyFile,
int addedRowCount) {
String filename = dailyFile != null && !blank(dailyFile.getResultFilename())
? dailyFile.getResultFilename()
String filename = baseDailyFile != null && !blank(baseDailyFile.getResultFilename())
? baseDailyFile.getResultFilename()
: buildTaskWorkbookFilename(task);
String existingObjectKey = dailyFile == null ? null : dailyFile.getResultFileUrl();
String existingObjectKey = baseDailyFile == null ? null : baseDailyFile.getResultFileUrl();
// A result with no new rows only needs a new database membership. Reusing
// the canonical daily object avoids both a local copy and an OSS round trip.
if (dailyFile != null && addedRowCount == 0 && !blank(existingObjectKey)) {
if (baseDailyFile != null && addedRowCount == 0 && !blank(existingObjectKey)) {
return new DailyWorkbookArtifact(
existingObjectKey,
Math.max(0L, Objects.requireNonNullElse(dailyFile.getResultFileSize(), 0L)),
Math.max(0L, Objects.requireNonNullElse(baseDailyFile.getResultFileSize(), 0L)),
false,
filename);
filename,
Math.max(0, Objects.requireNonNullElse(baseDailyFile.getRowCount(), 0)));
}
File workRoot = FileUtil.mkdir(FileUtil.file(
@@ -1770,21 +1784,25 @@ public class ShopDataCrawlTaskService {
File baseXlsx = FileUtil.file(workRoot, "base.xlsx");
File outputXlsx = FileUtil.file(workRoot, filename);
try {
if (dailyFile != null && !blank(existingObjectKey)) {
if (baseDailyFile != null && !blank(existingObjectKey)) {
try {
Files.write(baseXlsx.toPath(), ossStorageService.readObjectBytes(existingObjectKey));
} catch (Exception ex) {
throw new BusinessException("读取当天累计文件失败: " + safeMessage(ex));
throw new BusinessException("读取累计文件失败: " + safeMessage(ex));
}
excelAssemblyService.appendWorkbook(baseXlsx, outputXlsx, List.of(snapshot));
} else {
excelAssemblyService.writeWorkbook(outputXlsx, List.of(snapshot));
int rowCount = excelAssemblyService.replaceCountriesWorkbook(baseXlsx, outputXlsx, List.of(snapshot));
String objectKey = ossStorageService.uploadResultFile(outputXlsx, MODULE_TYPE);
if (blank(objectKey)) {
throw new BusinessException("累计文件上传后未返回文件地址");
}
return new DailyWorkbookArtifact(objectKey, outputXlsx.length(), true, filename, rowCount);
}
excelAssemblyService.writeWorkbook(outputXlsx, List.of(snapshot));
String objectKey = ossStorageService.uploadResultFile(outputXlsx, MODULE_TYPE);
if (blank(objectKey)) {
throw new BusinessException("当天累计文件上传后未返回文件地址");
throw new BusinessException("累计文件上传后未返回文件地址");
}
return new DailyWorkbookArtifact(objectKey, outputXlsx.length(), true, filename);
return new DailyWorkbookArtifact(objectKey, outputXlsx.length(), true, filename, addedRowCount);
} finally {
FileUtil.del(baseXlsx);
FileUtil.del(outputXlsx);
@@ -1799,8 +1817,7 @@ public class ShopDataCrawlTaskService {
String shopKeyHash,
LocalDate businessDate,
DailyAggregationPreparation preparation,
DailyWorkbookArtifact artifact,
int addedRowCount) {
DailyWorkbookArtifact artifact) {
ShopDataCrawlDailyFileEntity dailyFile = dailyFileService.findForUpdate(
userId, shopKeyHash, businessDate);
if (handleExistingDailyMembership(row, dailyFile)) {
@@ -1826,9 +1843,7 @@ public class ShopDataCrawlTaskService {
row.setResultFileUrl(objectKey);
row.setResultFileSize(artifact.fileSize());
row.setResultContentType(CONTENT_TYPE_XLSX);
row.setRowCount(dailyFile == null
? addedRowCount
: Math.max(0, Objects.requireNonNullElse(dailyFile.getRowCount(), 0)) + addedRowCount);
row.setRowCount(artifact.rowCount());
fileResultMapper.updateById(row);
LocalDateTime now = dailyFileService.currentBusinessDateTime();
@@ -2140,7 +2155,12 @@ public class ShopDataCrawlTaskService {
private record DailyWorkbookArtifact(String objectKey,
long fileSize,
boolean uploaded,
String filename) {
String filename,
int rowCount) {
DailyWorkbookArtifact(String objectKey, long fileSize, boolean uploaded, String filename) {
this(objectKey, fileSize, uploaded, filename, 0);
}
}
private record DailyAggregationResult(List<String> obsoleteObjectKeys,