后端优化,增加新需求

This commit is contained in:
super
2026-07-21 19:53:47 +08:00
parent ba48bbc6af
commit bca1335dc6
16 changed files with 529 additions and 61 deletions

View File

@@ -1,8 +1,10 @@
package com.nanri.aiimage.modules.dedupe.service;
import com.nanri.aiimage.common.exception.BusinessException;
import com.nanri.aiimage.modules.dedupe.mapper.DedupeTotalDataMapper;
import com.nanri.aiimage.modules.dedupe.model.dto.DedupeTotalDataCreateRequest;
import com.nanri.aiimage.modules.dedupe.model.entity.DedupeTotalDataEntity;
import com.nanri.aiimage.modules.dedupe.model.vo.DedupeTotalDataImportProgressVo;
import com.nanri.aiimage.modules.dedupe.model.vo.DedupeTotalDataItemVo;
import com.nanri.aiimage.modules.dedupe.model.vo.DedupeTotalDataPageVo;
import com.nanri.aiimage.modules.permission.mapper.AdminUserMapper;
@@ -15,15 +17,23 @@ import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.server.ResponseStatusException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -42,6 +52,8 @@ class DedupeTotalDataServiceTest {
private ShopManageGroupMapper shopManageGroupMapper;
@Mock
private PlatformTransactionManager transactionManager;
@Mock
private TransactionStatus transactionStatus;
@InjectMocks
private DedupeTotalDataService service;
@@ -65,13 +77,14 @@ class DedupeTotalDataServiceTest {
});
DedupeTotalDataCreateRequest request = new DedupeTotalDataCreateRequest();
request.setDataValue(" B012345678 ");
request.setDataValue(" b012345678 ");
DedupeTotalDataItemVo item = service.create(request, 23L);
ArgumentCaptor<DedupeTotalDataEntity> captor = ArgumentCaptor.forClass(DedupeTotalDataEntity.class);
verify(dedupeTotalDataMapper).insert(captor.capture());
assertEquals(23L, captor.getValue().getUploaderUserId());
assertEquals("member-a", captor.getValue().getUploaderUsername());
assertEquals("B012345678", captor.getValue().getDataValue());
assertEquals("member-a", item.getUsername());
}
@@ -125,31 +138,106 @@ class DedupeTotalDataServiceTest {
}
@Test
void usernameSearchCannotExpandLeaderScope() {
void concurrentDuplicateDuringImportIsSkipped() throws Exception {
when(adminUserMapper.selectById(23L)).thenReturn(user(23L, "normal", "member-a"));
when(dedupeTotalDataMapper.selectOne(any())).thenReturn(null);
when(transactionManager.getTransaction(any())).thenReturn(transactionStatus);
when(dedupeTotalDataMapper.insert(any(DedupeTotalDataEntity.class)))
.thenThrow(new DuplicateKeyException("duplicate"));
var result = service.importFromExcel(asinWorkbook("b012345678"), 23L);
assertEquals(0, result.getInsertedCount());
assertEquals(1, result.getSkippedCount());
verify(transactionManager).rollback(transactionStatus);
}
@Test
void usernameSearchKeepsLeaderScope() {
when(adminUserMapper.selectById(10L)).thenReturn(user(10L, "admin", "leader"));
when(shopManageGroupMapper.selectManagedMemberUserIds(10L)).thenReturn(List.of(23L));
when(adminUserMapper.selectIdsByUsernameLike("other")).thenReturn(List.of(99L));
when(dedupeTotalDataMapper.selectCount(any())).thenReturn(0L);
when(dedupeTotalDataMapper.selectList(any())).thenReturn(List.of());
DedupeTotalDataPageVo page = service.page(1, 15, "", "other", 10L);
assertEquals(0L, page.getTotal());
assertTrue(page.getItems().isEmpty());
verify(dedupeTotalDataMapper, never()).selectCount(any());
verify(dedupeTotalDataMapper, never()).selectList(any());
verify(shopManageGroupMapper).selectManagedMemberUserIds(10L);
verify(dedupeTotalDataMapper).selectCount(any());
verify(dedupeTotalDataMapper).selectList(any());
}
@Test
void comparableValueLookupRemainsGlobal() {
when(dedupeTotalDataMapper.selectExistingDataValues(List.of("B012345678")))
.thenReturn(List.of("B012345678"));
.thenReturn(List.of("b012345678"));
Set<String> values = service.findExistingComparableValues(List.of(" B012345678 "));
Set<String> values = service.findExistingComparableValues(List.of(" b012345678 "));
assertEquals(Set.of("B012345678"), values);
verify(adminUserMapper, never()).selectById(any(Long.class));
verify(shopManageGroupMapper, never()).selectManagedMemberUserIds(any(Long.class));
}
@Test
void exportUsesScopedUsernameAndProducesWorkbook() throws Exception {
when(adminUserMapper.selectById(10L)).thenReturn(user(10L, "admin", "leader"));
when(shopManageGroupMapper.selectManagedMemberUserIds(10L)).thenReturn(List.of(23L));
DedupeTotalDataEntity entity = data(91L, 23L);
entity.setUploaderUsername("member-a");
entity.setCreatedAt(LocalDateTime.of(2026, 7, 20, 12, 30));
when(dedupeTotalDataMapper.selectList(any())).thenReturn(List.of(entity));
byte[] bytes = service.export(
"member",
LocalDate.of(2026, 7, 1),
LocalDate.of(2026, 7, 20),
10L);
try (XSSFWorkbook workbook = new XSSFWorkbook(new ByteArrayInputStream(bytes))) {
var sheet = workbook.getSheetAt(0);
assertEquals("ASIN值", sheet.getRow(0).getCell(1).getStringCellValue());
assertEquals("B012345678", sheet.getRow(1).getCell(1).getStringCellValue());
assertEquals("member-a", sheet.getRow(1).getCell(2).getStringCellValue());
assertEquals("2026-07-20 12:30:00", sheet.getRow(1).getCell(3).getStringCellValue());
}
verify(shopManageGroupMapper).selectManagedMemberUserIds(10L);
verify(dedupeTotalDataMapper).selectList(any());
}
@Test
void exportRejectsReversedDateRange() {
assertThrows(BusinessException.class, () -> service.export(
"",
LocalDate.of(2026, 7, 20),
LocalDate.of(2026, 7, 1),
10L));
verify(dedupeTotalDataMapper, never()).selectList(any());
}
@Test
@SuppressWarnings("unchecked")
void expiredCompletedProgressIsRemovedOnNextLookup() {
Map<String, DedupeTotalDataImportProgressVo> progressMap =
(Map<String, DedupeTotalDataImportProgressVo>) ReflectionTestUtils.getField(service, "importProgressMap");
Map<String, Long> ownerMap = (Map<String, Long>) ReflectionTestUtils.getField(service, "importOwnerMap");
Map<String, Long> completedAtMap =
(Map<String, Long>) ReflectionTestUtils.getField(service, "importCompletedAtMap");
DedupeTotalDataImportProgressVo progress = new DedupeTotalDataImportProgressVo();
progress.setStatus("success");
progressMap.put("expired", progress);
ownerMap.put("expired", 23L);
completedAtMap.put("expired", System.currentTimeMillis() - (2 * 60 * 60 * 1000L));
assertThrows(BusinessException.class, () -> service.getImportProgress("expired", 23L));
assertFalse(progressMap.containsKey("expired"));
assertFalse(ownerMap.containsKey("expired"));
assertFalse(completedAtMap.containsKey("expired"));
}
private AdminUserEntity user(Long id, String role, String username) {
AdminUserEntity user = new AdminUserEntity();
user.setId(id);

View File

@@ -0,0 +1,82 @@
package com.nanri.aiimage.modules.task.service;
import com.nanri.aiimage.config.InstanceMetadata;
import com.nanri.aiimage.modules.appearancepatent.service.AppearancePatentTaskService;
import com.nanri.aiimage.modules.brand.service.BrandTaskService;
import com.nanri.aiimage.modules.collectdata.service.CollectDataService;
import com.nanri.aiimage.modules.deletebrand.service.DeleteBrandRunService;
import com.nanri.aiimage.modules.patroldelete.service.PatrolDeleteTaskService;
import com.nanri.aiimage.modules.pricetrack.service.PriceTrackTaskService;
import com.nanri.aiimage.modules.productrisk.service.ProductRiskTaskService;
import com.nanri.aiimage.modules.queryasin.service.QueryAsinTaskService;
import com.nanri.aiimage.modules.shopmatch.service.ShopMatchTaskService;
import com.nanri.aiimage.modules.similarasin.service.SimilarAsinTaskService;
import com.nanri.aiimage.modules.task.mapper.FileResultMapper;
import com.nanri.aiimage.modules.task.model.entity.FileResultEntity;
import com.nanri.aiimage.modules.task.model.entity.TaskFileJobEntity;
import com.nanri.aiimage.modules.withdraw.service.WithdrawTaskService;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InOrder;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class TaskResultFileJobWorkerTest {
@Mock private TaskFileJobService taskFileJobService;
@Mock private TaskDistributedLockService taskDistributedLockService;
@Mock private TaskResultPayloadService taskResultPayloadService;
@Mock private FileResultMapper fileResultMapper;
@Mock private TaskFileJobLocalDispatcher taskFileJobLocalDispatcher;
@Mock private InstanceMetadata instanceMetadata;
@Mock private ShopMatchTaskService shopMatchTaskService;
@Mock private PriceTrackTaskService priceTrackTaskService;
@Mock private ProductRiskTaskService productRiskTaskService;
@Mock private QueryAsinTaskService queryAsinTaskService;
@Mock private WithdrawTaskService withdrawTaskService;
@Mock private PatrolDeleteTaskService patrolDeleteTaskService;
@Mock private AppearancePatentTaskService appearancePatentTaskService;
@Mock private SimilarAsinTaskService similarAsinTaskService;
@Mock private DeleteBrandRunService deleteBrandRunService;
@Mock private BrandTaskService brandTaskService;
@Mock private CollectDataService collectDataService;
@InjectMocks private TaskResultFileJobWorker worker;
@Test
void withdrawFileSuccessFinalizesTaskAfterReleasingLock() {
long jobId = 13640L;
long taskId = 20140L;
long resultId = 22928L;
TaskFileJobEntity job = new TaskFileJobEntity();
job.setId(jobId);
job.setTaskId(taskId);
job.setResultId(resultId);
job.setModuleType("WITHDRAW");
job.setScopeKey("withdraw:20140");
FileResultEntity result = new FileResultEntity();
result.setResultFileUrl("result/withdraw/20140.xlsx");
TaskDistributedLockService.LockHandle lock = mock(TaskDistributedLockService.LockHandle.class);
when(taskFileJobService.markRunning(jobId)).thenReturn(true);
when(taskDistributedLockService.acquire("WITHDRAW", taskId, TaskDistributedLockService.DEFAULT_WAIT_MILLIS))
.thenReturn(lock);
when(fileResultMapper.selectById(resultId)).thenReturn(result);
worker.process(job);
verify(taskFileJobService).markSuccess(job, "result/withdraw/20140.xlsx");
verify(taskResultPayloadService).deleteLatest(taskId, "WITHDRAW", "withdraw:20140");
InOrder order = inOrder(lock, withdrawTaskService);
order.verify(lock).close();
order.verify(withdrawTaskService).tryFinalizeTask(taskId, false);
}
}