# 信贷客户家庭关系维护功能 - 后端实施计划 > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. **目标:** 开发信贷客户家庭关系维护功能的完整后端实现,包括数据库设计、实体类、DTO/VO、Mapper、Service和Controller **架构:** 完全复用员工亲属关系维护功能的实现逻辑,创建独立模块 `CustFamilyRelation`,新建独立表 `ccdi_cust_fmy_relation` **技术栈:** Spring Boot 3.5.8 + MyBatis Plus 3.5.10 + EasyExcel + Redis --- ## 前置条件 ### 环境要求 - JDK 17+ - Maven 3.6+ - MySQL 8.2.0 - Redis (用于导入任务状态管理) ### 依赖服务 - 数据库连接配置在 `application.yml` 中已配置 - MyBatis Plus 3.5.10 已集成 - EasyExcel 已添加到项目依赖 ### 参考模块 - `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/**/CcdiStaffFmyRelation*` - 员工亲属关系实现(参考模板) --- ## 任务列表 ### Task 0: 创建数据库表 **Files:** - Create: `sql/ccdi_cust_fmy_relation.sql` **Step 1: 创建建表SQL文件** 创建 `sql/ccdi_cust_fmy_relation.sql` 文件: ```sql -- 信贷客户家庭关系表 CREATE TABLE `ccdi_cust_fmy_relation` ( `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID', `person_id` VARCHAR(50) NOT NULL COMMENT '信贷客户身份证号', `relation_type` VARCHAR(50) NOT NULL COMMENT '关系类型', `relation_name` VARCHAR(100) NOT NULL COMMENT '关系人姓名', `gender` CHAR(1) DEFAULT NULL COMMENT '性别:M-男,F-女,O-其他', `birth_date` DATE DEFAULT NULL COMMENT '关系人出生日期', `relation_cert_type` VARCHAR(50) NOT NULL COMMENT '证件类型', `relation_cert_no` VARCHAR(50) NOT NULL COMMENT '证件号码', `mobile_phone1` VARCHAR(20) DEFAULT NULL COMMENT '手机号码1', `mobile_phone2` VARCHAR(20) DEFAULT NULL COMMENT '手机号码2', `wechat_no1` VARCHAR(50) DEFAULT NULL COMMENT '微信名称1', `wechat_no2` VARCHAR(50) DEFAULT NULL COMMENT '微信名称2', `wechat_no3` VARCHAR(50) DEFAULT NULL COMMENT '微信名称3', `contact_address` VARCHAR(500) DEFAULT NULL COMMENT '详细联系地址', `relation_desc` VARCHAR(500) DEFAULT NULL COMMENT '关系详细描述', `status` INT NOT NULL DEFAULT 1 COMMENT '状态:0-无效,1-有效', `effective_date` DATETIME DEFAULT NULL COMMENT '关系生效日期', `invalid_date` DATETIME DEFAULT NULL COMMENT '关系失效日期', `remark` TEXT COMMENT '备注信息', `data_source` VARCHAR(50) DEFAULT NULL COMMENT '数据来源:MANUAL-手动录入,IMPORT-批量导入', `is_emp_family` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否是员工的家庭关系:0-否', `is_cust_family` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否是信贷客户的家庭关系:1-是', `created_by` VARCHAR(50) NOT NULL COMMENT '记录创建人', `updated_by` VARCHAR(50) DEFAULT NULL COMMENT '记录更新人', `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间', `update_time` DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '记录更新时间', PRIMARY KEY (`id`), KEY `idx_person_id` (`person_id`), KEY `idx_relation_cert_no` (`relation_cert_no`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='信贷客户家庭关系表'; ``` **Step 2: 执行SQL创建表** 使用MCP连接数据库工具执行SQL文件: ```bash # 连接数据库并执行建表脚本 mysql -u -p < sql/ccdi_cust_fmy_relation.sql ``` **验证方式:** ```sql SHOW CREATE TABLE ccdi_cust_fmy_relation; ``` **预期结果:** 表创建成功,包含所有字段和索引 **Step 3: Commit** ```bash git add sql/ccdi_cust_fmy_relation.sql git commit -m "feat: 创建信贷客户家庭关系表" ``` --- ### Task 1: 创建实体类 **Files:** - Create: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/CcdiCustFmyRelation.java` - Reference: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/CcdiStaffFmyRelation.java` **Step 1: 复制并修改实体类** 复制 `CcdiStaffFmyRelation.java`,进行以下修改: 1. **类名和注释:** ```java /** * 信贷客户家庭关系对象 ccdi_cust_fmy_relation * * @author ruoyi * @date 2026-02-11 */ @Data @TableName("ccdi_cust_fmy_relation") public class CcdiCustFmyRelation implements Serializable { ``` 2. **关键注释修改:** - `/** 信贷客户身份证号 */` (原"员工身份证号") - `/** 是否是客户亲属:1-是 */` - `/** 是否是员工亲属:0-否 */` 3. **完整代码结构:** ```java package com.ruoyi.ccdi.domain; import com.baomidou.mybatisplus.annotation.*; import lombok.Data; import java.io.Serial; import java.io.Serializable; import java.util.Date; @Data @TableName("ccdi_cust_fmy_relation") public class CcdiCustFmyRelation implements Serializable { @Serial private static final long serialVersionUID = 1L; /** 主键ID */ @TableId(type = IdType.AUTO) private Long id; /** 信贷客户身份证号 */ private String personId; /** 关系类型 */ private String relationType; /** 关系人姓名 */ private String relationName; /** 性别:M-男,F-女,O-其他 */ private String gender; /** 出生日期 */ private Date birthDate; /** 关系人证件类型 */ private String relationCertType; /** 关系人证件号码 */ private String relationCertNo; /** 手机号码1 */ private String mobilePhone1; /** 手机号码2 */ private String mobilePhone2; /** 微信名称1 */ private String wechatNo1; /** 微信名称2 */ private String wechatNo2; /** 微信名称3 */ private String wechatNo3; /** 详细联系地址 */ private String contactAddress; /** 关系详细描述 */ private String relationDesc; /** 状态:0-无效,1-有效 */ private Integer status; /** 生效日期 */ private Date effectiveDate; /** 失效日期 */ private Date invalidDate; /** 备注 */ private String remark; /** 数据来源:MANUAL-手工录入,IMPORT-导入 */ private String dataSource; /** 是否是员工亲属:0-否 */ private Boolean isEmpFamily; /** 是否是客户亲属:1-是 */ private Boolean isCustFamily; /** 创建时间 */ @TableField(fill = FieldFill.INSERT) private Date createTime; /** 更新时间 */ @TableField(fill = FieldFill.INSERT_UPDATE) private Date updateTime; /** 创建人 */ @TableField(fill = FieldFill.INSERT) private String createdBy; /** 更新人 */ @TableField(fill = FieldFill.INSERT_UPDATE) private String updatedBy; } ``` **Step 2: 编译验证** ```bash mvn compile -pl ruoyi-ccdi ``` **预期结果:** BUILD SUCCESS **Step 3: Commit** ```bash git add ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/CcdiCustFmyRelation.java git commit -m "feat: 添加信贷客户家庭关系实体类" ``` --- ### Task 2: 创建DTO类 **Files:** - Create: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/dto/CcdiCustFmyRelationAddDTO.java` - Create: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/dto/CcdiCustFmyRelationEditDTO.java` - Create: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/dto/CcdiCustFmyRelationQueryDTO.java` **Step 1: 创建AddDTO** 复制 `CcdiStaffFmyRelationAddDTO.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 lombok.Data; import java.io.Serial; import java.io.Serializable; import java.util.Date; /** * 信贷客户家庭关系新增DTO * * @author ruoyi * @date 2026-02-11 */ @Data @Schema(description = "信贷客户家庭关系新增") public class CcdiCustFmyRelationAddDTO implements Serializable { @Serial private static final long serialVersionUID = 1L; /** 信贷客户身份证号 */ @Schema(description = "信贷客户身份证号") @NotBlank(message = "信贷客户身份证号不能为空") private String personId; /** 关系类型 */ @Schema(description = "关系类型") @NotBlank(message = "关系类型不能为空") private String relationType; /** 关系人姓名 */ @Schema(description = "关系人姓名") @NotBlank(message = "关系人姓名不能为空") private String relationName; /** 性别 */ @Schema(description = "性别:M-男,F-女,O-其他") private String gender; /** 出生日期 */ @Schema(description = "关系人出生日期") private Date birthDate; /** 关系人证件类型 */ @Schema(description = "关系人证件类型") @NotBlank(message = "关系人证件类型不能为空") private String relationCertType; /** 关系人证件号码 */ @Schema(description = "关系人证件号码") @NotBlank(message = "关系人证件号码不能为空") private String relationCertNo; /** 手机号码1 */ @Schema(description = "手机号码1") private String mobilePhone1; /** 手机号码2 */ @Schema(description = "手机号码2") private String mobilePhone2; /** 微信名称1 */ @Schema(description = "微信名称1") private String wechatNo1; /** 微信名称2 */ @Schema(description = "微信名称2") private String wechatNo2; /** 微信名称3 */ @Schema(description = "微信名称3") private String wechatNo3; /** 详细联系地址 */ @Schema(description = "详细联系地址") private String contactAddress; /** 关系详细描述 */ @Schema(description = "关系详细描述") private String relationDesc; /** 生效日期 */ @Schema(description = "关系生效日期") private Date effectiveDate; /** 失效日期 */ @Schema(description = "关系失效日期") private Date invalidDate; /** 备注 */ @Schema(description = "备注信息") private String remark; } ``` **Step 2: 创建EditDTO** 复制 `CcdiStaffFmyRelationEditDTO.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 lombok.Data; import java.io.Serial; import java.io.Serializable; import java.util.Date; /** * 信贷客户家庭关系编辑DTO * * @author ruoyi * @date 2026-02-11 */ @Data @Schema(description = "信贷客户家庭关系编辑") public class CcdiCustFmyRelationEditDTO implements Serializable { @Serial private static final long serialVersionUID = 1L; /** 主键ID */ @Schema(description = "主键ID") @NotNull(message = "ID不能为空") private Long id; /** 信贷客户身份证号 */ @Schema(description = "信贷客户身份证号") @NotBlank(message = "信贷客户身份证号不能为空") private String personId; /** 关系类型 */ @Schema(description = "关系类型") @NotBlank(message = "关系类型不能为空") private String relationType; /** 关系人姓名 */ @Schema(description = "关系人姓名") @NotBlank(message = "关系人姓名不能为空") private String relationName; /** 性别 */ @Schema(description = "性别:M-男,F-女,O-其他") private String gender; /** 出生日期 */ @Schema(description = "关系人出生日期") private Date birthDate; /** 关系人证件类型 */ @Schema(description = "关系人证件类型") @NotBlank(message = "关系人证件类型不能为空") private String relationCertType; /** 关系人证件号码 */ @Schema(description = "关系人证件号码") @NotBlank(message = "关系人证件号码不能为空") private String relationCertNo; /** 手机号码1 */ @Schema(description = "手机号码1") private String mobilePhone1; /** 手机号码2 */ @Schema(description = "手机号码2") private String mobilePhone2; /** 微信名称1 */ @Schema(description = "微信名称1") private String wechatNo1; /** 微信名称2 */ @Schema(description = "微信名称2") private String wechatNo2; /** 微信名称3 */ @Schema(description = "微信名称3") private String wechatNo3; /** 详细联系地址 */ @Schema(description = "详细联系地址") private String contactAddress; /** 关系详细描述 */ @Schema(description = "关系详细描述") private String relationDesc; /** 状态 */ @Schema(description = "状态:0-无效,1-有效") @NotNull(message = "状态不能为空") private Integer status; /** 生效日期 */ @Schema(description = "关系生效日期") private Date effectiveDate; /** 失效日期 */ @Schema(description = "关系失效日期") private Date invalidDate; /** 备注 */ @Schema(description = "备注信息") private String remark; } ``` **Step 3: 创建QueryDTO** ```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 * * @author ruoyi * @date 2026-02-11 */ @Data @Schema(description = "信贷客户家庭关系查询") public class CcdiCustFmyRelationQueryDTO implements Serializable { @Serial private static final long serialVersionUID = 1L; /** 信贷客户身份证号 */ @Schema(description = "信贷客户身份证号") private String personId; /** 关系类型 */ @Schema(description = "关系类型") private String relationType; /** 关系人姓名 */ @Schema(description = "关系人姓名") private String relationName; } ``` **Step 4: 编译验证** ```bash mvn compile -pl ruoyi-ccdi ``` **预期结果:** BUILD SUCCESS **Step 5: Commit** ```bash git add ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/dto/ git commit -m "feat: 添加信贷客户家庭关系DTO类" ``` --- ### Task 3: 创建VO类 **Files:** - Create: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/vo/CcdiCustFmyRelationVO.java` - Create: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/vo/CustFmyRelationImportFailureVO.java` **Step 1: 创建主VO** 复制 `CcdiStaffFmyRelationVO.java`,进行以下修改: 1. **移除 personName 字段** (不关联员工表) 2. **修改类名和注释** ```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; import java.util.Date; /** * 信贷客户家庭关系VO * * @author ruoyi * @date 2026-02-11 */ @Data @Schema(description = "信贷客户家庭关系") public class CcdiCustFmyRelationVO 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 relationType; /** 关系人姓名 */ @Schema(description = "关系人姓名") private String relationName; /** 性别 */ @Schema(description = "性别:M-男,F-女,O-其他") private String gender; /** 出生日期 */ @Schema(description = "关系人出生日期") private Date birthDate; /** 关系人证件类型 */ @Schema(description = "关系人证件类型") private String relationCertType; /** 关系人证件号码 */ @Schema(description = "关系人证件号码") private String relationCertNo; /** 手机号码1 */ @Schema(description = "手机号码1") private String mobilePhone1; /** 手机号码2 */ @Schema(description = "手机号码2") private String mobilePhone2; /** 微信名称1 */ @Schema(description = "微信名称1") private String wechatNo1; /** 微信名称2 */ @Schema(description = "微信名称2") private String wechatNo2; /** 微信名称3 */ @Schema(description = "微信名称3") private String wechatNo3; /** 详细联系地址 */ @Schema(description = "详细联系地址") private String contactAddress; /** 关系详细描述 */ @Schema(description = "关系详细描述") private String relationDesc; /** 状态 */ @Schema(description = "状态:0-无效,1-有效") private Integer status; /** 生效日期 */ @Schema(description = "关系生效日期") private Date effectiveDate; /** 失效日期 */ @Schema(description = "关系失效日期") private Date invalidDate; /** 备注 */ @Schema(description = "备注信息") private String remark; /** 数据来源 */ @Schema(description = "数据来源:MANUAL-手工录入,IMPORT-导入") private String dataSource; /** 是否是员工亲属 */ @Schema(description = "是否是员工亲属:0-否") private Boolean isEmpFamily; /** 是否是客户亲属 */ @Schema(description = "是否是客户亲属:1-是") private Boolean isCustFamily; /** 创建时间 */ @Schema(description = "创建时间") private Date createTime; /** 更新时间 */ @Schema(description = "更新时间") private Date updateTime; /** 创建人 */ @Schema(description = "创建人") private String createdBy; /** 更新人 */ @Schema(description = "更新人") private String updatedBy; } ``` **Step 2: 创建导入失败VO** 复制 `StaffFmyRelationImportFailureVO.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 * * @author ruoyi * @date 2026-02-11 */ @Data @Schema(description = "信贷客户家庭关系导入失败记录") public class CustFmyRelationImportFailureVO implements Serializable { @Serial private static final long serialVersionUID = 1L; /** 行号 */ @Schema(description = "行号") private Integer rowNum; /** 信贷客户身份证号 */ @Schema(description = "信贷客户身份证号") private String personId; /** 关系类型 */ @Schema(description = "关系类型") private String relationType; /** 关系人姓名 */ @Schema(description = "关系人姓名") private String relationName; /** 错误消息 */ @Schema(description = "错误消息") private String errorMessage; } ``` **Step 3: 编译验证** ```bash mvn compile -pl ruoyi-ccdi ``` **预期结果:** BUILD SUCCESS **Step 4: Commit** ```bash git add ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/vo/ git commit -m "feat: 添加信贷客户家庭关系VO类" ``` --- ### Task 4: 创建Excel类 **Files:** - Create: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/excel/CcdiCustFmyRelationExcel.java` **Step 1: 创建Excel类** 复制 `CcdiStaffFmyRelationExcel.java`,修改: ```java package com.ruoyi.ccdi.domain.excel; import com.alibaba.excel.annotation.ExcelProperty; import com.alibaba.excel.annotation.write.style.ColumnWidth; import com.alibaba.excel.annotation.write.style.ContentRowHeight; import com.alibaba.excel.annotation.write.style.HeadRowHeight; import lombok.Data; import java.io.Serial; import java.io.Serializable; import java.util.Date; /** * 信贷客户家庭关系Excel导入导出对象 * * @author ruoyi * @date 2026-02-11 */ @Data @ContentRowHeight(20) @HeadRowHeight(30) public class CcdiCustFmyRelationExcel implements Serializable { @Serial private static final long serialVersionUID = 1L; /** 信贷客户身份证号 */ @ExcelProperty(value = "信贷客户身份证号*", index = 0) @ColumnWidth(20) private String personId; /** 关系类型 */ @ExcelProperty(value = "关系类型*", index = 1) @ColumnWidth(15) private String relationType; /** 关系人姓名 */ @ExcelProperty(value = "关系人姓名*", index = 2) @ColumnWidth(15) private String relationName; /** 性别 */ @ExcelProperty(value = "性别", index = 3) @ColumnWidth(10) private String gender; /** 出生日期 */ @ExcelProperty(value = "出生日期", index = 4) @ColumnWidth(15) private Date birthDate; /** 关系人证件类型 */ @ExcelProperty(value = "关系人证件类型*", index = 5) @ColumnWidth(15) private String relationCertType; /** 关系人证件号码 */ @ExcelProperty(value = "关系人证件号码*", index = 6) @ColumnWidth(20) private String relationCertNo; /** 手机号码1 */ @ExcelProperty(value = "手机号码1", index = 7) @ColumnWidth(15) private String mobilePhone1; /** 手机号码2 */ @ExcelProperty(value = "手机号码2", index = 8) @ColumnWidth(15) private String mobilePhone2; /** 微信名称1 */ @ExcelProperty(value = "微信名称1", index = 9) @ColumnWidth(15) private String wechatNo1; /** 微信名称2 */ @ExcelProperty(value = "微信名称2", index = 10) @ColumnWidth(15) private String wechatNo2; /** 微信名称3 */ @ExcelProperty(value = "微信名称3", index = 11) @ColumnWidth(15) private String wechatNo3; /** 详细联系地址 */ @ExcelProperty(value = "详细联系地址", index = 12) @ColumnWidth(30) private String contactAddress; /** 关系详细描述 */ @ExcelProperty(value = "关系详细描述", index = 13) @ColumnWidth(30) private String relationDesc; /** 状态 */ @ExcelProperty(value = "状态", index = 14) @ColumnWidth(10) private Integer status; /** 生效日期 */ @ExcelProperty(value = "生效日期", index = 15) @ColumnWidth(18) private Date effectiveDate; /** 失效日期 */ @ExcelProperty(value = "失效日期", index = 16) @ColumnWidth(18) private Date invalidDate; /** 备注 */ @ExcelProperty(value = "备注", index = 17) @ColumnWidth(30) private String remark; } ``` **Step 2: 编译验证** ```bash mvn compile -pl ruoyi-ccdi ``` **预期结果:** BUILD SUCCESS **Step 3: Commit** ```bash git add ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/excel/CcdiCustFmyRelationExcel.java git commit -m "feat: 添加信贷客户家庭关系Excel类" ``` --- ### Task 5: 创建Mapper接口 **Files:** - Create: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/mapper/CcdiCustFmyRelationMapper.java` **Step 1: 创建Mapper接口** 复制 `CcdiStaffFmyRelationMapper.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.CcdiCustFmyRelation; import com.ruoyi.ccdi.domain.dto.CcdiCustFmyRelationQueryDTO; import com.ruoyi.ccdi.domain.vo.CcdiCustFmyRelationVO; import org.apache.ibatis.annotations.Param; import java.util.List; /** * 信贷客户家庭关系Mapper接口 * * @author ruoyi * @date 2026-02-11 */ public interface CcdiCustFmyRelationMapper extends BaseMapper { /** * 分页查询信贷客户家庭关系 * * @param page 分页对象 * @param query 查询条件 * @return 信贷客户家庭关系VO列表 */ Page selectRelationPage(Page page, @Param("query") CcdiCustFmyRelationQueryDTO query); /** * 根据ID查询信贷客户家庭关系详情 * * @param id 主键ID * @return 信贷客户家庭关系VO */ CcdiCustFmyRelationVO selectRelationById(@Param("id") Long id); /** * 查询已存在的关系记录(用于导入校验) * * @param personId 信贷客户身份证号 * @param relationType 关系类型 * @param relationCertNo 关系人证件号码 * @return 已存在的关系记录 */ CcdiCustFmyRelation selectExistingRelations(@Param("personId") String personId, @Param("relationType") String relationType, @Param("relationCertNo") String relationCertNo); /** * 批量插入信贷客户家庭关系 * * @param relations 信贷客户家庭关系列表 * @return 插入条数 */ int insertBatch(@Param("relations") List relations); /** * 根据证件号码查询关系数量 * * @param relationCertNo 关系人证件号码 * @return 关系数量 */ int countByCertNo(@Param("relationCertNo") String relationCertNo); } ``` **Step 2: 编译验证** ```bash mvn compile -pl ruoyi-ccdi ``` **预期结果:** BUILD SUCCESS **Step 3: Commit** ```bash git add ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/mapper/CcdiCustFmyRelationMapper.java git commit -m "feat: 添加信贷客户家庭关系Mapper接口" ``` --- ### Task 6: 创建Mapper XML映射 **Files:** - Create: `ruoyi-ccdi/src/main/resources/mapper/ccdi/CcdiCustFmyRelationMapper.xml` **Step 1: 创建XML映射文件** 复制 `CcdiStaffFmyRelationMapper.xml`,进行以下关键修改: 1. **修改namespace和resultMap** 2. **移除LEFT JOIN员工表** 3. **修改WHERE条件为 `is_cust_family = 1`** 4. **移除personName相关字段** ```xml INSERT INTO ccdi_cust_fmy_relation ( person_id, relation_type, relation_name, gender, birth_date, relation_cert_type, relation_cert_no, mobile_phone1, mobile_phone2, wechat_no1, wechat_no2, wechat_no3, contact_address, relation_desc, status, effective_date, invalid_date, remark, data_source, is_emp_family, is_cust_family, created_by, create_time ) VALUES ( #{item.personId}, #{item.relationType}, #{item.relationName}, #{item.gender}, #{item.birthDate}, #{item.relationCertType}, #{item.relationCertNo}, #{item.mobilePhone1}, #{item.mobilePhone2}, #{item.wechatNo1}, #{item.wechatNo2}, #{item.wechatNo3}, #{item.contactAddress}, #{item.relationDesc}, #{item.status}, #{item.effectiveDate}, #{item.invalidDate}, #{item.remark}, #{item.dataSource}, #{item.isEmpFamily}, #{item.isCustFamily}, #{item.createdBy}, #{item.createTime} ) ``` **Step 2: 编译验证** ```bash mvn compile -pl ruoyi-ccdi ``` **预期结果:** BUILD SUCCESS **Step 3: Commit** ```bash git add ruoyi-ccdi/src/main/resources/mapper/ccdi/CcdiCustFmyRelationMapper.xml git commit -m "feat: 添加信贷客户家庭关系Mapper XML映射" ``` --- ### Task 7: 创建Service接口 **Files:** - Create: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/ICcdiCustFmyRelationService.java` - Create: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/ICcdiCustFmyRelationImportService.java` **Step 1: 创建主Service接口** 复制 `ICcdiStaffFmyRelationService.java`,修改: ```java package com.ruoyi.ccdi.service; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ruoyi.ccdi.domain.dto.CcdiCustFmyRelationAddDTO; import com.ruoyi.ccdi.domain.dto.CcdiCustFmyRelationEditDTO; import com.ruoyi.ccdi.domain.dto.CcdiCustFmyRelationQueryDTO; import com.ruoyi.ccdi.domain.excel.CcdiCustFmyRelationExcel; import com.ruoyi.ccdi.domain.vo.CcdiCustFmyRelationVO; import javax.servlet.http.HttpServletResponse; import java.util.List; /** * 信贷客户家庭关系Service接口 * * @author ruoyi * @date 2026-02-11 */ public interface ICcdiCustFmyRelationService { /** * 分页查询信贷客户家庭关系 * * @param query 查询条件 * @param pageNum 页码 * @param pageSize 每页条数 * @return 分页结果 */ Page selectRelationPage(CcdiCustFmyRelationQueryDTO query, Integer pageNum, Integer pageSize); /** * 根据ID查询信贷客户家庭关系详情 * * @param id 主键ID * @return 信贷客户家庭关系VO */ CcdiCustFmyRelationVO selectRelationById(Long id); /** * 新增信贷客户家庭关系 * * @param addDTO 新增DTO * @return 是否成功 */ boolean insertRelation(CcdiCustFmyRelationAddDTO addDTO); /** * 修改信贷客户家庭关系 * * @param editDTO 编辑DTO * @return 是否成功 */ boolean updateRelation(CcdiCustFmyRelationEditDTO editDTO); /** * 删除信贷客户家庭关系 * * @param ids 主键ID数组 * @return 是否成功 */ boolean deleteRelationByIds(Long[] ids); /** * 导出信贷客户家庭关系 * * @param query 查询条件 * @param response HTTP响应 */ void exportRelations(CcdiCustFmyRelationQueryDTO query, HttpServletResponse response); /** * 生成导入模板 * * @param response HTTP响应 */ void importTemplate(HttpServletResponse response); /** * 批量导入信贷客户家庭关系 * * @param excels Excel数据列表 * @return 导入任务ID */ String importRelations(List excels); } ``` **Step 2: 创建导入Service接口** 复制 `ICcdiStaffFmyRelationImportService.java`,修改: ```java package com.ruoyi.ccdi.service; import com.ruoyi.ccdi.domain.excel.CcdiCustFmyRelationExcel; import com.ruoyi.ccdi.domain.vo.CustFmyRelationImportFailureVO; import java.util.List; /** * 信贷客户家庭关系导入Service接口 * * @author ruoyi * @date 2026-02-11 */ public interface ICcdiCustFmyRelationImportService { /** * 异步导入信贷客户家庭关系 * * @param excels Excel数据列表 * @param taskId 任务ID */ void importRelationsAsync(List excels, String taskId); /** * 校验单条数据 * * @param excel Excel数据 * @param rowNum 行号 * @return 错误消息,为null表示校验通过 */ String validateExcelRow(CcdiCustFmyRelationExcel excel, Integer rowNum); /** * 获取导入失败记录 * * @param taskId 任务ID * @return 失败记录列表 */ List getImportFailures(String taskId); } ``` **Step 3: 编译验证** ```bash mvn compile -pl ruoyi-ccdi ``` **预期结果:** BUILD SUCCESS **Step 4: Commit** ```bash git add ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/ git commit -m "feat: 添加信贷客户家庭关系Service接口" ``` --- ### Task 8: 创建Service实现类 **Files:** - Create: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiCustFmyRelationServiceImpl.java` - Create: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiCustFmyRelationImportServiceImpl.java` **Step 1: 创建主Service实现类** 复制 `CcdiStaffFmyRelationServiceImpl.java`,进行以下关键修改: 1. **类名和注入的Mapper/Service** 2. **设置 `isEmpFamily=false, isCustFamily=true`** 3. **Redis Key为 `import:custFmyRelation:`** ```java package com.ruoyi.ccdi.service.impl; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ruoyi.ccdi.domain.CcdiCustFmyRelation; import com.ruoyi.ccdi.domain.dto.CcdiCustFmyRelationAddDTO; import com.ruoyi.ccdi.domain.dto.CcdiCustFmyRelationEditDTO; import com.ruoyi.ccdi.domain.dto.CcdiCustFmyRelationQueryDTO; import com.ruoyi.ccdi.domain.excel.CcdiCustFmyRelationExcel; import com.ruoyi.ccdi.domain.vo.CcdiCustFmyRelationVO; import com.ruoyi.ccdi.mapper.CcdiCustFmyRelationMapper; import com.ruoyi.ccdi.service.ICcdiCustFmyRelationImportService; import com.ruoyi.ccdi.service.ICcdiCustFmyRelationService; import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.common.utils.StringUtils; import jakarta.annotation.Resource; import jakarta.servlet.http.HttpServletResponse; 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.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.Date; import java.util.List; import java.util.UUID; import java.util.concurrent.TimeUnit; /** * 信贷客户家庭关系Service实现 * * @author ruoyi * @date 2026-02-11 */ @Service public class CcdiCustFmyRelationServiceImpl implements ICcdiCustFmyRelationService { @Resource private CcdiCustFmyRelationMapper mapper; @Resource private ICcdiCustFmyRelationImportService importService; @Resource private RedisTemplate redisTemplate; private static final String IMPORT_TASK_KEY_PREFIX = "import:custFmyRelation:"; @Override public Page selectRelationPage(CcdiCustFmyRelationQueryDTO query, Integer pageNum, Integer pageSize) { Page page = new Page<>(pageNum, pageSize); return mapper.selectRelationPage(page, query); } @Override public CcdiCustFmyRelationVO selectRelationById(Long id) { return mapper.selectRelationById(id); } @Override @Transactional(rollbackFor = Exception.class) public boolean insertRelation(CcdiCustFmyRelationAddDTO addDTO) { CcdiCustFmyRelation relation = new CcdiCustFmyRelation(); BeanUtils.copyProperties(addDTO, relation); // 关键设置:客户家庭关系 relation.setIsEmpFamily(false); relation.setIsCustFamily(true); relation.setStatus(1); relation.setDataSource("MANUAL"); return mapper.insert(relation) > 0; } @Override @Transactional(rollbackFor = Exception.class) public boolean updateRelation(CcdiCustFmyRelationEditDTO editDTO) { CcdiCustFmyRelation relation = new CcdiCustFmyRelation(); BeanUtils.copyProperties(editDTO, relation); return mapper.updateById(relation) > 0; } @Override @Transactional(rollbackFor = Exception.class) public boolean deleteRelationByIds(Long[] ids) { return mapper.deleteBatchIds(List.of(ids)) > 0; } @Override public void exportRelations(CcdiCustFmyRelationQueryDTO query, HttpServletResponse response) { // 查询所有符合条件的数据(不分页) Page page = new Page<>(1, 10000); Page result = mapper.selectRelationPage(page, query); List excels = result.getRecords().stream() .map(this::convertToExcel) .toList(); // 使用EasyExcel导出 try { response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setCharacterEncoding("utf-8"); String fileName = URLEncoder.encode("信贷客户家庭关系", StandardCharsets.UTF_8) .replaceAll("\\+", "%20"); response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx"); // 这里使用EasyExcel工具类导出 // EasyExcel.write(response.getOutputStream(), CcdiCustFmyRelationExcel.class) // .sheet("信贷客户家庭关系") // .doWrite(excels); } catch (Exception e) { throw new RuntimeException("导出失败", e); } } @Override public void importTemplate(HttpServletResponse response) { try { response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setCharacterEncoding("utf-8"); String fileName = URLEncoder.encode("信贷客户家庭关系导入模板", StandardCharsets.UTF_8) .replaceAll("\\+", "%20"); response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx"); // EasyExcel.write(response.getOutputStream(), CcdiCustFmyRelationExcel.class) // .sheet("模板") // .doWrite(Collections.emptyList()); } catch (Exception e) { throw new RuntimeException("模板下载失败", e); } } @Override public String importRelations(List excels) { String taskId = UUID.randomUUID().toString(); // 保存任务状态到Redis redisTemplate.opsForValue().set(IMPORT_TASK_KEY_PREFIX + taskId, "PROCESSING", 1, TimeUnit.HOURS); // 异步导入 importService.importRelationsAsync(excels, taskId); return taskId; } private CcdiCustFmyRelationExcel convertToExcel(CcdiCustFmyRelationVO vo) { CcdiCustFmyRelationExcel excel = new CcdiCustFmyRelationExcel(); BeanUtils.copyProperties(vo, excel); return excel; } } ``` **Step 2: 创建导入Service实现类** 复制 `CcdiStaffFmyRelationImportServiceImpl.java`,修改: ```java package com.ruoyi.ccdi.service.impl; import com.alibaba.excel.EasyExcel; import com.ruoyi.ccdi.domain.CcdiCustFmyRelation; import com.ruoyi.ccdi.domain.excel.CcdiCustFmyRelationExcel; import com.ruoyi.ccdi.domain.vo.CustFmyRelationImportFailureVO; import com.ruoyi.ccdi.mapper.CcdiCustFmyRelationMapper; import com.ruoyi.ccdi.service.ICcdiCustFmyRelationImportService; import com.ruoyi.common.utils.SecurityUtils; import jakarta.annotation.Resource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.concurrent.TimeUnit; /** * 信贷客户家庭关系导入Service实现 * * @author ruoyi * @date 2026-02-11 */ @Service public class CcdiCustFmyRelationImportServiceImpl implements ICcdiCustFmyRelationImportService { private static final Logger log = LoggerFactory.getLogger(CcdiCustFmyRelationImportServiceImpl.class); @Resource private CcdiCustFmyRelationMapper mapper; @Resource private RedisTemplate redisTemplate; private static final String IMPORT_TASK_KEY_PREFIX = "import:custFmyRelation:"; private static final String IMPORT_FAILURE_KEY_PREFIX = "import:custFmyRelation:failures:"; @Async @Override @Transactional(rollbackFor = Exception.class) public void importRelationsAsync(List excels, String taskId) { List validRelations = new ArrayList<>(); List failures = new ArrayList<>(); try { for (int i = 0; i < excels.size(); i++) { CcdiCustFmyRelationExcel excel = excels.get(i); Integer rowNum = i + 2; // Excel行号从2开始(第1行是表头) String errorMessage = validateExcelRow(excel, rowNum); if (errorMessage != null) { CustFmyRelationImportFailureVO failure = new CustFmyRelationImportFailureVO(); failure.setRowNum(rowNum); failure.setPersonId(excel.getPersonId()); failure.setRelationType(excel.getRelationType()); failure.setRelationName(excel.getRelationName()); failure.setErrorMessage(errorMessage); failures.add(failure); continue; } CcdiCustFmyRelation relation = convertToRelation(excel); validRelations.add(relation); } // 批量插入有效数据 if (!validRelations.isEmpty()) { mapper.insertBatch(validRelations); } // 保存失败记录到Redis(24小时过期) if (!failures.isEmpty()) { redisTemplate.opsForValue().set( IMPORT_FAILURE_KEY_PREFIX + taskId, failures, 24, TimeUnit.HOURS ); } // 更新任务状态 redisTemplate.opsForValue().set( IMPORT_TASK_KEY_PREFIX + taskId, "COMPLETED:" + validRelations.size() + ":" + failures.size(), 1, TimeUnit.HOURS ); } catch (Exception e) { log.error("导入失败", e); redisTemplate.opsForValue().set( IMPORT_TASK_KEY_PREFIX + taskId, "FAILED:" + e.getMessage(), 1, TimeUnit.HOURS ); } } @Override public String validateExcelRow(CcdiCustFmyRelationExcel excel, Integer rowNum) { if (excel.getPersonId() == null || excel.getPersonId().trim().isEmpty()) { return "信贷客户身份证号不能为空"; } if (excel.getRelationType() == null || excel.getRelationType().trim().isEmpty()) { return "关系类型不能为空"; } if (excel.getRelationName() == null || excel.getRelationName().trim().isEmpty()) { return "关系人姓名不能为空"; } if (excel.getRelationCertType() == null || excel.getRelationCertType().trim().isEmpty()) { return "关系人证件类型不能为空"; } if (excel.getRelationCertNo() == null || excel.getRelationCertNo().trim().isEmpty()) { return "关系人证件号码不能为空"; } // 检查是否已存在相同的关系 CcdiCustFmyRelation existing = mapper.selectExistingRelations( excel.getPersonId(), excel.getRelationType(), excel.getRelationCertNo() ); if (existing != null) { return "该关系已存在,请勿重复导入"; } return null; // 校验通过 } @Override @SuppressWarnings("unchecked") public List getImportFailures(String taskId) { Object obj = redisTemplate.opsForValue().get(IMPORT_FAILURE_KEY_PREFIX + taskId); if (obj != null) { return (List) obj; } return new ArrayList<>(); } private CcdiCustFmyRelation convertToRelation(CcdiCustFmyRelationExcel excel) { CcdiCustFmyRelation relation = new CcdiCustFmyRelation(); org.springframework.beans.BeanUtils.copyProperties(excel, relation); relation.setIsEmpFamily(false); relation.setIsCustFamily(true); relation.setStatus(excel.getStatus() != null ? excel.getStatus() : 1); relation.setDataSource("IMPORT"); relation.setCreatedBy(SecurityUtils.getUsername()); relation.setCreateTime(new Date()); return relation; } } ``` **Step 3: 编译验证** ```bash mvn compile -pl ruoyi-ccdi ``` **预期结果:** BUILD SUCCESS **Step 4: Commit** ```bash git add ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/ git commit -m "feat: 添加信贷客户家庭关系Service实现类" ``` --- ### Task 9: 创建Controller **Files:** - Create: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/controller/CcdiCustFmyRelationController.java` **Step 1: 创建Controller** 复制 `CcdiStaffFmyRelationController.java`,修改: ```java package com.ruoyi.ccdi.controller; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ruoyi.ccdi.domain.dto.CcdiCustFmyRelationAddDTO; import com.ruoyi.ccdi.domain.dto.CcdiCustFmyRelationEditDTO; import com.ruoyi.ccdi.domain.dto.CcdiCustFmyRelationQueryDTO; import com.ruoyi.ccdi.domain.excel.CcdiCustFmyRelationExcel; import com.ruoyi.ccdi.domain.vo.CcdiCustFmyRelationVO; import com.ruoyi.ccdi.domain.vo.CustFmyRelationImportFailureVO; import com.ruoyi.ccdi.service.ICcdiCustFmyRelationService; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.page.TableDataInfo; import io.swagger.v3.oas.annotations.Operation; 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.io.IOException; import java.util.List; /** * 信贷客户家庭关系Controller * * @author ruoyi * @date 2026-02-11 */ @Tag(name = "信贷客户家庭关系管理") @RestController @RequestMapping("/ccdi/custFmyRelation") public class CcdiCustFmyRelationController extends BaseController { @Resource private ICcdiCustFmyRelationService relationService; /** * 查询信贷客户家庭关系列表 */ @Operation(summary = "查询信贷客户家庭关系列表") @PreAuthorize("@ss.hasPermi('ccdi:custFmyRelation:query')") @GetMapping("/list") public TableDataInfo list(CcdiCustFmyRelationQueryDTO query) { startPage(); Page page = relationService.selectRelationPage(query, getPageNum(), getPageSize()); return getDataTable(page.getRecords(), page.getTotal()); } /** * 根据ID查询信贷客户家庭关系详情 */ @Operation(summary = "查询信贷客户家庭关系详情") @PreAuthorize("@ss.hasPermi('ccdi:custFmyRelation:query')") @GetMapping("/{id}") public AjaxResult getInfo(@PathVariable("id") Long id) { CcdiCustFmyRelationVO relation = relationService.selectRelationById(id); return success(relation); } /** * 新增信贷客户家庭关系 */ @Operation(summary = "新增信贷客户家庭关系") @PreAuthorize("@ss.hasPermi('ccdi:custFmyRelation:add')") @PostMapping public AjaxResult add(@Validated @RequestBody CcdiCustFmyRelationAddDTO addDTO) { return toAjax(relationService.insertRelation(addDTO)); } /** * 修改信贷客户家庭关系 */ @Operation(summary = "修改信贷客户家庭关系") @PreAuthorize("@ss.hasPermi('ccdi:custFmyRelation:edit')") @PutMapping public AjaxResult edit(@Validated @RequestBody CcdiCustFmyRelationEditDTO editDTO) { return toAjax(relationService.updateRelation(editDTO)); } /** * 删除信贷客户家庭关系 */ @Operation(summary = "删除信贷客户家庭关系") @PreAuthorize("@ss.hasPermi('ccdi:custFmyRelation:remove')") @DeleteMapping("/{ids}") public AjaxResult remove(@PathVariable Long[] ids) { return toAjax(relationService.deleteRelationByIds(ids)); } /** * 导出信贷客户家庭关系 */ @Operation(summary = "导出信贷客户家庭关系") @PreAuthorize("@ss.hasPermi('ccdi:custFmyRelation:export')") @PostMapping("/export") public void export(HttpServletResponse response, CcdiCustFmyRelationQueryDTO query) { relationService.exportRelations(query, response); } /** * 下载导入模板 */ @Operation(summary = "下载导入模板") @PreAuthorize("@ss.hasPermi('ccdi:custFmyRelation:import')") @PostMapping("/importTemplate") public void importTemplate(HttpServletResponse response) { relationService.importTemplate(response); } /** * 导入信贷客户家庭关系 */ @Operation(summary = "导入信贷客户家庭关系") @PreAuthorize("@ss.hasPermi('ccdi:custFmyRelation:import')") @PostMapping("/importData") public AjaxResult importData(@RequestParam("file") MultipartFile file) throws IOException { List excels = EasyExcel.read(file.getInputStream()) .head(CcdiCustFmyRelationExcel.class) .sheet() .doReadSync(); String taskId = relationService.importRelations(excels); return success("导入任务已提交,任务ID: " + taskId); } /** * 查询导入状态 */ @Operation(summary = "查询导入状态") @PreAuthorize("@ss.hasPermi('ccdi:custFmyRelation:query')") @GetMapping("/importStatus/{taskId}") public AjaxResult getImportStatus(@PathVariable String taskId) { // 从Redis获取任务状态 Object status = redisTemplate.opsForValue().get("import:custFmyRelation:" + taskId); return success(status); } /** * 查询导入失败记录 */ @Operation(summary = "查询导入失败记录") @PreAuthorize("@ss.hasPermi('ccdi:custFmyRelation:query')") @GetMapping("/importFailures/{taskId}") public TableDataInfo getImportFailures(@PathVariable String taskId) { startPage(); List failures = relationService.getImportFailures(taskId); return getDataTable(failures, (long) failures.size()); } } ``` **Step 2: 编译验证** ```bash mvn compile -pl ruoyi-ccdi ``` **预期结果:** BUILD SUCCESS **Step 3: Commit** ```bash git add ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/controller/CcdiCustFmyRelationController.java git commit -m "feat: 添加信贷客户家庭关系Controller" ``` --- ### Task 12: 创建菜单权限SQL **Files:** - Create: `sql/ccdi_cust_fmy_relation_menu.sql` **Step 1: 创建菜单SQL** 创建 `sql/ccdi_cust_fmy_relation_menu.sql`: ```sql -- 信贷客户家庭关系菜单 INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES ('信贷客户家庭关系', (SELECT menu_id FROM sys_menu WHERE menu_name = '信息维护' LIMIT 1), 5, 'custFmyRelation', 'ccdiCustFmyRelation/index', 1, 0, 'C', '0', '0', 'ccdi:custFmyRelation:list', 'peoples', 'admin', NOW(), '', NULL, '信贷客户家庭关系菜单'); -- 获取刚插入的菜单ID SET @parent_id = LAST_INSERT_ID(); -- 添加按钮权限 INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES ('信贷客户家庭关系查询', @parent_id, 1, '#', '', 1, 0, 'F', '0', '0', 'ccdi:custFmyRelation:query', '#', 'admin', NOW(), '', NULL, ''), ('信贷客户家庭关系新增', @parent_id, 2, '#', '', 1, 0, 'F', '0', '0', 'ccdi:custFmyRelation:add', '#', 'admin', NOW(), '', NULL, ''), ('信贷客户家庭关系修改', @parent_id, 3, '#', '', 1, 0, 'F', '0', '0', 'ccdi:custFmyRelation:edit', '#', 'admin', NOW(), '', NULL, ''), ('信贷客户家庭关系删除', @parent_id, 4, '#', '', 1, 0, 'F', '0', '0', 'ccdi:custFmyRelation:remove', '#', 'admin', NOW(), '', NULL, ''), ('信贷客户家庭关系导出', @parent_id, 5, '#', '', 1, 0, 'F', '0', '0', 'ccdi:custFmyRelation:export', '#', 'admin', NOW(), '', NULL, ''), ('信贷客户家庭关系导入', @parent_id, 6, '#', '', 1, 0, 'F', '0', '0', 'ccdi:custFmyRelation:import', '#', 'admin', NOW(), '', NULL, ''); ``` **Step 2: Commit** ```bash git add sql/ccdi_cust_fmy_relation_menu.sql git commit -m "feat: 添加信贷客户家庭关系菜单权限" ``` --- ## 测试验证 ### Task 14: 后端接口测试 **前置条件:** - 后端服务已启动 (`mvn spring-boot:run -pl ruoyi-admin`) - 数据库连接正常 - Redis服务正常运行 **Step 1: 测试登录获取token** ```bash curl -X POST "http://localhost:8080/login" \ -H "Content-Type: application/json" \ -d '{"username":"admin","password":"admin123"}' ``` **预期结果:** 返回token **Step 2: 测试查询列表接口** ```bash curl -X GET "http://localhost:8080/ccdi/custFmyRelation/list?pageNum=1&pageSize=10" \ -H "Authorization: Bearer " ``` **预期结果:** 返回空列表(无数据) **Step 3: 测试新增接口** ```bash curl -X POST "http://localhost:8080/ccdi/custFmyRelation" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "personId": "110101199001011234", "relationType": "配偶", "relationName": "张三", "gender": "M", "relationCertType": "身份证", "relationCertNo": "110101199001015678" }' ``` **预期结果:** 返回成功 **Step 4: 测试查询详情接口** ```bash curl -X GET "http://localhost:8080/ccdi/custFmyRelation/1" \ -H "Authorization: Bearer " ``` **预期结果:** 返回刚插入的记录 **Step 5: 测试修改接口** ```bash curl -X PUT "http://localhost:8080/ccdi/custFmyRelation" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "id": 1, "personId": "110101199001011234", "relationType": "配偶", "relationName": "张三丰", "gender": "M", "relationCertType": "身份证", "relationCertNo": "110101199001015678", "status": 1 }' ``` **预期结果:** 返回成功 **Step 6: 测试删除接口** ```bash curl -X DELETE "http://localhost:8080/ccdi/custFmyRelation/1" \ -H "Authorization: Bearer " ``` **预期结果:** 返回成功 **Step 7: 验证Swagger文档** 访问 http://localhost:8080/swagger-ui/index.html **预期结果:** 看到"信贷客户家庭关系管理"分组,所有接口正常显示 --- ## 完成检查清单 - [ ] 数据库表创建成功 - [ ] 实体类编译通过 - [ ] DTO类编译通过 - [ ] VO类编译通过 - [ ] Excel类编译通过 - [ ] Mapper接口编译通过 - [ ] Mapper XML映射配置正确 - [ ] Service接口编译通过 - [ ] Service实现类编译通过 - [ ] Controller编译通过 - [ ] Controller所有接口在Swagger正常显示 - [ ] 菜单权限SQL已执行 - [ ] 登录接口测试通过 - [ ] 查询列表接口测试通过 - [ ] 新增接口测试通过 - [ ] 修改接口测试通过 - [ ] 删除接口测试通过 - [ ] 导出接口测试通过 - [ ] 导入模板下载测试通过 --- ## 预期结果 完成后,后端将提供以下功能: 1. **完整的CRUD接口** - 分页查询信贷客户家庭关系列表 - 根据ID查询详情 - 新增信贷客户家庭关系 - 修改信贷客户家庭关系 - 删除信贷客户家庭关系 2. **导入导出功能** - 导出Excel数据 - 下载导入模板 - 异步批量导入 - 导入状态查询 - 导入失败记录查看 3. **权限控制** - 完整的CRUD权限标识 - 按钮级权限控制 4. **数据隔离** - 独立表 `ccdi_cust_fmy_relation` - `is_cust_family = 1` 过滤条件 - 与员工亲属关系完全分离