diff --git a/CLAUDE.md b/CLAUDE.md index ba7b98d..4cd78f6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,6 +2,29 @@ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +## 快速参考 + +**启动项目:** +- 后端: `mvn spring-boot:run` 或运行 `ry.bat` +- 前端: `cd ruoyi-ui && npm run dev` + +**访问地址:** +- 前端: http://localhost:80 +- 后端: http://localhost:8080 +- Swagger: http://localhost:8080/swagger-ui/index.html +- Druid 监控: http://localhost:8080/druid/ (ruoyi/123456) + +**测试账号:** +- 用户名: `admin` +- 密码: `admin123` + +**获取 Token:** +```bash +POST http://localhost:8080/login/test?username=admin&password=admin123 +``` + +--- + ## 项目概述 **纪检初核系统** - 基于 **若依管理系统 v3.9.1** 构建的企业级前后端分离管理系统,用于员工异常行为风险识别。 @@ -62,10 +85,23 @@ npm run preview ### 数据库初始化 ```bash +# 初始化若依框架基础表 mysql -u root -p < sql/ry_20250522.sql + +# 初始化定时任务表 mysql -u root -p < sql/quartz.sql + +# 导入业务表(根据需要执行) +mysql -u root -p ccdi < sql/dpc_employee.sql +mysql -u root -p ccdi < sql/dpc_intermediary_blacklist.sql +# ... 其他业务表脚本 ``` +**注意:** +- 业务表脚本文件名以 `ccdi_` 或 `dpc_` 开头 +- 部分脚本包含菜单数据,需要按顺序执行 +- 数据库需要先创建(数据库名: `ccdi`) + --- ## 模块架构 @@ -95,8 +131,15 @@ ruoyi-admin (启动模块) ├── ruoyi-quartz (定时任务) ├── ruoyi-generator (代码生成) └── ruoyi-info-collection (信息采集模块) + └── 依赖 ruoyi-common ``` +**添加新业务模块:** +1. 在根目录 `pom.xml` 的 `` 中添加新模块 +2. 在新模块的 `pom.xml` 中添加对 `ruoyi-common` 的依赖 +3. 在 `ruoyi-admin/pom.xml` 中添加对新模块的依赖 +4. 在新模块中按照分层规范创建 controller/service/mapper/domain 包 + ### ruoyi-info-collection 业务模块 (核心) 自定义业务模块,包含以下核心功能: @@ -161,6 +204,14 @@ public class CcdiBaseStaff { - **Controller**: 所有接口添加 Swagger 注释,分页使用 MyBatis Plus Page - **Service**: 简单 CRUD 用 MyBatis Plus 方法,复杂操作在 XML 写 SQL - **DTO/VO**: 接口传参使用独立 DTO,返回使用独立 VO,不与 entity 混用 +- **Mapper**: 简单操作继承 BaseMapper,复杂操作在 XML 中定义 + +### 禁止事项 + +- **禁止使用全限定类名**: 必须使用 `import` 语句导入类,不要在代码中使用 `java.util.List` 这样的全限定名 +- **禁止使用 `extends ServiceImpl<>`**: Service 接口和实现类分离定义 +- **禁止 Entity 混用**: DTO、VO、Excel 类必须独立,不与 Entity 混用 +- **禁止缺少 `@Resource`**: Service 注入必须使用 `@Resource` 注解 ### API 响应格式 @@ -234,9 +285,20 @@ public AjaxResult getImportStatus(@PathVariable String taskId) { } ``` +**导入流程:** +1. 前端上传 Excel 文件 +2. 后端异步处理,返回 taskId +3. 前端轮询 `/import/status/{taskId}` 获取导入进度 +4. 导入完成后,可获取成功/失败数据统计 + +**导入结果处理:** +- 只返回导入失败的数据(含失败原因) +- 成功数据不返回,减少响应体积 +- 支持批量插入,提高性能 + ### EasyExcel 字典下拉框 -导入模板支持字典下拉框配置,提升数据录入准确性。 +导入模板支持字典下拉框配置,提升数据录入准确性。使用 `DictDropdownWriteHandler` 实现。 ### 权限控制 @@ -272,6 +334,24 @@ POST /login/test?username=admin&password=admin123 - 生成可执行的测试脚本进行验证 - 测试完成后保存接口输出并生成测试用例报告 +### 开发调试技巧 + +**使用 Swagger 测试接口:** +1. 访问 `/swagger-ui/index.html` +2. 点击接口展开详情 +3. 点击 "Try it out" 进行测试 +4. 填写参数后点击 "Execute" 执行 + +**查看 SQL 执行日志:** +- 在 `application.yml` 中设置日志级别: `com.ruoyi: debug` +- 使用 Druid 监控台查看慢 SQL + +**前端代理配置:** +前端开发服务器通过代理转发请求到后端: +- 前端地址: `http://localhost:80` +- 后端地址: `http://localhost:8080` +- 代理配置文件: `ruoyi-ui/vue.config.js` + --- ## 配置说明 @@ -293,6 +373,30 @@ POST /login/test?username=admin&password=admin123 | 数据库连接 | `application-dev.yml` | | Redis 配置 | `application-dev.yml` | +### 数据源配置 + +项目使用 Druid 连接池,支持主从分离(默认关闭从库): + +- **数据库连接**: `jdbc:mysql://host:3306/ccdi` +- **初始连接数**: 5 +- **最小连接数**: 10 +- **最大连接数**: 20 +- **慢 SQL 记录**: 超过 1000ms 的 SQL 会被记录 + +### Redis 配置 + +- **默认端口**: 6379 +- **数据库索引**: 0 +- **连接超时**: 10s + +### Druid 监控台 + +访问地址: `http://localhost:8080/druid/` +- 用户名: `ruoyi` +- 密码: `123456` + +用于监控 SQL 执行情况、连接池状态等。 + --- ## 重要文件路径 @@ -360,3 +464,65 @@ doc/ ## 沟通规范 - 永远使用简体中文进行思考和对话 + +--- + +## 常见问题排查 + +### 数据库连接失败 + +**检查项:** +1. 确认 MySQL 服务已启动 +2. 检查 `application-dev.yml` 中的数据库连接配置 +3. 确认数据库用户名和密码正确 +4. 检查数据库是否已创建(数据库名: `ccdi`) + +### Redis 连接失败 + +**检查项:** +1. 确认 Redis 服务已启动 +2. 检查 `application-dev.yml` 中的 Redis 配置 +3. 如果 Redis 不需要密码,将 `password` 配置注释掉 + +### 前端无法访问后端接口 + +**检查项:** +1. 确认后端已启动(端口 8080) +2. 检查前端代理配置(`ruoyi-ui/vue.config.js`) +3. 确认后端接口路径正确(查看 Controller 的 `@RequestMapping`) + +### 导入功能无响应 + +**检查项:** +1. 检查文件大小是否超过限制(默认 10MB) +2. 查看后端日志是否有异常 +3. 确认 Excel 模板格式正确 +4. 检查必填字段是否为空 + +--- + +## MyBatis Plus 分页使用 + +```java +// Controller 层 +@GetMapping("/list") +public TableDataInfo list(QueryDTO queryDTO) { + PageDomain pageDomain = TableSupport.buildPageRequest(); + Page page = new Page<>(pageDomain.getPageNum(), pageDomain.getPageSize()); + Page result = service.selectPage(page, queryDTO); + return getDataTable(result.getRecords(), result.getTotal()); +} + +// Service 层 +Page selectPage(Page page, QueryDTO queryDTO); + +// Mapper 层 (使用 XML) + +``` diff --git a/doc/参数配置功能/ScreenShot_2026-02-25_162807_126.png b/doc/参数配置功能/ScreenShot_2026-02-25_162807_126.png new file mode 100644 index 0000000..0840a2e Binary files /dev/null and b/doc/参数配置功能/ScreenShot_2026-02-25_162807_126.png differ diff --git a/doc/参数配置功能/ScreenShot_2026-02-25_162819_927.png b/doc/参数配置功能/ScreenShot_2026-02-25_162819_927.png new file mode 100644 index 0000000..75c5aba Binary files /dev/null and b/doc/参数配置功能/ScreenShot_2026-02-25_162819_927.png differ diff --git a/doc/参数配置功能/ScreenShot_2026-02-25_162831_473.png b/doc/参数配置功能/ScreenShot_2026-02-25_162831_473.png new file mode 100644 index 0000000..2321a36 Binary files /dev/null and b/doc/参数配置功能/ScreenShot_2026-02-25_162831_473.png differ diff --git a/doc/参数配置功能/task.md b/doc/参数配置功能/task.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/plans/2026-02-11-cust-fmy-relation-backend.md b/docs/plans/2026-02-11-cust-fmy-relation-backend.md deleted file mode 100644 index 9af7315..0000000 --- a/docs/plans/2026-02-11-cust-fmy-relation-backend.md +++ /dev/null @@ -1,2008 +0,0 @@ -# 信贷客户家庭关系维护功能 - 后端实施计划 - -> **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` 过滤条件 - - 与员工亲属关系完全分离 diff --git a/docs/plans/2026-02-11-cust-fmy-relation-frontend.md b/docs/plans/2026-02-11-cust-fmy-relation-frontend.md deleted file mode 100644 index 423095f..0000000 --- a/docs/plans/2026-02-11-cust-fmy-relation-frontend.md +++ /dev/null @@ -1,1084 +0,0 @@ -# 信贷客户家庭关系维护功能 - 前端实施计划 - -> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. - -**目标:** 开发信贷客户家庭关系维护功能的完整前端实现,包括API接口、页面组件和交互功能 - -**架构:** 完全复用员工亲属关系维护功能的实现逻辑,创建独立页面组件 `ccdiCustFmyRelation` - -**技术栈:** Vue 2.6.12 + Element UI 2.15.14 + Vuex 3.6.0 + Axios 0.28.1 - ---- - -## 前置条件 - -### 环境要求 -- Node.js 14+ -- npm 6+ -- 现代浏览器(Chrome/Edge/Firefox) - -### 依赖服务 -- 后端服务已启动并运行在 `http://localhost:8080` -- 菜单权限SQL已执行,菜单已添加到系统 - -### 参考模块 -- `ruoyi-ui/src/api/ccdiStaffFmyRelation.js` - 员工亲属关系API(参考模板) -- `ruoyi-ui/src/views/ccdiStaffFmyRelation/index.vue` - 员工亲属关系页面(参考模板) - ---- - -## 任务列表 - -### Task 10: 创建API接口文件 - -**Files:** -- Create: `ruoyi-ui/src/api/ccdiCustFmyRelation.js` -- Reference: `ruoyi-ui/src/api/ccdiStaffFmyRelation.js` - -**Step 1: 创建API文件** - -创建 `ruoyi-ui/src/api/ccdiCustFmyRelation.js`: - -```javascript -import request from '@/utils/request' - -// 查询信贷客户家庭关系列表 -export function listRelation(query) { - return request({ - url: '/ccdi/custFmyRelation/list', - method: 'get', - params: query - }) -} - -// 查询信贷客户家庭关系详细 -export function getRelation(id) { - return request({ - url: '/ccdi/custFmyRelation/' + id, - method: 'get' - }) -} - -// 新增信贷客户家庭关系 -export function addRelation(data) { - return request({ - url: '/ccdi/custFmyRelation', - method: 'post', - data: data - }) -} - -// 修改信贷客户家庭关系 -export function updateRelation(data) { - return request({ - url: '/ccdi/custFmyRelation', - method: 'put', - data: data - }) -} - -// 删除信贷客户家庭关系 -export function delRelation(ids) { - return request({ - url: '/ccdi/custFmyRelation/' + ids, - method: 'delete' - }) -} - -// 导出信贷客户家庭关系 -export function exportRelation(query) { - return request({ - url: '/ccdi/custFmyRelation/export', - method: 'post', - params: query - }) -} - -// 下载导入模板 -export function importTemplate() { - return request({ - url: '/ccdi/custFmyRelation/importTemplate', - method: 'post' - }) -} - -// 导入信贷客户家庭关系 -export function importData(file) { - const formData = new FormData() - formData.append('file', file) - return request({ - url: '/ccdi/custFmyRelation/importData', - method: 'post', - data: formData - }) -} - -// 查询导入状态 -export function getImportStatus(taskId) { - return request({ - url: '/ccdi/custFmyRelation/importStatus/' + taskId, - method: 'get' - }) -} - -// 查询导入失败记录 -export function getImportFailures(taskId, pageNum, pageSize) { - return request({ - url: '/ccdi/custFmyRelation/importFailures/' + taskId, - method: 'get', - params: { pageNum, pageSize } - }) -} -``` - -**Step 2: Commit** - -```bash -git add ruoyi-ui/src/api/ccdiCustFmyRelation.js -git commit -m "feat: 添加信贷客户家庭关系API接口" -``` - ---- - -### Task 11: 创建主页面组件 - -**Files:** -- Create: `ruoyi-ui/src/views/ccdiCustFmyRelation/index.vue` -- Reference: `ruoyi-ui/src/views/ccdiStaffFmyRelation/index.vue` - -**Step 1: 创建页面组件** - -复制 `ccdiStaffFmyRelation/index.vue`,进行以下关键修改: - -1. **移除员工姓名相关功能** - 只保留信贷客户身份证号输入 -2. **简化查询条件** - 移除状态下拉框 -3. **修改表单** - personId改为普通输入框,不使用远程搜索 -4. **修改权限标识** - 全部 `staffFmyRelation` → `custFmyRelation` -5. **修改localStorage键** - `cust_fmy_relation_import_last_task` -6. **修改字典类型引用** - -完整代码结构: - -```vue - - - -``` - -**Step 2: Commit** - -```bash -git add ruoyi-ui/src/views/ccdiCustFmyRelation/index.vue -git commit -m "feat: 添加信贷客户家庭关系页面组件" -``` - ---- - -### Task 13: 配置字典数据 - -**前置条件:** -- 已登录系统 -- 具有系统管理权限 - -**Step 1: 确认字典类型存在** - -1. 登录系统 -2. 导航到: 系统管理 → 字典管理 -3. 确认以下字典类型已存在: - - `ccdi_relation_type`: 关系类型 - - `ccdi_indiv_gender`: 性别 - - `ccdi_certificate_type`: 证件类型 - -**Step 2: 添加字典数据(如不存在)** - -如果字典类型不存在,参考以下数据添加: - -**关系类型 (ccdi_relation_type):** -``` -配偶 | 配偶 -父亲 | 父亲 -母亲 | 母亲 -子女 | 子女 -其他 | 其他 -``` - -**性别 (ccdi_indiv_gender):** -``` -M | 男 -F | 女 -O | 其他 -``` - -**证件类型 (ccdi_certificate_type):** -``` -身份证 | 身份证 -护照 | 护照 -军官证 | 军官证 -其他 | 其他 -``` - ---- - -## 测试验证 - -### Task 15: 前端功能测试 - -**前置条件:** -- 后端服务已启动并运行正常 -- 前端服务已启动 (`npm run dev`) -- 菜单权限已配置 -- 已登录系统(admin/admin123) - -**Step 1: 启动前端服务** - -```bash -cd ruoyi-ui -npm run dev -``` - -**预期结果:** 服务启动成功,访问 http://localhost - -**Step 2: 登录系统** - -用户名: `admin` -密码: `admin123` - -**Step 3: 导航到信贷客户家庭关系页面** - -路径: 信息维护 → 信贷客户家庭关系 - -**预期结果:** 页面正常加载,显示查询条件和列表 - -**Step 4: 测试新增功能** - -1. 点击"新增"按钮 -2. 填写表单: - - 信贷客户身份证号: `110101199001011234` - - 关系类型: `配偶` - - 关系人姓名: `张三` - - 性别: `男` - - 证件类型: `身份证` - - 证件号码: `110101199001015678` - - 手机号码1: `13800138000` -3. 点击"确定" - -**预期结果:** 新增成功,列表显示新记录,弹出成功提示 - -**Step 5: 测试查询功能** - -1. 在查询条件中输入: - - 信贷客户身份证号: `110101199001011234` -2. 点击"搜索" - -**预期结果:** 列表显示符合条件的记录 - -**Step 6: 测试编辑功能** - -1. 点击记录的"编辑"按钮 -2. 修改关系人姓名为 `张三丰` -3. 点击"确定" - -**预期结果:** 修改成功,列表显示更新后的数据 - -**Step 7: 测试删除功能** - -1. 勾选一条记录 -2. 点击"删除"按钮 -3. 确认删除 - -**预期结果:** 删除成功,列表不再显示该记录 - -**Step 8: 测试导出功能** - -1. 添加几条测试数据 -2. 点击"导出"按钮 - -**预期结果:** 下载Excel文件,数据正确 - -**Step 9: 测试导入模板下载** - -1. 点击"导入"按钮 -2. 在导入对话框中点击"下载模板" - -**预期结果:** 下载Excel模板文件,包含所有必填字段 - -**Step 10: 测试导入功能** - -1. 准备测试数据Excel文件 -2. 点击"导入"按钮 -3. 上传准备好的Excel文件 -4. 等待异步导入完成 - -**预期结果:** -- 显示导入任务已提交提示 -- 导入完成后显示导入结果对话框 -- 成功数据出现在列表中 -- 失败数据显示在失败记录表格中 - -**Step 11: 测试字典显示** - -验证以下字段正确显示字典标签: -- 关系类型: 显示"配偶"、"父亲"等 -- 性别: 显示"男"、"女"等 -- 证件类型: 显示"身份证"、"护照"等 - -**预期结果:** 所有字典字段正确显示标签值 - -**Step 12: 测试权限控制** - -1. 退出登录,使用没有权限的账号登录 -2. 导航到信贷客户家庭关系页面 - -**预期结果:** 相应的操作按钮不显示或禁用 - ---- - -### Task 16: 浏览器控制台测试 - -**Step 1: 打开浏览器开发者工具** - -按 `F12` 打开开发者工具 - -**Step 2: 检查网络请求** - -切换到 Network 标签,执行以下操作并检查请求: - -1. **列表请求:** - - Method: `GET` - - URL: `/ccdi/custFmyRelation/list` - - Status: `200` - - Response: 包含 `rows` 和 `total` - -2. **新增请求:** - - Method: `POST` - - URL: `/ccdi/custFmyRelation` - - Status: `200` - - Response: `{ "code": 200, "msg": "操作成功" }` - -3. **修改请求:** - - Method: `PUT` - - URL: `/ccdi/custFmyRelation` - - Status: `200` - - Response: `{ "code": 200, "msg": "操作成功" }` - -4. **删除请求:** - - Method: `DELETE` - - URL: `/ccdi/custFmyRelation/{ids}` - - Status: `200` - - Response: `{ "code": 200, "msg": "操作成功" }` - -**Step 3: 检查控制台错误** - -切换到 Console 标签,确认没有JavaScript错误 - -**预期结果:** 控制台无红色错误信息 - -**Step 4: 检查页面性能** - -1. 切换到 Performance 标签 -2. 录制页面操作 -3. 检查页面渲染性能 - -**预期结果:** 页面响应流畅,无明显卡顿 - ---- - -## 完成检查清单 - -### API接口 -- [ ] API文件创建完成 -- [ ] 所有接口方法定义正确 -- [ ] 请求路径正确(`/ccdi/custFmyRelation`) -- [ ] 请求方法正确(GET/POST/PUT/DELETE) - -### 页面组件 -- [ ] 页面组件创建完成 -- [ ] 查询条件显示正确 -- [ ] 列表数据显示正确 -- [ ] 新增对话框正常 -- [ ] 编辑对话框正常 -- [ ] 表单验证规则正确 -- [ ] 字典数据正确显示 -- [ ] 权限标识正确 - -### 导入导出 -- [ ] 导出功能正常 -- [ ] 导入模板下载正常 -- [ ] 导入对话框正常 -- [ ] 文件上传功能正常 -- [ ] 异步导入状态轮询正常 -- [ ] 导入结果显示正常 -- [ ] 失败记录显示正常 - -### 交互功能 -- [ ] 分页功能正常 -- [ ] 搜索功能正常 -- [ ] 重置功能正常 -- [ ] 多选功能正常 -- [ ] 批量删除功能正常 -- [ ] 单条删除功能正常 -- [ ] 新增功能正常 -- [ ] 编辑功能正常 -- [ ] 表单验证正常 - -### 权限控制 -- [ ] 菜单权限已配置 -- [ ] 按钮权限控制生效 -- [ ] 无权限时按钮不显示 - -### 用户体验 -- [ ] 页面加载速度正常 -- [ ] 操作响应及时 -- [ ] 错误提示清晰 -- [ ] 成功提示友好 -- [ ] 控制台无错误 - ---- - -## 预期结果 - -完成后,前端将提供以下功能: - -1. **完整的CRUD界面** - - 列表展示(分页) - - 简化查询(身份证号、关系类型、关系人姓名) - - 新增/编辑/删除/详情 - -2. **导入导出界面** - - 一键导出Excel - - 下载导入模板 - - 文件拖拽上传 - - 异步导入状态轮询 - - 导入结果可视化展示 - - 失败记录详细显示 - -3. **用户体验优化** - - 响应式布局 - - 字典下拉选择 - - 表单验证提示 - - 加载状态提示 - - 操作结果反馈 - -4. **权限控制** - - 菜单级权限 - - 按钮级权限 - - 操作前权限校验 diff --git a/docs/plans/2026-02-11-cust-fmy-relation-implementation.md b/docs/plans/2026-02-11-cust-fmy-relation-implementation.md deleted file mode 100644 index 5bc34e1..0000000 --- a/docs/plans/2026-02-11-cust-fmy-relation-implementation.md +++ /dev/null @@ -1,962 +0,0 @@ -# 信贷客户家庭关系维护功能实施计划 - -> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. - -**目标:** 开发信贷客户家庭关系维护功能,实现对信贷客户家庭成员信息的完整CRUD操作 - -**架构:** 完全复用员工亲属关系维护功能的实现逻辑,创建独立模块 `CustFamilyRelation`,新建独立表 `ccdi_cust_fmy_relation` - -**技术栈:** Spring Boot 3.5.8 + MyBatis Plus 3.5.10 + Vue 2.6.12 + Element UI 2.15.14 + EasyExcel + Redis - ---- - -## 前置准备 - -### Task 0: 创建数据库表 - -**Files:** -- Create: `sql/ccdi_cust_fmy_relation.sql` - -**Step 1: 创建建表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创建表** - -Run: 连接数据库并执行 `sql/ccdi_cust_fmy_relation.sql` -Expected: 表创建成功 - -**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:1-108` - -**Step 1: 创建实体类** - -复制 `CcdiStaffFmyRelation.java`,修改以下内容: -- 类名: `CcdiCustFmyRelation` -- 注释: `信贷客户家庭关系对象 ccdi_cust_fmy_relation` -- 表名: `@TableName("ccdi_cust_fmy_relation")` -- JavaDoc: 全部替换"员工"为"信贷客户" - -```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; - -/** - * 信贷客户家庭关系对象 ccdi_cust_fmy_relation - * - * @author ruoyi - * @date 2026-02-11 - */ -@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: Compile** - -Run: `mvn compile -pl ruoyi-ccdi` -Expected: 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` -- Reference: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/dto/CcdiStaffFmyRelationAddDTO.java` - -**Step 1: 创建AddDTO** - -复制 `CcdiStaffFmyRelationAddDTO.java`,修改: -- 类名: `CcdiCustFmyRelationAddDTO` -- 注释中"员工" → "信贷客户" -- personId字段注释: `@Schema(description = "信贷客户身份证号")` -- 验证消息: "员工身份证号" → "信贷客户身份证号" - -**Step 2: 创建EditDTO** - -复制 `CcdiStaffFmyRelationEditDTO.java`,修改: -- 类名: `CcdiCustFmyRelationEditDTO` -- 注释中"员工" → "信贷客户" -- 添加 `id` 字段和 `@NotNull` 验证 - -**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: Compile** - -Run: `mvn compile -pl ruoyi-ccdi` -Expected: 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` -- Reference: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/vo/CcdiStaffFmyRelationVO.java` - -**Step 1: 创建主VO** - -复制 `CcdiStaffFmyRelationVO.java`,修改: -- 类名: `CcdiCustFmyRelationVO` -- 移除 `personName` 字段(不关联其他表) -- 注释中"员工" → "信贷客户" - -**Step 2: 创建导入失败VO** - -复制 `StaffFmyRelationImportFailureVO.java`,修改: -- 类名: `CustFmyRelationImportFailureVO` -- 注释中"员工" → "信贷客户" - -**Step 3: Compile** - -Run: `mvn compile -pl ruoyi-ccdi` -Expected: 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` -- Reference: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/excel/CcdiStaffFmyRelationExcel.java` - -**Step 1: 创建Excel类** - -复制 `CcdiStaffFmyRelationExcel.java`,修改: -- 类名: `CcdiCustFmyRelationExcel` -- 注释: `信贷客户家庭关系Excel导入导出对象` -- personId字段: `@ExcelProperty(value = "信贷客户身份证号*", index = 0)` -- 其他保持不变 - -**Step 2: Compile** - -Run: `mvn compile -pl ruoyi-ccdi` -Expected: 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` -- Reference: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/mapper/CcdiStaffFmyRelationMapper.java` - -**Step 1: 创建Mapper接口** - -复制 `CcdiStaffFmyRelationMapper.java`,修改: -- 包名和导入: 全部 `Staff` → `Cust` -- 类名: `CcdiCustFmyRelationMapper` -- 泛型: `CcdiCustFmyRelation` -- DTO: `CcdiCustFmyRelationQueryDTO` -- VO: `CcdiCustFmyRelationVO` -- 方法注释: "员工" → "信贷客户" - -**Step 2: Compile** - -Run: `mvn compile -pl ruoyi-ccdi` -Expected: 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` -- Reference: `ruoyi-ccdi/src/main/resources/mapper/ccdi/CcdiStaffFmyRelationMapper.xml` - -**Step 1: 创建XML映射文件** - -复制 `CcdiStaffFmyRelationMapper.xml`,修改: -- namespace: `com.ruoyi.ccdi.mapper.CcdiCustFmyRelationMapper` -- resultMap: `CcdiCustFmyRelationVOResult` -- type: `com.ruoyi.ccdi.domain.vo.CcdiCustFmyRelationVO` -- 表名: `ccdi_cust_fmy_relation` -- **移除 LEFT JOIN**(不关联员工表) -- WHERE条件: `r.is_cust_family = 1` -- **移除 personName 相关字段** - -```xml - - -``` - -- selectExistingRelations: `is_cust_family = 1` - -**Step 2: Compile** - -Run: `mvn compile -pl ruoyi-ccdi` -Expected: 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` -- Reference: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/ICcdiStaffFmyRelationService.java` - -**Step 1: 创建主Service接口** - -复制 `ICcdiStaffFmyRelationService.java`,修改: -- 接口名: `ICcdiCustFmyRelationService` -- 泛型: `CcdiCustFmyRelationVO`, `CcdiCustFmyRelationQueryDTO`, `CcdiCustFmyRelationAddDTO`, `CcdiCustFmyRelationEditDTO`, `CcdiCustFmyRelationExcel` - -**Step 2: 创建导入Service接口** - -复制 `ICcdiStaffFmyRelationImportService.java`,修改: -- 接口名: `ICcdiCustFmyRelationImportService` -- 泛型: `CcdiCustFmyRelationExcel`, `CustFmyRelationImportFailureVO` - -**Step 3: Compile** - -Run: `mvn compile -pl ruoyi-ccdi` -Expected: 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` -- Reference: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiStaffFmyRelationServiceImpl.java` - -**Step 1: 创建主Service实现类** - -复制 `CcdiStaffFmyRelationServiceImpl.java`,修改: -- 类名: `CcdiCustFmyRelationServiceImpl` -- Mapper注入: `CcdiCustFmyRelationMapper` -- ImportService注入: `ICcdiCustFmyRelationImportService` -- 泛型: `CcdiCustFmyRelationVO`, `CcdiCustFmyRelationQueryDTO` 等 -- **关键修改**: - - `relation.setIsEmpFamily(false);` - - `relation.setIsCustFamily(true);` - - Redis Key: `import:custFmyRelation:` - -**Step 2: 创建导入Service实现类** - -复制 `CcdiStaffFmyRelationImportServiceImpl.java`,修改: -- 类名: `CcdiCustFmyRelationImportServiceImpl` -- Mapper注入: `CcdiCustFmyRelationMapper` -- 泛型: `CcdiCustFmyRelationExcel`, `CustFmyRelationImportFailureVO` -- Redis Key: `import:custFmyRelation:` -- 错误消息: "信贷客户身份证号" - -**Step 3: Compile** - -Run: `mvn compile -pl ruoyi-ccdi` -Expected: 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` -- Reference: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/controller/CcdiStaffFmyRelationController.java` - -**Step 1: 创建Controller** - -复制 `CcdiStaffFmyRelationController.java`,修改: -- 类名: `CcdiCustFmyRelationController` -- Tag: `@Tag(name = "信贷客户家庭关系管理")` -- RequestMapping: `/ccdi/custFmyRelation` -- Service注入: `ICcdiCustFmyRelationService`, `ICcdiCustFmyRelationImportService` -- DTO/VO: 对应的 `CcdiCust...` 类型 -- 权限标识: `ccdi:custFmyRelation:*` -- 注释: "员工" → "信贷客户" - -**Step 2: Compile** - -Run: `mvn compile -pl ruoyi-ccdi` -Expected: 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 10: 创建API接口文件 - -**Files:** -- Create: `ruoyi-ui/src/api/ccdiCustFmyRelation.js` -- Reference: `ruoyi-ui/src/api/ccdiStaffFmyRelation.js` - -**Step 1: 创建API文件** - -复制 `ccdiStaffFmyRelation.js`,修改: -- url路径: `/ccdi/custFmyRelation` -- 移除 `getStaffList` 方法(不需要) - -```javascript -import request from '@/utils/request' - -// 查询信贷客户家庭关系列表 -export function listRelation(query) { - return request({ - url: '/ccdi/custFmyRelation/list', - method: 'get', - params: query - }) -} - -// 查询信贷客户家庭关系详细 -export function getRelation(id) { - return request({ - url: '/ccdi/custFmyRelation/' + id, - method: 'get' - }) -} - -// 新增信贷客户家庭关系 -export function addRelation(data) { - return request({ - url: '/ccdi/custFmyRelation', - method: 'post', - data: data - }) -} - -// 修改信贷客户家庭关系 -export function updateRelation(data) { - return request({ - url: '/ccdi/custFmyRelation', - method: 'put', - data: data - }) -} - -// 删除信贷客户家庭关系 -export function delRelation(ids) { - return request({ - url: '/ccdi/custFmyRelation/' + ids, - method: 'delete' - }) -} - -// 导出信贷客户家庭关系 -export function exportRelation(query) { - return request({ - url: '/ccdi/custFmyRelation/export', - method: 'post', - params: query - }) -} - -// 下载导入模板 -export function importTemplate() { - return request({ - url: '/ccdi/custFmyRelation/importTemplate', - method: 'post' - }) -} - -// 导入信贷客户家庭关系 -export function importData(file) { - const formData = new FormData() - formData.append('file', file) - return request({ - url: '/ccdi/custFmyRelation/importData', - method: 'post', - data: formData - }) -} - -// 查询导入状态 -export function getImportStatus(taskId) { - return request({ - url: '/ccdi/custFmyRelation/importStatus/' + taskId, - method: 'get' - }) -} - -// 查询导入失败记录 -export function getImportFailures(taskId, pageNum, pageSize) { - return request({ - url: '/ccdi/custFmyRelation/importFailures/' + taskId, - method: 'get', - params: { pageNum, pageSize } - }) -} -``` - -**Step 2: Commit** - -```bash -git add ruoyi-ui/src/api/ccdiCustFmyRelation.js -git commit -m "feat: 添加信贷客户家庭关系API接口" -``` - ---- - -### Task 11: 创建主页面组件 - -**Files:** -- Create: `ruoyi-ui/src/views/ccdiCustFmyRelation/index.vue` -- Reference: `ruoyi-ui/src/views/ccdiStaffFmyRelation/index.vue` - -**Step 1: 创建页面组件** - -复制 `ccdiStaffFmyRelation/index.vue`,修改: - -1. **查询条件**(简化版): -```vue - - - - - - - - - - - - - -``` - -2. **列表列**(移除personName): -```vue - - -``` - -3. **表单**(使用普通输入框): -```vue - - - - -``` - -4. **权限标识**:全部 `staffFmyRelation` → `custFmyRelation` - -5. **导入localStorage**: -```javascript -const STORAGE_KEY = 'cust_fmy_relation_import_last_task'; -``` - -6. **字典类型**: -```vue - - -``` - -**Step 2: Commit** - -```bash -git add ruoyi-ui/src/views/ccdiCustFmyRelation/index.vue -git commit -m "feat: 添加信贷客户家庭关系页面组件" -``` - ---- - -## 系统配置 - -### Task 12: 创建菜单权限SQL - -**Files:** -- Create: `sql/ccdi_cust_fmy_relation_menu.sql` -- Reference: `sql/ccdi_staff_fmy_relation_menu.sql` - -**Step 1: 创建菜单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 13: 配置字典数据 - -**Files:** -- Modify: 通过系统管理界面配置 - -**Step 1: 确认字典存在** - -登录系统 → 系统管理 → 字典管理,确认以下字典类型已存在: -- `ccdi_relation_type`:关系类型 -- `ccdi_indiv_gender`:性别 -- `ccdi_certificate_type`:证件类型 - -如不存在,参考员工亲属关系的字典数据添加。 - ---- - -## 测试验证 - -### Task 14: 后端接口测试 - -**Files:** -- Create: `doc/reviews/cust-fmy-relation-api-test.md` - -**Step 1: 启动后端服务** - -Run: `mvn spring-boot:run -pl ruoyi-admin` -Expected: 服务启动成功,访问 http://localhost:8080/swagger-ui/index.html - -**Step 2: 测试登录获取token** - -Run: -```bash -curl -X POST "http://localhost:8080/login" \ - -H "Content-Type: application/json" \ - -d '{"username":"admin","password":"admin123"}' -``` -Expected: 返回token - -**Step 3: 测试查询列表接口** - -Run: -```bash -curl -X GET "http://localhost:8080/ccdi/custFmyRelation/list?pageNum=1&pageSize=10" \ - -H "Authorization: Bearer " -``` -Expected: 返回空列表(无数据) - -**Step 4: 测试新增接口** - -Run: -```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" - }' -``` -Expected: 返回成功 - -**Step 5: 测试查询详情接口** - -Run: -```bash -curl -X GET "http://localhost:8080/ccdi/custFmyRelation/1" \ - -H "Authorization: Bearer " -``` -Expected: 返回刚插入的记录 - ---- - -### Task 15: 前端功能测试 - -**Step 1: 启动前端服务** - -Run: `cd ruoyi-ui && npm run dev` -Expected: 服务启动成功,访问 http://localhost - -**Step 2: 登录系统** - -用户名: admin -密码: admin123 - -**Step 3: 导航到信贷客户家庭关系页面** - -路径: 信息维护 → 信贷客户家庭关系 - -**Step 4: 测试新增功能** - -1. 点击"新增"按钮 -2. 填写表单: - - 信贷客户身份证号: `110101199001011234` - - 关系类型: `配偶` - - 关系人姓名: `张三` - - 性别: `男` - - 证件类型: `身份证` - - 证件号码: `110101199001015678` -3. 点击"确定" -Expected: 新增成功,列表显示新记录 - -**Step 5: 测试编辑功能** - -1. 点击"编辑"按钮 -2. 修改关系人姓名为 `张三丰` -3. 点击"确定" -Expected: 修改成功,列表显示更新 - -**Step 6: 测试删除功能** - -1. 勾选记录 -2. 点击"删除"按钮 -3. 确认删除 -Expected: 删除成功,列表不再显示该记录 - -**Step 7: 测试导出功能** - -1. 添加几条测试数据 -2. 点击"导出"按钮 -Expected: 下载Excel文件,数据正确 - -**Step 8: 测试导入功能** - -1. 点击"导入"按钮 -2. 下载模板 -3. 填写数据后上传 -4. 等待异步导入完成 -Expected: 导入成功,显示结果通知 - ---- - -### Task 16: API文档生成 - -**Step 1: 访问Swagger文档** - -URL: http://localhost:8080/swagger-ui/index.html -Expected: 看到"信贷客户家庭关系管理"分组,所有接口正常显示 - -**Step 2: 导出API文档** - -使用 Swagger 导出功能,保存到: `doc/api-docs/cust-fmy-relation-api.md` - ---- - -## 完成检查清单 - -- [ ] 数据库表创建成功 -- [ ] 后端所有类编译通过 -- [ ] Controller所有接口在Swagger正常显示 -- [ ] 前端页面正常加载 -- [ ] 增删改查功能正常 -- [ ] 导入导出功能正常 -- [ ] 权限控制生效 -- [ ] 字典数据正确显示 -- [ ] 测试文档完整 - ---- - -## 预期结果 - -完成后,系统将具备以下功能: - -1. **信贷客户家庭关系管理页面** - - 列表展示(分页) - - 简化查询(身份证号、关系类型、关系人姓名) - - 新增/编辑/删除/详情 - -2. **导入导出功能** - - 带字典下拉框的Excel模板 - - 异步导入,实时状态查询 - - 失败记录查看 - -3. **权限控制** - - 完整的CRUD权限 - - 按钮级权限控制 - -4. **数据隔离** - - 独立表 `ccdi_cust_fmy_relation` - - `is_cust_family = 1` diff --git a/docs/plans/2026-02-11-cust-fmy-relation-import-alignment.md b/docs/plans/2026-02-11-cust-fmy-relation-import-alignment.md deleted file mode 100644 index 269be46..0000000 --- a/docs/plans/2026-02-11-cust-fmy-relation-import-alignment.md +++ /dev/null @@ -1,373 +0,0 @@ -# 信贷客户家庭关系导入功能对齐方案 - -## 概述 - -本文档描述了如何将**信贷客户家庭关系**功能的导入实现完全对齐到**员工亲属关系**的成熟模式。 - -**参考模板**: `CcdiStaffEnterpriseRelationImportServiceImpl` -**修改对象**: `CcdiCustFmyRelationImportServiceImpl` - -## 设计目标 - -1. 提升代码质量和可维护性 -2. 优化性能,避免 N+1 查询问题 -3. 改善用户体验,提供详细的导入进度和状态反馈 -4. 统一日志记录和错误处理机制 - -## 架构调整 - -### 1. 引入导入工具类 - -复用 `ImportLogUtils` 进行统一的日志记录: -- 导入开始/结束日志 -- 批量查询日志 -- 进度跟踪日志 -- 验证错误日志 -- 批量操作日志 - -### 2. Redis 状态管理升级 - -**现状**: 简单 String 值存储状态 -``` -"COMPLETED:10:5" -``` - -**优化**: Hash 结构存储详细状态 -```java -{ - "taskId": "uuid", - "status": "SUCCESS" | "PARTIAL_SUCCESS" | "PROCESSING", - "totalCount": 100, - "successCount": 95, - "failureCount": 5, - "progress": 100, - "startTime": 1234567890, - "endTime": 1234567900, - "message": "成功95条,失败5条" -} -``` - -- 过期时间: 7 天 -- 失败记录: 单独 Key, JSON 序列化, 7 天过期 - -### 3. 批量查询优化 - -**实现 `batchExistsByCombinations` 方法**: -- 提取所有 `person_id + relation_type + relation_cert_no` 组合 -- 一次性批量查询已存在的组合 -- 避免循环查询导致的 N+1 问题 - -### 4. 导入结果封装 - -创建/复用统一的 VO: -- `ImportStatusVO`: 导入状态详情 -- `ImportResultVO`: 导入提交结果 -- `CustFmyRelationImportFailureVO`: 失败记录详情 - -## 数据验证逻辑 - -### 唯一性检查 - -**优化前**: 每条记录单独查询 -```java -for (excel : excels) { - CcdiCustFmyRelation existing = mapper.selectExistingRelations(...); - // N 次数据库查询 -} -``` - -**优化后**: 批量查询 -```java -Set existingCombinations = getExistingCombinations(excels); -// 1 次数据库查询 - -for (excel : excels) { - String combination = excel.getPersonId() + "|" + ...; - if (existingCombinations.contains(combination)) { - throw new RuntimeException("该关系已存在"); - } -} -``` - -### Excel 内部重复检查 - -```java -Set processedCombinations = new HashSet<>(); - -for (excel : excels) { - String combination = ...; - - if (processedCombinations.contains(combination)) { - throw new RuntimeException("该关系在导入文件中重复"); - } - - processedCombinations.add(combination); -} -``` - -### 验证规则 - -**必填字段**: -- 信贷客户身份证号 -- 关系类型 -- 关系人姓名 -- 关系人证件类型 -- 关系人证件号码 - -**格式验证**: -- 身份证号: 18位有效格式 -- 证件号码: 根据证件类型验证 - -**长度限制**: -- 关系人姓名: ≤ 50 -- 关系类型: ≤ 20 -- 证件号码: ≤ 50 - -## 批量操作优化 - -### 分批插入策略 - -```java -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); - mapper.insertBatch(subList); - } -} - -// 调用: 每 500 条为一批 -saveBatch(newRecords, 500); -``` - -### 批量操作日志 - -``` -开始批量插入: 总批次数=5, 每批大小=500 -批量插入完成: 成功=2500, 耗时=1234ms -``` - -## 失败记录处理 - -### 失败记录数据结构 - -```java -public class CustFmyRelationImportFailureVO { - private Integer rowNum; // Excel 行号 - private String personId; // 信贷客户身份证号 - private String relationType; // 关系类型 - private String relationName; // 关系人姓名 - private String errorMessage; // 错误消息 -} -``` - -### Redis 存储优化 - -**Key**: `import:custFmyRelation:{taskId}:failures` -**序列化**: JSON -**过期时间**: 7 天 -**反序列化**: -```java -return JSON.parseArray( - JSON.toJSONString(failuresObj), - CustFmyRelationImportFailureVO.class -); -``` - -## Controller 层调整 - -### 导入接口 - -```java -@PostMapping("/importData") -public AjaxResult importData(@RequestParam("file") MultipartFile file) { - List excels = - EasyExcelUtil.importExcel(file.getInputStream(), ...); - - if (excels == null || excels.isEmpty()) { - return error("至少需要一条数据"); - } - - String taskId = relationService.importRelations(excels); - - ImportResultVO result = new ImportResultVO(); - result.setTaskId(taskId); - result.setStatus("PROCESSING"); - result.setMessage("导入任务已提交,正在后台处理"); - - return AjaxResult.success("导入任务已提交,正在后台处理", result); -} -``` - -### 导入状态查询 - -```java -@GetMapping("/importStatus/{taskId}") -public AjaxResult getImportStatus(@PathVariable String taskId) { - ImportStatusVO statusVO = relationImportService.getImportStatus(taskId); - return success(statusVO); -} -``` - -### 失败记录查询 - -```java -@GetMapping("/importFailures/{taskId}") -public TableDataInfo getImportFailures( - @PathVariable String taskId, - @RequestParam(defaultValue = "1") Integer pageNum, - @RequestParam(defaultValue = "10") Integer pageSize -) { - List 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 pageData = - failures.subList(fromIndex, toIndex); - - return getDataTable(pageData, failures.size()); -} -``` - -## 导入模板改进 - -### 使用字典下拉框 - -```java -@PostMapping("/importTemplate") -public void importTemplate(HttpServletResponse response) { - EasyExcelUtil.importTemplateWithDictDropdown( - response, - CcdiCustFmyRelationExcel.class, - "信贷客户家庭关系" - ); -} -``` - -### Excel 实体注解增强 - -```java -@DictDropdown(type = "ccdi_relation_type") -private String relationType; - -@DictDropdown(type = "ccdi_cert_type") -private String relationCertType; -``` - -## 修改文件清单 - -### 1. Service 层 -- `CcdiCustFmyRelationImportServiceImpl.java` - 核心导入逻辑重构 -- `CcdiCustFmyRelationServiceImpl.java` - 导入入口方法调整 - -### 2. Controller 层 -- `CcdiCustFmyRelationController.java` - 接口返回值优化 - -### 3. Mapper 层 -- `CcdiCustFmyRelationMapper.java` - 添加批量查询方法 -- Mapper XML - 实现批量查询 SQL - -### 4. VO 类 -- 检查/创建 `ImportStatusVO.java` -- 检查/创建 `ImportResultVO.java` -- 优化 `CustFmyRelationImportFailureVO.java` - -### 5. Excel 实体 -- `CcdiCustFmyRelationExcel.java` - 添加字典注解 - -### 6. 工具类 -- 复用 `ImportLogUtils.java` -- 复用 `EasyExcelUtil.java` - -## 关键代码片段 - -### Mapper 批量查询 - -```java -// Mapper 接口 -List batchExistsByCombinations( - @Param("combinations") List combinations -); - -// XML 实现 - -``` - -### 异步导入方法 - -```java -@Async -@Transactional(rollbackFor = Exception.class) -public void importRelationsAsync( - List excels, - String taskId, - String userName // 新增参数,用于审计 -) { - // 实现逻辑... -} -``` - -## 实施步骤 - -1. **添加 Mapper 批量查询方法** - - 在 Mapper 接口添加 `batchExistsByCombinations` - - 在 XML 实现 SQL - -2. **重构 ImportServiceImpl** - - 引入 `ImportLogUtils` - - 实现批量查询逻辑 - - 添加 Excel 内部重复检查 - - 优化 Redis 状态管理 - - 改进失败记录存储 - -3. **创建/优化 VO 类** - - 检查并复用已有的 `ImportStatusVO` - - 检查并复用已有的 `ImportResultVO` - - 优化失败记录 VO - -4. **调整 Controller** - - 修改导入接口返回值 - - 优化状态查询接口 - - 优化失败记录查询接口 - -5. **更新 Excel 实体** - - 添加 `@DictDropdown` 注解 - -6. **测试验证** - - 单元测试 - - 集成测试 - - 性能对比测试 - -## 预期效果 - -### 性能提升 -- 批量查询: 从 N 次减少到 1 次 -- 导入 1000 条数据预计提升 50-70% - -### 用户体验 -- 实时进度反馈 -- 详细的错误信息 -- 清晰的成功/失败统计 - -### 代码质量 -- 统一的日志记录 -- 完善的错误处理 -- 更好的可维护性 - -## 创建日期 - -2026-02-11