店铺数据抓取累计文件改为店铺级共享,按国家覆盖更新

同店同日在不同账号下产生多份互不相干的累计文件(唯一键含 user_id),
后台管理页每店只显示最新一份,其他账号抓的国家看起来丢失。

- V95 迁移:加 shop_key/country_codes_json/compensation_done 列,按店铺
  合并存量 daily_file 与成员行,唯一键改为 (shop_key, business_date)
- DailyFileService:findForUpdate/findOlder/acquireLock 去 user 维度,
  店铺级锁跨账号串行
- TaskService:聚合按店铺定位;applyCountryCoverage 按国家覆盖(本次
  回传的国家替换旧行,未更新的国家保留);启动补偿组件按成员快照重建
  合并文件并回填国家列表
- 管理页:country_codes 改从 daily_file.country_codes_json 取,行数用
  累计文件实际值
This commit is contained in:
2026-08-30 16:30:09 +08:00
parent ae95b294ec
commit 97ae8a7c51
11 changed files with 495 additions and 160 deletions
@@ -278,11 +278,11 @@ class ShopDataCrawlCleanupTest {
FileResultEntity row = invocation.getArgument(0);
return row == null ? null : row.getSourceFilename();
});
lenient().when(dailyFileService.acquireLock(anyLong(), anyString()))
lenient().when(dailyFileService.acquireLock(anyString()))
.thenReturn(mock(TaskDistributedLockService.LockHandle.class));
lenient().when(dailyFileService.findForUpdate(anyLong(), anyString(), any())).thenAnswer(invocation ->
copyDailyFile(findDailyFile(invocation.getArgument(0), invocation.getArgument(1))));
lenient().when(dailyFileService.findOlder(anyLong(), anyString(), any())).thenReturn(List.of());
lenient().when(dailyFileService.findForUpdate(anyString(), any())).thenAnswer(invocation ->
copyDailyFile(findDailyFile(invocation.getArgument(0))));
lenient().when(dailyFileService.findOlder(anyString(), any())).thenReturn(List.of());
lenient().when(dailyFileService.findByLatestResultId(anyLong())).thenReturn(List.of());
lenient().when(dailyFileService.findById(anyLong())).thenReturn(null);
lenient().when(dailyFileService.countObjectReferences(anyString())).thenAnswer(invocation -> {
@@ -423,7 +423,7 @@ class ShopDataCrawlCleanupTest {
taskStore.put(1L, task);
lenient().when(dailyFileService.findByLatestResultId(8102L))
.thenReturn(List.of(copyDailyFile(findDailyFile(1L, "hash:" + SHOP_NAME))));
.thenReturn(List.of(copyDailyFile(findDailyFile("hash:" + SHOP_NAME))));
String newObjectKey = "oss/daily/rebuilt.xlsx";
lenient().when(ossStorageService.uploadResultFile(any(), eq(MODULE_TYPE))).thenReturn(newObjectKey);
doAnswer(invocation -> {
@@ -459,7 +459,7 @@ class ShopDataCrawlCleanupTest {
taskStore.put(1L, task);
lenient().when(dailyFileService.findByLatestResultId(8105L))
.thenReturn(List.of(copyDailyFile(findDailyFile(1L, "hash:" + SHOP_NAME))));
.thenReturn(List.of(copyDailyFile(findDailyFile("hash:" + SHOP_NAME))));
lenient().when(taskResultItemService.getResultSnapshot(1L, MODULE_TYPE, 8103L,
ShopDataCrawlResultItemVo.class)).thenReturn(snapshot(8103L));
lenient().when(taskResultItemService.getResultSnapshot(1L, MODULE_TYPE, 8104L,
@@ -531,7 +531,7 @@ class ShopDataCrawlCleanupTest {
FileTaskEntity task = taskEntity(1L, "SUCCESS");
taskStore.put(1L, task);
lenient().when(dailyFileService.findByLatestResultId(8107L))
.thenReturn(List.of(copyDailyFile(findDailyFile(1L, "hash:" + SHOP_NAME))));
.thenReturn(List.of(copyDailyFile(findDailyFile("hash:" + SHOP_NAME))));
service.deleteHistory(8107L, USER_ID);
@@ -588,7 +588,7 @@ class ShopDataCrawlCleanupTest {
FileTaskEntity task = taskEntity(1L, "SUCCESS");
taskStore.put(1L, task);
lenient().when(dailyFileService.findByLatestResultId(resultIds[11]))
.thenReturn(List.of(copyDailyFile(findDailyFile(1L, "hash:" + SHOP_NAME))));
.thenReturn(List.of(copyDailyFile(findDailyFile("hash:" + SHOP_NAME))));
for (long resultId : resultIds) {
service.deleteHistory(resultId, USER_ID);
@@ -652,7 +652,7 @@ class ShopDataCrawlCleanupTest {
FileTaskEntity task = taskEntity(1L, "SUCCESS");
taskStore.put(1L, task);
lenient().when(dailyFileService.findByLatestResultId(8112L))
.thenReturn(List.of(copyDailyFile(findDailyFile(1L, "hash:" + SHOP_NAME))));
.thenReturn(List.of(copyDailyFile(findDailyFile("hash:" + SHOP_NAME))));
lenient().when(taskResultItemService.getResultSnapshot(1L, MODULE_TYPE, 8111L,
ShopDataCrawlResultItemVo.class)).thenReturn(snapshot(8111L));
doAnswer(invocation -> {
@@ -824,15 +824,14 @@ class ShopDataCrawlCleanupTest {
lastJobTaskId = 2L;
TaskFileJobEntity job2 = jobEntity(2L, 2L, MODULE_TYPE, 8402L);
AtomicLong calls = new AtomicLong();
lenient().when(dailyFileService.findForUpdate(anyLong(), anyString(), any())).thenAnswer(invocation -> {
Long userId = invocation.getArgument(0);
String shopKeyHash = invocation.getArgument(1);
lenient().when(dailyFileService.findForUpdate(anyString(), any())).thenAnswer(invocation -> {
String shopKeyHash = invocation.getArgument(0);
if (calls.incrementAndGet() == 2L) {
ShopDataCrawlDailyFileEntity conflicting = copyDailyFile(findDailyFile(userId, shopKeyHash));
ShopDataCrawlDailyFileEntity conflicting = copyDailyFile(findDailyFile(shopKeyHash));
conflicting.setVersion(99L);
return conflicting;
}
return copyDailyFile(findDailyFile(userId, shopKeyHash));
return copyDailyFile(findDailyFile(shopKeyHash));
});
service.processResultFileJob(job2);
@@ -1171,9 +1170,9 @@ class ShopDataCrawlCleanupTest {
return DigestUtil.sha256Hex("result-chunks:" + SHOP_NAME);
}
private ShopDataCrawlDailyFileEntity findDailyFile(Long userId, String shopKeyHash) {
private ShopDataCrawlDailyFileEntity findDailyFile(String shopKeyHash) {
for (ShopDataCrawlDailyFileEntity f : dbDailyFiles) {
if (Objects.equals(f.getUserId(), userId) && Objects.equals(f.getShopKeyHash(), shopKeyHash)) {
if (Objects.equals(f.getShopKeyHash(), shopKeyHash)) {
return f;
}
}
@@ -194,7 +194,7 @@ class ShopDataCrawlDailyFileIncrementalTest {
});
// 店铺级锁:每次返回独立 mock 句柄,供失败路径验证 close()。
lenient().when(dailyFileService.acquireLock(anyLong(), anyString())).thenAnswer(invocation -> {
lenient().when(dailyFileService.acquireLock(anyString())).thenAnswer(invocation -> {
TaskDistributedLockService.LockHandle handle = mock(TaskDistributedLockService.LockHandle.class);
lastLock.set(handle);
return handle;
@@ -209,9 +209,9 @@ class ShopDataCrawlDailyFileIncrementalTest {
FileResultEntity row = invocation.getArgument(0);
return row == null ? null : row.getSourceFilename();
});
lenient().when(dailyFileService.findForUpdate(anyLong(), anyString(), any()))
.thenAnswer(invocation -> findDailyFile(invocation.getArgument(0), invocation.getArgument(1)));
lenient().when(dailyFileService.findOlder(anyLong(), anyString(), any())).thenReturn(List.of());
lenient().when(dailyFileService.findForUpdate(anyString(), any()))
.thenAnswer(invocation -> findDailyFile(invocation.getArgument(0)));
lenient().when(dailyFileService.findOlder(anyString(), any())).thenReturn(List.of());
lenient().when(dailyFileService.findByLatestResultId(anyLong())).thenReturn(List.of());
lenient().when(dailyFileService.findById(anyLong())).thenReturn(null);
lenient().when(dailyFileService.countObjectReferences(anyString())).thenReturn(0L);
@@ -482,8 +482,7 @@ class ShopDataCrawlDailyFileIncrementalTest {
ShopDataCrawlDailyFileEntity entity = invocation.getArgument(0);
for (int i = 0; i < dbDailyFiles.size(); i++) {
ShopDataCrawlDailyFileEntity existing = dbDailyFiles.get(i);
if (Objects.equals(existing.getUserId(), entity.getUserId())
&& Objects.equals(existing.getShopKeyHash(), entity.getShopKeyHash())
if (Objects.equals(existing.getShopKeyHash(), entity.getShopKeyHash())
&& Objects.equals(existing.getBusinessDate(), entity.getBusinessDate())) {
entity.setId(existing.getId());
dbDailyFiles.set(i, entity);
@@ -517,9 +516,9 @@ class ShopDataCrawlDailyFileIncrementalTest {
});
}
private ShopDataCrawlDailyFileEntity findDailyFile(Long userId, String shopKeyHash) {
private ShopDataCrawlDailyFileEntity findDailyFile(String shopKeyHash) {
for (ShopDataCrawlDailyFileEntity f : dbDailyFiles) {
if (Objects.equals(f.getUserId(), userId) && Objects.equals(f.getShopKeyHash(), shopKeyHash)) {
if (Objects.equals(f.getShopKeyHash(), shopKeyHash)) {
return f;
}
}
@@ -535,4 +534,57 @@ class ShopDataCrawlDailyFileIncrementalTest {
FileResultEntity row = addResultRow(900L, 900L, 1, SHOP_NAME, null);
processJob(900L, List.of(row), snapshot(900L, SHOP_NAME, 1));
}
@Test
void test_task_095_shop_level_country_coverage_keeps_unupdated_countries() {
// V95 店铺级共享:跨账号同店结果汇聚到同一份累计文件,
// 同国家覆盖更新,未更新的国家保留。
// 账号A(user 7)先抓英国;账号B(user 8)抓德国;账号A再抓英国(覆盖)。
FileResultEntity uk1 = addResultRow(9501L, 9501L, 1, SHOP_NAME, null);
processJob(9501L, List.of(uk1), snapshotWithCountry(9501L, "UK", 100));
FileResultEntity de1 = addResultRow(9502L, 9502L, 1, SHOP_NAME, null);
de1.setUserId(8L); // 不同账号
processJob(9502L, List.of(de1), snapshotWithCountry(9502L, "DE", 200));
FileResultEntity uk2 = addResultRow(9503L, 9503L, 1, SHOP_NAME, null);
processJob(9503L, List.of(uk2), snapshotWithCountry(9503L, "UK", 150));
assertEquals(1, dbDailyFiles.size(), "跨账号同店同日只保留一份累计文件");
assertEquals(3, dbMembers.size(), "三个结果各有一个成员行");
// 整表重建:英国以最后一次(9503)为准,德国(9502)保留
triggerRebuild();
assertEquals(4, lastAssembledItems.size(), "成员快照按创建顺序累积,item 结构保持");
Map<String, Integer> countryRows = new java.util.HashMap<>();
for (ShopDataCrawlResultItemVo item : lastAssembledItems) {
if (item.getCountryResults() == null) continue;
for (com.nanri.aiimage.modules.shopdatacrawl.model.dto.ShopDataCrawlCountryResultDto cr
: item.getCountryResults()) {
countryRows.put(cr.getCountry(), cr.getItems() == null ? 0 : cr.getItems().size());
}
}
assertEquals(150, countryRows.getOrDefault("UK", -1), "英国以最后一次抓取为准(覆盖更新)");
assertEquals(200, countryRows.getOrDefault("DE", -1), "德国未更新,保留之前抓取的数据");
verify(ossStorageService, never()).readObjectBytes(anyString());
}
private ShopDataCrawlResultItemVo snapshotWithCountry(long resultId, String country, int rows) {
ShopDataCrawlResultItemVo item = snapshot(resultId, SHOP_NAME, rows);
com.nanri.aiimage.modules.shopdatacrawl.model.dto.ShopDataCrawlCountryResultDto countryResult =
new com.nanri.aiimage.modules.shopdatacrawl.model.dto.ShopDataCrawlCountryResultDto();
countryResult.setCountry(country);
List<com.nanri.aiimage.modules.shopdatacrawl.model.dto.ShopDataCrawlRowDto> rowList = new ArrayList<>();
for (int i = 0; i < rows; i++) {
com.nanri.aiimage.modules.shopdatacrawl.model.dto.ShopDataCrawlRowDto row =
new com.nanri.aiimage.modules.shopdatacrawl.model.dto.ShopDataCrawlRowDto();
row.setAsin("B0" + String.format("%08d", i));
row.setDate("2026-07-25");
rowList.add(row);
}
countryResult.setItems(rowList);
item.setCountryResults(List.of(countryResult));
item.setCountryCodes(List.of(country));
return item;
}
}
@@ -221,11 +221,11 @@ class ShopDataCrawlDailyFileJobSplitTest {
FileResultEntity row = invocation.getArgument(0);
return row == null ? null : row.getSourceFilename();
});
lenient().when(dailyFileService.acquireLock(anyLong(), anyString()))
lenient().when(dailyFileService.acquireLock(anyString()))
.thenReturn(mock(TaskDistributedLockService.LockHandle.class));
lenient().when(dailyFileService.findForUpdate(anyLong(), anyString(), any())).thenAnswer(invocation ->
copyDailyFile(findDailyFile(invocation.getArgument(0), invocation.getArgument(1))));
lenient().when(dailyFileService.findOlder(anyLong(), anyString(), any())).thenReturn(List.of());
lenient().when(dailyFileService.findForUpdate(anyString(), any())).thenAnswer(invocation ->
copyDailyFile(findDailyFile(invocation.getArgument(0))));
lenient().when(dailyFileService.findOlder(anyString(), any())).thenReturn(List.of());
lenient().when(dailyFileService.findByLatestResultId(anyLong())).thenReturn(List.of());
lenient().when(dailyFileService.findById(anyLong())).thenReturn(null);
lenient().when(dailyFileService.countObjectReferences(anyString())).thenReturn(0L);
@@ -622,9 +622,9 @@ class ShopDataCrawlDailyFileJobSplitTest {
});
}
private ShopDataCrawlDailyFileEntity findDailyFile(Long userId, String shopKeyHash) {
private ShopDataCrawlDailyFileEntity findDailyFile(String shopKeyHash) {
for (ShopDataCrawlDailyFileEntity f : dbDailyFiles) {
if (Objects.equals(f.getUserId(), userId) && Objects.equals(f.getShopKeyHash(), shopKeyHash)) {
if (Objects.equals(f.getShopKeyHash(), shopKeyHash)) {
return f;
}
}
@@ -200,7 +200,7 @@ class ShopDataCrawlDailyFileLockTest {
}).when(excelAssemblyService).writeWorkbook(any(), any());
// 店铺级锁:每次获取返回独立句柄并计数(两次短临界区各取一次)。
lenient().when(dailyFileService.acquireLock(anyLong(), anyString())).thenAnswer(invocation -> {
lenient().when(dailyFileService.acquireLock(anyString())).thenAnswer(invocation -> {
TaskDistributedLockService.LockHandle handle = mock(TaskDistributedLockService.LockHandle.class);
lockAcquireCount.incrementAndGet();
lastLock.set(handle);
@@ -217,10 +217,9 @@ class ShopDataCrawlDailyFileLockTest {
FileResultEntity row = invocation.getArgument(0);
return row == null ? null : row.getSourceFilename();
});
lenient().when(dailyFileService.findForUpdate(anyLong(), anyString(), any()))
.thenAnswer(invocation -> copyDailyFile(findDailyFile(
invocation.getArgument(0), invocation.getArgument(1))));
lenient().when(dailyFileService.findOlder(anyLong(), anyString(), any())).thenReturn(List.of());
lenient().when(dailyFileService.findForUpdate(anyString(), any()))
.thenAnswer(invocation -> copyDailyFile(findDailyFile(invocation.getArgument(0))));
lenient().when(dailyFileService.findOlder(anyString(), any())).thenReturn(List.of());
lenient().when(dailyFileService.findByLatestResultId(anyLong())).thenReturn(List.of());
lenient().when(dailyFileService.findById(anyLong())).thenReturn(null);
lenient().when(dailyFileService.countObjectReferences(anyString())).thenReturn(0L);
@@ -345,14 +344,14 @@ class ShopDataCrawlDailyFileLockTest {
seedDailyFile();
FileResultEntity row = addResultRow(7207L, 1L, 1, SHOP_NAME, null);
lenient().doAnswer(invocation -> {
ShopDataCrawlDailyFileEntity current = findDailyFile(invocation.getArgument(0), invocation.getArgument(1));
ShopDataCrawlDailyFileEntity current = findDailyFile(invocation.getArgument(0));
if (current == null) {
return null;
}
ShopDataCrawlDailyFileEntity readCopy = copyDailyFile(current);
current.setVersion(readCopy.getVersion() + 1L);
return readCopy;
}).when(dailyFileService).findForUpdate(anyLong(), anyString(), any());
}).when(dailyFileService).findForUpdate(anyString(), any());
Exception ex = assertThrows(BusinessException.class,
() -> processJob(1L, List.of(row), snapshot(7207L)));
@@ -566,9 +565,9 @@ class ShopDataCrawlDailyFileLockTest {
});
}
private ShopDataCrawlDailyFileEntity findDailyFile(Long userId, String shopKeyHash) {
private ShopDataCrawlDailyFileEntity findDailyFile(String shopKeyHash) {
for (ShopDataCrawlDailyFileEntity f : dbDailyFiles) {
if (Objects.equals(f.getUserId(), userId) && Objects.equals(f.getShopKeyHash(), shopKeyHash)) {
if (Objects.equals(f.getShopKeyHash(), shopKeyHash)) {
return f;
}
}
@@ -127,10 +127,10 @@ class ShopDataCrawlTaskServiceRetentionTest {
when(dailyFileService.currentBusinessDateTime()).thenReturn(BUSINESS_TIME);
when(dailyFileService.shopKey(any())).thenReturn("shop-id:shop-1");
when(dailyFileService.shopKeyHash(anyString())).thenReturn("hash-1");
when(dailyFileService.acquireLock(eq(USER_ID), eq("shop-id:shop-1")))
when(dailyFileService.acquireLock("shop-id:shop-1"))
.thenReturn(mock(TaskDistributedLockService.LockHandle.class));
when(dailyFileService.findMembersByResultId(RESULT_ID)).thenReturn(List.of());
when(dailyFileService.findOlder(USER_ID, "hash-1", BUSINESS_DATE)).thenReturn(List.of());
when(dailyFileService.findOlder("hash-1", BUSINESS_DATE)).thenReturn(List.of());
when(dailyFileService.countObjectReferences(anyString())).thenReturn(0L);
when(dailyFileService.addMemberWithPayload(anyLong(), anyLong(), anyLong(), anyString())).thenReturn(true);
when(transactionManager.getTransaction(any())).thenReturn(transactionStatus);
@@ -144,7 +144,7 @@ class ShopDataCrawlTaskServiceRetentionTest {
@Test
void firstSuccessCreatesDailyWorkbookAndMembership() {
when(fileResultMapper.selectList(any())).thenReturn(List.of(currentRow), List.of(currentRow));
when(dailyFileService.findForUpdate(USER_ID, "hash-1", BUSINESS_DATE)).thenReturn(null);
when(dailyFileService.findForUpdate("hash-1", BUSINESS_DATE)).thenReturn(null);
when(ossStorageService.uploadResultFile(any(), eq(MODULE_TYPE))).thenReturn("result/new.xlsx");
when(excelAssemblyService.writeWorkbook(any(), any())).thenReturn(1);
@@ -176,7 +176,7 @@ class ShopDataCrawlTaskServiceRetentionTest {
+ "\"shopId\":\"shop-1\",\"success\":true,\"countryResults\":[]}");
when(fileResultMapper.selectList(any())).thenReturn(List.of(currentRow), List.of(previous, currentRow));
when(fileTaskMapper.selectBatchIds(List.of(100L))).thenReturn(List.of(previousTask));
when(dailyFileService.findForUpdate(USER_ID, "hash-1", BUSINESS_DATE)).thenReturn(daily);
when(dailyFileService.findForUpdate("hash-1", BUSINESS_DATE)).thenReturn(daily);
when(dailyFileService.containsResult(301L, RESULT_ID)).thenReturn(false);
when(dailyFileService.listMembers(301L)).thenReturn(List.of(previousMember));
when(taskResultItemService.getResultSnapshot(
@@ -192,7 +192,8 @@ class ShopDataCrawlTaskServiceRetentionTest {
verify(ossStorageService, never()).readObjectBytes(anyString());
verify(dailyFileService).update(daily);
verify(ossStorageService).deleteObject("result/old.xlsx");
assertNull(previous.getResultFileUrl());
// 店铺级共享:历史结果行保留旧对象指针(可继续下载),累计文件本体被新对象取代
assertEquals("result/old.xlsx", previous.getResultFileUrl());
assertEquals("result/new.xlsx", currentRow.getResultFileUrl());
assertEquals(2, currentRow.getRowCount());
}
@@ -215,7 +216,7 @@ class ShopDataCrawlTaskServiceRetentionTest {
ShopDataCrawlDailyFileEntity daily = daily("result/old.xlsx", 2);
when(fileResultMapper.selectList(any())).thenReturn(List.of(currentRow));
when(dailyFileService.findForUpdate(USER_ID, "hash-1", BUSINESS_DATE)).thenReturn(daily);
when(dailyFileService.findForUpdate("hash-1", BUSINESS_DATE)).thenReturn(daily);
when(dailyFileService.containsResult(301L, RESULT_ID)).thenReturn(false);
doAnswer(invocation -> {
assertFalse(transactionActive.get(), "row counting must run outside the database transaction");
@@ -248,7 +249,7 @@ class ShopDataCrawlTaskServiceRetentionTest {
ShopDataCrawlDailyFileEntity daily = daily("result/current.xlsx", 3);
daily.setLatestResultId(RESULT_ID);
when(fileResultMapper.selectList(any())).thenReturn(List.of(currentRow));
when(dailyFileService.findForUpdate(USER_ID, "hash-1", BUSINESS_DATE)).thenReturn(daily);
when(dailyFileService.findForUpdate("hash-1", BUSINESS_DATE)).thenReturn(daily);
when(dailyFileService.containsResult(301L, RESULT_ID)).thenReturn(true);
service.processResultFileJob(job);
@@ -264,7 +265,7 @@ class ShopDataCrawlTaskServiceRetentionTest {
void zeroNewRowsReuseDailyObjectWithoutWorkbookIo() {
ShopDataCrawlDailyFileEntity daily = daily("result/current.xlsx", 3);
when(fileResultMapper.selectList(any())).thenReturn(List.of(currentRow));
when(dailyFileService.findForUpdate(USER_ID, "hash-1", BUSINESS_DATE)).thenReturn(daily);
when(dailyFileService.findForUpdate("hash-1", BUSINESS_DATE)).thenReturn(daily);
when(dailyFileService.containsResult(301L, RESULT_ID)).thenReturn(false);
when(excelAssemblyService.countRows(any())).thenReturn(0);
@@ -291,7 +292,7 @@ class ShopDataCrawlTaskServiceRetentionTest {
when(fileResultMapper.selectList(any())).thenReturn(List.of(currentRow), List.of(previous, currentRow));
when(fileTaskMapper.selectBatchIds(List.of(100L))).thenReturn(List.of(previousTask));
when(fileResultMapper.selectCount(any())).thenReturn(1L);
when(dailyFileService.findForUpdate(USER_ID, "hash-1", BUSINESS_DATE)).thenReturn(daily);
when(dailyFileService.findForUpdate("hash-1", BUSINESS_DATE)).thenReturn(daily);
when(dailyFileService.containsResult(301L, RESULT_ID)).thenReturn(false);
when(dailyFileService.listMembers(301L)).thenReturn(List.of());
when(taskResultItemService.getResultSnapshot(
@@ -314,7 +315,7 @@ class ShopDataCrawlTaskServiceRetentionTest {
yesterday.setLatestResultId(RESULT_ID);
ShopDataCrawlDailyMemberEntity member = member(301L, TASK_ID, RESULT_ID, BUSINESS_TIME.minusDays(1));
when(fileResultMapper.selectList(any())).thenReturn(List.of(currentRow));
when(dailyFileService.findForUpdate(USER_ID, "hash-1", BUSINESS_DATE)).thenReturn(today);
when(dailyFileService.findForUpdate("hash-1", BUSINESS_DATE)).thenReturn(today);
when(dailyFileService.findMembersByResultId(RESULT_ID)).thenReturn(List.of(member));
when(dailyFileService.findById(301L)).thenReturn(yesterday);
@@ -333,8 +334,8 @@ class ShopDataCrawlTaskServiceRetentionTest {
yesterday.setId(300L);
yesterday.setBusinessDate(BUSINESS_DATE.minusDays(1));
when(fileResultMapper.selectList(any())).thenReturn(List.of(currentRow), List.of(previous, currentRow));
when(dailyFileService.findForUpdate(USER_ID, "hash-1", BUSINESS_DATE)).thenReturn(null);
when(dailyFileService.findOlder(USER_ID, "hash-1", BUSINESS_DATE)).thenReturn(List.of(yesterday));
when(dailyFileService.findForUpdate("hash-1", BUSINESS_DATE)).thenReturn(null);
when(dailyFileService.findOlder("hash-1", BUSINESS_DATE)).thenReturn(List.of(yesterday));
when(dailyFileService.listMembers(300L)).thenReturn(List.of());
when(excelAssemblyService.writeWorkbook(any(), any())).thenReturn(1);
when(ossStorageService.uploadResultFile(any(), eq(MODULE_TYPE))).thenReturn("result/today.xlsx");
@@ -350,7 +351,8 @@ class ShopDataCrawlTaskServiceRetentionTest {
verify(ossStorageService).deleteObject("result/yesterday.xlsx");
assertEquals("result/today.xlsx", currentRow.getResultFileUrl());
assertEquals(1, currentRow.getRowCount());
assertNull(previous.getResultFileUrl());
// 店铺级共享:旧日历史结果行保留旧对象指针(可下载),仅累计文件行被删除
assertEquals("result/yesterday.xlsx", previous.getResultFileUrl());
}
@Test
@@ -359,8 +361,8 @@ class ShopDataCrawlTaskServiceRetentionTest {
yesterday.setId(300L);
yesterday.setBusinessDate(BUSINESS_DATE.minusDays(1));
when(fileResultMapper.selectList(any())).thenReturn(List.of(currentRow));
when(dailyFileService.findForUpdate(USER_ID, "hash-1", BUSINESS_DATE)).thenReturn(null);
when(dailyFileService.findOlder(USER_ID, "hash-1", BUSINESS_DATE)).thenReturn(List.of(yesterday));
when(dailyFileService.findForUpdate("hash-1", BUSINESS_DATE)).thenReturn(null);
when(dailyFileService.findOlder("hash-1", BUSINESS_DATE)).thenReturn(List.of(yesterday));
when(dailyFileService.listMembers(300L)).thenReturn(List.of());
when(excelAssemblyService.writeWorkbook(any(), any())).thenReturn(4);
doThrow(new IllegalStateException("upload failed"))
@@ -378,8 +380,8 @@ class ShopDataCrawlTaskServiceRetentionTest {
yesterday.setId(300L);
yesterday.setBusinessDate(BUSINESS_DATE.minusDays(1));
when(fileResultMapper.selectList(any())).thenReturn(List.of(currentRow));
when(dailyFileService.findForUpdate(USER_ID, "hash-1", BUSINESS_DATE)).thenReturn(null);
when(dailyFileService.findOlder(USER_ID, "hash-1", BUSINESS_DATE)).thenReturn(List.of(yesterday));
when(dailyFileService.findForUpdate("hash-1", BUSINESS_DATE)).thenReturn(null);
when(dailyFileService.findOlder("hash-1", BUSINESS_DATE)).thenReturn(List.of(yesterday));
when(dailyFileService.listMembers(300L)).thenReturn(List.of());
when(excelAssemblyService.writeWorkbook(any(), any())).thenReturn(4);
when(ossStorageService.uploadResultFile(any(), eq(MODULE_TYPE))).thenReturn("result/new.xlsx");
@@ -418,7 +420,7 @@ class ShopDataCrawlTaskServiceRetentionTest {
when(dailyFileService.findByLatestResultId(RESULT_ID)).thenReturn(List.of(daily));
when(dailyFileService.findMembersByResultId(RESULT_ID)).thenReturn(List.of(removedMember));
when(dailyFileService.findById(301L)).thenReturn(daily);
when(dailyFileService.findForUpdate(USER_ID, "hash-1", BUSINESS_DATE)).thenReturn(daily);
when(dailyFileService.findForUpdate("hash-1", BUSINESS_DATE)).thenReturn(daily);
when(dailyFileService.listMembers(301L)).thenReturn(List.of(removedMember, previousMember));
when(taskResultItemService.getResultSnapshot(
100L, MODULE_TYPE, 200L, ShopDataCrawlResultItemVo.class)).thenReturn(previousSnapshot);