Files
mp-java/src/main/java/com/gxwebsoft/credit/controller/CreditBankruptcyController.java
赵忠林 f016acda91 feat(credit): 优化破产重整数据导入功能
- 优先从名为"破产重整"的标签页导入数据,避免多工作表文件中的意外导入
- 当指定标签页不存在时,向后兼容使用任意工作表导入方式
- 添加详细的注释说明导入逻辑和向后兼容性处理
2026-03-03 14:41:46 +08:00

508 lines
21 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.CreditBankruptcy;
import com.gxwebsoft.credit.param.CreditBankruptcyImportParam;
import com.gxwebsoft.credit.param.CreditBankruptcyParam;
import com.gxwebsoft.credit.service.CreditCompanyService;
import com.gxwebsoft.credit.service.CreditCompanyRecordCountService;
import com.gxwebsoft.credit.service.CreditBankruptcyService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.apache.poi.ss.usermodel.Workbook;
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.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 破产重整控制器
*
* @author 科技小王子
* @since 2026-01-07 13:52:14
*/
@Tag(name = "破产重整管理")
@RestController
@RequestMapping("/api/credit/credit-bankruptcy")
public class CreditBankruptcyController extends BaseController {
@Resource
private CreditBankruptcyService creditBankruptcyService;
@Resource
private BatchImportSupport batchImportSupport;
@Resource
private CreditCompanyService creditCompanyService;
@Resource
private CreditCompanyRecordCountService creditCompanyRecordCountService;
@Operation(summary = "分页查询破产重整")
@GetMapping("/page")
public ApiResult<PageResult<CreditBankruptcy>> page(CreditBankruptcyParam param) {
// 使用关联查询
return success(creditBankruptcyService.pageRel(param));
}
@Operation(summary = "查询全部破产重整")
@GetMapping()
public ApiResult<List<CreditBankruptcy>> list(CreditBankruptcyParam param) {
// 使用关联查询
return success(creditBankruptcyService.listRel(param));
}
@Operation(summary = "根据id查询破产重整")
@GetMapping("/{id}")
public ApiResult<CreditBankruptcy> get(@PathVariable("id") Integer id) {
// 使用关联查询
return success(creditBankruptcyService.getByIdRel(id));
}
@PreAuthorize("hasAuthority('credit:creditBankruptcy:save')")
@OperationLog
@Operation(summary = "添加破产重整")
@PostMapping()
public ApiResult<?> save(@RequestBody CreditBankruptcy creditBankruptcy) {
// 记录当前登录用户id
// User loginUser = getLoginUser();
// if (loginUser != null) {
// creditBankruptcy.setUserId(loginUser.getUserId());
// }
if (creditBankruptcyService.save(creditBankruptcy)) {
return success("添加成功");
}
return fail("添加失败");
}
@PreAuthorize("hasAuthority('credit:creditBankruptcy:update')")
@OperationLog
@Operation(summary = "修改破产重整")
@PutMapping()
public ApiResult<?> update(@RequestBody CreditBankruptcy creditBankruptcy) {
if (creditBankruptcyService.updateById(creditBankruptcy)) {
return success("修改成功");
}
return fail("修改失败");
}
@PreAuthorize("hasAuthority('credit:creditBankruptcy:remove')")
@OperationLog
@Operation(summary = "删除破产重整")
@DeleteMapping("/{id}")
public ApiResult<?> remove(@PathVariable("id") Integer id) {
if (batchImportSupport.hardRemoveById(CreditBankruptcy.class, id)) {
return success("删除成功");
}
return fail("删除失败");
}
@PreAuthorize("hasAuthority('credit:creditBankruptcy:save')")
@OperationLog
@Operation(summary = "批量添加破产重整")
@PostMapping("/batch")
public ApiResult<?> saveBatch(@RequestBody List<CreditBankruptcy> list) {
if (creditBankruptcyService.saveBatch(list)) {
return success("添加成功");
}
return fail("添加失败");
}
@PreAuthorize("hasAuthority('credit:creditBankruptcy:update')")
@OperationLog
@Operation(summary = "批量修改破产重整")
@PutMapping("/batch")
public ApiResult<?> removeBatch(@RequestBody BatchParam<CreditBankruptcy> batchParam) {
if (batchParam.update(creditBankruptcyService, "id")) {
return success("修改成功");
}
return fail("修改失败");
}
@PreAuthorize("hasAuthority('credit:creditBankruptcy:remove')")
@OperationLog
@Operation(summary = "批量删除破产重整")
@DeleteMapping("/batch")
public ApiResult<?> removeBatch(@RequestBody List<Integer> ids) {
if (batchImportSupport.hardRemoveByIds(CreditBankruptcy.class, ids)) {
return success("删除成功");
}
return fail("删除失败");
}
/**
* 根据企业名称匹配企业并更新 companyId匹配 CreditCompany.name / CreditCompany.matchName
*
* <p>默认仅更新 companyId=0 的记录;如需覆盖更新,传 onlyNull=false。</p>
*/
@PreAuthorize("hasAuthority('credit:creditBankruptcy:update')")
@OperationLog
@Operation(summary = "根据企业名称匹配并更新companyId")
@PostMapping("/company-id/refresh")
public ApiResult<Map<String, Object>> refreshCompanyIdByCompanyName(
@RequestParam(value = "onlyNull", required = false, defaultValue = "true") Boolean onlyNull,
@RequestParam(value = "limit", required = false) Integer limit
) {
User loginUser = getLoginUser();
Integer currentTenantId = loginUser != null ? loginUser.getTenantId() : null;
BatchImportSupport.CompanyIdRefreshStats stats = batchImportSupport.refreshCompanyIdByCompanyName(
creditBankruptcyService,
creditCompanyService,
currentTenantId,
onlyNull,
limit,
CreditBankruptcy::getId,
CreditBankruptcy::setId,
CreditBankruptcy::getParty,
CreditBankruptcy::getCompanyId,
CreditBankruptcy::setCompanyId,
CreditBankruptcy::getHasData,
CreditBankruptcy::setHasData,
CreditBankruptcy::getTenantId,
CreditBankruptcy::new
);
if (!stats.anyDataRead) {
return success("无可更新数据", stats.toMap());
}
return success("更新完成,更新" + stats.updated + "", stats.toMap());
}
/**
* 批量导入破产重整
*/
@PreAuthorize("hasAuthority('credit:creditBankruptcy:save')")
@Operation(summary = "批量导入破产重整")
@PostMapping("/import")
public ApiResult<List<String>> importBatch(@RequestParam("file") MultipartFile file,
@RequestParam(value = "companyId", required = false) Integer companyId) {
List<String> errorMessages = new ArrayList<>();
int successCount = 0;
Set<Integer> touchedCompanyIds = new HashSet<>();
try {
// Prefer importing from the explicit tab name "破产重整" when present.
// This avoids accidentally importing from other sheets (e.g. "历史破产重整") in multi-sheet workbooks.
int sheetIndex = ExcelImportSupport.findSheetIndex(file, "破产重整");
ExcelImportSupport.ImportResult<CreditBankruptcyImportParam> importResult;
if (sheetIndex >= 0) {
importResult = ExcelImportSupport.read(file, CreditBankruptcyImportParam.class, this::isEmptyImportRow, sheetIndex);
} else {
// Backward compatible: try any sheet for older templates without the expected tab name.
importResult = ExcelImportSupport.readAnySheet(file, CreditBankruptcyImportParam.class, this::isEmptyImportRow);
}
List<CreditBankruptcyImportParam> list = importResult.getData();
int usedTitleRows = importResult.getTitleRows();
int usedHeadRows = importResult.getHeadRows();
int usedSheetIndex = importResult.getSheetIndex();
if (CollectionUtils.isEmpty(list)) {
return fail("未读取到数据,请确认模板表头与示例格式一致", null);
}
User loginUser = getLoginUser();
Integer currentUserId = loginUser != null ? loginUser.getUserId() : null;
Integer currentTenantId = loginUser != null ? loginUser.getTenantId() : null;
Map<String, String> urlByCode = ExcelImportSupport.readHyperlinksByHeaderKey(file, usedSheetIndex, usedTitleRows, usedHeadRows, "案号");
final int chunkSize = 500;
final int mpBatchSize = 500;
List<CreditBankruptcy> chunkItems = new ArrayList<>(chunkSize);
List<Integer> chunkRowNumbers = new ArrayList<>(chunkSize);
for (int i = 0; i < list.size(); i++) {
CreditBankruptcyImportParam param = list.get(i);
try {
CreditBankruptcy item = convertImportParamToEntity(param);
if (!ImportHelper.isBlank(item.getCode())) {
String link = urlByCode.get(item.getCode().trim());
if (link != null && !link.isEmpty()) {
item.setUrl(link);
}
}
if (item.getCompanyId() == null && companyId != null) {
item.setCompanyId(companyId);
}
if (item.getCompanyId() != null && item.getCompanyId() > 0) {
touchedCompanyIds.add(item.getCompanyId());
}
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);
}
int excelRowNumber = i + 1 + usedTitleRows + usedHeadRows;
if (ImportHelper.isBlank(item.getCode())) {
errorMessages.add("" + excelRowNumber + "行:案号不能为空");
continue;
}
if (item.getCompanyId() != null && item.getCompanyId() > 0) {
touchedCompanyIds.add(item.getCompanyId());
}
chunkItems.add(item);
chunkRowNumbers.add(excelRowNumber);
if (chunkItems.size() >= chunkSize) {
successCount += batchImportSupport.persistInsertOnlyChunk(
creditBankruptcyService,
chunkItems,
chunkRowNumbers,
mpBatchSize,
CreditBankruptcy::getCode,
"",
errorMessages
);
chunkItems.clear();
chunkRowNumbers.clear();
}
} catch (Exception e) {
int excelRowNumber = i + 1 + usedTitleRows + usedHeadRows;
errorMessages.add("" + excelRowNumber + "行:" + e.getMessage());
e.printStackTrace();
}
}
if (!chunkItems.isEmpty()) {
successCount += batchImportSupport.persistInsertOnlyChunk(
creditBankruptcyService,
chunkItems,
chunkRowNumbers,
mpBatchSize,
CreditBankruptcy::getCode,
"",
errorMessages
);
}
creditCompanyRecordCountService.refresh(CreditCompanyRecordCountService.CountType.BANKRUPTCY, touchedCompanyIds);
if (errorMessages.isEmpty()) {
return success("成功导入" + successCount + "条数据", null);
} else {
return success("导入完成,成功" + successCount + "条,失败" + errorMessages.size() + "", errorMessages);
}
} catch (Exception e) {
e.printStackTrace();
return fail("导入失败:" + e.getMessage(), null);
}
}
/**
* 批量导入历史破产重整(仅解析“历史破产重整”选项卡)
* 规则:使用数据库唯一索引约束,重复数据不导入。
*/
@PreAuthorize("hasAuthority('credit:creditBankruptcy:save')")
@Operation(summary = "批量导入历史破产重整")
@PostMapping("/import/history")
public ApiResult<List<String>> importHistoryBatch(@RequestParam("file") MultipartFile file,
@RequestParam(value = "companyId", required = false) Integer companyId) {
List<String> errorMessages = new ArrayList<>();
int successCount = 0;
Set<Integer> touchedCompanyIds = new HashSet<>();
try {
int sheetIndex = ExcelImportSupport.findSheetIndex(file, "历史破产重整");
if (sheetIndex < 0) {
return fail("未读取到数据,请确认文件中存在“历史破产重整”选项卡且表头与示例格式一致", null);
}
ExcelImportSupport.ImportResult<CreditBankruptcyImportParam> importResult = ExcelImportSupport.read(
file, CreditBankruptcyImportParam.class, this::isEmptyImportRow, sheetIndex);
List<CreditBankruptcyImportParam> list = importResult.getData();
int usedTitleRows = importResult.getTitleRows();
int usedHeadRows = importResult.getHeadRows();
int usedSheetIndex = importResult.getSheetIndex();
if (CollectionUtils.isEmpty(list)) {
return fail("未读取到数据,请确认模板表头与示例格式一致", null);
}
User loginUser = getLoginUser();
Integer currentUserId = loginUser != null ? loginUser.getUserId() : null;
Integer currentTenantId = loginUser != null ? loginUser.getTenantId() : null;
Map<String, String> urlByCode = ExcelImportSupport.readHyperlinksByHeaderKey(file, usedSheetIndex, usedTitleRows, usedHeadRows, "案号");
final int chunkSize = 500;
final int mpBatchSize = 500;
List<CreditBankruptcy> chunkItems = new ArrayList<>(chunkSize);
List<Integer> chunkRowNumbers = new ArrayList<>(chunkSize);
for (int i = 0; i < list.size(); i++) {
CreditBankruptcyImportParam param = list.get(i);
int excelRowNumber = i + 1 + usedTitleRows + usedHeadRows;
try {
CreditBankruptcy item = convertImportParamToEntity(param);
if (item.getCode() != null) {
item.setCode(item.getCode().trim());
}
if (ImportHelper.isBlank(item.getCode())) {
errorMessages.add("" + excelRowNumber + "行:案号不能为空");
continue;
}
String link = urlByCode.get(item.getCode());
if (!ImportHelper.isBlank(link)) {
item.setUrl(link.trim());
}
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.getDeleted() == null) {
item.setDeleted(0);
}
if (item.getRecommend() == null) {
item.setRecommend(0);
}
if (item.getCompanyId() != null && item.getCompanyId() > 0) {
touchedCompanyIds.add(item.getCompanyId());
}
chunkItems.add(item);
chunkRowNumbers.add(excelRowNumber);
if (chunkItems.size() >= chunkSize) {
successCount += batchImportSupport.persistInsertOnlyChunk(
creditBankruptcyService,
chunkItems,
chunkRowNumbers,
mpBatchSize,
CreditBankruptcy::getCode,
"",
errorMessages
);
chunkItems.clear();
chunkRowNumbers.clear();
}
} catch (Exception e) {
errorMessages.add("" + excelRowNumber + "行:" + e.getMessage());
e.printStackTrace();
}
}
if (!chunkItems.isEmpty()) {
successCount += batchImportSupport.persistInsertOnlyChunk(
creditBankruptcyService,
chunkItems,
chunkRowNumbers,
mpBatchSize,
CreditBankruptcy::getCode,
"",
errorMessages
);
}
creditCompanyRecordCountService.refresh(CreditCompanyRecordCountService.CountType.BANKRUPTCY, touchedCompanyIds);
if (errorMessages.isEmpty()) {
return success("成功导入" + successCount + "条数据", null);
}
return success("导入完成,成功" + successCount + "条,失败" + errorMessages.size() + "", errorMessages);
} catch (Exception e) {
e.printStackTrace();
return fail("导入失败:" + e.getMessage(), null);
}
}
/**
* 下载破产重整导入模板
*/
@Operation(summary = "下载破产重整导入模板")
@GetMapping("/import/template")
public void downloadTemplate(HttpServletResponse response) throws IOException {
List<CreditBankruptcyImportParam> templateList = new ArrayList<>();
CreditBankruptcyImportParam example = new CreditBankruptcyImportParam();
example.setCode("2024示例案号");
example.setType("破产清算");
example.setParty("某某公司");
example.setCourt("某某人民法院");
example.setPublicDate("2024-01-10");
example.setComments("备注信息");
templateList.add(example);
Workbook workbook = ExcelImportSupport.buildTemplate("破产重整导入模板", "破产重整", CreditBankruptcyImportParam.class, templateList);
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition", "attachment; filename=credit_bankruptcy_import_template.xlsx");
workbook.write(response.getOutputStream());
workbook.close();
}
private boolean isEmptyImportRow(CreditBankruptcyImportParam param) {
if (param == null) {
return true;
}
if (isImportHeaderRow(param)) {
return true;
}
return ImportHelper.isBlank(param.getCode())
&& ImportHelper.isBlank(param.getParty())
&& ImportHelper.isBlank(param.getCourt());
}
private boolean isImportHeaderRow(CreditBankruptcyImportParam param) {
return isHeaderValue(param.getCode(), "案号")
|| isHeaderValue(param.getType(), "案件类型")
|| isHeaderValue(param.getParty(), "当事人");
}
private static boolean isHeaderValue(String value, String headerText) {
if (value == null) {
return false;
}
return headerText.equals(value.trim());
}
private CreditBankruptcy convertImportParamToEntity(CreditBankruptcyImportParam param) {
CreditBankruptcy entity = new CreditBankruptcy();
entity.setCode(param.getCode());
entity.setType(param.getType());
entity.setParty(param.getParty());
entity.setCourt(param.getCourt());
entity.setPublicDate(param.getPublicDate());
entity.setComments(param.getComments());
return entity;
}
}