Files
ccdi/assets/信贷客户实体关联维护功能/后端实施方案.md
2026-03-03 16:14:16 +08:00

1516 lines
56 KiB
Markdown
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.
# 信贷客户实体关联维护功能 - 后端实施方案
## 一、功能概述
基于员工实体关系维护功能开发信贷客户实体关联维护功能,后端实现逻辑与员工实体关系完全一致,主要差异在于:
1. **不验证身份证号**:导入时不需要验证身份证号是否存在
2. **无远程搜索接口**:没有员工搜索功能
3. **身份标识默认值不同**`is_cust_family = 1`
---
## 二、数据库设计
### 2.1 表结构
表名:`ccdi_cust_enterprise_relation`
```sql
CREATE TABLE `ccdi_cust_enterprise_relation` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键,唯一标识',
`person_id` VARCHAR(18) NOT NULL COMMENT '身份证号',
`relation_person_post` VARCHAR(100) DEFAULT NULL COMMENT '关联人在企业的职务:股东、法人、高管、实际控制人等',
`social_credit_code` VARCHAR(18) NOT NULL COMMENT '统一社会信用代码,关联企业主体信息表的外键',
`enterprise_name` VARCHAR(200) DEFAULT NULL COMMENT '企业名称(冗余存储,便于快速查询)',
`status` INT NOT NULL DEFAULT 1 COMMENT '关系是否有效0 - 无效、1 - 有效(默认有效)',
`remark` TEXT COMMENT '补充说明',
`data_source` VARCHAR(50) DEFAULT NULL COMMENT '数据来源',
`is_employee` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否是员工0-否 1-是',
`is_emp_family` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否是员工家庭关联人0-否 1-是',
`is_customer` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否是信贷客户0-否 1-是',
`is_cust_family` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否是信贷客户关联人0-否 1-是',
`created_by` VARCHAR(64) NOT NULL COMMENT '记录创建人',
`updated_by` VARCHAR(64) DEFAULT NULL COMMENT '记录更新人',
`create_time` DATETIME NOT NULL COMMENT '记录创建时间',
`update_time` DATETIME NOT NULL COMMENT '记录更新时间',
PRIMARY KEY (`id`),
KEY `idx_person_id` (`person_id`),
KEY `idx_social_credit_code` (`social_credit_code`),
UNIQUE KEY `uk_person_enterprise` (`person_id`, `social_credit_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='信贷客户实体关联关系表';
```
### 2.2 唯一性约束
- 业务主键:`person_id` + `social_credit_code` 组合唯一
---
## 三、后端文件清单
### 3.1 Domain层
| 文件名 | 路径 | 说明 |
|--------------------------------------------|---------------|-------------|
| CcdiCustEnterpriseRelation.java | domain/ | 实体类 |
| CcdiCustEnterpriseRelationVO.java | domain/vo/ | 视图对象 |
| CcdiCustEnterpriseRelationAddDTO.java | domain/dto/ | 新增DTO |
| CcdiCustEnterpriseRelationEditDTO.java | domain/dto/ | 编辑DTO |
| CcdiCustEnterpriseRelationQueryDTO.java | domain/dto/ | 查询DTO |
| CcdiCustEnterpriseRelationExcel.java | domain/excel/ | Excel导入导出对象 |
| CustEnterpriseRelationImportFailureVO.java | domain/vo/ | 导入失败记录VO |
### 3.2 Mapper层
| 文件名 | 路径 | 说明 |
|---------------------------------------|------------------------|------------|
| CcdiCustEnterpriseRelationMapper.java | mapper/ | Mapper接口 |
| CcdiCustEnterpriseRelationMapper.xml | resources/mapper/ccdi/ | Mapper XML |
### 3.3 Service层
| 文件名 | 路径 | 说明 |
|--------------------------------------------------|---------------|----------|
| ICcdiCustEnterpriseRelationService.java | service/ | 服务接口 |
| CcdiCustEnterpriseRelationServiceImpl.java | service/impl/ | 服务实现 |
| ICcdiCustEnterpriseRelationImportService.java | service/ | 异步导入服务接口 |
| CcdiCustEnterpriseRelationImportServiceImpl.java | service/impl/ | 异步导入服务实现 |
### 3.4 Controller层
| 文件名 | 路径 | 说明 |
|-------------------------------------------|-------------|-----|
| CcdiCustEnterpriseRelationController.java | controller/ | 控制器 |
---
## 四、核心实现细节
### 4.1 实体类 CcdiCustEnterpriseRelation.java
```java
package com.ruoyi.ccdi.domain;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 信贷客户实体关联信息对象 ccdi_cust_enterprise_relation
*/
@Data
@TableName("ccdi_cust_enterprise_relation")
@Schema(description = "信贷客户实体关联信息")
public class CcdiCustEnterpriseRelation implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 主键ID */
@TableId(type = IdType.AUTO)
@Schema(description = "主键ID")
private Long id;
/** 身份证号 */
@Schema(description = "身份证号")
private String personId;
/** 关联人在企业的职务 */
@Schema(description = "关联人在企业的职务")
private String relationPersonPost;
/** 统一社会信用代码 */
@Schema(description = "统一社会信用代码")
private String socialCreditCode;
/** 企业名称 */
@Schema(description = "企业名称")
private String enterpriseName;
/** 状态0-无效 1-有效) */
@Schema(description = "状态0-无效 1-有效)")
private Integer status;
/** 补充说明 */
@Schema(description = "补充说明")
private String remark;
/** 数据来源 */
@Schema(description = "数据来源")
private String dataSource;
/** 是否为员工0-否 1-是) */
@Schema(description = "是否为员工0-否 1-是)")
private Integer isEmployee;
/** 是否为员工家属0-否 1-是) */
@Schema(description = "是否为员工家属0-否 1-是)")
private Integer isEmpFamily;
/** 是否为客户0-否 1-是) */
@Schema(description = "是否为客户0-否 1-是)")
private Integer isCustomer;
/** 是否为客户家属0-否 1-是) */
@Schema(description = "是否为客户家属0-否 1-是)")
private Integer isCustFamily;
/** 创建时间 */
@TableField(fill = FieldFill.INSERT)
@Schema(description = "创建时间")
private Date createTime;
/** 更新时间 */
@TableField(fill = FieldFill.INSERT_UPDATE)
@Schema(description = "更新时间")
private Date updateTime;
/** 创建人 */
@TableField(fill = FieldFill.INSERT)
@Schema(description = "创建人")
private String createdBy;
/** 更新人 */
@TableField(fill = FieldFill.INSERT_UPDATE)
@Schema(description = "更新人")
private String updatedBy;
}
```
### 4.2 VO类 CcdiCustEnterpriseRelationVO.java
**与员工实体关系VO的差异**
-`personName` 字段(因为没有关联员工表查询姓名)
- 类名和注释不同
```java
package com.ruoyi.ccdi.domain.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 信贷客户实体关联信息VO
*/
@Data
@Schema(description = "信贷客户实体关联信息")
public class CcdiCustEnterpriseRelationVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 主键ID */
@Schema(description = "主键ID")
private Long id;
/** 身份证号 */
@Schema(description = "身份证号")
private String personId;
/** 关联人在企业的职务 */
@Schema(description = "关联人在企业的职务")
private String relationPersonPost;
/** 统一社会信用代码 */
@Schema(description = "统一社会信用代码")
private String socialCreditCode;
/** 企业名称 */
@Schema(description = "企业名称")
private String enterpriseName;
/** 状态0-无效 1-有效) */
@Schema(description = "状态0-无效 1-有效)")
private Integer status;
/** 补充说明 */
@Schema(description = "补充说明")
private String remark;
/** 数据来源 */
@Schema(description = "数据来源")
private String dataSource;
/** 是否为员工0-否 1-是) */
@Schema(description = "是否为员工0-否 1-是)")
private Integer isEmployee;
/** 是否为员工家属0-否 1-是) */
@Schema(description = "是否为员工家属0-否 1-是)")
private Integer isEmpFamily;
/** 是否为客户0-否 1-是) */
@Schema(description = "是否为客户0-否 1-是)")
private Integer isCustomer;
/** 是否为客户家属0-否 1-是) */
@Schema(description = "是否为客户家属0-否 1-是)")
private Integer isCustFamily;
/** 创建时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "创建时间")
private Date createTime;
/** 更新时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "更新时间")
private Date updateTime;
/** 创建人 */
@Schema(description = "创建人")
private String createdBy;
/** 更新人 */
@Schema(description = "更新人")
private String updatedBy;
}
```
### 4.3 新增DTO CcdiCustEnterpriseRelationAddDTO.java
```java
package com.ruoyi.ccdi.domain.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 信贷客户实体关联信息新增DTO
*/
@Data
@Schema(description = "信贷客户实体关联信息新增")
public class CcdiCustEnterpriseRelationAddDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 身份证号 */
@NotBlank(message = "身份证号不能为空")
@Pattern(regexp = "^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[0-9Xx]$", message = "身份证号格式不正确")
@Schema(description = "身份证号")
private String personId;
/** 关联人在企业的职务 */
@Size(max = 100, message = "关联人在企业的职务长度不能超过100个字符")
@Schema(description = "关联人在企业的职务")
private String relationPersonPost;
/** 统一社会信用代码 */
@NotBlank(message = "统一社会信用代码不能为空")
@Pattern(regexp = "^[0-9A-HJ-NPQRTUWXY]{2}\\d{6}[0-9A-HJ-NPQRTUWXY]{10}$", message = "统一社会信用代码格式不正确")
@Schema(description = "统一社会信用代码")
private String socialCreditCode;
/** 企业名称 */
@NotBlank(message = "企业名称不能为空")
@Size(max = 200, message = "企业名称长度不能超过200个字符")
@Schema(description = "企业名称")
private String enterpriseName;
/** 状态0-无效 1-有效) */
@Schema(description = "状态0-无效 1-有效)")
private Integer status;
/** 补充说明 */
@Schema(description = "补充说明")
private String remark;
}
```
### 4.4 编辑DTO CcdiCustEnterpriseRelationEditDTO.java
```java
package com.ruoyi.ccdi.domain.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 信贷客户实体关联信息编辑DTO
*/
@Data
@Schema(description = "信贷客户实体关联信息编辑")
public class CcdiCustEnterpriseRelationEditDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 主键ID */
@NotNull(message = "主键ID不能为空")
@Schema(description = "主键ID")
private Long id;
/** 身份证号(不可修改) */
@Schema(description = "身份证号(不可修改)")
private String personId;
/** 关联人在企业的职务 */
@Size(max = 100, message = "关联人在企业的职务长度不能超过100个字符")
@Schema(description = "关联人在企业的职务")
private String relationPersonPost;
/** 统一社会信用代码(不可修改) */
@Schema(description = "统一社会信用代码(不可修改)")
private String socialCreditCode;
/** 企业名称 */
@NotBlank(message = "企业名称不能为空")
@Size(max = 200, message = "企业名称长度不能超过200个字符")
@Schema(description = "企业名称")
private String enterpriseName;
/** 状态0-无效 1-有效) */
@Schema(description = "状态0-无效 1-有效)")
private Integer status;
/** 补充说明 */
@Schema(description = "补充说明")
private String remark;
}
```
### 4.5 查询DTO CcdiCustEnterpriseRelationQueryDTO.java
```java
package com.ruoyi.ccdi.domain.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 信贷客户实体关联信息查询DTO
*/
@Data
@Schema(description = "信贷客户实体关联信息查询条件")
public class CcdiCustEnterpriseRelationQueryDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 身份证号 */
@Schema(description = "身份证号")
private String personId;
/** 统一社会信用代码 */
@Schema(description = "统一社会信用代码")
private String socialCreditCode;
/** 企业名称 */
@Schema(description = "企业名称")
private String enterpriseName;
/** 状态0-无效 1-有效) */
@Schema(description = "状态0-无效 1-有效)")
private Integer status;
}
```
### 4.6 Excel类 CcdiCustEnterpriseRelationExcel.java
```java
package com.ruoyi.ccdi.domain.excel;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.ruoyi.common.annotation.Required;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 信贷客户实体关联信息Excel导入导出对象
*/
@Data
@Schema(description = "信贷客户实体关联信息Excel导入导出对象")
public class CcdiCustEnterpriseRelationExcel implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 身份证号 */
@ExcelProperty(value = "身份证号", index = 0)
@ColumnWidth(20)
@Required
@Schema(description = "身份证号")
private String personId;
/** 统一社会信用代码 */
@ExcelProperty(value = "统一社会信用代码", index = 1)
@ColumnWidth(25)
@Required
@Schema(description = "统一社会信用代码")
private String socialCreditCode;
/** 企业名称 */
@ExcelProperty(value = "企业名称", index = 2)
@ColumnWidth(30)
@Required
@Schema(description = "企业名称")
private String enterpriseName;
/** 关联人在企业的职务 */
@ExcelProperty(value = "关联人在企业的职务", index = 3)
@ColumnWidth(25)
@Schema(description = "关联人在企业的职务")
private String relationPersonPost;
/** 补充说明 */
@ExcelProperty(value = "补充说明", index = 4)
@ColumnWidth(40)
@Schema(description = "补充说明")
private String remark;
}
```
### 4.7 导入失败VO CustEnterpriseRelationImportFailureVO.java
```java
package com.ruoyi.ccdi.domain.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 信贷客户实体关联信息导入失败记录VO
*/
@Data
@Schema(description = "信贷客户实体关联信息导入失败记录")
public class CustEnterpriseRelationImportFailureVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 身份证号 */
@Schema(description = "身份证号")
private String personId;
/** 统一社会信用代码 */
@Schema(description = "统一社会信用代码")
private String socialCreditCode;
/** 企业名称 */
@Schema(description = "企业名称")
private String enterpriseName;
/** 错误信息 */
@Schema(description = "错误信息")
private String errorMessage;
}
```
---
## 五、Mapper层实现
### 5.1 Mapper接口 CcdiCustEnterpriseRelationMapper.java
```java
package com.ruoyi.ccdi.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.ccdi.domain.CcdiCustEnterpriseRelation;
import com.ruoyi.ccdi.domain.dto.CcdiCustEnterpriseRelationQueryDTO;
import com.ruoyi.ccdi.domain.vo.CcdiCustEnterpriseRelationVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Set;
/**
* 信贷客户实体关联信息 数据层
*/
@Mapper
public interface CcdiCustEnterpriseRelationMapper extends BaseMapper<CcdiCustEnterpriseRelation> {
/**
* 分页查询信贷客户实体关联列表
*/
Page<CcdiCustEnterpriseRelationVO> selectRelationPage(@Param("page") Page<CcdiCustEnterpriseRelationVO> page,
@Param("query") CcdiCustEnterpriseRelationQueryDTO queryDTO);
/**
* 查询信贷客户实体关联详情
*/
CcdiCustEnterpriseRelationVO selectRelationById(@Param("id") Long id);
/**
* 判断身份证号和统一社会信用代码的组合是否已存在
*/
boolean existsByPersonIdAndSocialCreditCode(@Param("personId") String personId,
@Param("socialCreditCode") String socialCreditCode);
/**
* 批量查询已存在的person_id + social_credit_code组合
*/
Set<String> batchExistsByCombinations(@Param("combinations") List<String> combinations);
/**
* 批量插入信贷客户实体关联数据
*/
int insertBatch(@Param("list") List<CcdiCustEnterpriseRelation> list);
}
```
### 5.2 Mapper XML CcdiCustEnterpriseRelationMapper.xml
**与员工实体关系Mapper XML的关键差异**
-`personName` 字段查询
- 无 JOIN 员工表
- 表名不同
```xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.ccdi.mapper.CcdiCustEnterpriseRelationMapper">
<!-- 信贷客户实体关联信息ResultMap -->
<resultMap type="com.ruoyi.ccdi.domain.vo.CcdiCustEnterpriseRelationVO" id="CcdiCustEnterpriseRelationVOResult">
<id property="id" column="id"/>
<result property="personId" column="person_id"/>
<result property="relationPersonPost" column="relation_person_post"/>
<result property="socialCreditCode" column="social_credit_code"/>
<result property="enterpriseName" column="enterprise_name"/>
<result property="status" column="status"/>
<result property="remark" column="remark"/>
<result property="dataSource" column="data_source"/>
<result property="isEmployee" column="is_employee"/>
<result property="isEmpFamily" column="is_emp_family"/>
<result property="isCustomer" column="is_customer"/>
<result property="isCustFamily" column="is_cust_family"/>
<result property="createTime" column="create_time"/>
<result property="updateTime" column="update_time"/>
<result property="createdBy" column="created_by"/>
<result property="updatedBy" column="updated_by"/>
</resultMap>
<!-- 分页查询信贷客户实体关联列表 -->
<select id="selectRelationPage" resultMap="CcdiCustEnterpriseRelationVOResult">
SELECT
id, person_id, relation_person_post,
social_credit_code, enterprise_name, status, remark,
data_source, is_employee, is_emp_family, is_customer,
is_cust_family, created_by, create_time, updated_by,
update_time
FROM ccdi_cust_enterprise_relation
<where>
<if test="query.personId != null and query.personId != ''">
AND person_id LIKE CONCAT('%', #{query.personId}, '%')
</if>
<if test="query.socialCreditCode != null and query.socialCreditCode != ''">
AND social_credit_code LIKE CONCAT('%', #{query.socialCreditCode}, '%')
</if>
<if test="query.enterpriseName != null and query.enterpriseName != ''">
AND enterprise_name LIKE CONCAT('%', #{query.enterpriseName}, '%')
</if>
<if test="query.status != null">
AND status = #{query.status}
</if>
</where>
ORDER BY create_time DESC
</select>
<!-- 查询信贷客户实体关联详情 -->
<select id="selectRelationById" resultMap="CcdiCustEnterpriseRelationVOResult">
SELECT
id, person_id, relation_person_post,
social_credit_code, enterprise_name, status, remark,
data_source, is_employee, is_emp_family, is_customer,
is_cust_family, created_by, create_time, updated_by,
update_time
FROM ccdi_cust_enterprise_relation
WHERE id = #{id}
</select>
<!-- 判断身份证号和统一社会信用代码的组合是否已存在 -->
<select id="existsByPersonIdAndSocialCreditCode" resultType="boolean">
SELECT COUNT(1) > 0
FROM ccdi_cust_enterprise_relation
WHERE person_id = #{personId}
AND social_credit_code = #{socialCreditCode}
</select>
<!-- 批量查询已存在的person_id + social_credit_code组合 -->
<select id="batchExistsByCombinations" resultType="string">
SELECT CONCAT(person_id, '|', social_credit_code) AS combination
FROM ccdi_cust_enterprise_relation
WHERE CONCAT(person_id, '|', social_credit_code) IN
<foreach collection="combinations" item="combination" open="(" separator="," close=")">
#{combination}
</foreach>
</select>
<!-- 批量插入信贷客户实体关联数据 -->
<insert id="insertBatch">
INSERT INTO ccdi_cust_enterprise_relation
(person_id, relation_person_post, social_credit_code, enterprise_name,
status, remark, data_source, is_employee, is_emp_family, is_customer, is_cust_family,
created_by, create_time, updated_by, update_time)
VALUES
<foreach collection="list" item="item" separator=",">
(#{item.personId}, #{item.relationPersonPost}, #{item.socialCreditCode}, #{item.enterpriseName},
#{item.status}, #{item.remark}, #{item.dataSource}, #{item.isEmployee}, #{item.isEmpFamily}, #{item.isCustomer}, #{item.isCustFamily},
#{item.createdBy}, NOW(), #{item.updatedBy}, NOW())
</foreach>
</insert>
</mapper>
```
---
## 六、Service层实现
### 6.1 服务接口 ICcdiCustEnterpriseRelationService.java
```java
package com.ruoyi.ccdi.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.ccdi.domain.dto.CcdiCustEnterpriseRelationAddDTO;
import com.ruoyi.ccdi.domain.dto.CcdiCustEnterpriseRelationEditDTO;
import com.ruoyi.ccdi.domain.dto.CcdiCustEnterpriseRelationQueryDTO;
import com.ruoyi.ccdi.domain.excel.CcdiCustEnterpriseRelationExcel;
import com.ruoyi.ccdi.domain.vo.CcdiCustEnterpriseRelationVO;
import java.util.List;
/**
* 信贷客户实体关联信息 服务层
*/
public interface ICcdiCustEnterpriseRelationService {
/**
* 查询信贷客户实体关联列表
*/
List<CcdiCustEnterpriseRelationVO> selectRelationList(CcdiCustEnterpriseRelationQueryDTO queryDTO);
/**
* 分页查询信贷客户实体关联列表
*/
Page<CcdiCustEnterpriseRelationVO> selectRelationPage(Page<CcdiCustEnterpriseRelationVO> page, CcdiCustEnterpriseRelationQueryDTO queryDTO);
/**
* 查询信贷客户实体关联列表(用于导出)
*/
List<CcdiCustEnterpriseRelationExcel> selectRelationListForExport(CcdiCustEnterpriseRelationQueryDTO queryDTO);
/**
* 查询信贷客户实体关联详情
*/
CcdiCustEnterpriseRelationVO selectRelationById(Long id);
/**
* 新增信贷客户实体关联
*/
int insertRelation(CcdiCustEnterpriseRelationAddDTO addDTO);
/**
* 修改信贷客户实体关联
*/
int updateRelation(CcdiCustEnterpriseRelationEditDTO editDTO);
/**
* 批量删除信贷客户实体关联
*/
int deleteRelationByIds(Long[] ids);
/**
* 导入信贷客户实体关联数据(异步)
*/
String importRelation(List<CcdiCustEnterpriseRelationExcel> excelList);
}
```
### 6.2 服务接口 ICcdiCustEnterpriseRelationImportService.java
```java
package com.ruoyi.ccdi.service;
import com.ruoyi.ccdi.domain.excel.CcdiCustEnterpriseRelationExcel;
import com.ruoyi.ccdi.domain.vo.CustEnterpriseRelationImportFailureVO;
import com.ruoyi.ccdi.domain.vo.ImportStatusVO;
import java.util.List;
/**
* 信贷客户实体关联信息异步导入服务层
*/
public interface ICcdiCustEnterpriseRelationImportService {
/**
* 异步导入信贷客户实体关联数据
*/
void importRelationAsync(List<CcdiCustEnterpriseRelationExcel> excelList, String taskId, String userName);
/**
* 获取导入失败记录
*/
List<CustEnterpriseRelationImportFailureVO> getImportFailures(String taskId);
/**
* 查询导入状态
*/
ImportStatusVO getImportStatus(String taskId);
}
```
### 6.3 服务实现 CcdiCustEnterpriseRelationServiceImpl.java
**关键差异点**
- 身份标识默认值:`is_cust_family = 1`其他为0
- 无员工身份证号验证
```java
package com.ruoyi.ccdi.service.impl;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.ccdi.domain.CcdiCustEnterpriseRelation;
import com.ruoyi.ccdi.domain.dto.CcdiCustEnterpriseRelationAddDTO;
import com.ruoyi.ccdi.domain.dto.CcdiCustEnterpriseRelationEditDTO;
import com.ruoyi.ccdi.domain.dto.CcdiCustEnterpriseRelationQueryDTO;
import com.ruoyi.ccdi.domain.excel.CcdiCustEnterpriseRelationExcel;
import com.ruoyi.ccdi.domain.vo.CcdiCustEnterpriseRelationVO;
import com.ruoyi.ccdi.mapper.CcdiCustEnterpriseRelationMapper;
import com.ruoyi.ccdi.service.ICcdiCustEnterpriseRelationImportService;
import com.ruoyi.ccdi.service.ICcdiCustEnterpriseRelationService;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import jakarta.annotation.Resource;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* 信贷客户实体关联信息 服务层处理
*/
@Service
public class CcdiCustEnterpriseRelationServiceImpl implements ICcdiCustEnterpriseRelationService {
@Resource
private CcdiCustEnterpriseRelationMapper relationMapper;
@Resource
private ICcdiCustEnterpriseRelationImportService relationImportService;
@Resource
private RedisTemplate<String, Object> redisTemplate;
@Override
public java.util.List<CcdiCustEnterpriseRelationVO> selectRelationList(CcdiCustEnterpriseRelationQueryDTO queryDTO) {
Page<CcdiCustEnterpriseRelationVO> page = new Page<>(1, Integer.MAX_VALUE);
Page<CcdiCustEnterpriseRelationVO> resultPage = relationMapper.selectRelationPage(page, queryDTO);
return resultPage.getRecords();
}
@Override
public Page<CcdiCustEnterpriseRelationVO> selectRelationPage(Page<CcdiCustEnterpriseRelationVO> page, CcdiCustEnterpriseRelationQueryDTO queryDTO) {
return relationMapper.selectRelationPage(page, queryDTO);
}
@Override
public java.util.List<CcdiCustEnterpriseRelationExcel> selectRelationListForExport(CcdiCustEnterpriseRelationQueryDTO queryDTO) {
Page<CcdiCustEnterpriseRelationVO> page = new Page<>(1, Integer.MAX_VALUE);
Page<CcdiCustEnterpriseRelationVO> resultPage = relationMapper.selectRelationPage(page, queryDTO);
return resultPage.getRecords().stream().map(vo -> {
CcdiCustEnterpriseRelationExcel excel = new CcdiCustEnterpriseRelationExcel();
BeanUtils.copyProperties(vo, excel);
return excel;
}).collect(Collectors.toList());
}
@Override
public CcdiCustEnterpriseRelationVO selectRelationById(Long id) {
return relationMapper.selectRelationById(id);
}
@Override
@Transactional
public int insertRelation(CcdiCustEnterpriseRelationAddDTO addDTO) {
// 检查身份证号+统一社会信用代码唯一性
if (relationMapper.existsByPersonIdAndSocialCreditCode(addDTO.getPersonId(), addDTO.getSocialCreditCode())) {
throw new RuntimeException("该身份证号和统一社会信用代码组合已存在");
}
CcdiCustEnterpriseRelation relation = new CcdiCustEnterpriseRelation();
BeanUtils.copyProperties(addDTO, relation);
// 设置默认值
// 新增时强制设置状态为有效
relation.setStatus(1);
// 【关键差异】信贷客户实体关联的身份标识默认值
if (relation.getIsEmployee() == null) {
relation.setIsEmployee(0);
}
if (relation.getIsEmpFamily() == null) {
relation.setIsEmpFamily(0);
}
if (relation.getIsCustomer() == null) {
relation.setIsCustomer(0);
}
if (relation.getIsCustFamily() == null) {
relation.setIsCustFamily(1); // 信贷客户关联人标识为1
}
if (StringUtils.isEmpty(relation.getDataSource())) {
relation.setDataSource("MANUAL");
}
int result = relationMapper.insert(relation);
return result;
}
@Override
@Transactional
public int updateRelation(CcdiCustEnterpriseRelationEditDTO editDTO) {
// 使用LambdaUpdateWrapper只更新非null字段保护系统字段不被覆盖
LambdaUpdateWrapper<CcdiCustEnterpriseRelation> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(CcdiCustEnterpriseRelation::getId, editDTO.getId());
// 只更新前端可编辑的字段
updateWrapper.set(editDTO.getRelationPersonPost() != null, CcdiCustEnterpriseRelation::getRelationPersonPost, editDTO.getRelationPersonPost());
updateWrapper.set(editDTO.getEnterpriseName() != null, CcdiCustEnterpriseRelation::getEnterpriseName, editDTO.getEnterpriseName());
updateWrapper.set(editDTO.getStatus() != null, CcdiCustEnterpriseRelation::getStatus, editDTO.getStatus());
updateWrapper.set(editDTO.getRemark() != null, CcdiCustEnterpriseRelation::getRemark, editDTO.getRemark());
// 注意:以下字段不可修改
// - personId身份证号业务主键
// - socialCreditCode统一社会信用代码业务主键
// - dataSource数据来源系统字段
// - isEmployee是否为员工系统字段
// - isEmpFamily是否为员工家属系统字段
// - isCustomer是否为客户系统字段
// - isCustFamily是否为客户家属系统字段
int result = relationMapper.update(null, updateWrapper);
return result;
}
@Override
@Transactional
public int deleteRelationByIds(Long[] ids) {
return relationMapper.deleteBatchIds(java.util.List.of(ids));
}
@Override
@Transactional
public String importRelation(java.util.List<CcdiCustEnterpriseRelationExcel> excelList) {
if (StringUtils.isNull(excelList) || excelList.isEmpty()) {
throw new RuntimeException("至少需要一条数据");
}
// 生成任务ID
String taskId = UUID.randomUUID().toString();
long startTime = System.currentTimeMillis();
// 获取当前用户名
String userName = SecurityUtils.getUsername();
// 初始化Redis状态
String statusKey = "import:custEnterpriseRelation:" + taskId;
Map<String, Object> statusData = new HashMap<>();
statusData.put("taskId", taskId);
statusData.put("status", "PROCESSING");
statusData.put("totalCount", excelList.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);
// 调用异步导入服务
relationImportService.importRelationAsync(excelList, taskId, userName);
return taskId;
}
}
```
### 6.4 异步导入服务实现 CcdiCustEnterpriseRelationImportServiceImpl.java
**【关键实现】异步导入核心逻辑**
**与员工实体关系导入的关键差异**
- **不验证身份证号是否存在**(移除员工表验证逻辑)
- 身份标识默认值:`is_cust_family = 1`
- Redis key前缀`import:custEnterpriseRelation:`
```java
package com.ruoyi.ccdi.service.impl;
import com.alibaba.fastjson2.JSON;
import com.ruoyi.ccdi.domain.CcdiCustEnterpriseRelation;
import com.ruoyi.ccdi.domain.dto.CcdiCustEnterpriseRelationAddDTO;
import com.ruoyi.ccdi.domain.excel.CcdiCustEnterpriseRelationExcel;
import com.ruoyi.ccdi.domain.vo.CustEnterpriseRelationImportFailureVO;
import com.ruoyi.ccdi.domain.vo.ImportResult;
import com.ruoyi.ccdi.domain.vo.ImportStatusVO;
import com.ruoyi.ccdi.mapper.CcdiCustEnterpriseRelationMapper;
import com.ruoyi.ccdi.service.ICcdiCustEnterpriseRelationImportService;
import com.ruoyi.ccdi.utils.ImportLogUtils;
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.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* 信贷客户实体关联信息异步导入服务层处理
*/
@Service
@EnableAsync
public class CcdiCustEnterpriseRelationImportServiceImpl implements ICcdiCustEnterpriseRelationImportService {
private static final Logger log = LoggerFactory.getLogger(CcdiCustEnterpriseRelationImportServiceImpl.class);
@Resource
private CcdiCustEnterpriseRelationMapper relationMapper;
@Resource
private RedisTemplate<String, Object> redisTemplate;
@Override
@Async
@Transactional
public void importRelationAsync(List<CcdiCustEnterpriseRelationExcel> excelList, String taskId, String userName) {
long startTime = System.currentTimeMillis();
// 记录导入开始
ImportLogUtils.logImportStart(log, taskId, "信贷客户实体关联", excelList.size(), userName);
List<CcdiCustEnterpriseRelation> newRecords = new ArrayList<>();
List<CustEnterpriseRelationImportFailureVO> failures = new ArrayList<>();
// 【关键差异】不需要验证身份证号是否存在
// 员工实体关系导入会验证身份证号是否存在于员工表,信贷客户实体关联不需要此验证
// 批量查询已存在的person_id + social_credit_code组合
ImportLogUtils.logBatchQueryStart(log, taskId, "已存在的客户企业关系组合", excelList.size());
Set<String> existingCombinations = getExistingCombinations(excelList);
ImportLogUtils.logBatchQueryComplete(log, taskId, "客户企业关系组合", existingCombinations.size());
// 用于跟踪Excel文件内已处理的组合
Set<String> processedCombinations = new HashSet<>();
// 分类数据
for (int i = 0; i < excelList.size(); i++) {
CcdiCustEnterpriseRelationExcel excel = excelList.get(i);
try {
// 转换为AddDTO进行验证
CcdiCustEnterpriseRelationAddDTO addDTO = new CcdiCustEnterpriseRelationAddDTO();
BeanUtils.copyProperties(excel, addDTO);
// 验证数据(不验证身份证号是否存在)
validateRelationData(addDTO);
String combination = excel.getPersonId() + "|" + excel.getSocialCreditCode();
CcdiCustEnterpriseRelation relation = new CcdiCustEnterpriseRelation();
BeanUtils.copyProperties(excel, relation);
if (existingCombinations.contains(combination)) {
// 组合已存在,直接报错
throw new RuntimeException(String.format("身份证号[%s]和统一社会信用代码[%s]的组合已存在,请勿重复导入",
excel.getPersonId(), excel.getSocialCreditCode()));
} else if (processedCombinations.contains(combination)) {
// Excel文件内部重复
throw new RuntimeException(String.format("身份证号[%s]和统一社会信用代码[%s]的组合在导入文件中重复,已跳过此条记录",
excel.getPersonId(), excel.getSocialCreditCode()));
} else {
relation.setCreatedBy(userName);
relation.setUpdatedBy(userName);
// 设置默认值
relation.setStatus(1);
// 【关键差异】信贷客户实体关联的身份标识
relation.setIsEmployee(0);
relation.setIsEmpFamily(0);
relation.setIsCustomer(0);
relation.setIsCustFamily(1); // 信贷客户关联人标识为1
relation.setDataSource("IMPORT");
newRecords.add(relation);
processedCombinations.add(combination); // 标记为已处理
}
// 记录进度
ImportLogUtils.logProgress(log, taskId, i + 1, excelList.size(),
newRecords.size(), failures.size());
} catch (Exception e) {
CustEnterpriseRelationImportFailureVO failure = new CustEnterpriseRelationImportFailureVO();
BeanUtils.copyProperties(excel, failure);
failure.setErrorMessage(e.getMessage());
failures.add(failure);
// 记录验证失败日志
String keyData = String.format("身份证号=%s, 统一社会信用代码=%s, 企业名称=%s",
excel.getPersonId(), excel.getSocialCreditCode(), excel.getEnterpriseName());
ImportLogUtils.logValidationError(log, taskId, i + 1, e.getMessage(), keyData);
}
}
// 批量插入新数据
if (!newRecords.isEmpty()) {
ImportLogUtils.logBatchOperationStart(log, taskId, "插入",
(newRecords.size() + 499) / 500, 500);
saveBatch(newRecords, 500);
}
// 保存失败记录到Redis
if (!failures.isEmpty()) {
try {
String failuresKey = "import:custEnterpriseRelation:" + taskId + ":failures";
redisTemplate.opsForValue().set(failuresKey, failures, 7, TimeUnit.DAYS);
ImportLogUtils.logRedisOperation(log, taskId, "保存失败记录", failures.size());
} catch (Exception e) {
ImportLogUtils.logRedisError(log, taskId, "保存失败记录", e);
}
}
ImportResult result = new ImportResult();
result.setTotalCount(excelList.size());
result.setSuccessCount(newRecords.size());
result.setFailureCount(failures.size());
// 更新最终状态
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);
}
@Override
public List<CustEnterpriseRelationImportFailureVO> getImportFailures(String taskId) {
String key = "import:custEnterpriseRelation:" + taskId + ":failures";
Object failuresObj = redisTemplate.opsForValue().get(key);
if (failuresObj == null) {
return Collections.emptyList();
}
return JSON.parseArray(JSON.toJSONString(failuresObj), CustEnterpriseRelationImportFailureVO.class);
}
@Override
public ImportStatusVO getImportStatus(String taskId) {
String key = "import:custEnterpriseRelation:" + taskId;
Boolean hasKey = redisTemplate.hasKey(key);
if (Boolean.FALSE.equals(hasKey)) {
throw new RuntimeException("任务不存在或已过期");
}
Map<Object, Object> 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;
}
/**
* 更新导入状态
*/
private void updateImportStatus(String taskId, String status, ImportResult result) {
String key = "import:custEnterpriseRelation:" + taskId;
Map<String, Object> statusData = new HashMap<>();
statusData.put("status", status);
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);
}
/**
* 批量查询已存在的person_id + social_credit_code组合
*/
private Set<String> getExistingCombinations(List<CcdiCustEnterpriseRelationExcel> excelList) {
List<String> combinations = excelList.stream()
.map(excel -> excel.getPersonId() + "|" + excel.getSocialCreditCode())
.filter(Objects::nonNull)
.distinct()
.collect(Collectors.toList());
if (combinations.isEmpty()) {
return Collections.emptySet();
}
return new HashSet<>(relationMapper.batchExistsByCombinations(combinations));
}
/**
* 批量保存
*/
private void saveBatch(List<CcdiCustEnterpriseRelation> list, int batchSize) {
for (int i = 0; i < list.size(); i += batchSize) {
int end = Math.min(i + batchSize, list.size());
List<CcdiCustEnterpriseRelation> subList = list.subList(i, end);
relationMapper.insertBatch(subList);
}
}
/**
* 验证信贷客户实体关联数据
* 【关键差异】不验证身份证号是否存在于员工表
*/
private void validateRelationData(CcdiCustEnterpriseRelationAddDTO addDTO) {
// 验证必填字段
if (StringUtils.isEmpty(addDTO.getPersonId())) {
throw new RuntimeException("身份证号不能为空");
}
if (StringUtils.isEmpty(addDTO.getSocialCreditCode())) {
throw new RuntimeException("统一社会信用代码不能为空");
}
if (StringUtils.isEmpty(addDTO.getEnterpriseName())) {
throw new RuntimeException("企业名称不能为空");
}
// 验证身份证号格式18位
if (!addDTO.getPersonId().matches("^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[0-9Xx]$")) {
throw new RuntimeException("身份证号格式不正确必须为18位有效身份证号");
}
// 验证统一社会信用代码格式18位
if (!addDTO.getSocialCreditCode().matches("^[0-9A-HJ-NPQRTUWXY]{2}\\d{6}[0-9A-HJ-NPQRTUWXY]{10}$")) {
throw new RuntimeException("统一社会信用代码格式不正确必须为18位有效统一社会信用代码");
}
// 验证字段长度
if (StringUtils.isNotEmpty(addDTO.getRelationPersonPost()) && addDTO.getRelationPersonPost().length() > 100) {
throw new RuntimeException("关联人在企业的职务长度不能超过100个字符");
}
if (addDTO.getEnterpriseName().length() > 200) {
throw new RuntimeException("企业名称长度不能超过200个字符");
}
// 【注意】不验证身份证号是否存在于员工表
}
}
```
---
## 七、Controller层实现
### 7.1 CcdiCustEnterpriseRelationController.java
```java
package com.ruoyi.ccdi.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.ccdi.domain.dto.CcdiCustEnterpriseRelationAddDTO;
import com.ruoyi.ccdi.domain.dto.CcdiCustEnterpriseRelationEditDTO;
import com.ruoyi.ccdi.domain.dto.CcdiCustEnterpriseRelationQueryDTO;
import com.ruoyi.ccdi.domain.excel.CcdiCustEnterpriseRelationExcel;
import com.ruoyi.ccdi.domain.vo.CcdiCustEnterpriseRelationVO;
import com.ruoyi.ccdi.domain.vo.CustEnterpriseRelationImportFailureVO;
import com.ruoyi.ccdi.domain.vo.ImportResultVO;
import com.ruoyi.ccdi.domain.vo.ImportStatusVO;
import com.ruoyi.ccdi.service.ICcdiCustEnterpriseRelationImportService;
import com.ruoyi.ccdi.service.ICcdiCustEnterpriseRelationService;
import com.ruoyi.ccdi.utils.EasyExcelUtil;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.PageDomain;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.core.page.TableSupport;
import com.ruoyi.common.enums.BusinessType;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.ArrayList;
import java.util.List;
/**
* 信贷客户实体关联信息Controller
*/
@Tag(name = "信贷客户实体关联信息管理")
@RestController
@RequestMapping("/ccdi/custEnterpriseRelation")
public class CcdiCustEnterpriseRelationController extends BaseController {
@Resource
private ICcdiCustEnterpriseRelationService relationService;
@Resource
private ICcdiCustEnterpriseRelationImportService relationImportService;
/**
* 查询信贷客户实体关联列表
*/
@Operation(summary = "查询信贷客户实体关联列表")
@PreAuthorize("@ss.hasPermi('ccdi:custEnterpriseRelation:list')")
@GetMapping("/list")
public TableDataInfo list(CcdiCustEnterpriseRelationQueryDTO queryDTO) {
PageDomain pageDomain = TableSupport.buildPageRequest();
Page<CcdiCustEnterpriseRelationVO> page = new Page<>(pageDomain.getPageNum(), pageDomain.getPageSize());
Page<CcdiCustEnterpriseRelationVO> result = relationService.selectRelationPage(page, queryDTO);
return getDataTable(result.getRecords(), result.getTotal());
}
/**
* 导出信贷客户实体关联列表
*/
@Operation(summary = "导出信贷客户实体关联列表")
@PreAuthorize("@ss.hasPermi('ccdi:custEnterpriseRelation:export')")
@Log(title = "信贷客户实体关联信息", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, CcdiCustEnterpriseRelationQueryDTO queryDTO) {
List<CcdiCustEnterpriseRelationExcel> list = relationService.selectRelationListForExport(queryDTO);
EasyExcelUtil.exportExcel(response, list, CcdiCustEnterpriseRelationExcel.class, "信贷客户实体关联信息");
}
/**
* 获取信贷客户实体关联详细信息
*/
@Operation(summary = "获取信贷客户实体关联详细信息")
@Parameter(name = "id", description = "主键ID", required = true)
@PreAuthorize("@ss.hasPermi('ccdi:custEnterpriseRelation:query')")
@GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable Long id) {
return success(relationService.selectRelationById(id));
}
/**
* 新增信贷客户实体关联
*/
@Operation(summary = "新增信贷客户实体关联")
@PreAuthorize("@ss.hasPermi('ccdi:custEnterpriseRelation:add')")
@Log(title = "信贷客户实体关联信息", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@Validated @RequestBody CcdiCustEnterpriseRelationAddDTO addDTO) {
return toAjax(relationService.insertRelation(addDTO));
}
/**
* 修改信贷客户实体关联
*/
@Operation(summary = "修改信贷客户实体关联")
@PreAuthorize("@ss.hasPermi('ccdi:custEnterpriseRelation:edit')")
@Log(title = "信贷客户实体关联信息", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@Validated @RequestBody CcdiCustEnterpriseRelationEditDTO editDTO) {
return toAjax(relationService.updateRelation(editDTO));
}
/**
* 删除信贷客户实体关联
*/
@Operation(summary = "删除信贷客户实体关联")
@Parameter(name = "ids", description = "主键ID数组", required = true)
@PreAuthorize("@ss.hasPermi('ccdi:custEnterpriseRelation:remove')")
@Log(title = "信贷客户实体关联信息", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable Long[] ids) {
return toAjax(relationService.deleteRelationByIds(ids));
}
/**
* 下载导入模板
*/
@Operation(summary = "下载导入模板")
@PostMapping("/importTemplate")
public void importTemplate(HttpServletResponse response) {
EasyExcelUtil.importTemplateWithDictDropdown(response, CcdiCustEnterpriseRelationExcel.class, "信贷客户实体关联信息");
}
/**
* 异步导入信贷客户实体关联
*/
@Operation(summary = "异步导入信贷客户实体关联")
@Parameter(name = "file", description = "导入文件", required = true)
@PreAuthorize("@ss.hasPermi('ccdi:custEnterpriseRelation:import')")
@Log(title = "信贷客户实体关联信息", businessType = BusinessType.IMPORT)
@PostMapping("/importData")
public AjaxResult importData(@Parameter(description = "导入文件") MultipartFile file) throws Exception {
List<CcdiCustEnterpriseRelationExcel> list = EasyExcelUtil.importExcel(file.getInputStream(), CcdiCustEnterpriseRelationExcel.class);
if (list == null || list.isEmpty()) {
return error("至少需要一条数据");
}
// 提交异步任务
String taskId = relationService.importRelation(list);
// 立即返回
ImportResultVO result = new ImportResultVO();
result.setTaskId(taskId);
result.setStatus("PROCESSING");
result.setMessage("导入任务已提交,正在后台处理");
return AjaxResult.success("导入任务已提交,正在后台处理", result);
}
/**
* 查询导入状态
*/
@Operation(summary = "查询导入状态")
@Parameter(name = "taskId", description = "任务ID", required = true)
@PreAuthorize("@ss.hasPermi('ccdi:custEnterpriseRelation:import')")
@GetMapping("/importStatus/{taskId}")
public AjaxResult getImportStatus(@PathVariable String taskId) {
ImportStatusVO statusVO = relationImportService.getImportStatus(taskId);
return success(statusVO);
}
/**
* 查询导入失败记录
*/
@Operation(summary = "查询导入失败记录")
@Parameter(name = "taskId", description = "任务ID", required = true)
@Parameter(name = "pageNum", description = "页码", required = false)
@Parameter(name = "pageSize", description = "每页条数", required = false)
@PreAuthorize("@ss.hasPermi('ccdi:custEnterpriseRelation:import')")
@GetMapping("/importFailures/{taskId}")
public TableDataInfo getImportFailures(
@PathVariable String taskId,
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize) {
List<CustEnterpriseRelationImportFailureVO> failures = relationImportService.getImportFailures(taskId);
// 手动分页
int fromIndex = (pageNum - 1) * pageSize;
int toIndex = Math.min(fromIndex + pageSize, failures.size());
if (fromIndex >= failures.size()) {
return getDataTable(new ArrayList<>(), failures.size());
}
List<CustEnterpriseRelationImportFailureVO> pageData = failures.subList(fromIndex, toIndex);
return getDataTable(pageData, failures.size());
}
}
```
---
## 八、与员工实体关系代码对比
### 8.1 关键差异总结
| 对比项 | 员工实体关系 | 信贷客户实体关联 |
|------------------|---------------------------------|--------------------------------|
| 表名 | ccdi_staff_enterprise_relation | ccdi_cust_enterprise_relation |
| VO中是否有personName | 有JOIN员工表 | 无不JOIN |
| 身份证号验证 | 验证存在于员工表 | 不验证 |
| 员工搜索功能 | 有 | 无 |
| 身份标识默认值 | is_emp_family=1 | is_cust_family=1 |
| Redis key前缀 | import:staffEnterpriseRelation: | import:custEnterpriseRelation: |
| 权限标识 | ccdi:staffEnterpriseRelation:* | cdi:custEnterpriseRelation:* |
| API路径 | /ccdi/staffEnterpriseRelation/* | /ccdi/custEnterpriseRelation/* |
### 8.2 导入逻辑对比
| 步骤 | 员工实体关系 | 信贷客户实体关联 |
|-------------|-----------------|------------------|
| 1. 验证必填字段 | 相同 | 相同 |
| 2. 验证格式 | 相同 | 相同 |
| 3. 验证身份证号存在 | **验证** | **不验证** |
| 4. 检查组合唯一性 | 相同 | 相同 |
| 5. 设置身份标识 | is_emp_family=1 | is_cust_family=1 |
| 6. 批量插入 | 相同 | 相同 |
---
## 九、实施步骤
1. 执行数据库建表SQL
2. 创建Domain层文件Entity、VO、DTO、Excel
3. 创建Mapper层文件Mapper接口、Mapper XML
4. 创建Service层文件Service接口、Service实现
5. 创建Controller层文件
6. 配置菜单权限需执行菜单SQL
7. 编译测试