feat:导入excel返回错误最多1000条
This commit is contained in:
1 parent
aa8e823741
commit
bc2c7e2b06
3 files changed
+65
-7
No files matched your search
+22
-3
@@ -55,6 +55,9 @@ public class WalletActiveImportServiceImpl implements WalletActiveImportService
|
||||
/** 批量用户定位单次远程请求携带的最大键数量,防止 SQL 过长。 */
|
||||
private static final int LOOKUP_BATCH_SIZE = 1000;
|
||||
|
||||
/** 单次导入同步返回的最大错误条数,超出部分以汇总行提示,避免大文件全错时响应体过大。 */
|
||||
private static final int MAX_RETURNED_ERRORS = 1000;
|
||||
|
||||
/** 用户远程服务,用于按默认模板或学生身份条件精确定位系统用户。 */
|
||||
private final RemoteUserService remoteUserService;
|
||||
|
||||
@@ -84,7 +87,7 @@ public class WalletActiveImportServiceImpl implements WalletActiveImportService
|
||||
.filter(row -> !ObjectUtil.isAllEmpty(row.getUsername(), row.getUserMobile(), row.getCardNumber()))
|
||||
.toList();
|
||||
if (effectiveRows.isEmpty()) {
|
||||
return errors.isEmpty() ? R.failed("Excel内容为空") : R.failed(errors);
|
||||
return errors.isEmpty() ? R.failed("Excel内容为空") : R.failed(capErrors(errors));
|
||||
}
|
||||
|
||||
effectiveRows.forEach(row -> {
|
||||
@@ -140,7 +143,7 @@ public class WalletActiveImportServiceImpl implements WalletActiveImportService
|
||||
.filter(row -> !ObjectUtil.isAllEmpty(row.getStudentName(), row.getSchoolName(), row.getStudentNo(), row.getCardNumber()))
|
||||
.toList();
|
||||
if (effectiveRows.isEmpty()) {
|
||||
return errors.isEmpty() ? R.failed("Excel内容为空") : R.failed(errors);
|
||||
return errors.isEmpty() ? R.failed("Excel内容为空") : R.failed(capErrors(errors));
|
||||
}
|
||||
|
||||
effectiveRows.forEach(row -> {
|
||||
@@ -235,7 +238,7 @@ public class WalletActiveImportServiceImpl implements WalletActiveImportService
|
||||
});
|
||||
validateCardOccupancy(rows, errors);
|
||||
if (!errors.isEmpty()) {
|
||||
return R.failed(errors);
|
||||
return R.failed(capErrors(errors));
|
||||
}
|
||||
AsyncTaskProgressVO progress = asyncTaskExecutorService.submit(redisKey, taskName, rows.size(), reporter -> {
|
||||
for (ValidatedImportRow row : rows) {
|
||||
@@ -406,6 +409,22 @@ public class WalletActiveImportServiceImpl implements WalletActiveImportService
|
||||
return studentByKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* 截断超量的逐行错误,仅保留前 {@value #MAX_RETURNED_ERRORS} 条并追加一条汇总说明。
|
||||
*
|
||||
* @param errors 完整校验错误集合
|
||||
* @return 不超过上限的错误列表;未超量时原样返回
|
||||
*/
|
||||
private List<ErrorMessage> capErrors(List<ErrorMessage> errors) {
|
||||
if (errors.size() <= MAX_RETURNED_ERRORS) {
|
||||
return errors;
|
||||
}
|
||||
List<ErrorMessage> capped = new ArrayList<>(errors.subList(0, MAX_RETURNED_ERRORS));
|
||||
capped.add(new ErrorMessage(null, Set.of(
|
||||
"错误共 " + errors.size() + " 条,仅展示前 " + MAX_RETURNED_ERRORS + " 条,请修正后重新导入")));
|
||||
return capped;
|
||||
}
|
||||
|
||||
/**
|
||||
* 统计非空文本在 Excel 中的出现次数。
|
||||
*
|
||||
|
||||
+23
-4
@@ -68,6 +68,9 @@ public class WalletRechargeApplyImportServiceImpl implements WalletRechargeApply
|
||||
/** 异步落库阶段单批保存的明细行数,配合进度报告器逐批推进。 */
|
||||
private static final int SAVE_BATCH_SIZE = 500;
|
||||
|
||||
/** 单次导入同步返回的最大错误条数,超出部分以汇总行提示,避免大文件全错时响应体过大。 */
|
||||
private static final int MAX_RETURNED_ERRORS = 1000;
|
||||
|
||||
/** 充值/扣款申请 Mapper,用于创建不绑定发票的申请主记录。 */
|
||||
private final WalletRechargeApplyMapper walletRechargeApplyMapper;
|
||||
|
||||
@@ -352,7 +355,7 @@ public class WalletRechargeApplyImportServiceImpl implements WalletRechargeApply
|
||||
.toList();
|
||||
if (effectiveRows.isEmpty()) {
|
||||
// 无有效行时优先回显解析期逐行错误(如字典未配置),确实为空表时给出明确提示
|
||||
return ImmutablePair.of(errors.isEmpty() ? List.of(new ErrorMessage(1L, Set.of("Excel内容为空"))) : errors, null);
|
||||
return ImmutablePair.of(errors.isEmpty() ? List.of(new ErrorMessage(1L, Set.of("Excel内容为空"))) : capErrors(errors), null);
|
||||
}
|
||||
resolveTransactionTagValues(effectiveRows, errors);
|
||||
// 预统计手机号出现次数,替代逐行全表扫描的重复校验
|
||||
@@ -371,7 +374,7 @@ public class WalletRechargeApplyImportServiceImpl implements WalletRechargeApply
|
||||
errors.add(new ErrorMessage(row.getLineNum(), rowErrors));
|
||||
}
|
||||
}
|
||||
return errors.isEmpty() ? ImmutablePair.of(null, effectiveRows) : ImmutablePair.of(errors, null);
|
||||
return errors.isEmpty() ? ImmutablePair.of(null, effectiveRows) : ImmutablePair.of(capErrors(errors), null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -390,7 +393,7 @@ public class WalletRechargeApplyImportServiceImpl implements WalletRechargeApply
|
||||
.filter(row -> !ObjectUtil.isAllEmpty(row.getStudentName(), row.getSchoolName(), row.getStudentNo(), row.getPrice()))
|
||||
.toList();
|
||||
if (effectiveRows.isEmpty()) {
|
||||
return ImmutablePair.of(errors.isEmpty() ? List.of(new ErrorMessage(1L, Set.of("Excel内容为空"))) : errors, null);
|
||||
return ImmutablePair.of(errors.isEmpty() ? List.of(new ErrorMessage(1L, Set.of("Excel内容为空"))) : capErrors(errors), null);
|
||||
}
|
||||
resolveStudentTransactionTagValues(effectiveRows, errors);
|
||||
Map<String, Long> studentCounts = countValues(effectiveRows.stream()
|
||||
@@ -409,7 +412,7 @@ public class WalletRechargeApplyImportServiceImpl implements WalletRechargeApply
|
||||
errors.add(new ErrorMessage(row.getLineNum(), rowErrors));
|
||||
}
|
||||
}
|
||||
return errors.isEmpty() ? ImmutablePair.of(null, effectiveRows) : ImmutablePair.of(errors, null);
|
||||
return errors.isEmpty() ? ImmutablePair.of(null, effectiveRows) : ImmutablePair.of(capErrors(errors), null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -595,6 +598,22 @@ public class WalletRechargeApplyImportServiceImpl implements WalletRechargeApply
|
||||
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 截断超量的逐行错误,仅保留前 {@value #MAX_RETURNED_ERRORS} 条并追加一条汇总说明。
|
||||
*
|
||||
* @param errors 完整校验错误集合
|
||||
* @return 不超过上限的错误列表;未超量时原样返回
|
||||
*/
|
||||
private List<ErrorMessage> capErrors(List<ErrorMessage> errors) {
|
||||
if (errors.size() <= MAX_RETURNED_ERRORS) {
|
||||
return errors;
|
||||
}
|
||||
List<ErrorMessage> capped = new ArrayList<>(errors.subList(0, MAX_RETURNED_ERRORS));
|
||||
capped.add(new ErrorMessage(null, Set.of(
|
||||
"错误共 " + errors.size() + " 条,仅展示前 " + MAX_RETURNED_ERRORS + " 条,请修正后重新导入")));
|
||||
return capped;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取公共 Excel 解析器写入 BindingResult 的逐行错误集合。
|
||||
*
|
||||
|
||||
+20
@@ -43,6 +43,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyList;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
@@ -279,6 +280,25 @@ class WalletRechargeApplyImportServiceImplTest {
|
||||
assertTrue(errors.get(0).getErrors().contains("Excel内容为空"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 全部行都有错误时应只返回前 1000 条错误,并追加一条汇总说明行。
|
||||
*/
|
||||
@Test
|
||||
void importDefaultCapsReturnedErrorsAtThousand() {
|
||||
List<WalletRechargeExcelDto> rows = new ArrayList<>();
|
||||
for (int i = 0; i < 1005; i++) {
|
||||
rows.add(buildDefaultRow(2L + i, "张三", String.format("1380000%04d", i)));
|
||||
}
|
||||
when(remoteUserService.getUsersByMobiles(anyList())).thenReturn(R.ok(List.of()));
|
||||
|
||||
R<?> response = service.importDefault(rows, emptyBindingResult(), "1");
|
||||
|
||||
assertFalse(response.isOk());
|
||||
List<ErrorMessage> errors = assertInstanceOf(List.class, response.getData());
|
||||
assertEquals(1001, errors.size());
|
||||
assertTrue(errors.get(1000).getErrors().toString().contains("仅展示前 1000 条"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 全部行被解析监听器拒绝时应回显监听器错误,而不是覆盖为通用提示。
|
||||
*/
|
||||
|
||||
Reference in new issue
Block a user