feat(batch-import): 扩展批量导入支持多列企业名称匹配
- 新增 PARTY_SPLIT_PATTERN 正则表达式用于分割当事人名称 - 实现 refreshCompanyIdByCompanyNames 方法支持多列名称匹配 - 添加 splitPartyNames 工具方法处理当事人名称分割 - 优化公司ID刷新逻辑支持原告/被告等多个当事人字段 - 更新信用公示登记控制器使用多列名称
This commit is contained in:
@@ -22,6 +22,7 @@ import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* credit 模块 Excel 导入批处理支持:
|
||||
@@ -32,6 +33,7 @@ import java.util.function.Supplier;
|
||||
public class BatchImportSupport {
|
||||
|
||||
private final TransactionTemplate requiresNewTx;
|
||||
private static final Pattern PARTY_SPLIT_PATTERN = Pattern.compile("[,,;;、\\n\\r\\t/|]+");
|
||||
|
||||
public BatchImportSupport(PlatformTransactionManager transactionManager) {
|
||||
TransactionTemplate template = new TransactionTemplate(transactionManager);
|
||||
@@ -71,7 +73,7 @@ public class BatchImportSupport {
|
||||
/**
|
||||
* 按企业名称匹配 CreditCompany(name / matchName) 并回填 companyId。
|
||||
*
|
||||
* <p>默认仅更新 companyId=0 的记录(onlyNull=true);onlyNull=false 时会覆盖更新(仅当 companyId 不同)。</p>
|
||||
* <p>默认仅更新 companyId 为空/0 的记录(onlyNull=true);onlyNull=false 时会覆盖更新(仅当 companyId 不同)。</p>
|
||||
*
|
||||
* <p>注意:为避免跨租户误更新,当 currentTenantId 为空时会按记录自身 tenantId 维度匹配,
|
||||
* tenantId 为空的记录将被跳过并计入 notFound。</p>
|
||||
@@ -90,15 +92,80 @@ public class BatchImportSupport {
|
||||
BiConsumer<T, Boolean> hasDataSetter,
|
||||
SFunction<T, Integer> tenantIdGetter,
|
||||
Supplier<T> patchFactory) {
|
||||
// Keep existing API; delegate to the multi-column implementation.
|
||||
return refreshCompanyIdByCompanyNames(service,
|
||||
creditCompanyService,
|
||||
currentTenantId,
|
||||
onlyNull,
|
||||
limit,
|
||||
idGetter,
|
||||
idSetter,
|
||||
companyIdGetter,
|
||||
companyIdSetter,
|
||||
hasDataGetter,
|
||||
hasDataSetter,
|
||||
tenantIdGetter,
|
||||
patchFactory,
|
||||
nameGetter);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按多列“当事人/企业名称”匹配 CreditCompany(name / matchName) 并回填 companyId。
|
||||
*
|
||||
* <p>按传入列顺序优先匹配:原告/上诉人 > 被告/被上诉人 > 其他当事人/第三人等。</p>
|
||||
*
|
||||
* <p>同一列若匹配到多个不同企业则视为歧义;若最终无法得到唯一 companyId,则跳过并计入 ambiguous/notFound。</p>
|
||||
*/
|
||||
@SafeVarargs
|
||||
public final <T> CompanyIdRefreshStats refreshCompanyIdByCompanyNames(IService<T> service,
|
||||
CreditCompanyService creditCompanyService,
|
||||
Integer currentTenantId,
|
||||
Boolean onlyNull,
|
||||
Integer limit,
|
||||
SFunction<T, Integer> idGetter,
|
||||
BiConsumer<T, Integer> idSetter,
|
||||
SFunction<T, Integer> companyIdGetter,
|
||||
BiConsumer<T, Integer> companyIdSetter,
|
||||
SFunction<T, Boolean> hasDataGetter,
|
||||
BiConsumer<T, Boolean> hasDataSetter,
|
||||
SFunction<T, Integer> tenantIdGetter,
|
||||
Supplier<T> patchFactory,
|
||||
SFunction<T, String>... nameGetters) {
|
||||
boolean onlyNullFlag = (onlyNull == null) || Boolean.TRUE.equals(onlyNull);
|
||||
|
||||
if (nameGetters == null || nameGetters.length == 0) {
|
||||
return new CompanyIdRefreshStats(false, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
// 1) 读取待处理数据(仅取必要字段,避免一次性拉全表字段)
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
SFunction<T, ?>[] selectColumns = (SFunction<T, ?>[]) new SFunction[4 + nameGetters.length];
|
||||
int colIdx = 0;
|
||||
selectColumns[colIdx++] = idGetter;
|
||||
selectColumns[colIdx++] = companyIdGetter;
|
||||
selectColumns[colIdx++] = hasDataGetter;
|
||||
selectColumns[colIdx++] = tenantIdGetter;
|
||||
for (SFunction<T, String> ng : nameGetters) {
|
||||
selectColumns[colIdx++] = ng;
|
||||
}
|
||||
|
||||
var query = service.lambdaQuery()
|
||||
.select(idGetter, nameGetter, companyIdGetter, hasDataGetter, tenantIdGetter)
|
||||
.select(selectColumns)
|
||||
.eq(currentTenantId != null, tenantIdGetter, currentTenantId)
|
||||
.isNotNull(nameGetter);
|
||||
.and(w -> {
|
||||
// Only process rows that have at least one name column populated.
|
||||
for (int i = 0; i < nameGetters.length; i++) {
|
||||
if (i == 0) {
|
||||
w.isNotNull(nameGetters[i]);
|
||||
} else {
|
||||
w.or().isNotNull(nameGetters[i]);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (onlyNullFlag) {
|
||||
query.eq(companyIdGetter, 0);
|
||||
// Historically some tables used 0 as the "unset" companyId, while others left it NULL.
|
||||
// Treat both as "unset" so refresh won't silently do nothing.
|
||||
query.and(w -> w.isNull(companyIdGetter).or().eq(companyIdGetter, 0));
|
||||
}
|
||||
if (limit != null && limit > 0) {
|
||||
query.last("limit " + Math.min(limit, 200000));
|
||||
@@ -146,11 +213,17 @@ public class BatchImportSupport {
|
||||
LinkedHashMap<String, Integer> ambiguousByName = new LinkedHashMap<>();
|
||||
LinkedHashSet<String> nameSet = new LinkedHashSet<>();
|
||||
for (T row : tenantRows) {
|
||||
String name = normalizeCompanyName(row != null ? nameGetter.apply(row) : null);
|
||||
if (row == null) {
|
||||
continue;
|
||||
}
|
||||
for (SFunction<T, String> ng : nameGetters) {
|
||||
for (String name : splitPartyNames(ng.apply(row))) {
|
||||
if (name != null) {
|
||||
nameSet.add(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
List<String> allNames = new ArrayList<>(nameSet);
|
||||
for (int i = 0; i < allNames.size(); i += inChunkSize) {
|
||||
List<String> chunk = allNames.subList(i, Math.min(allNames.size(), i + inChunkSize));
|
||||
@@ -174,20 +247,44 @@ public class BatchImportSupport {
|
||||
|
||||
// 3.2) 更新当前租户下的数据 companyId
|
||||
for (T row : tenantRows) {
|
||||
String key = normalizeCompanyName(row != null ? nameGetter.apply(row) : null);
|
||||
if (row == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Integer companyId = null;
|
||||
boolean hasAmbiguousName = false;
|
||||
for (SFunction<T, String> ng : nameGetters) {
|
||||
LinkedHashSet<Integer> idsForColumn = new LinkedHashSet<>();
|
||||
for (String key : splitPartyNames(ng.apply(row))) {
|
||||
if (key == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Integer amb = ambiguousByName.get(key);
|
||||
if (amb != null && amb > 0) {
|
||||
ambiguous++;
|
||||
hasAmbiguousName = true;
|
||||
continue;
|
||||
}
|
||||
Integer cid = companyIdByName.get(key);
|
||||
if (cid != null) {
|
||||
idsForColumn.add(cid);
|
||||
}
|
||||
}
|
||||
if (idsForColumn.size() == 1) {
|
||||
companyId = idsForColumn.iterator().next();
|
||||
break;
|
||||
}
|
||||
if (idsForColumn.size() > 1) {
|
||||
// Multiple companies matched within one column (e.g. multiple plaintiffs) -> ambiguous.
|
||||
hasAmbiguousName = true;
|
||||
}
|
||||
}
|
||||
|
||||
Integer companyId = companyIdByName.get(key);
|
||||
if (companyId == null) {
|
||||
if (hasAmbiguousName) {
|
||||
ambiguous++;
|
||||
} else {
|
||||
notFound++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
matched++;
|
||||
@@ -196,7 +293,7 @@ public class BatchImportSupport {
|
||||
Boolean oldHasData = row != null ? hasDataGetter.apply(row) : null;
|
||||
boolean needUpdate;
|
||||
if (onlyNullFlag) {
|
||||
needUpdate = oldCompanyId != null && oldCompanyId == 0;
|
||||
needUpdate = (oldCompanyId == null) || oldCompanyId == 0;
|
||||
} else {
|
||||
needUpdate = oldCompanyId == null || !companyId.equals(oldCompanyId);
|
||||
}
|
||||
@@ -710,6 +807,30 @@ public class BatchImportSupport {
|
||||
return v.isEmpty() ? null : v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Split a "party names" cell into normalized company name candidates.
|
||||
* Supports common separators used in Excel/web copy (comma/semicolon/Chinese list delimiter/newlines).
|
||||
*/
|
||||
private static List<String> splitPartyNames(String raw) {
|
||||
List<String> result = new ArrayList<>();
|
||||
String v = normalizeCompanyName(raw);
|
||||
if (v == null) {
|
||||
return result;
|
||||
}
|
||||
String[] parts = PARTY_SPLIT_PATTERN.split(v);
|
||||
if (parts == null || parts.length == 0) {
|
||||
result.add(v);
|
||||
return result;
|
||||
}
|
||||
for (String p : parts) {
|
||||
String item = normalizeCompanyName(p);
|
||||
if (item != null) {
|
||||
result.add(item);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void addCompanyNameMapping(Map<String, Integer> idByName,
|
||||
Map<String, Integer> ambiguousByName,
|
||||
String key,
|
||||
|
||||
@@ -145,9 +145,9 @@ public class CreditGqdjController extends BaseController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据企业名称匹配企业并更新 companyId(匹配 CreditCompany.name / CreditCompany.matchName)
|
||||
* 根据当事人/企业名称匹配企业并更新 companyId(匹配 CreditCompany.name / CreditCompany.matchName)
|
||||
*
|
||||
* <p>默认仅更新 companyId=0 的记录;如需覆盖更新,传 onlyNull=false。</p>
|
||||
* <p>默认仅更新 companyId 为空/0 的记录;如需覆盖更新,传 onlyNull=false。</p>
|
||||
*/
|
||||
@PreAuthorize("hasAuthority('credit:creditGqdj:update')")
|
||||
@OperationLog
|
||||
@@ -160,7 +160,8 @@ public class CreditGqdjController extends BaseController {
|
||||
User loginUser = getLoginUser();
|
||||
Integer currentTenantId = loginUser != null ? loginUser.getTenantId() : null;
|
||||
|
||||
BatchImportSupport.CompanyIdRefreshStats stats = batchImportSupport.refreshCompanyIdByCompanyName(
|
||||
// Match companyId by any party/company-name column (e.g. plaintiff/appellant, defendant/appellee).
|
||||
BatchImportSupport.CompanyIdRefreshStats stats = batchImportSupport.refreshCompanyIdByCompanyNames(
|
||||
creditGqdjService,
|
||||
creditCompanyService,
|
||||
currentTenantId,
|
||||
@@ -168,13 +169,14 @@ public class CreditGqdjController extends BaseController {
|
||||
limit,
|
||||
CreditGqdj::getId,
|
||||
CreditGqdj::setId,
|
||||
CreditGqdj::getAppellee,
|
||||
CreditGqdj::getCompanyId,
|
||||
CreditGqdj::setCompanyId,
|
||||
CreditGqdj::getHasData,
|
||||
CreditGqdj::setHasData,
|
||||
CreditGqdj::getTenantId,
|
||||
CreditGqdj::new
|
||||
CreditGqdj::new,
|
||||
CreditGqdj::getPlaintiffAppellant,
|
||||
CreditGqdj::getAppellee
|
||||
);
|
||||
|
||||
if (!stats.anyDataRead) {
|
||||
|
||||
@@ -603,11 +603,24 @@ public class CreditXgxfController extends BaseController {
|
||||
entity.setCaseNumber(param.getCaseNumber());
|
||||
entity.setType(param.getType());
|
||||
entity.setDataType(param.getDataType());
|
||||
entity.setPlaintiffUser(param.getPlaintiffUser());
|
||||
entity.setDefendantUser(param.getDefendantUser());
|
||||
entity.setOtherPartiesThirdParty(param.getOtherPartiesThirdParty());
|
||||
entity.setPlaintiffAppellant(param.getPlaintiffAppellant());
|
||||
entity.setDataStatus(param.getDataStatus());
|
||||
entity.setAppellee(param.getAppellee());
|
||||
entity.setInvolvedAmount(param.getInvolvedAmount());
|
||||
entity.setOccurrenceTime(param.getOccurrenceTime());
|
||||
entity.setCourtName(param.getCourtName());
|
||||
|
||||
// 兼容不同模板字段:如果 *2 有值则以 *2 为准写入主字段
|
||||
entity.setInvolvedAmount(!ImportHelper.isBlank(param.getInvolvedAmount2())
|
||||
? param.getInvolvedAmount2()
|
||||
: param.getInvolvedAmount());
|
||||
entity.setOccurrenceTime(!ImportHelper.isBlank(param.getOccurrenceTime2())
|
||||
? param.getOccurrenceTime2()
|
||||
: param.getOccurrenceTime());
|
||||
entity.setCourtName(!ImportHelper.isBlank(param.getCourtName2())
|
||||
? param.getCourtName2()
|
||||
: param.getCourtName());
|
||||
|
||||
entity.setReleaseDate(param.getReleaseDate());
|
||||
entity.setComments(param.getComments());
|
||||
|
||||
|
||||
@@ -39,4 +39,7 @@ public class CreditBreachOfTrustImportParam implements Serializable {
|
||||
@Excel(name = "备注")
|
||||
private String comments;
|
||||
|
||||
// @Excel(name = "原告/上诉人")
|
||||
// private String plaintiffAppellant2;
|
||||
|
||||
}
|
||||
|
||||
@@ -31,9 +31,15 @@ public class CreditXgxfImportParam implements Serializable {
|
||||
@Excel(name = "涉案金额(元)")
|
||||
private String involvedAmount;
|
||||
|
||||
@Excel(name = "涉案金额")
|
||||
private String involvedAmount2;
|
||||
|
||||
@Excel(name = "立案日期")
|
||||
private String occurrenceTime;
|
||||
|
||||
@Excel(name = "发生时间")
|
||||
private String occurrenceTime2;
|
||||
|
||||
@Excel(name = "执行法院")
|
||||
private String courtName;
|
||||
|
||||
@@ -43,4 +49,19 @@ public class CreditXgxfImportParam implements Serializable {
|
||||
@Excel(name = "备注")
|
||||
private String comments;
|
||||
|
||||
@Excel(name = "原告/上诉人")
|
||||
private String plaintiffUser;
|
||||
|
||||
@Excel(name = "被告/被上诉人")
|
||||
private String defendantUser;
|
||||
|
||||
@Excel(name = "其他当事人/第三人")
|
||||
private String otherPartiesThirdParty;
|
||||
|
||||
@Excel(name = "数据状态")
|
||||
private String dataStatus;
|
||||
|
||||
@Excel(name = "法院")
|
||||
private String courtName2;
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user