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.Consumer;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* credit 模块 Excel 导入批处理支持:
|
* credit 模块 Excel 导入批处理支持:
|
||||||
@@ -32,6 +33,7 @@ import java.util.function.Supplier;
|
|||||||
public class BatchImportSupport {
|
public class BatchImportSupport {
|
||||||
|
|
||||||
private final TransactionTemplate requiresNewTx;
|
private final TransactionTemplate requiresNewTx;
|
||||||
|
private static final Pattern PARTY_SPLIT_PATTERN = Pattern.compile("[,,;;、\\n\\r\\t/|]+");
|
||||||
|
|
||||||
public BatchImportSupport(PlatformTransactionManager transactionManager) {
|
public BatchImportSupport(PlatformTransactionManager transactionManager) {
|
||||||
TransactionTemplate template = new TransactionTemplate(transactionManager);
|
TransactionTemplate template = new TransactionTemplate(transactionManager);
|
||||||
@@ -71,7 +73,7 @@ public class BatchImportSupport {
|
|||||||
/**
|
/**
|
||||||
* 按企业名称匹配 CreditCompany(name / matchName) 并回填 companyId。
|
* 按企业名称匹配 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 维度匹配,
|
* <p>注意:为避免跨租户误更新,当 currentTenantId 为空时会按记录自身 tenantId 维度匹配,
|
||||||
* tenantId 为空的记录将被跳过并计入 notFound。</p>
|
* tenantId 为空的记录将被跳过并计入 notFound。</p>
|
||||||
@@ -90,15 +92,80 @@ public class BatchImportSupport {
|
|||||||
BiConsumer<T, Boolean> hasDataSetter,
|
BiConsumer<T, Boolean> hasDataSetter,
|
||||||
SFunction<T, Integer> tenantIdGetter,
|
SFunction<T, Integer> tenantIdGetter,
|
||||||
Supplier<T> patchFactory) {
|
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);
|
boolean onlyNullFlag = (onlyNull == null) || Boolean.TRUE.equals(onlyNull);
|
||||||
|
|
||||||
|
if (nameGetters == null || nameGetters.length == 0) {
|
||||||
|
return new CompanyIdRefreshStats(false, 0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
// 1) 读取待处理数据(仅取必要字段,避免一次性拉全表字段)
|
// 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()
|
var query = service.lambdaQuery()
|
||||||
.select(idGetter, nameGetter, companyIdGetter, hasDataGetter, tenantIdGetter)
|
.select(selectColumns)
|
||||||
.eq(currentTenantId != null, tenantIdGetter, currentTenantId)
|
.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) {
|
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) {
|
if (limit != null && limit > 0) {
|
||||||
query.last("limit " + Math.min(limit, 200000));
|
query.last("limit " + Math.min(limit, 200000));
|
||||||
@@ -146,9 +213,15 @@ public class BatchImportSupport {
|
|||||||
LinkedHashMap<String, Integer> ambiguousByName = new LinkedHashMap<>();
|
LinkedHashMap<String, Integer> ambiguousByName = new LinkedHashMap<>();
|
||||||
LinkedHashSet<String> nameSet = new LinkedHashSet<>();
|
LinkedHashSet<String> nameSet = new LinkedHashSet<>();
|
||||||
for (T row : tenantRows) {
|
for (T row : tenantRows) {
|
||||||
String name = normalizeCompanyName(row != null ? nameGetter.apply(row) : null);
|
if (row == null) {
|
||||||
if (name != null) {
|
continue;
|
||||||
nameSet.add(name);
|
}
|
||||||
|
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);
|
List<String> allNames = new ArrayList<>(nameSet);
|
||||||
@@ -174,20 +247,44 @@ public class BatchImportSupport {
|
|||||||
|
|
||||||
// 3.2) 更新当前租户下的数据 companyId
|
// 3.2) 更新当前租户下的数据 companyId
|
||||||
for (T row : tenantRows) {
|
for (T row : tenantRows) {
|
||||||
String key = normalizeCompanyName(row != null ? nameGetter.apply(row) : null);
|
if (row == null) {
|
||||||
if (key == null) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Integer amb = ambiguousByName.get(key);
|
Integer companyId = null;
|
||||||
if (amb != null && amb > 0) {
|
boolean hasAmbiguousName = false;
|
||||||
ambiguous++;
|
for (SFunction<T, String> ng : nameGetters) {
|
||||||
continue;
|
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) {
|
||||||
|
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 (companyId == null) {
|
||||||
notFound++;
|
if (hasAmbiguousName) {
|
||||||
|
ambiguous++;
|
||||||
|
} else {
|
||||||
|
notFound++;
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
matched++;
|
matched++;
|
||||||
@@ -196,7 +293,7 @@ public class BatchImportSupport {
|
|||||||
Boolean oldHasData = row != null ? hasDataGetter.apply(row) : null;
|
Boolean oldHasData = row != null ? hasDataGetter.apply(row) : null;
|
||||||
boolean needUpdate;
|
boolean needUpdate;
|
||||||
if (onlyNullFlag) {
|
if (onlyNullFlag) {
|
||||||
needUpdate = oldCompanyId != null && oldCompanyId == 0;
|
needUpdate = (oldCompanyId == null) || oldCompanyId == 0;
|
||||||
} else {
|
} else {
|
||||||
needUpdate = oldCompanyId == null || !companyId.equals(oldCompanyId);
|
needUpdate = oldCompanyId == null || !companyId.equals(oldCompanyId);
|
||||||
}
|
}
|
||||||
@@ -710,6 +807,30 @@ public class BatchImportSupport {
|
|||||||
return v.isEmpty() ? null : v;
|
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,
|
private static void addCompanyNameMapping(Map<String, Integer> idByName,
|
||||||
Map<String, Integer> ambiguousByName,
|
Map<String, Integer> ambiguousByName,
|
||||||
String key,
|
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')")
|
@PreAuthorize("hasAuthority('credit:creditGqdj:update')")
|
||||||
@OperationLog
|
@OperationLog
|
||||||
@@ -160,7 +160,8 @@ public class CreditGqdjController extends BaseController {
|
|||||||
User loginUser = getLoginUser();
|
User loginUser = getLoginUser();
|
||||||
Integer currentTenantId = loginUser != null ? loginUser.getTenantId() : null;
|
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,
|
creditGqdjService,
|
||||||
creditCompanyService,
|
creditCompanyService,
|
||||||
currentTenantId,
|
currentTenantId,
|
||||||
@@ -168,13 +169,14 @@ public class CreditGqdjController extends BaseController {
|
|||||||
limit,
|
limit,
|
||||||
CreditGqdj::getId,
|
CreditGqdj::getId,
|
||||||
CreditGqdj::setId,
|
CreditGqdj::setId,
|
||||||
CreditGqdj::getAppellee,
|
|
||||||
CreditGqdj::getCompanyId,
|
CreditGqdj::getCompanyId,
|
||||||
CreditGqdj::setCompanyId,
|
CreditGqdj::setCompanyId,
|
||||||
CreditGqdj::getHasData,
|
CreditGqdj::getHasData,
|
||||||
CreditGqdj::setHasData,
|
CreditGqdj::setHasData,
|
||||||
CreditGqdj::getTenantId,
|
CreditGqdj::getTenantId,
|
||||||
CreditGqdj::new
|
CreditGqdj::new,
|
||||||
|
CreditGqdj::getPlaintiffAppellant,
|
||||||
|
CreditGqdj::getAppellee
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!stats.anyDataRead) {
|
if (!stats.anyDataRead) {
|
||||||
|
|||||||
@@ -603,11 +603,24 @@ public class CreditXgxfController extends BaseController {
|
|||||||
entity.setCaseNumber(param.getCaseNumber());
|
entity.setCaseNumber(param.getCaseNumber());
|
||||||
entity.setType(param.getType());
|
entity.setType(param.getType());
|
||||||
entity.setDataType(param.getDataType());
|
entity.setDataType(param.getDataType());
|
||||||
|
entity.setPlaintiffUser(param.getPlaintiffUser());
|
||||||
|
entity.setDefendantUser(param.getDefendantUser());
|
||||||
|
entity.setOtherPartiesThirdParty(param.getOtherPartiesThirdParty());
|
||||||
entity.setPlaintiffAppellant(param.getPlaintiffAppellant());
|
entity.setPlaintiffAppellant(param.getPlaintiffAppellant());
|
||||||
|
entity.setDataStatus(param.getDataStatus());
|
||||||
entity.setAppellee(param.getAppellee());
|
entity.setAppellee(param.getAppellee());
|
||||||
entity.setInvolvedAmount(param.getInvolvedAmount());
|
|
||||||
entity.setOccurrenceTime(param.getOccurrenceTime());
|
// 兼容不同模板字段:如果 *2 有值则以 *2 为准写入主字段
|
||||||
entity.setCourtName(param.getCourtName());
|
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.setReleaseDate(param.getReleaseDate());
|
||||||
entity.setComments(param.getComments());
|
entity.setComments(param.getComments());
|
||||||
|
|
||||||
|
|||||||
@@ -39,4 +39,7 @@ public class CreditBreachOfTrustImportParam implements Serializable {
|
|||||||
@Excel(name = "备注")
|
@Excel(name = "备注")
|
||||||
private String comments;
|
private String comments;
|
||||||
|
|
||||||
|
// @Excel(name = "原告/上诉人")
|
||||||
|
// private String plaintiffAppellant2;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,9 +31,15 @@ public class CreditXgxfImportParam implements Serializable {
|
|||||||
@Excel(name = "涉案金额(元)")
|
@Excel(name = "涉案金额(元)")
|
||||||
private String involvedAmount;
|
private String involvedAmount;
|
||||||
|
|
||||||
|
@Excel(name = "涉案金额")
|
||||||
|
private String involvedAmount2;
|
||||||
|
|
||||||
@Excel(name = "立案日期")
|
@Excel(name = "立案日期")
|
||||||
private String occurrenceTime;
|
private String occurrenceTime;
|
||||||
|
|
||||||
|
@Excel(name = "发生时间")
|
||||||
|
private String occurrenceTime2;
|
||||||
|
|
||||||
@Excel(name = "执行法院")
|
@Excel(name = "执行法院")
|
||||||
private String courtName;
|
private String courtName;
|
||||||
|
|
||||||
@@ -43,4 +49,19 @@ public class CreditXgxfImportParam implements Serializable {
|
|||||||
@Excel(name = "备注")
|
@Excel(name = "备注")
|
||||||
private String comments;
|
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