后台去重数据新增国家
This commit is contained in:
+5
-3
@@ -87,10 +87,11 @@ public class DedupeTotalDataController {
|
||||
@Parameter(description = "结束日期(包含)")
|
||||
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate,
|
||||
@Parameter(description = "分组 ID") @RequestParam(required = false) Long groupId,
|
||||
@Parameter(description = "国家代码(如 DE、UK)") @RequestParam(required = false) String country,
|
||||
HttpServletRequest request) {
|
||||
RequestOperator operator = requireDedupeTotalDataAccess(request);
|
||||
return ApiResponse.success(dedupeTotalDataService.page(
|
||||
page, pageSize, keyword, username, startDate, endDate, groupId, operator.id()));
|
||||
page, pageSize, keyword, username, startDate, endDate, groupId, country, operator.id()));
|
||||
}
|
||||
|
||||
@GetMapping("/export")
|
||||
@@ -102,6 +103,7 @@ public class DedupeTotalDataController {
|
||||
@Parameter(description = "结束日期(包含)")
|
||||
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate,
|
||||
@Parameter(description = "分组 ID") @RequestParam(required = false) Long groupId,
|
||||
@Parameter(description = "国家代码(如 DE、UK)") @RequestParam(required = false) String country,
|
||||
HttpServletRequest request) {
|
||||
if (startDate != null && endDate != null && startDate.isAfter(endDate)) {
|
||||
throw new BusinessException(400, "invalid export date range");
|
||||
@@ -112,9 +114,9 @@ public class DedupeTotalDataController {
|
||||
+ (monthlyZip ? ".zip" : ".xlsx");
|
||||
StreamingResponseBody body = monthlyZip
|
||||
? outputStream -> dedupeTotalDataService.writeMonthlyZipExport(
|
||||
outputStream, username, startDate, endDate, groupId, operator.id())
|
||||
outputStream, username, startDate, endDate, groupId, country, operator.id())
|
||||
: outputStream -> dedupeTotalDataService.writeExport(
|
||||
outputStream, username, startDate, endDate, groupId, operator.id());
|
||||
outputStream, username, startDate, endDate, groupId, country, operator.id());
|
||||
return ResponseEntity.ok()
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, DownloadHeaderUtil.contentDisposition(filename))
|
||||
.contentType(MediaType.parseMediaType(monthlyZip ? ZIP_CONTENT_TYPE : XLSX_CONTENT_TYPE))
|
||||
|
||||
+2
-2
@@ -28,10 +28,10 @@ public interface DedupeTotalDataMapper extends BaseMapper<DedupeTotalDataEntity>
|
||||
@Insert("""
|
||||
<script>
|
||||
INSERT IGNORE INTO biz_dedupe_total_data
|
||||
(data_value, group_id, uploader_user_id, uploader_username)
|
||||
(data_value, country, group_id, uploader_user_id, uploader_username)
|
||||
VALUES
|
||||
<foreach collection="rows" item="row" separator=",">
|
||||
(#{row.dataValue}, #{row.groupId}, #{row.uploaderUserId}, #{row.uploaderUsername})
|
||||
(#{row.dataValue}, #{row.country}, #{row.groupId}, #{row.uploaderUserId}, #{row.uploaderUsername})
|
||||
</foreach>
|
||||
</script>
|
||||
""")
|
||||
|
||||
+1
@@ -14,6 +14,7 @@ public class DedupeTotalDataEntity {
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
private String dataValue;
|
||||
private String country;
|
||||
private Long groupId;
|
||||
private Long uploaderUserId;
|
||||
private String uploaderUsername;
|
||||
|
||||
+3
@@ -15,6 +15,9 @@ public class DedupeTotalDataItemVo {
|
||||
@Schema(description = "总数据值")
|
||||
private String dataValue;
|
||||
|
||||
@Schema(description = "国家代码,逗号分隔,如 DE,UK")
|
||||
private String country;
|
||||
|
||||
@Schema(description = "分组 ID")
|
||||
private Long groupId;
|
||||
|
||||
|
||||
+115
-17
@@ -94,6 +94,11 @@ public class DedupeTotalDataService {
|
||||
|
||||
public DedupeTotalDataPageVo page(long page, long pageSize, String keyword, String username,
|
||||
LocalDate startDate, LocalDate endDate, Long groupId, Long operatorId) {
|
||||
return page(page, pageSize, keyword, username, startDate, endDate, groupId, null, operatorId);
|
||||
}
|
||||
|
||||
public DedupeTotalDataPageVo page(long page, long pageSize, String keyword, String username,
|
||||
LocalDate startDate, LocalDate endDate, Long groupId, String country, Long operatorId) {
|
||||
if (startDate != null && endDate != null && startDate.isAfter(endDate)) {
|
||||
throw new BusinessException("开始日期不能晚于结束日期");
|
||||
}
|
||||
@@ -101,10 +106,14 @@ public class DedupeTotalDataService {
|
||||
long safePageSize = Math.min(Math.max(pageSize, 1), 100);
|
||||
String safeKeyword = keyword == null ? "" : keyword.trim();
|
||||
String safeUsername = username == null ? "" : username.trim();
|
||||
String safeCountry = country == null ? "" : country.trim();
|
||||
AccessScope scope = resolveAccessScope(operatorId);
|
||||
LambdaQueryWrapper<DedupeTotalDataEntity> query = new LambdaQueryWrapper<DedupeTotalDataEntity>()
|
||||
.like(!safeKeyword.isEmpty(), DedupeTotalDataEntity::getDataValue, safeKeyword)
|
||||
.like(!safeUsername.isEmpty(), DedupeTotalDataEntity::getUploaderUsername, safeUsername)
|
||||
.apply(!safeCountry.isEmpty(),
|
||||
"FIND_IN_SET({0}, IFNULL(country, '')) > 0",
|
||||
safeCountry)
|
||||
.ge(startDate != null, DedupeTotalDataEntity::getCreatedAt,
|
||||
startDate == null ? null : startDate.atStartOfDay())
|
||||
.lt(endDate != null, DedupeTotalDataEntity::getCreatedAt,
|
||||
@@ -134,13 +143,18 @@ public class DedupeTotalDataService {
|
||||
|
||||
public byte[] export(String username, LocalDate startDate, LocalDate endDate,
|
||||
Long groupId, Long operatorId) {
|
||||
return export(username, startDate, endDate, groupId, null, operatorId);
|
||||
}
|
||||
|
||||
public byte[] export(String username, LocalDate startDate, LocalDate endDate,
|
||||
Long groupId, String country, Long operatorId) {
|
||||
if (startDate != null && endDate != null && startDate.isAfter(endDate)) {
|
||||
throw new BusinessException("开始日期不能晚于结束日期");
|
||||
}
|
||||
String safeUsername = username == null ? "" : username.trim();
|
||||
AccessScope scope = resolveAccessScope(operatorId);
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
buildExportWorkbook(outputStream, safeUsername, startDate, endDate, groupId, scope);
|
||||
buildExportWorkbook(outputStream, safeUsername, startDate, endDate, groupId, country, scope);
|
||||
return outputStream.toByteArray();
|
||||
}
|
||||
|
||||
@@ -150,6 +164,16 @@ public class DedupeTotalDataService {
|
||||
LocalDate endDate,
|
||||
Long groupId,
|
||||
Long operatorId) {
|
||||
writeExport(outputStream, username, startDate, endDate, groupId, null, operatorId);
|
||||
}
|
||||
|
||||
public void writeExport(OutputStream outputStream,
|
||||
String username,
|
||||
LocalDate startDate,
|
||||
LocalDate endDate,
|
||||
Long groupId,
|
||||
String country,
|
||||
Long operatorId) {
|
||||
if (outputStream == null) {
|
||||
throw new BusinessException("导出输出流不能为空");
|
||||
}
|
||||
@@ -158,7 +182,7 @@ public class DedupeTotalDataService {
|
||||
}
|
||||
String safeUsername = username == null ? "" : username.trim();
|
||||
AccessScope scope = resolveAccessScope(operatorId);
|
||||
buildExportWorkbook(outputStream, safeUsername, startDate, endDate, groupId, scope);
|
||||
buildExportWorkbook(outputStream, safeUsername, startDate, endDate, groupId, country, scope);
|
||||
}
|
||||
|
||||
public void writeMonthlyZipExport(OutputStream outputStream,
|
||||
@@ -167,17 +191,28 @@ public class DedupeTotalDataService {
|
||||
LocalDate endDate,
|
||||
Long groupId,
|
||||
Long operatorId) {
|
||||
writeMonthlyZipExport(outputStream, username, startDate, endDate, groupId, null, operatorId);
|
||||
}
|
||||
|
||||
public void writeMonthlyZipExport(OutputStream outputStream,
|
||||
String username,
|
||||
LocalDate startDate,
|
||||
LocalDate endDate,
|
||||
Long groupId,
|
||||
String country,
|
||||
Long operatorId) {
|
||||
if (outputStream == null) {
|
||||
throw new BusinessException("export output stream cannot be null");
|
||||
}
|
||||
if (startDate == null || endDate == null || YearMonth.from(startDate).equals(YearMonth.from(endDate))) {
|
||||
writeExport(outputStream, username, startDate, endDate, groupId, operatorId);
|
||||
writeExport(outputStream, username, startDate, endDate, groupId, country, operatorId);
|
||||
return;
|
||||
}
|
||||
if (startDate.isAfter(endDate)) {
|
||||
throw new BusinessException("invalid export date range");
|
||||
}
|
||||
String safeUsername = username == null ? "" : username.trim();
|
||||
String safeCountry = country == null ? "" : country.trim();
|
||||
AccessScope scope = resolveAccessScope(operatorId);
|
||||
boolean groupPrevalidated = groupId != null && groupId > 0;
|
||||
if (groupPrevalidated) {
|
||||
@@ -194,7 +229,7 @@ public class DedupeTotalDataService {
|
||||
LocalDate entryEndDate = endDate.isBefore(monthEnd) ? endDate : monthEnd;
|
||||
zipOutputStream.putNextEntry(new ZipEntry(monthlyExportEntryName(currentMonth)));
|
||||
buildExportWorkbook(zipOutputStream, safeUsername, entryStartDate, entryEndDate,
|
||||
groupId, scope, groupPrevalidated);
|
||||
groupId, safeCountry, scope, groupPrevalidated);
|
||||
zipOutputStream.closeEntry();
|
||||
currentMonth = currentMonth.plusMonths(1);
|
||||
}
|
||||
@@ -211,8 +246,9 @@ public class DedupeTotalDataService {
|
||||
LocalDate startDate,
|
||||
LocalDate endDate,
|
||||
Long groupId,
|
||||
String country,
|
||||
AccessScope scope) {
|
||||
buildExportWorkbook(outputStream, username, startDate, endDate, groupId, scope, false);
|
||||
buildExportWorkbook(outputStream, username, startDate, endDate, groupId, country, scope, false);
|
||||
}
|
||||
|
||||
private void buildExportWorkbook(OutputStream outputStream,
|
||||
@@ -220,6 +256,7 @@ public class DedupeTotalDataService {
|
||||
LocalDate startDate,
|
||||
LocalDate endDate,
|
||||
Long groupId,
|
||||
String country,
|
||||
AccessScope scope,
|
||||
boolean groupPrevalidated) {
|
||||
try (SXSSFWorkbook workbook = new SXSSFWorkbook(100)) {
|
||||
@@ -227,14 +264,15 @@ public class DedupeTotalDataService {
|
||||
Row header = sheet.createRow(0);
|
||||
header.createCell(0).setCellValue("ID");
|
||||
header.createCell(1).setCellValue("ASIN值");
|
||||
header.createCell(2).setCellValue("用户名");
|
||||
header.createCell(3).setCellValue("分组");
|
||||
header.createCell(4).setCellValue("创建时间");
|
||||
header.createCell(2).setCellValue("国家");
|
||||
header.createCell(3).setCellValue("用户名");
|
||||
header.createCell(4).setCellValue("分组");
|
||||
header.createCell(5).setCellValue("创建时间");
|
||||
Long lastId = null;
|
||||
int rowIndex = 1;
|
||||
while (true) {
|
||||
LambdaQueryWrapper<DedupeTotalDataEntity> pageQuery = buildExportQuery(
|
||||
username, startDate, endDate, groupId, scope, groupPrevalidated);
|
||||
username, startDate, endDate, groupId, country, scope, groupPrevalidated);
|
||||
if (lastId != null) {
|
||||
pageQuery.lt(DedupeTotalDataEntity::getId, lastId);
|
||||
}
|
||||
@@ -248,11 +286,12 @@ public class DedupeTotalDataService {
|
||||
Row row = sheet.createRow(rowIndex++);
|
||||
row.createCell(0).setCellValue(entity.getId() == null ? "" : String.valueOf(entity.getId()));
|
||||
row.createCell(1).setCellValue(entity.getDataValue() == null ? "" : entity.getDataValue());
|
||||
row.createCell(2).setCellValue(entity.getUploaderUsername() == null ? "" : entity.getUploaderUsername());
|
||||
row.createCell(3).setCellValue(entity.getGroupId() == null
|
||||
row.createCell(2).setCellValue(entity.getCountry() == null ? "" : entity.getCountry());
|
||||
row.createCell(3).setCellValue(entity.getUploaderUsername() == null ? "" : entity.getUploaderUsername());
|
||||
row.createCell(4).setCellValue(entity.getGroupId() == null
|
||||
? ""
|
||||
: groupNames.getOrDefault(entity.getGroupId(), ""));
|
||||
row.createCell(4).setCellValue(formatExportTime(entity.getCreatedAt()));
|
||||
row.createCell(5).setCellValue(formatExportTime(entity.getCreatedAt()));
|
||||
}
|
||||
DedupeTotalDataEntity lastRow = rows.getLast();
|
||||
lastId = lastRow == null ? null : lastRow.getId();
|
||||
@@ -265,9 +304,10 @@ public class DedupeTotalDataService {
|
||||
}
|
||||
sheet.setColumnWidth(0, 3600);
|
||||
sheet.setColumnWidth(1, 5200);
|
||||
sheet.setColumnWidth(2, 5200);
|
||||
sheet.setColumnWidth(2, 3200);
|
||||
sheet.setColumnWidth(3, 5200);
|
||||
sheet.setColumnWidth(4, 5600);
|
||||
sheet.setColumnWidth(4, 5200);
|
||||
sheet.setColumnWidth(5, 5600);
|
||||
workbook.write(outputStream);
|
||||
workbook.dispose();
|
||||
} catch (Exception ex) {
|
||||
@@ -280,18 +320,23 @@ public class DedupeTotalDataService {
|
||||
LocalDate endDate,
|
||||
Long groupId,
|
||||
AccessScope scope) {
|
||||
return buildExportQuery(username, startDate, endDate, groupId, scope, false);
|
||||
return buildExportQuery(username, startDate, endDate, groupId, null, scope, false);
|
||||
}
|
||||
|
||||
private LambdaQueryWrapper<DedupeTotalDataEntity> buildExportQuery(String username,
|
||||
LocalDate startDate,
|
||||
LocalDate endDate,
|
||||
Long groupId,
|
||||
String country,
|
||||
AccessScope scope,
|
||||
boolean groupPrevalidated) {
|
||||
String safeUsername = username == null ? "" : username.trim();
|
||||
String safeCountry = country == null ? "" : country.trim();
|
||||
LambdaQueryWrapper<DedupeTotalDataEntity> query = new LambdaQueryWrapper<DedupeTotalDataEntity>()
|
||||
.like(!safeUsername.isEmpty(), DedupeTotalDataEntity::getUploaderUsername, safeUsername)
|
||||
.apply(!safeCountry.isEmpty(),
|
||||
"FIND_IN_SET({0}, IFNULL(country, '')) > 0",
|
||||
safeCountry)
|
||||
.ge(startDate != null, DedupeTotalDataEntity::getCreatedAt,
|
||||
startDate == null ? null : startDate.atStartOfDay())
|
||||
.lt(endDate != null, DedupeTotalDataEntity::getCreatedAt,
|
||||
@@ -608,9 +653,11 @@ public class DedupeTotalDataService {
|
||||
if (asinIndex == null) {
|
||||
throw new BusinessException("缺少 ASIN 列");
|
||||
}
|
||||
Integer countryIndex = headerMap.get("国家");
|
||||
|
||||
Set<String> seenInFile = new HashSet<>();
|
||||
List<String> pendingValues = new ArrayList<>(IMPORT_BATCH_SIZE);
|
||||
Map<String, String> pendingCountries = new HashMap<>();
|
||||
int totalRows = Math.max(sheet.getLastRowNum(), 0);
|
||||
int asinCount = 0;
|
||||
int insertedCount = 0;
|
||||
@@ -644,21 +691,26 @@ public class DedupeTotalDataService {
|
||||
continue;
|
||||
}
|
||||
pendingValues.add(asin);
|
||||
if (countryIndex != null) {
|
||||
pendingCountries.put(asin, parseCountries(formatter.formatCellValue(row.getCell(countryIndex))));
|
||||
}
|
||||
if (pendingValues.size() >= IMPORT_BATCH_SIZE) {
|
||||
ImportBatchResult batch = insertImportBatch(
|
||||
pendingValues, groupId, uploaderUserId, uploaderUsername);
|
||||
pendingValues, pendingCountries, groupId, uploaderUserId, uploaderUsername);
|
||||
insertedCount += batch.insertedCount();
|
||||
skippedCount += batch.skippedCount();
|
||||
pendingValues.clear();
|
||||
pendingCountries.clear();
|
||||
}
|
||||
updateImportProgress(progress, rowNum, asinCount, insertedCount, skippedCount);
|
||||
}
|
||||
if (!pendingValues.isEmpty()) {
|
||||
ImportBatchResult batch = insertImportBatch(
|
||||
pendingValues, groupId, uploaderUserId, uploaderUsername);
|
||||
pendingValues, pendingCountries, groupId, uploaderUserId, uploaderUsername);
|
||||
insertedCount += batch.insertedCount();
|
||||
skippedCount += batch.skippedCount();
|
||||
pendingValues.clear();
|
||||
pendingCountries.clear();
|
||||
}
|
||||
updateImportProgress(progress, totalRows, asinCount, insertedCount, skippedCount);
|
||||
|
||||
@@ -676,6 +728,7 @@ public class DedupeTotalDataService {
|
||||
}
|
||||
|
||||
private ImportBatchResult insertImportBatch(List<String> values,
|
||||
Map<String, String> countriesByValue,
|
||||
Long groupId,
|
||||
Long uploaderUserId,
|
||||
String uploaderUsername) {
|
||||
@@ -690,6 +743,7 @@ public class DedupeTotalDataService {
|
||||
}
|
||||
DedupeTotalDataEntity entity = new DedupeTotalDataEntity();
|
||||
entity.setDataValue(value);
|
||||
entity.setCountry(countriesByValue == null ? null : countriesByValue.get(value));
|
||||
entity.setGroupId(groupId);
|
||||
entity.setUploaderUserId(uploaderUserId);
|
||||
entity.setUploaderUsername(uploaderUsername);
|
||||
@@ -1076,6 +1130,49 @@ public class DedupeTotalDataService {
|
||||
.replaceAll("\\s+", " ");
|
||||
}
|
||||
|
||||
private static final List<String> COUNTRY_CODE_MAP = List.of(
|
||||
"DE", "UK", "FR", "IT", "ES", "US",
|
||||
"美国", "德国", "英国", "法国", "意大利", "西班牙");
|
||||
|
||||
private String normalizeCountryCode(String raw) {
|
||||
String upper = normalizeExcelText(raw).toUpperCase(Locale.ROOT);
|
||||
return upper
|
||||
.replace("GERMANY", "DE")
|
||||
.replace("UNITED KINGDOM", "UK")
|
||||
.replace("UNITEDKINGDOM", "UK")
|
||||
.replace("ENGLAND", "UK")
|
||||
.replace("FRANCE", "FR")
|
||||
.replace("ITALY", "IT")
|
||||
.replace("SPAIN", "ES")
|
||||
.replace("USA", "US")
|
||||
.replace("UNITED STATES", "US")
|
||||
.replace("UNITEDSTATES", "US")
|
||||
.replace("AMERICA", "US")
|
||||
.replace("德国", "DE")
|
||||
.replace("英国", "UK")
|
||||
.replace("法国", "FR")
|
||||
.replace("意大利", "IT")
|
||||
.replace("西班牙", "ES")
|
||||
.replace("美国", "US");
|
||||
}
|
||||
|
||||
/** 将国家列单元格内容解析为标准国家代码(去重后按出现顺序),无有效国家返回 null。 */
|
||||
private String parseCountries(String raw) {
|
||||
if (raw == null || raw.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
// 先对整个单元格做别名归一(含多词国家名如 "United Kingdom"),再按分隔符/空格拆分。
|
||||
String normalized = normalizeCountryCode(raw);
|
||||
LinkedHashSet<String> codes = new LinkedHashSet<>();
|
||||
String[] tokens = normalized.split("[,,;;、/\\\\|\\s]+");
|
||||
for (String token : tokens) {
|
||||
if (COUNTRY_CODE_MAP.contains(token)) {
|
||||
codes.add(token);
|
||||
}
|
||||
}
|
||||
return codes.isEmpty() ? null : String.join(",", codes);
|
||||
}
|
||||
|
||||
private Map<Long, String> loadGroupNames(List<DedupeTotalDataEntity> rows) {
|
||||
List<Long> groupIds = rows.stream()
|
||||
.map(DedupeTotalDataEntity::getGroupId)
|
||||
@@ -1098,6 +1195,7 @@ public class DedupeTotalDataService {
|
||||
DedupeTotalDataItemVo vo = new DedupeTotalDataItemVo();
|
||||
vo.setId(entity.getId());
|
||||
vo.setDataValue(entity.getDataValue());
|
||||
vo.setCountry(entity.getCountry());
|
||||
vo.setGroupId(entity.getGroupId());
|
||||
vo.setGroupName(groupName == null ? "" : groupName);
|
||||
vo.setUploaderUserId(entity.getUploaderUserId());
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
SET @country_col_exists := (
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'biz_dedupe_total_data'
|
||||
AND COLUMN_NAME = 'country'
|
||||
);
|
||||
|
||||
SET @sql_add_country := IF(
|
||||
@country_col_exists = 0,
|
||||
'ALTER TABLE biz_dedupe_total_data ADD COLUMN country VARCHAR(64) DEFAULT NULL COMMENT ''国家代码,逗号分隔,如 DE,UK'' AFTER data_value',
|
||||
'SELECT 1'
|
||||
);
|
||||
PREPARE stmt_add_country FROM @sql_add_country;
|
||||
EXECUTE stmt_add_country;
|
||||
DEALLOCATE PREPARE stmt_add_country;
|
||||
|
||||
SET @country_idx_exists := (
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.STATISTICS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'biz_dedupe_total_data'
|
||||
AND INDEX_NAME = 'idx_country'
|
||||
);
|
||||
|
||||
SET @sql_add_country_idx := IF(
|
||||
@country_idx_exists = 0,
|
||||
'ALTER TABLE biz_dedupe_total_data ADD INDEX idx_country (country)',
|
||||
'SELECT 1'
|
||||
);
|
||||
PREPARE stmt_add_country_idx FROM @sql_add_country_idx;
|
||||
EXECUTE stmt_add_country_idx;
|
||||
DEALLOCATE PREPARE stmt_add_country_idx;
|
||||
+12
-3
@@ -56,6 +56,7 @@ class DedupeTotalDataControllerTest {
|
||||
eq(LocalDate.of(2026, 7, 1)),
|
||||
eq(LocalDate.of(2026, 7, 31)),
|
||||
eq(3L),
|
||||
isNull(),
|
||||
eq(8L));
|
||||
|
||||
ResponseEntity<StreamingResponseBody> response = controller.export(
|
||||
@@ -63,6 +64,7 @@ class DedupeTotalDataControllerTest {
|
||||
LocalDate.of(2026, 7, 1),
|
||||
LocalDate.of(2026, 7, 31),
|
||||
3L,
|
||||
null,
|
||||
request);
|
||||
|
||||
assertNotNull(response.getBody());
|
||||
@@ -84,9 +86,10 @@ class DedupeTotalDataControllerTest {
|
||||
eq(LocalDate.of(2026, 7, 1)),
|
||||
eq(LocalDate.of(2026, 7, 31)),
|
||||
eq(3L),
|
||||
isNull(),
|
||||
eq(8L));
|
||||
verify(service, never()).writeMonthlyZipExport(
|
||||
any(OutputStream.class), any(), any(), any(), any(), any());
|
||||
any(OutputStream.class), any(), any(), any(), any(), any(), any());
|
||||
verifyNoInteractions(permissionMenuService);
|
||||
}
|
||||
|
||||
@@ -109,6 +112,7 @@ class DedupeTotalDataControllerTest {
|
||||
null,
|
||||
LocalDate.of(2026, 8, 2),
|
||||
3L,
|
||||
null,
|
||||
request);
|
||||
|
||||
assertEquals(MediaType.parseMediaType(
|
||||
@@ -125,9 +129,10 @@ class DedupeTotalDataControllerTest {
|
||||
isNull(),
|
||||
eq(LocalDate.of(2026, 8, 2)),
|
||||
eq(3L),
|
||||
isNull(),
|
||||
eq(8L));
|
||||
verify(service, never()).writeMonthlyZipExport(
|
||||
any(OutputStream.class), any(), any(), any(), any(), any());
|
||||
any(OutputStream.class), any(), any(), any(), any(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -153,6 +158,7 @@ class DedupeTotalDataControllerTest {
|
||||
eq(LocalDate.of(2026, 7, 15)),
|
||||
eq(LocalDate.of(2026, 8, 2)),
|
||||
eq(3L),
|
||||
isNull(),
|
||||
eq(8L));
|
||||
|
||||
ResponseEntity<StreamingResponseBody> response = controller.export(
|
||||
@@ -160,6 +166,7 @@ class DedupeTotalDataControllerTest {
|
||||
LocalDate.of(2026, 7, 15),
|
||||
LocalDate.of(2026, 8, 2),
|
||||
3L,
|
||||
null,
|
||||
request);
|
||||
|
||||
assertEquals(MediaType.parseMediaType("application/zip"), response.getHeaders().getContentType());
|
||||
@@ -176,8 +183,9 @@ class DedupeTotalDataControllerTest {
|
||||
eq(LocalDate.of(2026, 7, 15)),
|
||||
eq(LocalDate.of(2026, 8, 2)),
|
||||
eq(3L),
|
||||
isNull(),
|
||||
eq(8L));
|
||||
verify(service, never()).writeExport(any(OutputStream.class), any(), any(), any(), any(), any());
|
||||
verify(service, never()).writeExport(any(OutputStream.class), any(), any(), any(), any(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -194,6 +202,7 @@ class DedupeTotalDataControllerTest {
|
||||
LocalDate.of(2026, 8, 2),
|
||||
LocalDate.of(2026, 7, 15),
|
||||
3L,
|
||||
null,
|
||||
request));
|
||||
|
||||
assertEquals(400, exception.getCode());
|
||||
|
||||
+78
-3
@@ -21,6 +21,7 @@ import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.apache.ibatis.builder.MapperBuilderAssistant;
|
||||
import org.apache.poi.ss.usermodel.Row;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
@@ -188,6 +189,58 @@ class DedupeTotalDataServiceTest {
|
||||
verify(dedupeTotalDataMapper, never()).insert(any(DedupeTotalDataEntity.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void importReadsAndNormalizesCountryColumn() throws Exception {
|
||||
when(adminUserMapper.selectById(23L)).thenReturn(user(23L, "normal", "member-a"));
|
||||
stubWritableGroup(23L, 7L);
|
||||
when(dedupeTotalDataMapper.selectExistingDataValues(List.of("B012345678"))).thenReturn(List.of());
|
||||
when(dedupeTotalDataMapper.insertBatchIgnore(any())).thenReturn(1);
|
||||
MockMultipartFile file = asinCountryWorkbook("B012345678", "德国, US,United Kingdom");
|
||||
|
||||
var result = service.importFromExcel(file, 7L, 23L);
|
||||
|
||||
assertEquals(1, result.getInsertedCount());
|
||||
ArgumentCaptor<List<DedupeTotalDataEntity>> captor = ArgumentCaptor.forClass(List.class);
|
||||
verify(dedupeTotalDataMapper).insertBatchIgnore(captor.capture());
|
||||
assertEquals("DE,US,UK", captor.getValue().getFirst().getCountry());
|
||||
}
|
||||
|
||||
@Test
|
||||
void importKeepsCountryBlankWhenHeaderMissing() throws Exception {
|
||||
when(adminUserMapper.selectById(23L)).thenReturn(user(23L, "normal", "member-a"));
|
||||
stubWritableGroup(23L, 7L);
|
||||
when(dedupeTotalDataMapper.selectExistingDataValues(List.of("B012345678"))).thenReturn(List.of());
|
||||
when(dedupeTotalDataMapper.insertBatchIgnore(any())).thenReturn(1);
|
||||
|
||||
var result = service.importFromExcel(asinWorkbook("B012345678"), 7L, 23L);
|
||||
|
||||
assertEquals(1, result.getInsertedCount());
|
||||
ArgumentCaptor<List<DedupeTotalDataEntity>> captor = ArgumentCaptor.forClass(List.class);
|
||||
verify(dedupeTotalDataMapper).insertBatchIgnore(captor.capture());
|
||||
assertEquals(null, captor.getValue().getFirst().getCountry());
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
void pageFiltersByCountryWithFindInSet() {
|
||||
TableInfoHelper.initTableInfo(
|
||||
new MapperBuilderAssistant(new MybatisConfiguration(), ""),
|
||||
DedupeTotalDataEntity.class);
|
||||
when(adminUserMapper.selectById(1L)).thenReturn(user(1L, "super_admin", "root"));
|
||||
when(dedupeTotalDataMapper.selectCount(any())).thenReturn(0L);
|
||||
when(dedupeTotalDataMapper.selectList(any())).thenReturn(List.of());
|
||||
|
||||
service.page(1, 15, "", "", null, null, null, "UK", 1L);
|
||||
|
||||
ArgumentCaptor<LambdaQueryWrapper<DedupeTotalDataEntity>> queryCaptor =
|
||||
ArgumentCaptor.forClass((Class) LambdaQueryWrapper.class);
|
||||
verify(dedupeTotalDataMapper).selectCount(queryCaptor.capture());
|
||||
LambdaQueryWrapper<DedupeTotalDataEntity> query = queryCaptor.getValue();
|
||||
query.getSqlSegment();
|
||||
assertTrue(query.getParamNameValuePairs().containsValue("UK"));
|
||||
assertTrue(query.getCustomSqlSegment().contains("FIND_IN_SET"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void concurrentDuplicateDuringImportIsSkipped() throws Exception {
|
||||
when(adminUserMapper.selectById(23L)).thenReturn(user(23L, "normal", "member-a"));
|
||||
@@ -294,6 +347,7 @@ class DedupeTotalDataServiceTest {
|
||||
when(shopManageGroupMapper.selectManagedMemberUserIds(10L)).thenReturn(List.of(23L));
|
||||
DedupeTotalDataEntity entity = data(91L, 23L);
|
||||
entity.setUploaderUsername("member-a");
|
||||
entity.setCountry("DE,UK");
|
||||
entity.setCreatedAt(LocalDateTime.of(2026, 7, 20, 12, 30));
|
||||
when(dedupeTotalDataMapper.selectList(any())).thenReturn(List.of(entity));
|
||||
|
||||
@@ -305,11 +359,13 @@ class DedupeTotalDataServiceTest {
|
||||
|
||||
try (XSSFWorkbook workbook = new XSSFWorkbook(new ByteArrayInputStream(bytes))) {
|
||||
var sheet = workbook.getSheetAt(0);
|
||||
assertEquals("国家", sheet.getRow(0).getCell(2).getStringCellValue());
|
||||
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("", sheet.getRow(1).getCell(3).getStringCellValue());
|
||||
assertEquals("2026-07-20 12:30:00", sheet.getRow(1).getCell(4).getStringCellValue());
|
||||
assertEquals("DE,UK", sheet.getRow(1).getCell(2).getStringCellValue());
|
||||
assertEquals("member-a", sheet.getRow(1).getCell(3).getStringCellValue());
|
||||
assertEquals("", sheet.getRow(1).getCell(4).getStringCellValue());
|
||||
assertEquals("2026-07-20 12:30:00", sheet.getRow(1).getCell(5).getStringCellValue());
|
||||
}
|
||||
|
||||
verify(shopManageGroupMapper).selectManagedMemberUserIds(10L);
|
||||
@@ -548,4 +604,23 @@ class DedupeTotalDataServiceTest {
|
||||
output.toByteArray());
|
||||
}
|
||||
}
|
||||
|
||||
private MockMultipartFile asinCountryWorkbook(String asin, String country) throws Exception {
|
||||
try (XSSFWorkbook workbook = new XSSFWorkbook();
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream()) {
|
||||
var sheet = workbook.createSheet("data");
|
||||
Row header = sheet.createRow(0);
|
||||
header.createCell(0).setCellValue("ASIN");
|
||||
header.createCell(1).setCellValue("国家");
|
||||
Row row = sheet.createRow(1);
|
||||
row.createCell(0).setCellValue(asin);
|
||||
row.createCell(1).setCellValue(country);
|
||||
workbook.write(output);
|
||||
return new MockMultipartFile(
|
||||
"file",
|
||||
"data.xlsx",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
output.toByteArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user