feat(credit): 添加历史被执行人批量导入功能

- 新增 /import/history 接口支持历史被执行人数据批量导入
- 实现 Excel 和 ZIP 格式文件的历史被执行人数据解析
- 添加案号重复时取最后一条记录的去重逻辑
- 支持 ZIP 文件自动解压并逐个处理内部 Excel 文件
- 实现导入过程中的数据验证和错误信息收集
- 添加 hyperlink 链接提取功能用于案号和被执行人名称
- 实现按案号 upsert 更新或插入历史被执行人记录
- 支持导入时设置企业 ID、用户 ID 和租户 ID 等上下文信息
- 提供详细的导入结果统计包括成功和失败数量
- 删除不再使用的 CreditJudgmentDebtorHistory 相关代码文件
This commit is contained in:
2026-01-20 00:24:32 +08:00
parent 850c18d639
commit fc0dc99ccc
10 changed files with 298 additions and 1045 deletions

View File

@@ -29,6 +29,7 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Locale;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
@@ -175,6 +176,40 @@ public class CreditJudgmentDebtorController extends BaseController {
}
}
/**
* 批量导入历史被执行人(写入被执行人表 credit_judgment_debtor仅解析“历史被执行人”选项卡
* 规则:案号相同则更新;案号不存在则插入;导入文件内案号重复时取最后一条覆盖。
*/
@PreAuthorize("hasAuthority('credit:creditJudgmentDebtor:save')")
@Operation(summary = "批量导入历史被执行人")
@PostMapping("/import/history")
public ApiResult<List<String>> importHistoryBatch(@RequestParam("file") MultipartFile file,
@RequestParam(value = "companyId", required = false) Integer companyId) {
try {
User loginUser = getLoginUser();
Integer currentUserId = loginUser != null ? loginUser.getUserId() : null;
Integer currentTenantId = loginUser != null ? loginUser.getTenantId() : null;
ImportOutcome outcome;
if (isZip(file)) {
outcome = importHistoryFromZip(file, currentUserId, currentTenantId, companyId);
} else {
outcome = importHistoryFromExcel(file, safeFileLabel(file.getOriginalFilename()), currentUserId, currentTenantId, companyId);
}
if (!outcome.anyDataRead) {
return fail("未读取到数据,请确认文件中存在“历史被执行人”选项卡且表头与示例格式一致", null);
}
if (outcome.errorMessages.isEmpty()) {
return success("成功导入" + outcome.successCount + "条数据", null);
}
return success("导入完成,成功" + outcome.successCount + "条,失败" + outcome.errorMessages.size() + "", outcome.errorMessages);
} catch (Exception e) {
e.printStackTrace();
return fail("导入失败:" + e.getMessage(), null);
}
}
/**
* 下载被执行人导入模板
*/
@@ -348,6 +383,224 @@ public class CreditJudgmentDebtorController extends BaseController {
return new ImportOutcome(true, successCount, errorMessages);
}
private ImportOutcome importHistoryFromExcel(MultipartFile excelFile, String fileLabel, Integer currentUserId, Integer currentTenantId, Integer companyId) throws Exception {
List<String> errorMessages = new ArrayList<>();
int successCount = 0;
int historySheetIndex = ExcelImportSupport.findSheetIndex(excelFile, "历史被执行人");
if (historySheetIndex < 0) {
return new ImportOutcome(false, 0, errorMessages);
}
ExcelImportSupport.ImportResult<CreditJudgmentDebtorImportParam> importResult = ExcelImportSupport.readBest(
excelFile,
CreditJudgmentDebtorImportParam.class,
this::isEmptyImportRow,
this::isScoreImportRow,
historySheetIndex
);
List<CreditJudgmentDebtorImportParam> list = importResult.getData();
int usedTitleRows = importResult.getTitleRows();
int usedHeadRows = importResult.getHeadRows();
int usedSheetIndex = importResult.getSheetIndex();
if (CollectionUtils.isEmpty(list)) {
return new ImportOutcome(false, 0, errorMessages);
}
Map<String, String> urlByCaseNumber = ExcelImportSupport.readHyperlinksByHeaderKey(excelFile, usedSheetIndex, usedTitleRows, usedHeadRows, "案号");
Map<String, String> urlByName = ExcelImportSupport.readHyperlinksByHeaderKey(excelFile, usedSheetIndex, usedTitleRows, usedHeadRows, "被执行人名称");
Map<String, String> urlByName1 = ExcelImportSupport.readHyperlinksByHeaderKey(excelFile, usedSheetIndex, usedTitleRows, usedHeadRows, "被执行人");
String prefix = ImportHelper.isBlank(fileLabel) ? "" : "" + fileLabel + "";
// 同案号多条:以导入文件中“最后一条”为准(视为最新),避免批处理中重复 upsert。
LinkedHashMap<String, CreditJudgmentDebtor> latestByCaseNumber = new LinkedHashMap<>();
LinkedHashMap<String, Integer> latestRowByCaseNumber = new LinkedHashMap<>();
for (int i = 0; i < list.size(); i++) {
CreditJudgmentDebtorImportParam param = list.get(i);
int excelRowNumber = i + 1 + usedTitleRows + usedHeadRows;
try {
CreditJudgmentDebtor item = convertImportParamToEntity(param);
if (item.getCaseNumber() != null) {
item.setCaseNumber(item.getCaseNumber().trim());
}
if (ImportHelper.isBlank(item.getCaseNumber())) {
errorMessages.add(prefix + "" + excelRowNumber + "行:案号不能为空");
continue;
}
String link = urlByCaseNumber.get(item.getCaseNumber());
if ((link == null || link.isEmpty()) && !ImportHelper.isBlank(item.getName())) {
link = urlByName.get(item.getName().trim());
}
if ((link == null || link.isEmpty()) && !ImportHelper.isBlank(item.getName1())) {
link = urlByName1.get(item.getName1().trim());
}
if (link != null && !link.isEmpty()) {
item.setUrl(link);
}
if (item.getCompanyId() == null && companyId != null) {
item.setCompanyId(companyId);
}
if (item.getUserId() == null && currentUserId != null) {
item.setUserId(currentUserId);
}
if (item.getTenantId() == null && currentTenantId != null) {
item.setTenantId(currentTenantId);
}
if (item.getStatus() == null) {
item.setStatus(0);
}
if (item.getRecommend() == null) {
item.setRecommend(0);
}
if (item.getDeleted() == null) {
item.setDeleted(0);
}
latestByCaseNumber.put(item.getCaseNumber(), item);
latestRowByCaseNumber.put(item.getCaseNumber(), excelRowNumber);
} catch (Exception e) {
errorMessages.add(prefix + "" + excelRowNumber + "行:" + e.getMessage());
e.printStackTrace();
}
}
if (latestByCaseNumber.isEmpty()) {
return new ImportOutcome(true, 0, errorMessages);
}
final int chunkSize = 500;
final int mpBatchSize = 500;
List<CreditJudgmentDebtor> chunkItems = new ArrayList<>(chunkSize);
List<Integer> chunkRowNumbers = new ArrayList<>(chunkSize);
for (Map.Entry<String, CreditJudgmentDebtor> entry : latestByCaseNumber.entrySet()) {
String caseNumber = entry.getKey();
CreditJudgmentDebtor item = entry.getValue();
Integer rowNo = latestRowByCaseNumber.get(caseNumber);
chunkItems.add(item);
chunkRowNumbers.add(rowNo != null ? rowNo : -1);
if (chunkItems.size() >= chunkSize) {
successCount += persistHistoryImportChunk(chunkItems, chunkRowNumbers, prefix, mpBatchSize, errorMessages);
chunkItems.clear();
chunkRowNumbers.clear();
}
}
if (!chunkItems.isEmpty()) {
successCount += persistHistoryImportChunk(chunkItems, chunkRowNumbers, prefix, mpBatchSize, errorMessages);
}
return new ImportOutcome(true, successCount, errorMessages);
}
private int persistHistoryImportChunk(List<CreditJudgmentDebtor> items,
List<Integer> excelRowNumbers,
String prefix,
int mpBatchSize,
List<String> errorMessages) {
if (CollectionUtils.isEmpty(items)) {
return 0;
}
try {
return batchImportSupport.runInNewTx(() -> {
List<String> keys = new ArrayList<>(items.size());
for (CreditJudgmentDebtor item : items) {
if (item == null || ImportHelper.isBlank(item.getCaseNumber())) {
continue;
}
keys.add(item.getCaseNumber().trim());
}
Map<String, CreditJudgmentDebtor> existingByCaseNumber = new java.util.HashMap<>();
if (!keys.isEmpty()) {
List<CreditJudgmentDebtor> existingList = creditJudgmentDebtorService.lambdaQuery()
.in(CreditJudgmentDebtor::getCaseNumber, keys)
.select(CreditJudgmentDebtor::getId, CreditJudgmentDebtor::getCaseNumber, CreditJudgmentDebtor::getRecommend)
.list();
for (CreditJudgmentDebtor existing : existingList) {
if (existing == null || ImportHelper.isBlank(existing.getCaseNumber())) {
continue;
}
existingByCaseNumber.putIfAbsent(existing.getCaseNumber().trim(), existing);
}
}
List<CreditJudgmentDebtor> updates = new ArrayList<>();
List<CreditJudgmentDebtor> inserts = new ArrayList<>();
for (CreditJudgmentDebtor item : items) {
if (item == null || ImportHelper.isBlank(item.getCaseNumber())) {
continue;
}
String caseNumber = item.getCaseNumber().trim();
CreditJudgmentDebtor existing = existingByCaseNumber.get(caseNumber);
if (existing != null && existing.getId() != null) {
// 覆盖更新recommend 记录“被更新次数”,每次更新 +1
item.setId(existing.getId());
Integer old = existing.getRecommend();
item.setRecommend(old == null ? 1 : old + 1);
updates.add(item);
} else {
if (item.getRecommend() == null) {
item.setRecommend(0);
}
inserts.add(item);
}
}
if (!updates.isEmpty()) {
creditJudgmentDebtorService.updateBatchById(updates, mpBatchSize);
}
if (!inserts.isEmpty()) {
creditJudgmentDebtorService.saveBatch(inserts, mpBatchSize);
}
return updates.size() + inserts.size();
});
} catch (Exception batchException) {
int successCount = 0;
for (int i = 0; i < items.size(); i++) {
CreditJudgmentDebtor item = items.get(i);
int excelRowNumber = (excelRowNumbers != null && i < excelRowNumbers.size()) ? excelRowNumbers.get(i) : -1;
try {
int delta = batchImportSupport.runInNewTx(() -> {
if (item == null || ImportHelper.isBlank(item.getCaseNumber())) {
return 0;
}
String caseNumber = item.getCaseNumber().trim();
CreditJudgmentDebtor existing = creditJudgmentDebtorService.lambdaQuery()
.eq(CreditJudgmentDebtor::getCaseNumber, caseNumber)
.select(CreditJudgmentDebtor::getId, CreditJudgmentDebtor::getRecommend)
.one();
if (existing != null && existing.getId() != null) {
item.setId(existing.getId());
Integer old = existing.getRecommend();
item.setRecommend(old == null ? 1 : old + 1);
return creditJudgmentDebtorService.updateById(item) ? 1 : 0;
}
if (item.getRecommend() == null) {
item.setRecommend(0);
}
return creditJudgmentDebtorService.save(item) ? 1 : 0;
});
if (delta > 0) {
successCount += delta;
} else {
errorMessages.add(prefix + "" + excelRowNumber + "行:保存失败");
}
} catch (Exception e) {
errorMessages.add(prefix + "" + excelRowNumber + "行:" + e.getMessage());
}
}
return successCount;
}
}
private int persistImportChunk(List<CreditJudgmentDebtor> items,
List<Integer> excelRowNumbers,
String prefix,
@@ -446,6 +699,49 @@ public class CreditJudgmentDebtorController extends BaseController {
return new ImportOutcome(anyDataRead, successCount, errorMessages);
}
private ImportOutcome importHistoryFromZip(MultipartFile zipFile, Integer currentUserId, Integer currentTenantId, Integer companyId) throws Exception {
try {
return importHistoryFromZip(zipFile, currentUserId, currentTenantId, companyId, StandardCharsets.UTF_8);
} catch (IllegalArgumentException e) {
return importHistoryFromZip(zipFile, currentUserId, currentTenantId, companyId, Charset.forName("GBK"));
}
}
private ImportOutcome importHistoryFromZip(MultipartFile zipFile, Integer currentUserId, Integer currentTenantId, Integer companyId, Charset charset) throws Exception {
List<String> errorMessages = new ArrayList<>();
int successCount = 0;
boolean anyDataRead = false;
try (InputStream is = zipFile.getInputStream(); ZipInputStream zis = new ZipInputStream(is, charset)) {
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
if (entry.isDirectory()) {
continue;
}
String entryName = entry.getName();
if (!isExcelFileName(entryName)) {
continue;
}
byte[] bytes = readAllBytes(zis);
String entryFileName = safeFileLabel(entryName);
MultipartFile excelFile = new InMemoryMultipartFile(entryFileName, bytes);
try {
ImportOutcome outcome = importHistoryFromExcel(excelFile, entryFileName, currentUserId, currentTenantId, companyId);
if (outcome.anyDataRead) {
anyDataRead = true;
successCount += outcome.successCount;
errorMessages.addAll(outcome.errorMessages);
}
} catch (Exception e) {
errorMessages.add("" + entryFileName + "】解析失败:" + e.getMessage());
}
}
}
return new ImportOutcome(anyDataRead, successCount, errorMessages);
}
private static boolean isZip(MultipartFile file) {
String filename = file != null ? file.getOriginalFilename() : null;
if (filename == null) {

View File

@@ -1,604 +0,0 @@
package com.gxwebsoft.credit.controller;
import com.gxwebsoft.common.core.annotation.OperationLog;
import com.gxwebsoft.common.core.web.ApiResult;
import com.gxwebsoft.common.core.web.BaseController;
import com.gxwebsoft.common.core.web.BatchParam;
import com.gxwebsoft.common.core.web.PageResult;
import com.gxwebsoft.common.system.entity.User;
import com.gxwebsoft.credit.entity.CreditJudgmentDebtorHistory;
import com.gxwebsoft.credit.param.CreditJudgmentDebtorHistoryImportParam;
import com.gxwebsoft.credit.param.CreditJudgmentDebtorHistoryParam;
import com.gxwebsoft.credit.service.CreditJudgmentDebtorHistoryService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
/**
* 被执行人控制器
*
* @author 科技小王子
* @since 2026-01-12 08:10:44
*/
@Tag(name = "被执行人管理")
@RestController
@RequestMapping("/api/credit/credit-judgment-debtor-history")
public class CreditJudgmentDebtorHistoryController extends BaseController {
@Resource
private CreditJudgmentDebtorHistoryService creditJudgmentDebtorHistoryService;
@Resource
private BatchImportSupport batchImportSupport;
@Operation(summary = "分页查询被执行人")
@GetMapping("/page")
public ApiResult<PageResult<CreditJudgmentDebtorHistory>> page(CreditJudgmentDebtorHistoryParam param) {
// 使用关联查询
return success(creditJudgmentDebtorHistoryService.pageRel(param));
}
@Operation(summary = "查询全部被执行人")
@GetMapping()
public ApiResult<List<CreditJudgmentDebtorHistory>> list(CreditJudgmentDebtorHistoryParam param) {
// 使用关联查询
return success(creditJudgmentDebtorHistoryService.listRel(param));
}
@Operation(summary = "根据id查询被执行人")
@GetMapping("/{id}")
public ApiResult<CreditJudgmentDebtorHistory> get(@PathVariable("id") Integer id) {
// 使用关联查询
return success(creditJudgmentDebtorHistoryService.getByIdRel(id));
}
@PreAuthorize("hasAuthority('credit:creditJudgmentDebtorHistory:save')")
@OperationLog
@Operation(summary = "添加被执行人")
@PostMapping()
public ApiResult<?> save(@RequestBody CreditJudgmentDebtorHistory creditJudgmentDebtorHistory) {
if (creditJudgmentDebtorHistoryService.save(creditJudgmentDebtorHistory)) {
return success("添加成功");
}
return fail("添加失败");
}
@PreAuthorize("hasAuthority('credit:creditJudgmentDebtorHistory:update')")
@OperationLog
@Operation(summary = "修改被执行人")
@PutMapping()
public ApiResult<?> update(@RequestBody CreditJudgmentDebtorHistory creditJudgmentDebtorHistory) {
if (creditJudgmentDebtorHistoryService.updateById(creditJudgmentDebtorHistory)) {
return success("修改成功");
}
return fail("修改失败");
}
@PreAuthorize("hasAuthority('credit:creditJudgmentDebtorHistory:remove')")
@OperationLog
@Operation(summary = "删除被执行人")
@DeleteMapping("/{id}")
public ApiResult<?> remove(@PathVariable("id") Integer id) {
if (creditJudgmentDebtorHistoryService.removeById(id)) {
return success("删除成功");
}
return fail("删除失败");
}
@PreAuthorize("hasAuthority('credit:creditJudgmentDebtorHistory:save')")
@OperationLog
@Operation(summary = "批量添加被执行人")
@PostMapping("/batch")
public ApiResult<?> saveBatch(@RequestBody List<CreditJudgmentDebtorHistory> list) {
if (creditJudgmentDebtorHistoryService.saveBatch(list)) {
return success("添加成功");
}
return fail("添加失败");
}
@PreAuthorize("hasAuthority('credit:creditJudgmentDebtorHistory:update')")
@OperationLog
@Operation(summary = "批量修改被执行人")
@PutMapping("/batch")
public ApiResult<?> removeBatch(@RequestBody BatchParam<CreditJudgmentDebtorHistory> batchParam) {
if (batchParam.update(creditJudgmentDebtorHistoryService, "id")) {
return success("修改成功");
}
return fail("修改失败");
}
@PreAuthorize("hasAuthority('credit:creditJudgmentDebtorHistory:remove')")
@OperationLog
@Operation(summary = "批量删除被执行人")
@DeleteMapping("/batch")
public ApiResult<?> removeBatch(@RequestBody List<Integer> ids) {
if (creditJudgmentDebtorHistoryService.removeByIds(ids)) {
return success("删除成功");
}
return fail("删除失败");
}
/**
* 批量导入被执行人历史
*/
@PreAuthorize("hasAuthority('credit:creditJudgmentDebtorHistory:save')")
@Operation(summary = "批量导入被执行人历史")
@PostMapping("/import")
public ApiResult<List<String>> importBatch(@RequestParam("file") MultipartFile file,
@RequestParam(value = "companyId", required = false) Integer companyId) {
try {
User loginUser = getLoginUser();
Integer currentUserId = loginUser != null ? loginUser.getUserId() : null;
Integer currentTenantId = loginUser != null ? loginUser.getTenantId() : null;
ImportOutcome outcome;
if (isZip(file)) {
outcome = importFromZip(file, currentUserId, currentTenantId, companyId);
} else {
outcome = importFromExcel(file, safeFileLabel(file.getOriginalFilename()), currentUserId, currentTenantId, companyId, false);
}
if (!outcome.anyDataRead) {
return fail("未读取到数据,请确认模板表头与示例格式一致", null);
}
if (outcome.errorMessages.isEmpty()) {
return success("成功导入" + outcome.successCount + "条数据", null);
}
return success("导入完成,成功" + outcome.successCount + "条,失败" + outcome.errorMessages.size() + "", outcome.errorMessages);
} catch (Exception e) {
e.printStackTrace();
return fail("导入失败:" + e.getMessage(), null);
}
}
/**
* 下载被执行人历史导入模板
*/
@Operation(summary = "下载被执行人历史导入模板")
@GetMapping("/import/template")
public void downloadTemplate(HttpServletResponse response) throws IOException {
List<CreditJudgmentDebtorHistoryImportParam> templateList = new ArrayList<>();
CreditJudgmentDebtorHistoryImportParam example = new CreditJudgmentDebtorHistoryImportParam();
example.setCaseNumber("2024示例案号");
example.setName("某某公司");
example.setCode("1234567890");
example.setOccurrenceTime("2024-01-10");
example.setAmount("100000");
example.setDataStatus("已公开");
example.setComments("备注信息");
templateList.add(example);
Workbook workbook = ExcelImportSupport.buildTemplate("被执行人历史导入模板", "历史被执行人", CreditJudgmentDebtorHistoryImportParam.class, templateList);
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition", "attachment; filename=credit_judgment_debtor_history_import_template.xlsx");
workbook.write(response.getOutputStream());
workbook.close();
}
private boolean isEmptyImportRow(CreditJudgmentDebtorHistoryImportParam param) {
if (param == null) {
return true;
}
if (isImportHeaderRow(param)) {
return true;
}
return ImportHelper.isBlank(param.getCaseNumber())
&& ImportHelper.isBlank(param.getName())
&& ImportHelper.isBlank(param.getName1())
&& ImportHelper.isBlank(param.getCode());
}
private boolean isImportHeaderRow(CreditJudgmentDebtorHistoryImportParam param) {
return isHeaderValue(param.getName(), "序号")
|| isHeaderValue(param.getName1(), "序号")
|| isHeaderValue(param.getCaseNumber(), "案号")
|| isHeaderValue(param.getName(), "被执行人名称")
|| isHeaderValue(param.getName1(), "被执行人")
|| isHeaderValue(param.getCode(), "证件号/组织机构代码")
|| isHeaderValue(param.getOccurrenceTime(), "立案日期")
|| isHeaderValue(param.getCourtName(), "法院")
|| isHeaderValue(param.getAmount(), "执行标的(元)")
|| isHeaderValue(param.getDataStatus(), "数据状态");
}
private static boolean isHeaderValue(String value, String headerText) {
if (value == null) {
return false;
}
return headerText.equals(value.trim());
}
private CreditJudgmentDebtorHistory convertImportParamToEntity(CreditJudgmentDebtorHistoryImportParam param) {
CreditJudgmentDebtorHistory entity = new CreditJudgmentDebtorHistory();
entity.setCaseNumber(param.getCaseNumber());
entity.setName1(param.getName1());
String debtorName = ImportHelper.isBlank(param.getName()) ? param.getName1() : param.getName();
entity.setName(debtorName);
entity.setCode(param.getCode());
entity.setOccurrenceTime(param.getOccurrenceTime());
entity.setAmount(param.getAmount());
entity.setCourtName(param.getCourtName());
entity.setDataStatus(param.getDataStatus());
entity.setComments(param.getComments());
return entity;
}
private static class ImportOutcome {
private final boolean anyDataRead;
private final int successCount;
private final List<String> errorMessages;
private ImportOutcome(boolean anyDataRead, int successCount, List<String> errorMessages) {
this.anyDataRead = anyDataRead;
this.successCount = successCount;
this.errorMessages = errorMessages;
}
}
private ImportOutcome importFromExcel(MultipartFile excelFile, String fileLabel, Integer currentUserId, Integer currentTenantId, Integer companyId, boolean strictHistorySheet) throws Exception {
List<String> errorMessages = new ArrayList<>();
int successCount = 0;
ExcelImportSupport.ImportResult<CreditJudgmentDebtorHistoryImportParam> importResult = readHistoryImport(excelFile, strictHistorySheet);
List<CreditJudgmentDebtorHistoryImportParam> list = importResult.getData();
int usedTitleRows = importResult.getTitleRows();
int usedHeadRows = importResult.getHeadRows();
int usedSheetIndex = importResult.getSheetIndex();
if (CollectionUtils.isEmpty(list)) {
return new ImportOutcome(false, 0, errorMessages);
}
Map<String, String> urlByCaseNumber = ExcelImportSupport.readHyperlinksByHeaderKey(excelFile, usedSheetIndex, usedTitleRows, usedHeadRows, "案号");
Map<String, String> urlByName = ExcelImportSupport.readHyperlinksByHeaderKey(excelFile, usedSheetIndex, usedTitleRows, usedHeadRows, "被执行人名称");
Map<String, String> urlByName1 = ExcelImportSupport.readHyperlinksByHeaderKey(excelFile, usedSheetIndex, usedTitleRows, usedHeadRows, "被执行人");
String prefix = ImportHelper.isBlank(fileLabel) ? "" : "" + fileLabel + "";
final int chunkSize = 500;
final int mpBatchSize = 500;
List<CreditJudgmentDebtorHistory> chunkItems = new ArrayList<>(chunkSize);
List<Integer> chunkRowNumbers = new ArrayList<>(chunkSize);
for (int i = 0; i < list.size(); i++) {
CreditJudgmentDebtorHistoryImportParam param = list.get(i);
try {
CreditJudgmentDebtorHistory item = convertImportParamToEntity(param);
String link = null;
if (!ImportHelper.isBlank(item.getCaseNumber())) {
link = urlByCaseNumber.get(item.getCaseNumber().trim());
}
if ((link == null || link.isEmpty()) && !ImportHelper.isBlank(item.getName())) {
link = urlByName.get(item.getName().trim());
}
if ((link == null || link.isEmpty()) && !ImportHelper.isBlank(item.getName1())) {
link = urlByName1.get(item.getName1().trim());
}
if (link != null && !link.isEmpty()) {
item.setUrl(link);
}
if (item.getCompanyId() == null && companyId != null) {
item.setCompanyId(companyId);
}
if (item.getUserId() == null && currentUserId != null) {
item.setUserId(currentUserId);
}
if (item.getTenantId() == null && currentTenantId != null) {
item.setTenantId(currentTenantId);
}
if (item.getType() == null) {
item.setType(0);
}
if (item.getStatus() == null) {
item.setStatus(0);
}
if (item.getRecommend() == null) {
item.setRecommend(0);
}
if (item.getDeleted() == null) {
item.setDeleted(0);
}
int excelRowNumber = i + 1 + usedTitleRows + usedHeadRows;
if (ImportHelper.isBlank(item.getCaseNumber())) {
errorMessages.add(prefix + "" + excelRowNumber + "行:案号不能为空");
continue;
}
chunkItems.add(item);
chunkRowNumbers.add(excelRowNumber);
if (chunkItems.size() >= chunkSize) {
successCount += persistImportChunk(chunkItems, chunkRowNumbers, prefix, mpBatchSize, errorMessages);
chunkItems.clear();
chunkRowNumbers.clear();
}
} catch (Exception e) {
int excelRowNumber = i + 1 + usedTitleRows + usedHeadRows;
errorMessages.add(prefix + "" + excelRowNumber + "行:" + e.getMessage());
e.printStackTrace();
}
}
if (!chunkItems.isEmpty()) {
successCount += persistImportChunk(chunkItems, chunkRowNumbers, prefix, mpBatchSize, errorMessages);
}
return new ImportOutcome(true, successCount, errorMessages);
}
private int persistImportChunk(List<CreditJudgmentDebtorHistory> items,
List<Integer> excelRowNumbers,
String prefix,
int mpBatchSize,
List<String> errorMessages) {
if (CollectionUtils.isEmpty(items)) {
return 0;
}
try {
return batchImportSupport.runInNewTx(() -> batchImportSupport.upsertBySingleKey(
creditJudgmentDebtorHistoryService,
items,
CreditJudgmentDebtorHistory::getId,
CreditJudgmentDebtorHistory::setId,
CreditJudgmentDebtorHistory::getCaseNumber,
CreditJudgmentDebtorHistory::getCaseNumber,
null,
mpBatchSize
));
} catch (Exception batchException) {
int successCount = 0;
for (int i = 0; i < items.size(); i++) {
CreditJudgmentDebtorHistory item = items.get(i);
int excelRowNumber = (excelRowNumbers != null && i < excelRowNumbers.size()) ? excelRowNumbers.get(i) : -1;
try {
int delta = batchImportSupport.runInNewTx(() -> {
boolean saved = creditJudgmentDebtorHistoryService.save(item);
if (!saved) {
CreditJudgmentDebtorHistory existing = creditJudgmentDebtorHistoryService.lambdaQuery()
.eq(CreditJudgmentDebtorHistory::getCaseNumber, item.getCaseNumber())
.one();
if (existing != null) {
item.setId(existing.getId());
if (creditJudgmentDebtorHistoryService.updateById(item)) {
return 1;
}
}
} else {
return 1;
}
return 0;
});
if (delta > 0) {
successCount += delta;
} else {
errorMessages.add(prefix + "" + excelRowNumber + "行:保存失败");
}
} catch (Exception e) {
errorMessages.add(prefix + "" + excelRowNumber + "行:" + e.getMessage());
}
}
return successCount;
}
}
private ImportOutcome importFromZip(MultipartFile zipFile, Integer currentUserId, Integer currentTenantId, Integer companyId) throws Exception {
try {
return importFromZip(zipFile, currentUserId, currentTenantId, companyId, StandardCharsets.UTF_8);
} catch (IllegalArgumentException e) {
return importFromZip(zipFile, currentUserId, currentTenantId, companyId, Charset.forName("GBK"));
}
}
private ImportOutcome importFromZip(MultipartFile zipFile, Integer currentUserId, Integer currentTenantId, Integer companyId, Charset charset) throws Exception {
List<String> errorMessages = new ArrayList<>();
int successCount = 0;
boolean anyDataRead = false;
try (InputStream is = zipFile.getInputStream(); ZipInputStream zis = new ZipInputStream(is, charset)) {
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
if (entry.isDirectory()) {
continue;
}
String entryName = entry.getName();
if (!isExcelFileName(entryName)) {
continue;
}
byte[] bytes = readAllBytes(zis);
String entryFileName = safeFileLabel(entryName);
MultipartFile excelFile = new InMemoryMultipartFile(entryFileName, bytes);
try {
ImportOutcome outcome = importFromExcel(excelFile, entryFileName, currentUserId, currentTenantId, companyId, true);
if (outcome.anyDataRead) {
anyDataRead = true;
successCount += outcome.successCount;
errorMessages.addAll(outcome.errorMessages);
}
} catch (Exception e) {
errorMessages.add("" + entryFileName + "】解析失败:" + e.getMessage());
}
}
}
return new ImportOutcome(anyDataRead, successCount, errorMessages);
}
private static boolean isZip(MultipartFile file) {
String filename = file != null ? file.getOriginalFilename() : null;
if (filename == null) {
return false;
}
return filename.toLowerCase(Locale.ROOT).endsWith(".zip");
}
private ExcelImportSupport.ImportResult<CreditJudgmentDebtorHistoryImportParam> readHistoryImport(MultipartFile excelFile, boolean strictHistorySheet) throws Exception {
int namedSheetIndex = ExcelImportSupport.findSheetIndex(excelFile, "历史被执行人");
if (namedSheetIndex >= 0) {
ExcelImportSupport.ImportResult<CreditJudgmentDebtorHistoryImportParam> namedSheetResult = ExcelImportSupport.readBest(
excelFile,
CreditJudgmentDebtorHistoryImportParam.class,
this::isEmptyImportRow,
this::isScoreImportRow,
namedSheetIndex
);
if (!CollectionUtils.isEmpty(namedSheetResult.getData())) {
return namedSheetResult;
}
}
List<Integer> historySheetIndices = findHistorySheetIndices(excelFile);
for (Integer sheetIndex : historySheetIndices) {
if (sheetIndex != null && sheetIndex == namedSheetIndex) {
continue;
}
ExcelImportSupport.ImportResult<CreditJudgmentDebtorHistoryImportParam> sheetResult = ExcelImportSupport.readBest(
excelFile,
CreditJudgmentDebtorHistoryImportParam.class,
this::isEmptyImportRow,
this::isScoreImportRow,
sheetIndex
);
if (!CollectionUtils.isEmpty(sheetResult.getData())) {
return sheetResult;
}
}
if (strictHistorySheet) {
return new ExcelImportSupport.ImportResult<>(new ArrayList<>(), 0, 0);
}
return ExcelImportSupport.readAnySheetBest(excelFile, CreditJudgmentDebtorHistoryImportParam.class, this::isEmptyImportRow, this::isScoreImportRow);
}
private boolean isScoreImportRow(CreditJudgmentDebtorHistoryImportParam param) {
if (param == null) {
return false;
}
if (isImportHeaderRow(param)) {
return false;
}
return !ImportHelper.isBlank(param.getCaseNumber());
}
private List<Integer> findHistorySheetIndices(MultipartFile excelFile) throws Exception {
List<Integer> indices = new ArrayList<>();
try (InputStream is = excelFile.getInputStream(); Workbook workbook = WorkbookFactory.create(is)) {
int sheetCount = workbook.getNumberOfSheets();
for (int i = 0; i < sheetCount; i++) {
String sheetName = workbook.getSheetName(i);
if (isHistorySheetName(sheetName)) {
indices.add(i);
}
}
}
return indices;
}
private static boolean isHistorySheetName(String sheetName) {
if (sheetName == null) {
return false;
}
String normalized = sheetName.replace(" ", "").trim();
return normalized.contains("被执行人") && normalized.contains("历史") && !normalized.contains("失信");
}
private static boolean isExcelFileName(String name) {
if (name == null) {
return false;
}
String lower = name.toLowerCase(Locale.ROOT);
return lower.endsWith(".xlsx") || lower.endsWith(".xls") || lower.endsWith(".xlsm");
}
private static String safeFileLabel(String name) {
if (ImportHelper.isBlank(name)) {
return "";
}
int lastSlash = name.lastIndexOf('/');
if (lastSlash >= 0 && lastSlash + 1 < name.length()) {
return name.substring(lastSlash + 1);
}
return name;
}
private static byte[] readAllBytes(InputStream inputStream) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buffer = new byte[8192];
int read;
while ((read = inputStream.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
return out.toByteArray();
}
private static class InMemoryMultipartFile implements MultipartFile {
private final String originalFilename;
private final byte[] bytes;
private InMemoryMultipartFile(String originalFilename, byte[] bytes) {
this.originalFilename = originalFilename;
this.bytes = bytes != null ? bytes : new byte[0];
}
@Override
public String getName() {
return originalFilename;
}
@Override
public String getOriginalFilename() {
return originalFilename;
}
@Override
public String getContentType() {
return null;
}
@Override
public boolean isEmpty() {
return bytes.length == 0;
}
@Override
public long getSize() {
return bytes.length;
}
@Override
public byte[] getBytes() {
return bytes;
}
@Override
public InputStream getInputStream() {
return new java.io.ByteArrayInputStream(bytes);
}
@Override
public void transferTo(java.io.File dest) throws IOException {
Files.write(dest.toPath(), bytes);
}
}
}