diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiIntermediaryController.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiIntermediaryController.java index b1c5692f..6a5a4050 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiIntermediaryController.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiIntermediaryController.java @@ -2,10 +2,10 @@ package com.ruoyi.info.collection.controller; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ruoyi.info.collection.domain.dto.*; -import com.ruoyi.info.collection.domain.excel.CcdiIntermediaryEntityExcel; +import com.ruoyi.info.collection.domain.excel.CcdiIntermediaryEnterpriseRelationExcel; import com.ruoyi.info.collection.domain.excel.CcdiIntermediaryPersonExcel; import com.ruoyi.info.collection.domain.vo.*; -import com.ruoyi.info.collection.service.ICcdiIntermediaryEntityImportService; +import com.ruoyi.info.collection.service.ICcdiIntermediaryEnterpriseRelationImportService; import com.ruoyi.info.collection.service.ICcdiIntermediaryPersonImportService; import com.ruoyi.info.collection.service.ICcdiIntermediaryService; import com.ruoyi.info.collection.utils.EasyExcelUtil; @@ -46,7 +46,7 @@ public class CcdiIntermediaryController extends BaseController { private ICcdiIntermediaryPersonImportService personImportService; @Resource - private ICcdiIntermediaryEntityImportService entityImportService; + private ICcdiIntermediaryEnterpriseRelationImportService enterpriseRelationImportService; /** * 查询中介列表 @@ -277,10 +277,10 @@ public class CcdiIntermediaryController extends BaseController { /** * 下载实体中介导入模板 */ - @Operation(summary = "下载实体中介导入模板") - @PostMapping("/importEntityTemplate") - public void importEntityTemplate(HttpServletResponse response) { - EasyExcelUtil.importTemplateWithDictDropdown(response, CcdiIntermediaryEntityExcel.class, "实体中介信息"); + @Operation(summary = "下载中介实体关联关系导入模板") + @PostMapping("/importEnterpriseRelationTemplate") + public void importEnterpriseRelationTemplate(HttpServletResponse response) { + EasyExcelUtil.importTemplateWithDictDropdown(response, CcdiIntermediaryEnterpriseRelationExcel.class, "中介实体关联关系信息"); } /** @@ -313,20 +313,19 @@ public class CcdiIntermediaryController extends BaseController { /** * 导入实体中介数据(异步) */ - @Operation(summary = "导入实体中介数据") + @Operation(summary = "导入中介实体关联关系数据") @PreAuthorize("@ss.hasPermi('ccdi:intermediary:import')") - @Log(title = "实体中介", businessType = BusinessType.IMPORT) - @PostMapping("/importEntityData") - public AjaxResult importEntityData(MultipartFile file) throws Exception { - List list = EasyExcelUtil.importExcel( - file.getInputStream(), CcdiIntermediaryEntityExcel.class); + @Log(title = "中介实体关联关系", businessType = BusinessType.IMPORT) + @PostMapping("/importEnterpriseRelationData") + public AjaxResult importEnterpriseRelationData(MultipartFile file) throws Exception { + List list = EasyExcelUtil.importExcel( + file.getInputStream(), CcdiIntermediaryEnterpriseRelationExcel.class); if (list == null || list.isEmpty()) { return error("至少需要一条数据"); } - // 提交异步任务 - String taskId = intermediaryService.importIntermediaryEntity(list); + String taskId = intermediaryService.importIntermediaryEnterpriseRelation(list); // 立即返回,不等待后台任务完成 ImportResultVO result = new ImportResultVO(); @@ -383,12 +382,12 @@ public class CcdiIntermediaryController extends BaseController { /** * 查询实体中介导入状态 */ - @Operation(summary = "查询实体中介导入状态") + @Operation(summary = "查询中介实体关联关系导入状态") @PreAuthorize("@ss.hasPermi('ccdi:intermediary:import')") - @GetMapping("/importEntityStatus/{taskId}") - public AjaxResult getEntityImportStatus(@PathVariable String taskId) { + @GetMapping("/importEnterpriseRelationStatus/{taskId}") + public AjaxResult getEnterpriseRelationImportStatus(@PathVariable String taskId) { try { - ImportStatusVO status = entityImportService.getImportStatus(taskId); + ImportStatusVO status = enterpriseRelationImportService.getImportStatus(taskId); return success(status); } catch (Exception e) { return error(e.getMessage()); @@ -396,18 +395,18 @@ public class CcdiIntermediaryController extends BaseController { } /** - * 查询实体中介导入失败记录 + * 查询中介实体关联关系导入失败记录 */ - @Operation(summary = "查询实体中介导入失败记录") + @Operation(summary = "查询中介实体关联关系导入失败记录") @PreAuthorize("@ss.hasPermi('ccdi:intermediary:import')") - @GetMapping("/importEntityFailures/{taskId}") - public TableDataInfo getEntityImportFailures( + @GetMapping("/importEnterpriseRelationFailures/{taskId}") + public TableDataInfo getEnterpriseRelationImportFailures( @PathVariable String taskId, @RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize) { - List failures = - entityImportService.getImportFailures(taskId); + List failures = + enterpriseRelationImportService.getImportFailures(taskId); // 手动分页 int fromIndex = (pageNum - 1) * pageSize; @@ -418,7 +417,7 @@ public class CcdiIntermediaryController extends BaseController { return getDataTable(new ArrayList<>(), failures.size()); } - List pageData = failures.subList(fromIndex, toIndex); + List pageData = failures.subList(fromIndex, toIndex); return getDataTable(pageData, failures.size()); } diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/CcdiBizIntermediary.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/CcdiBizIntermediary.java index 2317ae0f..2972668d 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/CcdiBizIntermediary.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/CcdiBizIntermediary.java @@ -63,7 +63,7 @@ public class CcdiBizIntermediary implements Serializable { /** 职位 */ private String position; - /** 关联人员ID */ + /** 关联中介本人证件号码 */ private String relatedNumId; /** 数据来源,MANUAL:手动录入, SYSTEM:系统同步, IMPORT:批量导入, API:接口获取 */ diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiIntermediaryPersonAddDTO.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiIntermediaryPersonAddDTO.java index fb54d563..c67a19b0 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiIntermediaryPersonAddDTO.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiIntermediaryPersonAddDTO.java @@ -67,8 +67,8 @@ public class CcdiIntermediaryPersonAddDTO implements Serializable { @Size(max = 100, message = "职位长度不能超过100个字符") private String position; - @Schema(description = "关联人员ID") - @Size(max = 50, message = "关联人员ID长度不能超过50个字符") + @Schema(description = "关联中介本人证件号码") + @Size(max = 50, message = "关联中介本人证件号码长度不能超过50个字符") private String relatedNumId; @Schema(description = "关联关系") diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiIntermediaryPersonEditDTO.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiIntermediaryPersonEditDTO.java index 9a7ae6c2..19ca6af6 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiIntermediaryPersonEditDTO.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiIntermediaryPersonEditDTO.java @@ -70,8 +70,8 @@ public class CcdiIntermediaryPersonEditDTO implements Serializable { @Size(max = 100, message = "职位长度不能超过100个字符") private String position; - @Schema(description = "关联人员ID") - @Size(max = 50, message = "关联人员ID长度不能超过50个字符") + @Schema(description = "关联中介本人证件号码") + @Size(max = 50, message = "关联中介本人证件号码长度不能超过50个字符") private String relatedNumId; @Schema(description = "关联关系") diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiIntermediaryEnterpriseRelationExcel.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiIntermediaryEnterpriseRelationExcel.java new file mode 100644 index 00000000..f6aed554 --- /dev/null +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiIntermediaryEnterpriseRelationExcel.java @@ -0,0 +1,38 @@ +package com.ruoyi.info.collection.domain.excel; + +import com.alibaba.excel.annotation.ExcelProperty; +import com.alibaba.excel.annotation.write.style.ColumnWidth; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 中介实体关联关系导入对象 + */ +@Data +public class CcdiIntermediaryEnterpriseRelationExcel implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** 中介本人证件号码 */ + @ExcelProperty(value = "中介本人证件号码*", index = 0) + @ColumnWidth(24) + private String ownerPersonId; + + /** 统一社会信用代码 */ + @ExcelProperty(value = "统一社会信用代码*", index = 1) + @ColumnWidth(24) + private String socialCreditCode; + + /** 关联人职务 */ + @ExcelProperty(value = "关联人职务", index = 2) + @ColumnWidth(20) + private String relationPersonPost; + + /** 备注 */ + @ExcelProperty(value = "备注", index = 3) + @ColumnWidth(30) + private String remark; +} diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiIntermediaryPersonExcel.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiIntermediaryPersonExcel.java index a9a8c84e..8da1599a 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiIntermediaryPersonExcel.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiIntermediaryPersonExcel.java @@ -34,6 +34,7 @@ public class CcdiIntermediaryPersonExcel implements Serializable { /** 人员子类型 */ @ExcelProperty(value = "人员子类型", index = 2) @ColumnWidth(15) + @DictDropdown(dictType = "ccdi_person_sub_type") private String personSubType; /** 性别 */ @@ -83,19 +84,13 @@ public class CcdiIntermediaryPersonExcel implements Serializable { @ColumnWidth(15) private String position; - /** 关联人员ID */ - @ExcelProperty(value = "关联人员ID", index = 12) - @ColumnWidth(15) + /** 关联中介本人证件号码 */ + @ExcelProperty(value = "关联中介本人证件号码", index = 12) + @ColumnWidth(24) private String relatedNumId; - /** 关系类型 */ - @ExcelProperty(value = "关系类型", index = 13) - @ColumnWidth(15) - @DictDropdown(dictType = "ccdi_relation_type") - private String relationType; - /** 备注 */ - @ExcelProperty(value = "备注", index = 14) + @ExcelProperty(value = "备注", index = 13) @ColumnWidth(30) private String remark; } diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CcdiIntermediaryPersonDetailVO.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CcdiIntermediaryPersonDetailVO.java index b0de70b4..def4d3a2 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CcdiIntermediaryPersonDetailVO.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CcdiIntermediaryPersonDetailVO.java @@ -63,7 +63,7 @@ public class CcdiIntermediaryPersonDetailVO implements Serializable { @Schema(description = "职位") private String position; - @Schema(description = "关联人员ID") + @Schema(description = "关联中介本人证件号码") private String relatedNumId; @Schema(description = "关联关系") diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CcdiIntermediaryRelativeVO.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CcdiIntermediaryRelativeVO.java index dcc60cfe..6d10ae43 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CcdiIntermediaryRelativeVO.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CcdiIntermediaryRelativeVO.java @@ -21,7 +21,7 @@ public class CcdiIntermediaryRelativeVO implements Serializable { @Schema(description = "人员ID") private String bizId; - @Schema(description = "所属中介ID") + @Schema(description = "关联中介本人证件号码") private String relatedNumId; @Schema(description = "姓名") diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/IntermediaryEnterpriseRelationImportFailureVO.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/IntermediaryEnterpriseRelationImportFailureVO.java new file mode 100644 index 00000000..a60f581d --- /dev/null +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/IntermediaryEnterpriseRelationImportFailureVO.java @@ -0,0 +1,33 @@ +package com.ruoyi.info.collection.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 中介实体关联关系导入失败记录 + */ +@Data +@Schema(description = "中介实体关联关系导入失败记录") +public class IntermediaryEnterpriseRelationImportFailureVO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Schema(description = "中介本人证件号码") + private String ownerPersonId; + + @Schema(description = "统一社会信用代码") + private String socialCreditCode; + + @Schema(description = "关联人职务") + private String relationPersonPost; + + @Schema(description = "备注") + private String remark; + + @Schema(description = "错误信息") + private String errorMessage; +} diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/IntermediaryPersonImportFailureVO.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/IntermediaryPersonImportFailureVO.java index 86cd9097..1917ae97 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/IntermediaryPersonImportFailureVO.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/IntermediaryPersonImportFailureVO.java @@ -22,21 +22,45 @@ public class IntermediaryPersonImportFailureVO implements Serializable { @Schema(description = "姓名") private String name; - @Schema(description = "证件号码") - private String personId; - @Schema(description = "人员类型") private String personType; + @Schema(description = "人员子类型") + private String personSubType; + @Schema(description = "性别") private String gender; + @Schema(description = "证件类型") + private String idType; + + @Schema(description = "证件号码") + private String personId; + @Schema(description = "手机号码") private String mobile; + @Schema(description = "微信号") + private String wechatNo; + + @Schema(description = "联系地址") + private String contactAddress; + @Schema(description = "所在公司") private String company; + @Schema(description = "企业统一信用码") + private String socialCreditCode; + + @Schema(description = "职位") + private String position; + + @Schema(description = "关联中介本人证件号码") + private String relatedNumId; + + @Schema(description = "备注") + private String remark; + @Schema(description = "错误信息") private String errorMessage; } diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/mapper/CcdiIntermediaryEnterpriseRelationMapper.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/mapper/CcdiIntermediaryEnterpriseRelationMapper.java index 9bc27ff7..c1bffbeb 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/mapper/CcdiIntermediaryEnterpriseRelationMapper.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/mapper/CcdiIntermediaryEnterpriseRelationMapper.java @@ -14,10 +14,14 @@ import java.util.List; @Mapper public interface CcdiIntermediaryEnterpriseRelationMapper extends BaseMapper { + int insertBatch(@Param("list") List list); + List selectByIntermediaryBizId(@Param("bizId") String bizId); CcdiIntermediaryEnterpriseRelationVO selectDetailById(@Param("id") Long id); boolean existsByIntermediaryBizIdAndSocialCreditCode(@Param("bizId") String bizId, @Param("socialCreditCode") String socialCreditCode); + + List batchExistsByCombinations(@Param("combinations") List combinations); } diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiIntermediaryEnterpriseRelationImportService.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiIntermediaryEnterpriseRelationImportService.java new file mode 100644 index 00000000..9e9df4aa --- /dev/null +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiIntermediaryEnterpriseRelationImportService.java @@ -0,0 +1,38 @@ +package com.ruoyi.info.collection.service; + +import com.ruoyi.info.collection.domain.excel.CcdiIntermediaryEnterpriseRelationExcel; +import com.ruoyi.info.collection.domain.vo.ImportStatusVO; +import com.ruoyi.info.collection.domain.vo.IntermediaryEnterpriseRelationImportFailureVO; + +import java.util.List; + +/** + * 中介实体关联关系异步导入服务接口 + */ +public interface ICcdiIntermediaryEnterpriseRelationImportService { + + /** + * 异步导入中介实体关联关系 + * + * @param excelList Excel数据 + * @param taskId 任务ID + * @param userName 当前用户名 + */ + void importAsync(List excelList, String taskId, String userName); + + /** + * 查询导入状态 + * + * @param taskId 任务ID + * @return 导入状态 + */ + ImportStatusVO getImportStatus(String taskId); + + /** + * 查询导入失败记录 + * + * @param taskId 任务ID + * @return 失败记录 + */ + List getImportFailures(String taskId); +} diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiIntermediaryPersonImportService.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiIntermediaryPersonImportService.java index af2981e7..838db1f7 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiIntermediaryPersonImportService.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiIntermediaryPersonImportService.java @@ -7,7 +7,7 @@ import com.ruoyi.info.collection.domain.vo.IntermediaryPersonImportFailureVO; import java.util.List; /** - * 个人中介异步导入Service接口 + * 中介信息异步导入Service接口 * * @author ruoyi * @date 2026-02-06 @@ -15,7 +15,7 @@ import java.util.List; public interface ICcdiIntermediaryPersonImportService { /** - * 异步导入个人中介数据 + * 异步导入中介信息 * * @param excelList Excel数据列表 * @param taskId 任务ID diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiIntermediaryService.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiIntermediaryService.java index 195633a0..24a52664 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiIntermediaryService.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiIntermediaryService.java @@ -2,6 +2,7 @@ package com.ruoyi.info.collection.service; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ruoyi.info.collection.domain.dto.*; +import com.ruoyi.info.collection.domain.excel.CcdiIntermediaryEnterpriseRelationExcel; import com.ruoyi.info.collection.domain.excel.CcdiIntermediaryEntityExcel; import com.ruoyi.info.collection.domain.excel.CcdiIntermediaryPersonExcel; import com.ruoyi.info.collection.domain.vo.CcdiIntermediaryEnterpriseRelationVO; @@ -168,7 +169,7 @@ public interface ICcdiIntermediaryService { int deleteIntermediaryByIds(String[] ids); /** - * 校验人员ID唯一性 + * 校验中介本人证件号码唯一性 * * @param personId 人员ID * @param bizId 排除的人员ID @@ -193,6 +194,14 @@ public interface ICcdiIntermediaryService { */ String importIntermediaryPerson(List list); + /** + * 导入中介实体关联关系 + * + * @param list Excel实体列表 + * @return 任务ID + */ + String importIntermediaryEnterpriseRelation(List list); + /** * 导入实体中介数据 * diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiIntermediaryEnterpriseRelationImportServiceImpl.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiIntermediaryEnterpriseRelationImportServiceImpl.java new file mode 100644 index 00000000..dd9c7c06 --- /dev/null +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiIntermediaryEnterpriseRelationImportServiceImpl.java @@ -0,0 +1,266 @@ +package com.ruoyi.info.collection.service.impl; + +import com.alibaba.fastjson2.JSON; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.ruoyi.info.collection.domain.CcdiBizIntermediary; +import com.ruoyi.info.collection.domain.CcdiEnterpriseBaseInfo; +import com.ruoyi.info.collection.domain.CcdiIntermediaryEnterpriseRelation; +import com.ruoyi.info.collection.domain.excel.CcdiIntermediaryEnterpriseRelationExcel; +import com.ruoyi.info.collection.domain.vo.ImportResult; +import com.ruoyi.info.collection.domain.vo.ImportStatusVO; +import com.ruoyi.info.collection.domain.vo.IntermediaryEnterpriseRelationImportFailureVO; +import com.ruoyi.info.collection.mapper.CcdiBizIntermediaryMapper; +import com.ruoyi.info.collection.mapper.CcdiEnterpriseBaseInfoMapper; +import com.ruoyi.info.collection.mapper.CcdiIntermediaryEnterpriseRelationMapper; +import com.ruoyi.info.collection.service.ICcdiIntermediaryEnterpriseRelationImportService; +import com.ruoyi.info.collection.utils.ImportLogUtils; +import com.ruoyi.common.utils.IdCardUtil; +import com.ruoyi.common.utils.StringUtils; +import jakarta.annotation.Resource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeanUtils; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +/** + * 中介实体关联关系异步导入实现 + */ +@Service +@EnableAsync +public class CcdiIntermediaryEnterpriseRelationImportServiceImpl implements ICcdiIntermediaryEnterpriseRelationImportService { + + private static final Logger log = LoggerFactory.getLogger(CcdiIntermediaryEnterpriseRelationImportServiceImpl.class); + + private static final String STATUS_KEY_PREFIX = "import:intermediary-enterprise-relation:"; + + @Resource + private CcdiIntermediaryEnterpriseRelationMapper relationMapper; + + @Resource + private CcdiBizIntermediaryMapper intermediaryMapper; + + @Resource + private CcdiEnterpriseBaseInfoMapper enterpriseBaseInfoMapper; + + @Resource + private RedisTemplate redisTemplate; + + @Override + @Async + @Transactional(rollbackFor = Exception.class) + public void importAsync(List excelList, String taskId, String userName) { + long startTime = System.currentTimeMillis(); + ImportLogUtils.logImportStart(log, taskId, "中介实体关联关系", excelList.size(), userName); + + Map ownerBizIdByPersonId = getOwnerBizIdByPersonId(excelList); + Set existingEnterpriseCodes = getExistingEnterpriseCodes(excelList); + Set existingCombinations = getExistingRelationCombinations(ownerBizIdByPersonId, excelList); + + List successRecords = new ArrayList<>(); + List failures = new ArrayList<>(); + Set processedCombinations = new HashSet<>(); + + for (int i = 0; i < excelList.size(); i++) { + CcdiIntermediaryEnterpriseRelationExcel excel = excelList.get(i); + try { + validateExcel(excel); + + String ownerBizId = ownerBizIdByPersonId.get(excel.getOwnerPersonId()); + if (StringUtils.isEmpty(ownerBizId)) { + throw new RuntimeException("中介本人不存在,请先导入或维护中介本人信息"); + } + if (!existingEnterpriseCodes.contains(excel.getSocialCreditCode())) { + throw new RuntimeException("统一社会信用代码不存在于系统机构表"); + } + + String combination = ownerBizId + "|" + excel.getSocialCreditCode(); + if (existingCombinations.contains(combination)) { + throw new RuntimeException("中介实体关联关系已存在,请勿重复导入"); + } + if (!processedCombinations.add(combination)) { + throw new RuntimeException("同一中介本人与统一社会信用代码组合在导入文件中重复"); + } + + CcdiIntermediaryEnterpriseRelation relation = new CcdiIntermediaryEnterpriseRelation(); + BeanUtils.copyProperties(excel, relation); + relation.setIntermediaryBizId(ownerBizId); + relation.setCreatedBy(userName); + relation.setUpdatedBy(userName); + successRecords.add(relation); + } catch (Exception e) { + failures.add(createFailureVO(excel, e.getMessage())); + ImportLogUtils.logValidationError(log, taskId, i + 1, e.getMessage(), + String.format("中介本人证件号码=%s, 统一社会信用代码=%s", excel.getOwnerPersonId(), excel.getSocialCreditCode())); + } + } + + if (!successRecords.isEmpty()) { + saveBatch(successRecords, 500); + } + if (!failures.isEmpty()) { + redisTemplate.opsForValue().set(failureKey(taskId), failures, 7, TimeUnit.DAYS); + } + + ImportResult result = new ImportResult(); + result.setTotalCount(excelList.size()); + result.setSuccessCount(successRecords.size()); + result.setFailureCount(failures.size()); + updateImportStatus(taskId, result); + + long duration = System.currentTimeMillis() - startTime; + ImportLogUtils.logImportComplete(log, taskId, "中介实体关联关系", + excelList.size(), result.getSuccessCount(), result.getFailureCount(), duration); + } + + @Override + public ImportStatusVO getImportStatus(String taskId) { + String key = statusKey(taskId); + if (Boolean.FALSE.equals(redisTemplate.hasKey(key))) { + throw new RuntimeException("任务不存在或已过期"); + } + + Map statusMap = redisTemplate.opsForHash().entries(key); + ImportStatusVO statusVO = new ImportStatusVO(); + statusVO.setTaskId((String) statusMap.get("taskId")); + statusVO.setStatus((String) statusMap.get("status")); + statusVO.setTotalCount((Integer) statusMap.get("totalCount")); + statusVO.setSuccessCount((Integer) statusMap.get("successCount")); + statusVO.setFailureCount((Integer) statusMap.get("failureCount")); + statusVO.setProgress((Integer) statusMap.get("progress")); + statusVO.setStartTime((Long) statusMap.get("startTime")); + statusVO.setEndTime((Long) statusMap.get("endTime")); + statusVO.setMessage((String) statusMap.get("message")); + return statusVO; + } + + @Override + public List getImportFailures(String taskId) { + Object failuresObj = redisTemplate.opsForValue().get(failureKey(taskId)); + if (failuresObj == null) { + return Collections.emptyList(); + } + return JSON.parseArray(JSON.toJSONString(failuresObj), IntermediaryEnterpriseRelationImportFailureVO.class); + } + + private Map getOwnerBizIdByPersonId(List excelList) { + List ownerPersonIds = excelList.stream() + .map(CcdiIntermediaryEnterpriseRelationExcel::getOwnerPersonId) + .filter(StringUtils::isNotEmpty) + .distinct() + .collect(Collectors.toList()); + if (ownerPersonIds.isEmpty()) { + return Collections.emptyMap(); + } + + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(CcdiBizIntermediary::getPersonSubType, "本人") + .in(CcdiBizIntermediary::getPersonId, ownerPersonIds); + return intermediaryMapper.selectList(wrapper).stream() + .collect(Collectors.toMap(CcdiBizIntermediary::getPersonId, CcdiBizIntermediary::getBizId, (left, right) -> left)); + } + + private Set getExistingEnterpriseCodes(List excelList) { + List socialCreditCodes = excelList.stream() + .map(CcdiIntermediaryEnterpriseRelationExcel::getSocialCreditCode) + .filter(StringUtils::isNotEmpty) + .distinct() + .collect(Collectors.toList()); + if (socialCreditCodes.isEmpty()) { + return Collections.emptySet(); + } + + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.in(CcdiEnterpriseBaseInfo::getSocialCreditCode, socialCreditCodes); + return enterpriseBaseInfoMapper.selectList(wrapper).stream() + .map(CcdiEnterpriseBaseInfo::getSocialCreditCode) + .collect(Collectors.toSet()); + } + + private Set getExistingRelationCombinations(Map ownerBizIdByPersonId, + List excelList) { + List combinations = excelList.stream() + .map(excel -> { + String ownerBizId = ownerBizIdByPersonId.get(excel.getOwnerPersonId()); + if (StringUtils.isEmpty(ownerBizId) || StringUtils.isEmpty(excel.getSocialCreditCode())) { + return null; + } + return ownerBizId + "|" + excel.getSocialCreditCode(); + }) + .filter(StringUtils::isNotEmpty) + .distinct() + .collect(Collectors.toList()); + if (combinations.isEmpty()) { + return Collections.emptySet(); + } + return new HashSet<>(relationMapper.batchExistsByCombinations(combinations)); + } + + private void validateExcel(CcdiIntermediaryEnterpriseRelationExcel excel) { + if (StringUtils.isEmpty(excel.getOwnerPersonId())) { + throw new RuntimeException("中介本人证件号码不能为空"); + } + if (StringUtils.isEmpty(excel.getSocialCreditCode())) { + throw new RuntimeException("统一社会信用代码不能为空"); + } + String ownerPersonIdError = IdCardUtil.getErrorMessage(excel.getOwnerPersonId()); + if (ownerPersonIdError != null) { + throw new RuntimeException("中介本人证件号码" + ownerPersonIdError); + } + if (StringUtils.isNotEmpty(excel.getRelationPersonPost()) && excel.getRelationPersonPost().length() > 100) { + throw new RuntimeException("关联人职务长度不能超过100个字符"); + } + if (StringUtils.isNotEmpty(excel.getRemark()) && excel.getRemark().length() > 500) { + throw new RuntimeException("备注长度不能超过500个字符"); + } + } + + private IntermediaryEnterpriseRelationImportFailureVO createFailureVO(CcdiIntermediaryEnterpriseRelationExcel excel, + String errorMessage) { + IntermediaryEnterpriseRelationImportFailureVO failure = new IntermediaryEnterpriseRelationImportFailureVO(); + BeanUtils.copyProperties(excel, failure); + failure.setErrorMessage(errorMessage); + return failure; + } + + private void saveBatch(List list, int batchSize) { + for (int i = 0; i < list.size(); i += batchSize) { + int end = Math.min(i + batchSize, list.size()); + relationMapper.insertBatch(list.subList(i, end)); + } + } + + private void updateImportStatus(String taskId, ImportResult result) { + Map statusData = new HashMap<>(); + statusData.put("status", result.getFailureCount() == 0 ? "SUCCESS" : "PARTIAL_SUCCESS"); + statusData.put("successCount", result.getSuccessCount()); + statusData.put("failureCount", result.getFailureCount()); + statusData.put("progress", 100); + statusData.put("endTime", System.currentTimeMillis()); + statusData.put("message", result.getFailureCount() == 0 + ? "全部成功!共导入" + result.getTotalCount() + "条数据" + : "成功" + result.getSuccessCount() + "条,失败" + result.getFailureCount() + "条"); + redisTemplate.opsForHash().putAll(statusKey(taskId), statusData); + } + + private String statusKey(String taskId) { + return STATUS_KEY_PREFIX + taskId; + } + + private String failureKey(String taskId) { + return statusKey(taskId) + ":failures"; + } +} diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiIntermediaryPersonImportServiceImpl.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiIntermediaryPersonImportServiceImpl.java index d0614bee..56466f58 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiIntermediaryPersonImportServiceImpl.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiIntermediaryPersonImportServiceImpl.java @@ -22,15 +22,18 @@ import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; /** - * 个人中介异步导入Service实现 - * - * @author ruoyi - * @date 2026-02-06 + * 中介信息异步导入实现 */ @Service @EnableAsync @@ -38,6 +41,8 @@ public class CcdiIntermediaryPersonImportServiceImpl implements ICcdiIntermediar private static final Logger log = LoggerFactory.getLogger(CcdiIntermediaryPersonImportServiceImpl.class); + private static final String STATUS_KEY_PREFIX = "import:intermediary:"; + @Resource private CcdiBizIntermediaryMapper intermediaryMapper; @@ -47,110 +52,104 @@ public class CcdiIntermediaryPersonImportServiceImpl implements ICcdiIntermediar @Override @Async @Transactional(rollbackFor = Exception.class) - public void importPersonAsync(List excelList, - String taskId, - String userName) { + public void importPersonAsync(List excelList, String taskId, String userName) { long startTime = System.currentTimeMillis(); + ImportLogUtils.logImportStart(log, taskId, "中介信息", excelList.size(), userName); - // 记录导入开始 - ImportLogUtils.logImportStart(log, taskId, "个人中介", excelList.size(), userName); - - List newRecords = new ArrayList<>(); + List ownerRows = new ArrayList<>(); + List relativeRows = new ArrayList<>(); List failures = new ArrayList<>(); - // 批量查询已存在的证件号 - ImportLogUtils.logBatchQueryStart(log, taskId, "已存在的证件号", excelList.size()); - Set existingPersonIds = getExistingPersonIds(excelList); - ImportLogUtils.logBatchQueryComplete(log, taskId, "证件号", existingPersonIds.size()); - - // 用于检测Excel内部的重复ID - Set excelProcessedIds = new HashSet<>(); - - // 分类数据 for (int i = 0; i < excelList.size(); i++) { CcdiIntermediaryPersonExcel excel = excelList.get(i); - try { - // 验证数据 - validatePersonData(excel, existingPersonIds); - - CcdiBizIntermediary intermediary = new CcdiBizIntermediary(); - BeanUtils.copyProperties(excel, intermediary); - - // 设置数据来源和审计字段 - intermediary.setDataSource("IMPORT"); - intermediary.setCreatedBy(userName); - intermediary.setUpdatedBy(userName); - - if (existingPersonIds.contains(excel.getPersonId())) { - // 证件号码在数据库中已存在,直接报错 - throw new RuntimeException(String.format("证件号码[%s]已存在,请勿重复导入", excel.getPersonId())); - } else if (excelProcessedIds.contains(excel.getPersonId())) { - // 证件号码在Excel文件内部重复 - throw new RuntimeException(String.format("证件号码[%s]在导入文件中重复,已跳过此条记录", excel.getPersonId())); + validateCommonRow(excel); + if (isOwnerRow(excel)) { + validateOwnerRow(excel); + ownerRows.add(excel); } else { - newRecords.add(intermediary); - excelProcessedIds.add(excel.getPersonId()); // 标记为已处理 + validateRelativeRow(excel); + relativeRows.add(excel); } - - // 记录进度 - ImportLogUtils.logProgress(log, taskId, i + 1, excelList.size(), - newRecords.size(), failures.size()); - } catch (Exception e) { failures.add(createFailureVO(excel, e.getMessage())); - - // 记录验证失败日志 - String keyData = String.format("姓名=%s, 证件号码=%s", - excel.getName(), excel.getPersonId()); - ImportLogUtils.logValidationError(log, taskId, i + 1, e.getMessage(), keyData); + ImportLogUtils.logValidationError(log, taskId, i + 1, e.getMessage(), + String.format("姓名=%s, 证件号码=%s", excel.getName(), excel.getPersonId())); } } - // 批量插入新数据 - if (!newRecords.isEmpty()) { - ImportLogUtils.logBatchOperationStart(log, taskId, "插入", - (newRecords.size() + 499) / 500, 500); - saveBatch(newRecords, 500); - } + Set existingOwnerPersonIds = getExistingOwnerPersonIds(ownerRows); + Set existingOwnerRefs = getExistingOwnerRefs(relativeRows); + Set existingRelativeCombinations = getExistingRelativeCombinations(relativeRows); - // 保存失败记录到Redis - if (!failures.isEmpty()) { + List successRecords = new ArrayList<>(); + Set importedOwnerPersonIds = new HashSet<>(); + + for (CcdiIntermediaryPersonExcel ownerExcel : ownerRows) { try { - String failuresKey = "import:intermediary:" + taskId + ":failures"; - redisTemplate.opsForValue().set(failuresKey, failures, 7, TimeUnit.DAYS); - ImportLogUtils.logRedisOperation(log, taskId, "保存失败记录", failures.size()); + String ownerPersonId = ownerExcel.getPersonId(); + if (existingOwnerPersonIds.contains(ownerPersonId)) { + throw new RuntimeException(String.format("中介本人证件号码[%s]已存在,请勿重复导入", ownerPersonId)); + } + if (!importedOwnerPersonIds.add(ownerPersonId)) { + throw new RuntimeException(String.format("中介本人证件号码[%s]在导入文件中重复", ownerPersonId)); + } + successRecords.add(buildRecord(ownerExcel, userName, null)); } catch (Exception e) { - ImportLogUtils.logRedisError(log, taskId, "保存失败记录", e); + failures.add(createFailureVO(ownerExcel, e.getMessage())); } } + Set validOwnerRefs = new HashSet<>(existingOwnerRefs); + validOwnerRefs.addAll(importedOwnerPersonIds); + Set processedRelativeCombinations = new HashSet<>(); + + for (CcdiIntermediaryPersonExcel relativeExcel : relativeRows) { + try { + String ownerPersonId = relativeExcel.getRelatedNumId(); + String combination = ownerPersonId + "|" + relativeExcel.getPersonId(); + if (!validOwnerRefs.contains(ownerPersonId)) { + throw new RuntimeException(String.format("关联中介本人证件号码[%s]不存在", ownerPersonId)); + } + if (existingRelativeCombinations.contains(combination)) { + throw new RuntimeException(String.format("同一中介本人名下证件号码[%s]的亲属已存在,请勿重复导入", relativeExcel.getPersonId())); + } + if (!processedRelativeCombinations.add(combination)) { + throw new RuntimeException(String.format("同一中介本人名下证件号码[%s]的亲属在导入文件中重复", relativeExcel.getPersonId())); + } + successRecords.add(buildRecord(relativeExcel, userName, ownerPersonId)); + } catch (Exception e) { + failures.add(createFailureVO(relativeExcel, e.getMessage())); + } + } + + if (!successRecords.isEmpty()) { + saveBatch(successRecords, 500); + } + + if (!failures.isEmpty()) { + redisTemplate.opsForValue().set(failureKey(taskId), failures, 7, TimeUnit.DAYS); + } + ImportResult result = new ImportResult(); result.setTotalCount(excelList.size()); - result.setSuccessCount(newRecords.size()); + result.setSuccessCount(successRecords.size()); result.setFailureCount(failures.size()); + updateImportStatus(taskId, result); - // 更新最终状态 - String finalStatus = result.getFailureCount() == 0 ? "SUCCESS" : "PARTIAL_SUCCESS"; - updateImportStatus(taskId, finalStatus, result); - - // 记录导入完成 long duration = System.currentTimeMillis() - startTime; - ImportLogUtils.logImportComplete(log, taskId, "个人中介", - excelList.size(), result.getSuccessCount(), result.getFailureCount(), duration); + ImportLogUtils.logImportComplete(log, taskId, "中介信息", + excelList.size(), result.getSuccessCount(), result.getFailureCount(), duration); } @Override public ImportStatusVO getImportStatus(String taskId) { - String key = "import:intermediary:" + taskId; - Boolean hasKey = redisTemplate.hasKey(key); - - if (Boolean.FALSE.equals(hasKey)) { + String key = statusKey(taskId); + if (Boolean.FALSE.equals(redisTemplate.hasKey(key))) { throw new RuntimeException("任务不存在或已过期"); } Map statusMap = redisTemplate.opsForHash().entries(key); - ImportStatusVO statusVO = new ImportStatusVO(); statusVO.setTaskId((String) statusMap.get("taskId")); statusVO.setStatus((String) statusMap.get("status")); @@ -161,83 +160,120 @@ public class CcdiIntermediaryPersonImportServiceImpl implements ICcdiIntermediar statusVO.setStartTime((Long) statusMap.get("startTime")); statusVO.setEndTime((Long) statusMap.get("endTime")); statusVO.setMessage((String) statusMap.get("message")); - return statusVO; } @Override public List getImportFailures(String taskId) { - String key = "import:intermediary:" + taskId + ":failures"; - Object failuresObj = redisTemplate.opsForValue().get(key); - + Object failuresObj = redisTemplate.opsForValue().get(failureKey(taskId)); if (failuresObj == null) { return Collections.emptyList(); } - return JSON.parseArray(JSON.toJSONString(failuresObj), IntermediaryPersonImportFailureVO.class); } - /** - * 批量查询已存在的证件号 - */ - private Set getExistingPersonIds(List excelList) { - List personIds = excelList.stream() - .map(CcdiIntermediaryPersonExcel::getPersonId) - .filter(StringUtils::isNotEmpty) - .collect(Collectors.toList()); + private boolean isOwnerRow(CcdiIntermediaryPersonExcel excel) { + return "本人".equals(excel.getPersonSubType()); + } - if (personIds.isEmpty()) { + private void validateCommonRow(CcdiIntermediaryPersonExcel excel) { + if (StringUtils.isEmpty(excel.getName())) { + throw new RuntimeException("姓名不能为空"); + } + if (StringUtils.isEmpty(excel.getPersonSubType())) { + throw new RuntimeException("人员子类型不能为空"); + } + if (StringUtils.isEmpty(excel.getPersonId())) { + throw new RuntimeException("证件号码不能为空"); + } + String idCardError = IdCardUtil.getErrorMessage(excel.getPersonId()); + if (idCardError != null) { + throw new RuntimeException("证件号码" + idCardError); + } + } + + private void validateOwnerRow(CcdiIntermediaryPersonExcel excel) { + if (StringUtils.isNotEmpty(excel.getRelatedNumId())) { + throw new RuntimeException("本人行关联中介本人证件号码必须为空"); + } + } + + private void validateRelativeRow(CcdiIntermediaryPersonExcel excel) { + if (StringUtils.isEmpty(excel.getRelatedNumId())) { + throw new RuntimeException("亲属行必须填写关联中介本人证件号码"); + } + } + + private Set getExistingOwnerPersonIds(List ownerRows) { + List ownerPersonIds = ownerRows.stream() + .map(CcdiIntermediaryPersonExcel::getPersonId) + .filter(StringUtils::isNotEmpty) + .distinct() + .collect(Collectors.toList()); + if (ownerPersonIds.isEmpty()) { return Collections.emptySet(); } LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.in(CcdiBizIntermediary::getPersonId, personIds); - List existingIntermediaries = intermediaryMapper.selectList(wrapper); - - return existingIntermediaries.stream() - .map(CcdiBizIntermediary::getPersonId) - .collect(Collectors.toSet()); + wrapper.eq(CcdiBizIntermediary::getPersonSubType, "本人") + .in(CcdiBizIntermediary::getPersonId, ownerPersonIds); + return intermediaryMapper.selectList(wrapper).stream() + .map(CcdiBizIntermediary::getPersonId) + .collect(Collectors.toSet()); } - /** - * 批量保存(使用ON DUPLICATE KEY UPDATE) - */ - private int saveBatchWithUpsert(List list, int batchSize) { - int totalCount = 0; - for (int i = 0; i < list.size(); i += batchSize) { - int end = Math.min(i + batchSize, list.size()); - List subList = list.subList(i, end); - int count = intermediaryMapper.importPersonBatch(subList); - totalCount += count; - } - return totalCount; - } - - /** - * 从数据库获取已存在的证件号 - */ - private Set getExistingPersonIdsFromDb(List records) { - List personIds = records.stream() - .map(CcdiBizIntermediary::getPersonId) - .filter(StringUtils::isNotEmpty) - .collect(Collectors.toList()); - - if (personIds.isEmpty()) { + private Set getExistingOwnerRefs(List relativeRows) { + List ownerRefs = relativeRows.stream() + .map(CcdiIntermediaryPersonExcel::getRelatedNumId) + .filter(StringUtils::isNotEmpty) + .distinct() + .collect(Collectors.toList()); + if (ownerRefs.isEmpty()) { return Collections.emptySet(); } LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.in(CcdiBizIntermediary::getPersonId, personIds); - List existing = intermediaryMapper.selectList(wrapper); - - return existing.stream() - .map(CcdiBizIntermediary::getPersonId) - .collect(Collectors.toSet()); + wrapper.eq(CcdiBizIntermediary::getPersonSubType, "本人") + .in(CcdiBizIntermediary::getPersonId, ownerRefs); + return intermediaryMapper.selectList(wrapper).stream() + .map(CcdiBizIntermediary::getPersonId) + .collect(Collectors.toSet()); + } + + private Set getExistingRelativeCombinations(List relativeRows) { + List ownerRefs = relativeRows.stream() + .map(CcdiIntermediaryPersonExcel::getRelatedNumId) + .filter(StringUtils::isNotEmpty) + .distinct() + .collect(Collectors.toList()); + List relativePersonIds = relativeRows.stream() + .map(CcdiIntermediaryPersonExcel::getPersonId) + .filter(StringUtils::isNotEmpty) + .distinct() + .collect(Collectors.toList()); + if (ownerRefs.isEmpty() || relativePersonIds.isEmpty()) { + return Collections.emptySet(); + } + + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.ne(CcdiBizIntermediary::getPersonSubType, "本人") + .in(CcdiBizIntermediary::getRelatedNumId, ownerRefs) + .in(CcdiBizIntermediary::getPersonId, relativePersonIds); + return intermediaryMapper.selectList(wrapper).stream() + .map(item -> item.getRelatedNumId() + "|" + item.getPersonId()) + .collect(Collectors.toSet()); + } + + private CcdiBizIntermediary buildRecord(CcdiIntermediaryPersonExcel excel, String userName, String ownerPersonId) { + CcdiBizIntermediary intermediary = new CcdiBizIntermediary(); + BeanUtils.copyProperties(excel, intermediary); + intermediary.setRelatedNumId(ownerPersonId); + intermediary.setDataSource("IMPORT"); + intermediary.setCreatedBy(userName); + intermediary.setUpdatedBy(userName); + return intermediary; } - /** - * 创建失败记录VO - */ private IntermediaryPersonImportFailureVO createFailureVO(CcdiIntermediaryPersonExcel excel, String errorMsg) { IntermediaryPersonImportFailureVO failure = new IntermediaryPersonImportFailureVO(); BeanUtils.copyProperties(excel, failure); @@ -245,73 +281,31 @@ public class CcdiIntermediaryPersonImportServiceImpl implements ICcdiIntermediar return failure; } - /** - * 创建失败记录VO(重载方法) - */ - private IntermediaryPersonImportFailureVO createFailureVO(CcdiBizIntermediary record, String errorMsg) { - CcdiIntermediaryPersonExcel excel = new CcdiIntermediaryPersonExcel(); - BeanUtils.copyProperties(record, excel); - return createFailureVO(excel, errorMsg); - } - - /** - * 批量保存 - */ - private int saveBatch(List list, int batchSize) { - // 使用真正的批量插入,分批次执行以提高性能 - int totalCount = 0; + private void saveBatch(List list, int batchSize) { for (int i = 0; i < list.size(); i += batchSize) { int end = Math.min(i + batchSize, list.size()); - List subList = list.subList(i, end); - int count = intermediaryMapper.insertBatch(subList); - totalCount += count; + intermediaryMapper.insertBatch(list.subList(i, end)); } - return totalCount; } - /** - * 更新导入状态 - */ - private void updateImportStatus(String taskId, String status, ImportResult result) { - String key = "import:intermediary:" + taskId; + private void updateImportStatus(String taskId, ImportResult result) { Map statusData = new HashMap<>(); - statusData.put("status", status); + statusData.put("status", result.getFailureCount() == 0 ? "SUCCESS" : "PARTIAL_SUCCESS"); statusData.put("successCount", result.getSuccessCount()); statusData.put("failureCount", result.getFailureCount()); statusData.put("progress", 100); statusData.put("endTime", System.currentTimeMillis()); - - if ("SUCCESS".equals(status)) { - statusData.put("message", "全部成功!共导入" + result.getTotalCount() + "条数据"); - } else { - statusData.put("message", "成功" + result.getSuccessCount() + "条,失败" + result.getFailureCount() + "条"); - } - - redisTemplate.opsForHash().putAll(key, statusData); + statusData.put("message", result.getFailureCount() == 0 + ? "全部成功!共导入" + result.getTotalCount() + "条数据" + : "成功" + result.getSuccessCount() + "条,失败" + result.getFailureCount() + "条"); + redisTemplate.opsForHash().putAll(statusKey(taskId), statusData); } - /** - * 验证个人中介数据 - * - * @param excel Excel数据 - * @param existingPersonIds 已存在的证件号集合 - */ - private void validatePersonData(CcdiIntermediaryPersonExcel excel, - Set existingPersonIds) { - // 验证必填字段:姓名 - if (StringUtils.isEmpty(excel.getName())) { - throw new RuntimeException("姓名不能为空"); - } + private String statusKey(String taskId) { + return STATUS_KEY_PREFIX + taskId; + } - // 验证必填字段:证件号码 - if (StringUtils.isEmpty(excel.getPersonId())) { - throw new RuntimeException("证件号码不能为空"); - } - - // 验证证件号码格式 - String idCardError = IdCardUtil.getErrorMessage(excel.getPersonId()); - if (idCardError != null) { - throw new RuntimeException("证件号码" + idCardError); - } + private String failureKey(String taskId) { + return statusKey(taskId) + ":failures"; } } diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiIntermediaryServiceImpl.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiIntermediaryServiceImpl.java index ddeb8a7d..766de88d 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiIntermediaryServiceImpl.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiIntermediaryServiceImpl.java @@ -6,6 +6,7 @@ import com.ruoyi.info.collection.domain.CcdiBizIntermediary; import com.ruoyi.info.collection.domain.CcdiEnterpriseBaseInfo; import com.ruoyi.info.collection.domain.CcdiIntermediaryEnterpriseRelation; import com.ruoyi.info.collection.domain.dto.*; +import com.ruoyi.info.collection.domain.excel.CcdiIntermediaryEnterpriseRelationExcel; import com.ruoyi.info.collection.domain.excel.CcdiIntermediaryEntityExcel; import com.ruoyi.info.collection.domain.excel.CcdiIntermediaryPersonExcel; import com.ruoyi.info.collection.domain.vo.CcdiIntermediaryEnterpriseRelationVO; @@ -17,6 +18,7 @@ import com.ruoyi.info.collection.mapper.CcdiBizIntermediaryMapper; import com.ruoyi.info.collection.mapper.CcdiEnterpriseBaseInfoMapper; import com.ruoyi.info.collection.mapper.CcdiIntermediaryEnterpriseRelationMapper; import com.ruoyi.info.collection.mapper.CcdiIntermediaryMapper; +import com.ruoyi.info.collection.service.ICcdiIntermediaryEnterpriseRelationImportService; import com.ruoyi.info.collection.service.ICcdiIntermediaryEntityImportService; import com.ruoyi.info.collection.service.ICcdiIntermediaryPersonImportService; import com.ruoyi.info.collection.service.ICcdiIntermediaryService; @@ -61,6 +63,9 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService { @Resource private ICcdiIntermediaryEntityImportService entityImportService; + @Resource + private ICcdiIntermediaryEnterpriseRelationImportService enterpriseRelationImportService; + @Resource private RedisTemplate redisTemplate; @@ -101,8 +106,9 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService { @Override public List selectIntermediaryRelativeList(String bizId) { + CcdiBizIntermediary owner = requireIntermediaryPerson(bizId); LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(CcdiBizIntermediary::getRelatedNumId, bizId) + wrapper.eq(CcdiBizIntermediary::getRelatedNumId, owner.getPersonId()) .ne(CcdiBizIntermediary::getPersonSubType, "本人") .orderByDesc(CcdiBizIntermediary::getCreateTime); return bizIntermediaryMapper.selectList(wrapper).stream().map(this::buildRelativeVo).toList(); @@ -187,8 +193,9 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService { BeanUtils.copyProperties(editDTO, person); person.setPersonSubType("本人"); person.setRelatedNumId(null); - - return bizIntermediaryMapper.updateById(person); + int updated = bizIntermediaryMapper.updateById(person); + syncRelativeOwnerPersonId(existing.getPersonId(), editDTO.getPersonId()); + return updated; } @Override @@ -196,13 +203,13 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService { public int insertIntermediaryRelative(String bizId, CcdiIntermediaryRelativeAddDTO addDTO) { CcdiBizIntermediary owner = requireIntermediaryPerson(bizId); validateRelativePersonSubType(addDTO.getPersonSubType()); - if (!checkPersonIdUnique(addDTO.getPersonId(), null)) { - throw new RuntimeException("该证件号已存在"); + if (!checkRelativePersonUnique(owner.getPersonId(), addDTO.getPersonId(), null)) { + throw new RuntimeException("该中介本人下已存在相同证件号亲属"); } CcdiBizIntermediary relative = new CcdiBizIntermediary(); BeanUtils.copyProperties(addDTO, relative); - relative.setRelatedNumId(owner.getBizId()); + relative.setRelatedNumId(owner.getPersonId()); relative.setDataSource("MANUAL"); return bizIntermediaryMapper.insert(relative); } @@ -216,8 +223,8 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService { } validateRelativePersonSubType(editDTO.getPersonSubType()); if (StringUtils.isNotEmpty(editDTO.getPersonId()) - && !checkPersonIdUnique(editDTO.getPersonId(), editDTO.getBizId())) { - throw new RuntimeException("该证件号已存在"); + && !checkRelativePersonUnique(existing.getRelatedNumId(), editDTO.getPersonId(), editDTO.getBizId())) { + throw new RuntimeException("该中介本人下已存在相同证件号亲属"); } CcdiBizIntermediary relative = new CcdiBizIntermediary(); @@ -334,7 +341,8 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService { if (intermediary != null) { if (isIntermediaryPerson(intermediary)) { bizIntermediaryMapper.delete(new LambdaQueryWrapper() - .eq(CcdiBizIntermediary::getRelatedNumId, id)); + .eq(CcdiBizIntermediary::getRelatedNumId, intermediary.getPersonId()) + .ne(CcdiBizIntermediary::getPersonSubType, "本人")); enterpriseRelationMapper.delete(new LambdaQueryWrapper() .eq(CcdiIntermediaryEnterpriseRelation::getIntermediaryBizId, id)); } @@ -359,7 +367,8 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService { @Override public boolean checkPersonIdUnique(String personId, String bizId) { LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(CcdiBizIntermediary::getPersonId, personId); + wrapper.eq(CcdiBizIntermediary::getPersonId, personId) + .eq(CcdiBizIntermediary::getPersonSubType, "本人"); if (StringUtils.isNotEmpty(bizId)) { wrapper.ne(CcdiBizIntermediary::getBizId, bizId); } @@ -419,6 +428,31 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService { return taskId; } + @Override + @Transactional + public String importIntermediaryEnterpriseRelation(List list) { + String taskId = UUID.randomUUID().toString(); + long startTime = System.currentTimeMillis(); + + String statusKey = "import:intermediary-enterprise-relation:" + taskId; + Map statusData = new HashMap<>(); + statusData.put("taskId", taskId); + statusData.put("status", "PROCESSING"); + statusData.put("totalCount", list.size()); + statusData.put("successCount", 0); + statusData.put("failureCount", 0); + statusData.put("progress", 0); + statusData.put("startTime", startTime); + statusData.put("message", "正在处理..."); + + redisTemplate.opsForHash().putAll(statusKey, statusData); + redisTemplate.expire(statusKey, 7, TimeUnit.DAYS); + + String userName = SecurityUtils.getUsername(); + enterpriseRelationImportService.importAsync(list, taskId, userName); + return taskId; + } + /** * 导入实体中介数据(异步) * @@ -473,6 +507,17 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService { } } + private boolean checkRelativePersonUnique(String ownerPersonId, String personId, String excludeBizId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(CcdiBizIntermediary::getRelatedNumId, ownerPersonId) + .eq(CcdiBizIntermediary::getPersonId, personId) + .ne(CcdiBizIntermediary::getPersonSubType, "本人"); + if (StringUtils.isNotEmpty(excludeBizId)) { + wrapper.ne(CcdiBizIntermediary::getBizId, excludeBizId); + } + return bizIntermediaryMapper.selectCount(wrapper) == 0; + } + private void validateEnterpriseRelation(String bizId, String socialCreditCode, Long excludeId) { requireIntermediaryPerson(bizId); if (enterpriseBaseInfoMapper.selectById(socialCreditCode) == null) { @@ -490,6 +535,20 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService { } } + private void syncRelativeOwnerPersonId(String oldOwnerPersonId, String newOwnerPersonId) { + if (StringUtils.isEmpty(oldOwnerPersonId) + || StringUtils.isEmpty(newOwnerPersonId) + || oldOwnerPersonId.equals(newOwnerPersonId)) { + return; + } + + CcdiBizIntermediary relative = new CcdiBizIntermediary(); + relative.setRelatedNumId(newOwnerPersonId); + bizIntermediaryMapper.update(relative, new LambdaQueryWrapper() + .eq(CcdiBizIntermediary::getRelatedNumId, oldOwnerPersonId) + .ne(CcdiBizIntermediary::getPersonSubType, "本人")); + } + private CcdiIntermediaryRelativeVO buildRelativeVo(CcdiBizIntermediary relative) { CcdiIntermediaryRelativeVO vo = new CcdiIntermediaryRelativeVO(); BeanUtils.copyProperties(relative, vo); diff --git a/ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiIntermediaryEnterpriseRelationMapper.xml b/ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiIntermediaryEnterpriseRelationMapper.xml index 7c143fad..9f172c7c 100644 --- a/ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiIntermediaryEnterpriseRelationMapper.xml +++ b/ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiIntermediaryEnterpriseRelationMapper.xml @@ -4,6 +4,19 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> + + INSERT INTO ccdi_intermediary_enterprise_relation ( + intermediary_biz_id, social_credit_code, relation_person_post, remark, + created_by, updated_by, create_time, update_time + ) VALUES + + ( + #{item.intermediaryBizId}, #{item.socialCreditCode}, #{item.relationPersonPost}, #{item.remark}, + #{item.createdBy}, #{item.updatedBy}, NOW(), NOW() + ) + + + @@ -63,4 +76,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" AND social_credit_code = #{socialCreditCode} + + diff --git a/ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiIntermediaryMapper.xml b/ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiIntermediaryMapper.xml index 3aa07391..2a40bdfb 100644 --- a/ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiIntermediaryMapper.xml +++ b/ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiIntermediaryMapper.xml @@ -32,7 +32,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" child.create_time FROM ccdi_biz_intermediary child INNER JOIN ccdi_biz_intermediary parent - ON child.related_num_id COLLATE utf8mb4_general_ci = parent.biz_id COLLATE utf8mb4_general_ci + ON child.related_num_id COLLATE utf8mb4_general_ci = parent.person_id COLLATE utf8mb4_general_ci AND parent.person_sub_type COLLATE utf8mb4_general_ci = '本人' COLLATE utf8mb4_general_ci WHERE child.person_sub_type IS NOT NULL AND child.person_sub_type COLLATE utf8mb4_general_ci != '本人' COLLATE utf8mb4_general_ci diff --git a/docs/design/2026-04-20-intermediary-import-refactor-design.md b/docs/design/2026-04-20-intermediary-import-refactor-design.md index e593d5b7..847ba89b 100644 --- a/docs/design/2026-04-20-intermediary-import-refactor-design.md +++ b/docs/design/2026-04-20-intermediary-import-refactor-design.md @@ -3,7 +3,7 @@ **模块**: 中介库管理 **日期**: 2026-04-20 **作者**: Codex -**状态**: 待评审 +**状态**: 已实现(SQL 已执行,历史脏数据待清洗) ## 一、背景 @@ -563,3 +563,11 @@ 10. 通过一次性历史数据迁移完成语义统一 该方案满足当前需求边界,且符合最短路径实现原则,不引入兼容性补丁方案。 + +## 十六、实施回写 + +- 2026-04-20 已完成中介模块 `related_num_id` 语义切换,手工新增亲属、统一列表查询、本人证件号变更同步和级联删除逻辑均按“关联中介本人证件号码”改造。 +- 2026-04-20 已完成“导入中介信息”和“导入中介实体关联关系”两条异步导入链路,并同步完成前端双按钮、双任务状态、双失败记录模式改造。 +- 2026-04-20 已完成后端目标测试回归、信息采集模块编译、前端静态单测和 `build:prod` 构建验证。 +- 2026-04-20 已执行 `bin/mysql_utf8_exec.sh sql/migration/2026-04-20-fix-ccdi-person-sub-type-dict.sql` 与 `bin/mysql_utf8_exec.sh sql/migration/2026-04-20-migrate-intermediary-related-num-id-to-person-id.sql`。 +- 迁移后核查结果:`legacy_biz_id_reference = 0`,说明旧 `biz_id` 语义残留已清零;`post_migration_missing_parent = 1025`、`owner_person_id_empty_after_migration = 754`,说明历史数据中仍存在无法关联到本人证件号的脏数据,需后续专项清洗。 diff --git a/docs/plans/backend/2026-04-20-intermediary-import-backend-implementation.md b/docs/plans/backend/2026-04-20-intermediary-import-backend-implementation.md index 107671cf..f389f058 100644 --- a/docs/plans/backend/2026-04-20-intermediary-import-backend-implementation.md +++ b/docs/plans/backend/2026-04-20-intermediary-import-backend-implementation.md @@ -435,3 +435,27 @@ mvn -pl ccdi-info-collection -am clean compile - `relationType` 已从导入链路中移除 - `personSubType` 模板下拉已切换为 `ccdi_person_sub_type` - 后端测试与编译验证通过 + +## 执行结果 + +- SQL 脚本: + `sql/migration/2026-04-20-fix-ccdi-person-sub-type-dict.sql` + `sql/migration/2026-04-20-migrate-intermediary-related-num-id-to-person-id.sql` + 结果:已执行 `bin/mysql_utf8_exec.sh`。 +- SQL 核查: + `post_migration_missing_parent = 1025` + `legacy_biz_id_reference = 0` + `owner_person_id_empty_after_migration = 754` + 结果:可迁移数据已切换完成,旧 `biz_id` 语义残留清零,但历史脏数据仍需后续清洗。 +- Maven 命令: + `mvn -pl ccdi-info-collection -am -Dsurefire.failIfNoSpecifiedTests=false -Dtest=CcdiIntermediaryServiceImplTest,CcdiIntermediaryMapperTest test` + 结果:PASS +- Maven 命令: + `mvn -pl ccdi-info-collection -am -Dsurefire.failIfNoSpecifiedTests=false -Dtest=CcdiIntermediaryPersonImportServiceImplTest,CcdiIntermediaryEnterpriseRelationImportServiceImplTest,CcdiIntermediaryControllerTest test` + 结果:PASS +- Maven 命令: + `mvn -pl ccdi-info-collection -am -Dsurefire.failIfNoSpecifiedTests=false -Dtest=CcdiIntermediaryServiceImplTest,CcdiIntermediaryMapperTest,CcdiIntermediaryControllerTest,CcdiIntermediaryPersonImportServiceImplTest,CcdiIntermediaryEnterpriseRelationImportServiceImplTest test` + 结果:PASS +- Maven 命令: + `mvn -pl ccdi-info-collection -am clean compile` + 结果:PASS(BUILD SUCCESS) diff --git a/docs/plans/frontend/2026-04-20-intermediary-import-frontend-implementation.md b/docs/plans/frontend/2026-04-20-intermediary-import-frontend-implementation.md index c0996e26..5a50ff08 100644 --- a/docs/plans/frontend/2026-04-20-intermediary-import-frontend-implementation.md +++ b/docs/plans/frontend/2026-04-20-intermediary-import-frontend-implementation.md @@ -399,3 +399,15 @@ source ~/.nvm/nvm.sh && nvm use 14.21.3 && npm run build:prod - 页面支持恢复两类最近一次导入任务状态 - 导入提示文案已经明确 `personSubType` 字典下拉、`relationType` 废弃、`relatedNumId` 新语义 - 已使用 `nvm use 14.21.3` 完成前端测试脚本和生产构建验证 + +## 执行结果 + +- Node 命令: + `source ~/.nvm/nvm.sh && cd ruoyi-ui && nvm use 14.21.3` + 结果:PASS(Now using node v14.21.3) +- Node 命令: + `source ~/.nvm/nvm.sh && cd ruoyi-ui && nvm use 14.21.3 >/dev/null && node tests/unit/intermediary-import-api.test.js && node tests/unit/intermediary-import-dialog.test.js && node tests/unit/intermediary-import-toolbar.test.js && node tests/unit/intermediary-import-state.test.js && node tests/unit/intermediary-person-edit-ui.test.js` + 结果:PASS +- Node 命令: + `source ~/.nvm/nvm.sh && cd ruoyi-ui && nvm use 14.21.3 >/dev/null && npm run build:prod` + 结果:PASS(构建成功,仅保留原有 bundle size warning) diff --git a/docs/reports/implementation/2026-04-20-intermediary-import-refactor-implementation.md b/docs/reports/implementation/2026-04-20-intermediary-import-refactor-implementation.md new file mode 100644 index 00000000..ed087c51 --- /dev/null +++ b/docs/reports/implementation/2026-04-20-intermediary-import-refactor-implementation.md @@ -0,0 +1,44 @@ +# 中介库导入改造实施记录 + +## 基本信息 + +- 日期:2026-04-20 +- 范围:中介库后端导入改造 + 前端导入入口与状态改造 +- 关联设计:`docs/design/2026-04-20-intermediary-import-refactor-design.md` +- 关联计划: + `docs/plans/backend/2026-04-20-intermediary-import-backend-implementation.md` + `docs/plans/frontend/2026-04-20-intermediary-import-frontend-implementation.md` + +## 实施内容 + +- 后端完成 `related_num_id` 语义切换,统一为“关联中介本人证件号码”,并补齐本人证件号变更同步、亲属唯一性收敛、统一列表联表条件切换。 +- 后端完成“导入中介信息”链路重构,支持本人与亲属混合导入、同文件内引用先成功导入的本人、同亲属证件号挂到不同本人。 +- 后端新增“导入中介实体关联关系”链路,按“本人证件号码 -> 本人 bizId -> 关系表”写入,并支持文件内去重、库内去重、失败记录回看。 +- 前端完成中介导入入口改造,页面顶部改为“导入中介信息”“导入中介实体关联关系”两个按钮,导入弹窗改为 `scene` 驱动。 +- 前端完成两类导入任务状态、本地缓存键、失败记录弹窗、历史任务恢复和完成态刷新逻辑,保留现有详情维护、亲属维护、关联机构维护的 `bizId` 契约。 + +## 验证结果 + +- 后端测试: + `mvn -pl ccdi-info-collection -am -Dsurefire.failIfNoSpecifiedTests=false -Dtest=CcdiIntermediaryServiceImplTest,CcdiIntermediaryMapperTest,CcdiIntermediaryControllerTest,CcdiIntermediaryPersonImportServiceImplTest,CcdiIntermediaryEnterpriseRelationImportServiceImplTest test` + 结果:PASS +- 后端编译: + `mvn -pl ccdi-info-collection -am clean compile` + 结果:PASS +- 前端静态测试: + `source ~/.nvm/nvm.sh && cd ruoyi-ui && nvm use 14.21.3 >/dev/null && node tests/unit/intermediary-import-api.test.js && node tests/unit/intermediary-import-dialog.test.js && node tests/unit/intermediary-import-toolbar.test.js && node tests/unit/intermediary-import-state.test.js && node tests/unit/intermediary-person-edit-ui.test.js` + 结果:PASS +- 前端构建: + `source ~/.nvm/nvm.sh && cd ruoyi-ui && nvm use 14.21.3 >/dev/null && npm run build:prod` + 结果:PASS(仅有原有 bundle size warning) + +## SQL 执行结果 + +- 已执行: + `bin/mysql_utf8_exec.sh sql/migration/2026-04-20-fix-ccdi-person-sub-type-dict.sql` + `bin/mysql_utf8_exec.sh sql/migration/2026-04-20-migrate-intermediary-related-num-id-to-person-id.sql` +- 迁移后核查: + `post_migration_missing_parent = 1025` + `legacy_biz_id_reference = 0` + `owner_person_id_empty_after_migration = 754` +- 结论:可迁移数据已经完成语义切换,旧 `biz_id` 语义残留已清零;历史中仍有缺失本人映射或 `related_num_id` 为空的脏数据,需要后续专项清洗。 diff --git a/ruoyi-ui/src/api/ccdiIntermediary.js b/ruoyi-ui/src/api/ccdiIntermediary.js index f6252390..74dce459 100644 --- a/ruoyi-ui/src/api/ccdiIntermediary.js +++ b/ruoyi-ui/src/api/ccdiIntermediary.js @@ -178,14 +178,6 @@ export function importPersonTemplate() { }) } -// 下载机构中介导入模板 -export function importEntityTemplate() { - return request({ - url: '/ccdi/intermediary/importEntityTemplate', - method: 'post' - }) -} - // 导入个人中介黑名单 export function importPersonData(data, updateSupport) { return request({ @@ -195,12 +187,11 @@ export function importPersonData(data, updateSupport) { }) } -// 导入机构中介黑名单 -export function importEntityData(data, updateSupport) { +// 下载中介实体关联关系导入模板 +export function importEnterpriseRelationTemplate() { return request({ - url: '/ccdi/intermediary/importEntityData?updateSupport=' + updateSupport, - method: 'post', - data: data + url: '/ccdi/intermediary/importEnterpriseRelationTemplate', + method: 'post' }) } @@ -221,18 +212,27 @@ export function getPersonImportFailures(taskId, pageNum, pageSize) { }) } -// 查询实体中介导入状态 -export function getEntityImportStatus(taskId) { +// 导入中介实体关联关系 +export function importEnterpriseRelationData(data, updateSupport) { return request({ - url: `/ccdi/intermediary/importEntityStatus/${taskId}`, + url: '/ccdi/intermediary/importEnterpriseRelationData?updateSupport=' + updateSupport, + method: 'post', + data: data + }) +} + +// 查询中介实体关联关系导入状态 +export function getEnterpriseRelationImportStatus(taskId) { + return request({ + url: `/ccdi/intermediary/importEnterpriseRelationStatus/${taskId}`, method: 'get' }) } -// 查询实体中介导入失败记录 -export function getEntityImportFailures(taskId, pageNum, pageSize) { +// 查询中介实体关联关系导入失败记录 +export function getEnterpriseRelationImportFailures(taskId, pageNum, pageSize) { return request({ - url: `/ccdi/intermediary/importEntityFailures/${taskId}`, + url: `/ccdi/intermediary/importEnterpriseRelationFailures/${taskId}`, method: 'get', params: { pageNum, pageSize } }) diff --git a/ruoyi-ui/src/views/ccdiIntermediary/components/DetailDialog.vue b/ruoyi-ui/src/views/ccdiIntermediary/components/DetailDialog.vue index 3dca432c..967e04ea 100644 --- a/ruoyi-ui/src/views/ccdiIntermediary/components/DetailDialog.vue +++ b/ruoyi-ui/src/views/ccdiIntermediary/components/DetailDialog.vue @@ -8,6 +8,7 @@ {{ detailData.name || '-' }} {{ detailData.personId || '-' }} {{ detailData.personType || '-' }} + {{ detailData.personSubType || detailData.relationType || '-' }} {{ formatGender(detailData.gender) }} {{ detailData.idType || '-' }} {{ detailData.position || '-' }} diff --git a/ruoyi-ui/src/views/ccdiIntermediary/components/EditDialog.vue b/ruoyi-ui/src/views/ccdiIntermediary/components/EditDialog.vue index 12c27f4d..a5f34ab6 100644 --- a/ruoyi-ui/src/views/ccdiIntermediary/components/EditDialog.vue +++ b/ruoyi-ui/src/views/ccdiIntermediary/components/EditDialog.vue @@ -26,6 +26,27 @@ + + + + + + + + + + @@ -131,6 +152,10 @@ export default { certTypeOptions: { type: Array, default: () => [] + }, + personSubTypeOptions: { + type: Array, + default: () => [] } }, data() { @@ -162,6 +187,12 @@ export default { } }, methods: { + handlePersonSubTypeChange(value) { + const typeMappings = [ + { label: '个人', value: '本人' } + ]; + return typeMappings.find(item => item.value === value) || null; + }, handleSubmit() { this.$refs.formRef.validate(valid => { if (valid) { diff --git a/ruoyi-ui/src/views/ccdiIntermediary/components/ImportDialog.vue b/ruoyi-ui/src/views/ccdiIntermediary/components/ImportDialog.vue index 3abecc3e..40ebcf46 100644 --- a/ruoyi-ui/src/views/ccdiIntermediary/components/ImportDialog.vue +++ b/ruoyi-ui/src/views/ccdiIntermediary/components/ImportDialog.vue @@ -1,6 +1,5 @@