refactor(credit): 重构客户导入功能的批处理逻辑
- 导入java.sql.SQLException依赖用于数据库异常处理 - 在处理导入参数时对客户名称进行预修剪操作 - 将原有的复杂批处理逻辑提取到persistImportChunk方法中 - 简化了批量导入的核心处理流程,提高代码可读性 - 优化了重复键检测逻辑,支持多种数据库错误码识别 - 移除了冗长的内联批处理实现,改用统一的方法调用
This commit is contained in:
@@ -24,6 +24,7 @@ import org.springframework.web.multipart.MultipartFile;
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@@ -219,7 +220,8 @@ public class CreditCustomerController extends BaseController {
|
||||
try {
|
||||
CreditCustomer item = convertImportParamToEntity(param);
|
||||
if (!ImportHelper.isBlank(item.getName())) {
|
||||
String link = urlByName.get(item.getName().trim());
|
||||
item.setName(item.getName().trim());
|
||||
String link = urlByName.get(item.getName());
|
||||
if (!ImportHelper.isBlank(link)) {
|
||||
item.setUrl(link.trim());
|
||||
}
|
||||
@@ -256,116 +258,7 @@ public class CreditCustomerController extends BaseController {
|
||||
chunkItems.add(item);
|
||||
chunkRowNumbers.add(excelRowNumber);
|
||||
if (chunkItems.size() >= chunkSize) {
|
||||
successCount += batchImportSupport.persistChunkWithFallback(
|
||||
chunkItems,
|
||||
chunkRowNumbers,
|
||||
() -> {
|
||||
// 批内一次查库,避免逐行查/写导致数据库压力过大
|
||||
List<String> names = new ArrayList<>(chunkItems.size());
|
||||
for (CreditCustomer it : chunkItems) {
|
||||
if (it != null && !ImportHelper.isBlank(it.getName())) {
|
||||
names.add(it.getName().trim());
|
||||
}
|
||||
}
|
||||
List<CreditCustomer> existingList = names.isEmpty()
|
||||
? new ArrayList<>()
|
||||
: creditCustomerService.lambdaQuery()
|
||||
.in(CreditCustomer::getName, names)
|
||||
.list();
|
||||
java.util.Map<String, CreditCustomer> existingByName = new java.util.HashMap<>();
|
||||
for (CreditCustomer existing : existingList) {
|
||||
if (existing != null && !ImportHelper.isBlank(existing.getName())) {
|
||||
existingByName.putIfAbsent(existing.getName().trim(), existing);
|
||||
}
|
||||
}
|
||||
|
||||
java.util.Map<String, CreditCustomer> latestByName = new java.util.HashMap<>();
|
||||
int acceptedRows = 0;
|
||||
for (int idx = 0; idx < chunkItems.size(); idx++) {
|
||||
CreditCustomer it = chunkItems.get(idx);
|
||||
int rowNo = (idx < chunkRowNumbers.size()) ? chunkRowNumbers.get(idx) : -1;
|
||||
if (it == null || ImportHelper.isBlank(it.getName())) {
|
||||
continue;
|
||||
}
|
||||
String name = it.getName().trim();
|
||||
CreditCustomer existing = existingByName.get(name);
|
||||
if (existing != null) {
|
||||
Integer existingTenantId = existing.getTenantId();
|
||||
if (it.getTenantId() != null
|
||||
&& existingTenantId != null
|
||||
&& !it.getTenantId().equals(existingTenantId)) {
|
||||
errorMessages.add("第" + rowNo + "行:客户名称已存在且归属其他租户,无法导入");
|
||||
continue;
|
||||
}
|
||||
it.setId(existing.getId());
|
||||
if (existingTenantId != null) {
|
||||
it.setTenantId(existingTenantId);
|
||||
}
|
||||
}
|
||||
// 同名多行:保留最后一行的值(等价于“先插入/更新,再被后续行更新”)
|
||||
latestByName.put(name, it);
|
||||
acceptedRows++;
|
||||
}
|
||||
|
||||
List<CreditCustomer> updates = new ArrayList<>();
|
||||
List<CreditCustomer> inserts = new ArrayList<>();
|
||||
for (CreditCustomer it : latestByName.values()) {
|
||||
if (it.getId() != null) {
|
||||
updates.add(it);
|
||||
} else {
|
||||
inserts.add(it);
|
||||
}
|
||||
}
|
||||
if (!updates.isEmpty()) {
|
||||
creditCustomerService.updateBatchById(updates, mpBatchSize);
|
||||
}
|
||||
if (!inserts.isEmpty()) {
|
||||
creditCustomerService.saveBatch(inserts, mpBatchSize);
|
||||
}
|
||||
return acceptedRows;
|
||||
},
|
||||
(rowItem, rowNumber) -> {
|
||||
CreditCustomer existing = creditCustomerService.lambdaQuery()
|
||||
.eq(CreditCustomer::getName, rowItem.getName())
|
||||
.one();
|
||||
if (existing != null) {
|
||||
Integer existingTenantId = existing.getTenantId();
|
||||
if (rowItem.getTenantId() != null
|
||||
&& existingTenantId != null
|
||||
&& !rowItem.getTenantId().equals(existingTenantId)) {
|
||||
errorMessages.add("第" + rowNumber + "行:客户名称已存在且归属其他租户,无法导入");
|
||||
return false;
|
||||
}
|
||||
rowItem.setId(existing.getId());
|
||||
if (existingTenantId != null) {
|
||||
rowItem.setTenantId(existingTenantId);
|
||||
}
|
||||
return creditCustomerService.updateById(rowItem);
|
||||
}
|
||||
|
||||
try {
|
||||
return creditCustomerService.save(rowItem);
|
||||
} catch (DataIntegrityViolationException e) {
|
||||
if (!isDuplicateCustomerName(e)) {
|
||||
throw e;
|
||||
}
|
||||
CreditCustomer dbExisting = creditCustomerService.lambdaQuery()
|
||||
.eq(CreditCustomer::getName, rowItem.getName())
|
||||
.one();
|
||||
if (dbExisting != null) {
|
||||
Integer existingTenantId = dbExisting.getTenantId();
|
||||
rowItem.setId(dbExisting.getId());
|
||||
if (existingTenantId != null) {
|
||||
rowItem.setTenantId(existingTenantId);
|
||||
}
|
||||
return creditCustomerService.updateById(rowItem);
|
||||
}
|
||||
}
|
||||
errorMessages.add("第" + rowNumber + "行:保存失败");
|
||||
return false;
|
||||
},
|
||||
errorMessages
|
||||
);
|
||||
successCount += persistImportChunk(chunkItems, chunkRowNumbers, mpBatchSize, errorMessages);
|
||||
chunkItems.clear();
|
||||
chunkRowNumbers.clear();
|
||||
}
|
||||
@@ -376,114 +269,7 @@ public class CreditCustomerController extends BaseController {
|
||||
}
|
||||
|
||||
if (!chunkItems.isEmpty()) {
|
||||
successCount += batchImportSupport.persistChunkWithFallback(
|
||||
chunkItems,
|
||||
chunkRowNumbers,
|
||||
() -> {
|
||||
List<String> names = new ArrayList<>(chunkItems.size());
|
||||
for (CreditCustomer it : chunkItems) {
|
||||
if (it != null && !ImportHelper.isBlank(it.getName())) {
|
||||
names.add(it.getName().trim());
|
||||
}
|
||||
}
|
||||
List<CreditCustomer> existingList = names.isEmpty()
|
||||
? new ArrayList<>()
|
||||
: creditCustomerService.lambdaQuery()
|
||||
.in(CreditCustomer::getName, names)
|
||||
.list();
|
||||
java.util.Map<String, CreditCustomer> existingByName = new java.util.HashMap<>();
|
||||
for (CreditCustomer existing : existingList) {
|
||||
if (existing != null && !ImportHelper.isBlank(existing.getName())) {
|
||||
existingByName.putIfAbsent(existing.getName().trim(), existing);
|
||||
}
|
||||
}
|
||||
|
||||
java.util.Map<String, CreditCustomer> latestByName = new java.util.HashMap<>();
|
||||
int acceptedRows = 0;
|
||||
for (int idx = 0; idx < chunkItems.size(); idx++) {
|
||||
CreditCustomer it = chunkItems.get(idx);
|
||||
int rowNo = (idx < chunkRowNumbers.size()) ? chunkRowNumbers.get(idx) : -1;
|
||||
if (it == null || ImportHelper.isBlank(it.getName())) {
|
||||
continue;
|
||||
}
|
||||
String name = it.getName().trim();
|
||||
CreditCustomer existing = existingByName.get(name);
|
||||
if (existing != null) {
|
||||
Integer existingTenantId = existing.getTenantId();
|
||||
if (it.getTenantId() != null
|
||||
&& existingTenantId != null
|
||||
&& !it.getTenantId().equals(existingTenantId)) {
|
||||
errorMessages.add("第" + rowNo + "行:客户名称已存在且归属其他租户,无法导入");
|
||||
continue;
|
||||
}
|
||||
it.setId(existing.getId());
|
||||
if (existingTenantId != null) {
|
||||
it.setTenantId(existingTenantId);
|
||||
}
|
||||
}
|
||||
latestByName.put(name, it);
|
||||
acceptedRows++;
|
||||
}
|
||||
|
||||
List<CreditCustomer> updates = new ArrayList<>();
|
||||
List<CreditCustomer> inserts = new ArrayList<>();
|
||||
for (CreditCustomer it : latestByName.values()) {
|
||||
if (it.getId() != null) {
|
||||
updates.add(it);
|
||||
} else {
|
||||
inserts.add(it);
|
||||
}
|
||||
}
|
||||
if (!updates.isEmpty()) {
|
||||
creditCustomerService.updateBatchById(updates, mpBatchSize);
|
||||
}
|
||||
if (!inserts.isEmpty()) {
|
||||
creditCustomerService.saveBatch(inserts, mpBatchSize);
|
||||
}
|
||||
return acceptedRows;
|
||||
},
|
||||
(rowItem, rowNumber) -> {
|
||||
CreditCustomer existing = creditCustomerService.lambdaQuery()
|
||||
.eq(CreditCustomer::getName, rowItem.getName())
|
||||
.one();
|
||||
if (existing != null) {
|
||||
Integer existingTenantId = existing.getTenantId();
|
||||
if (rowItem.getTenantId() != null
|
||||
&& existingTenantId != null
|
||||
&& !rowItem.getTenantId().equals(existingTenantId)) {
|
||||
errorMessages.add("第" + rowNumber + "行:客户名称已存在且归属其他租户,无法导入");
|
||||
return false;
|
||||
}
|
||||
rowItem.setId(existing.getId());
|
||||
if (existingTenantId != null) {
|
||||
rowItem.setTenantId(existingTenantId);
|
||||
}
|
||||
return creditCustomerService.updateById(rowItem);
|
||||
}
|
||||
|
||||
try {
|
||||
return creditCustomerService.save(rowItem);
|
||||
} catch (DataIntegrityViolationException e) {
|
||||
if (!isDuplicateCustomerName(e)) {
|
||||
throw e;
|
||||
}
|
||||
CreditCustomer dbExisting = creditCustomerService.lambdaQuery()
|
||||
.eq(CreditCustomer::getName, rowItem.getName())
|
||||
.one();
|
||||
if (dbExisting != null) {
|
||||
Integer existingTenantId = dbExisting.getTenantId();
|
||||
rowItem.setId(dbExisting.getId());
|
||||
if (existingTenantId != null) {
|
||||
rowItem.setTenantId(existingTenantId);
|
||||
}
|
||||
return creditCustomerService.updateById(rowItem);
|
||||
}
|
||||
}
|
||||
errorMessages.add("第" + rowNumber + "行:保存失败");
|
||||
return false;
|
||||
},
|
||||
errorMessages
|
||||
);
|
||||
successCount += persistImportChunk(chunkItems, chunkRowNumbers, mpBatchSize, errorMessages);
|
||||
}
|
||||
|
||||
creditCompanyRecordCountService.refresh(CreditCompanyRecordCountService.CountType.CUSTOMER, touchedCompanyIds);
|
||||
@@ -554,19 +340,71 @@ public class CreditCustomerController extends BaseController {
|
||||
return value.trim();
|
||||
}
|
||||
|
||||
private boolean isDuplicateCustomerName(DataIntegrityViolationException e) {
|
||||
private int persistImportChunk(List<CreditCustomer> items,
|
||||
List<Integer> excelRowNumbers,
|
||||
int mpBatchSize,
|
||||
List<String> errorMessages) {
|
||||
return batchImportSupport.persistChunkWithFallback(
|
||||
items,
|
||||
excelRowNumbers,
|
||||
() -> {
|
||||
boolean ok = creditCustomerService.saveBatch(items, mpBatchSize);
|
||||
if (!ok) {
|
||||
throw new RuntimeException("批量保存失败");
|
||||
}
|
||||
return items.size();
|
||||
},
|
||||
(rowItem, rowNumber) -> {
|
||||
try {
|
||||
boolean saved = creditCustomerService.save(rowItem);
|
||||
if (saved) {
|
||||
return true;
|
||||
}
|
||||
if (rowNumber != null && rowNumber > 0) {
|
||||
errorMessages.add("第" + rowNumber + "行:保存失败");
|
||||
} else {
|
||||
errorMessages.add("保存失败");
|
||||
}
|
||||
return false;
|
||||
} catch (DataIntegrityViolationException e) {
|
||||
if (isDuplicateKey(e)) {
|
||||
String name = rowItem != null ? rowItem.getName() : null;
|
||||
String label = ImportHelper.isBlank(name) ? "数据" : ("客户【" + name.trim() + "】");
|
||||
if (rowNumber != null && rowNumber > 0) {
|
||||
errorMessages.add("第" + rowNumber + "行:" + label + "重复(唯一索引冲突)");
|
||||
} else {
|
||||
errorMessages.add(label + "重复(唯一索引冲突)");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
},
|
||||
errorMessages
|
||||
);
|
||||
}
|
||||
|
||||
private static boolean isDuplicateKey(DataIntegrityViolationException e) {
|
||||
// Prefer structured detection (SQLState / vendor error code), fall back to message contains.
|
||||
for (Throwable t = e; t != null; t = t.getCause()) {
|
||||
if (t instanceof SQLException) {
|
||||
SQLException se = (SQLException) t;
|
||||
// MySQL: 1062 Duplicate entry; PostgreSQL/H2: SQLState 23505 unique_violation
|
||||
if (se.getErrorCode() == 1062) {
|
||||
return true;
|
||||
}
|
||||
if ("23505".equals(se.getSQLState())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Throwable mostSpecificCause = e.getMostSpecificCause();
|
||||
String message = mostSpecificCause != null ? mostSpecificCause.getMessage() : e.getMessage();
|
||||
if (message == null) {
|
||||
return false;
|
||||
}
|
||||
String lower = message.toLowerCase();
|
||||
if (!lower.contains("duplicate")) {
|
||||
return false;
|
||||
}
|
||||
return lower.contains("credit_customer.name")
|
||||
|| lower.contains("for key 'name'")
|
||||
|| lower.contains("for key `name`");
|
||||
return lower.contains("duplicate") && lower.contains("key");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user