diff --git a/doc/api-docs/api/员工实体关系管理API文档.md b/doc/api-docs/api/员工实体关系管理API文档.md new file mode 100644 index 0000000..91f5528 --- /dev/null +++ b/doc/api-docs/api/员工实体关系管理API文档.md @@ -0,0 +1,484 @@ +# 员工实体关系管理 API 文档 + +## 概述 + +员工实体关系管理模块提供员工与企业关系的增删改查、批量导入导出功能。 + +**基础路径**: `/ccdi/staffEnterpriseRelation` + +**权限标识前缀**: `ccdi:staffEnterpriseRelation` + +**重要更新**: 自2026-02-11起,列表接口和详情接口响应中新增 `personName` 字段(员工姓名),该字段通过关联查询 `ccdi_base_staff` 表获取。 + +--- + +## API 接口列表 + +### 1. 查询员工实体关系列表 + +**接口地址**: `GET /ccdi/staffEnterpriseRelation/list` + +**权限要求**: `ccdi:staffEnterpriseRelation:list` + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| personId | String | 否 | 身份证号(精确查询) | +| socialCreditCode | String | 否 | 统一社会信用代码(精确查询) | +| status | Integer | 否 | 状态(0=无效, 1=有效) | +| pageNum | Integer | 否 | 页码(默认1) | +| pageSize | Integer | 否 | 每页数量(默认10) | + +**响应示例**: +```json +{ + "code": 200, + "msg": "查询成功", + "rows": [ + { + "id": 1, + "personId": "110101199001011234", + "personName": "张三", + "relationPersonPost": "法定代表人", + "socialCreditCode": "91110000MA000001XX", + "enterpriseName": "某某科技有限公司", + "status": 1, + "remark": "补充说明", + "dataSource": "人工导入", + "isEmployee": 1, + "isEmpFamily": 0, + "isCustomer": 1, + "isCustFamily": 0, + "createTime": "2026-02-09 10:00:00", + "updateTime": "2026-02-09 10:00:00", + "createdBy": "admin", + "updatedBy": "admin" + } + ], + "total": 1 +} +``` + +**响应字段说明**: + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| id | Long | 主键ID | +| personId | String | 身份证号 | +| personName | String | 员工姓名(通过关联查询获取) | +| relationPersonPost | String | 关联人在企业的职务 | +| socialCreditCode | String | 统一社会信用代码 | +| enterpriseName | String | 企业名称 | +| status | Integer | 状态(0=无效, 1=有效) | +| remark | String | 补充说明 | +| dataSource | String | 数据来源 | +| isEmployee | Integer | 是否为员工(0=否, 1=是) | +| isEmpFamily | Integer | 是否为员工家属(0=否, 1=是) | +| isCustomer | Integer | 是否为客户(0=否, 1=是) | +| isCustFamily | Integer | 是否为客户家属(0=否, 1=是) | +| createTime | Date | 创建时间 | +| updateTime | Date | 更新时间 | +| createdBy | String | 创建人 | +| updatedBy | String | 更新人 | + +**注意**: +- `personName` 字段通过 LEFT JOIN `ccdi_base_staff` 表获取 +- 如果 `personId` 在员工信息表中不存在,`personName` 为 `null` + +--- + +### 2. 查询员工实体关系详情 + +**接口地址**: `GET /ccdi/staffEnterpriseRelation/{id}` + +**权限要求**: `ccdi:staffEnterpriseRelation:query` + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| id | Long | 是 | 主键ID | + +**响应示例**: +```json +{ + "code": 200, + "msg": "操作成功", + "data": { + "id": 1, + "personId": "110101199001011234", + "personName": "张三", + "relationPersonPost": "法定代表人", + "socialCreditCode": "91110000MA000001XX", + "enterpriseName": "某某科技有限公司", + "status": 1, + "remark": "补充说明", + "dataSource": "人工导入", + "isEmployee": 1, + "isEmpFamily": 0, + "isCustomer": 1, + "isCustFamily": 0, + "createTime": "2026-02-09 10:00:00", + "updateTime": "2026-02-09 10:00:00", + "createdBy": "admin", + "updatedBy": "admin" + } +} +``` + +**响应字段说明**: + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| id | Long | 主键ID | +| personId | String | 身份证号 | +| personName | String | 员工姓名(通过关联查询获取) | +| relationPersonPost | String | 关联人在企业的职务 | +| socialCreditCode | String | 统一社会信用代码 | +| enterpriseName | String | 企业名称 | +| status | Integer | 状态(0=无效, 1=有效) | +| remark | String | 补充说明 | +| dataSource | String | 数据来源 | +| isEmployee | Integer | 是否为员工(0=否, 1=是) | +| isEmpFamily | Integer | 是否为员工家属(0=否, 1=是) | +| isCustomer | Integer | 是否为客户(0=否, 1=是) | +| isCustFamily | Integer | 是否为客户家属(0=否, 1=是) | +| createTime | Date | 创建时间 | +| updateTime | Date | 更新时间 | +| createdBy | String | 创建人 | +| updatedBy | String | 更新人 | + +**注意**: +- `personName` 字段通过 LEFT JOIN `ccdi_base_staff` 表获取 +- 如果 `personId` 在员工信息表中不存在,`personName` 为 `null` + +--- + +### 3. 新增员工实体关系 + +**接口地址**: `POST /ccdi/staffEnterpriseRelation` + +**权限要求**: `ccdi:staffEnterpriseRelation:add` + +**请求头**: +``` +Content-Type: application/json +Authorization: Bearer {token} +``` + +**请求体**: +```json +{ + "personId": "110101199001011234", + "relationPersonPost": "法定代表人", + "socialCreditCode": "91110000MA000001XX", + "status": 1, + "remark": "补充说明", + "dataSource": "人工导入", + "isEmployee": 1, + "isEmpFamily": 0, + "isCustomer": 1, + "isCustFamily": 0 +} +``` + +**字段说明**: + +| 字段名 | 类型 | 必填 | 说明 | 校验规则 | +|--------|------|------|------|----------| +| personId | String | 是 | 身份证号 | 18位,符合国标 | +| relationPersonPost | String | 是 | 关联人在企业的职务 | 最大100字符 | +| socialCreditCode | String | 是 | 统一社会信用代码 | 18位 | +| status | Integer | 否 | 状态 | 0=无效, 1=有效, 默认1 | +| remark | String | 否 | 补充说明 | 最大500字符 | +| dataSource | String | 否 | 数据来源 | 最大100字符 | +| isEmployee | Integer | 否 | 是否为员工 | 0=否, 1=是 | +| isEmpFamily | Integer | 否 | 是否为员工家属 | 0=否, 1=是 | +| isCustomer | Integer | 否 | 是否为客户 | 0=否, 1=是 | +| isCustFamily | Integer | 否 | 是否为客户家属 | 0=否, 1=是 | + +**响应示例**: +```json +{ + "code": 200, + "msg": "操作成功" +} +``` + +--- + +### 4. 修改员工实体关系 + +**接口地址**: `PUT /ccdi/staffEnterpriseRelation` + +**权限要求**: `ccdi:staffEnterpriseRelation:edit` + +**请求头**: +``` +Content-Type: application/json +Authorization: Bearer {token} +``` + +**请求体**: +```json +{ + "id": 1, + "personId": "110101199001011234", + "relationPersonPost": "法定代表人", + "socialCreditCode": "91110000MA000001XX", + "status": 1, + "remark": "补充说明", + "dataSource": "人工导入", + "isEmployee": 1, + "isEmpFamily": 0, + "isCustomer": 1, + "isCustFamily": 0 +} +``` + +**字段说明**: + +| 字段名 | 类型 | 必填 | 说明 | 校验规则 | +|--------|------|------|------|----------| +| id | Long | 是 | 主键ID | 必填 | +| personId | String | 是 | 身份证号 | 18位,符合国标 | +| relationPersonPost | String | 是 | 关联人在企业的职务 | 最大100字符 | +| socialCreditCode | String | 是 | 统一社会信用代码 | 18位 | +| status | Integer | 否 | 状态 | 0=无效, 1=有效 | +| remark | String | 否 | 补充说明 | 最大500字符 | +| dataSource | String | 否 | 数据来源 | 最大100字符 | +| isEmployee | Integer | 否 | 是否为员工 | 0=否, 1=是 | +| isEmpFamily | Integer | 否 | 是否为员工家属 | 0=否, 1=是 | +| isCustomer | Integer | 否 | 是否为客户 | 0=否, 1=是 | +| isCustFamily | Integer | 否 | 是否为客户家属 | 0=否, 1=是 | + +**响应示例**: +```json +{ + "code": 200, + "msg": "操作成功" +} +``` + +--- + +### 5. 删除员工实体关系 + +**接口地址**: `DELETE /ccdi/staffEnterpriseRelation/{ids}` + +**权限要求**: `ccdi:staffEnterpriseRelation:remove` + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| ids | Long[] | 是 | 主键ID数组(多个ID用逗号分隔) | + +**响应示例**: +```json +{ + "code": 200, + "msg": "操作成功" +} +``` + +--- + +### 6. 导出员工实体关系 + +**接口地址**: `POST /ccdi/staffEnterpriseRelation/export` + +**权限要求**: `ccdi:staffEnterpriseRelation:export` + +**请求参数**: 与列表查询参数相同 + +**响应**: Excel文件流 + +--- + +### 7. 下载导入模板 + +**接口地址**: `POST /ccdi/staffEnterpriseRelation/importTemplate` + +**权限要求**: 无 + +**响应**: Excel模板文件流(包含字典下拉框) + +**模板字段说明**: + +| 字段名 | 说明 | 是否必填 | 数据类型 | 示例值 | +|--------|------|----------|----------|--------| +| 身份证号 | 18位身份证号 | 是 | 文本 | 110101199001011234 | +| 关联人在企业的职务 | 职务名称 | 是 | 文本 | 法定代表人 | +| 统一社会信用代码 | 18位社会信用代码 | 是 | 文本 | 91110000MA000001XX | +| 状态 | 有效/无效 | 否 | 下拉选择 | 有效 | +| 补充说明 | 备注信息 | 否 | 文本 | - | +| 数据来源 | 数据来源 | 否 | 文本 | 人工导入 | +| 是否为员工 | 是/否 | 否 | 下拉选择 | 是 | +| 是否为员工家属 | 是/否 | 否 | 下拉选择 | 否 | +| 是否为客户 | 是/否 | 否 | 下拉选择 | 是 | +| 是否为客户家属 | 是/否 | 否 | 下拉选择 | 否 | + +--- + +### 8. 异步导入员工实体关系 + +**接口地址**: `POST /ccdi/staffEnterpriseRelation/importData` + +**权限要求**: `ccdi:staffEnterpriseRelation:import` + +**请求头**: +``` +Content-Type: multipart/form-data +Authorization: Bearer {token} +``` + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| file | File | 是 | Excel文件 | + +**响应示例**: +```json +{ + "code": 200, + "msg": "导入任务已提交,正在后台处理", + "data": { + "taskId": "import-task-20260209-100000", + "status": "PROCESSING", + "message": "导入任务已提交,正在后台处理" + } +} +``` + +**导入流程说明**: +1. 接口立即返回,不等待后台任务完成 +2. 通过 `taskId` 查询导入进度 +3. 导入完成后可查询失败记录 + +--- + +### 9. 查询导入状态 + +**接口地址**: `GET /ccdi/staffEnterpriseRelation/importStatus/{taskId}` + +**权限要求**: `ccdi:staffEnterpriseRelation:import` + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| taskId | String | 是 | 任务ID(从导入接口获取) | + +**响应示例**: +```json +{ + "code": 200, + "msg": "操作成功", + "data": { + "taskId": "import-task-20260209-100000", + "status": "COMPLETED", + "totalCount": 100, + "successCount": 95, + "failureCount": 5, + "message": "导入完成" + } +} +``` + +**状态说明**: +- `PROCESSING`: 处理中 +- `COMPLETED`: 已完成 +- `FAILED`: 失败 + +--- + +### 10. 查询导入失败记录 + +**接口地址**: `GET /ccdi/staffEnterpriseRelation/importFailures/{taskId}` + +**权限要求**: `ccdi:staffEnterpriseRelation:import` + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| taskId | String | 是 | 任务ID | + +**查询参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| pageNum | Integer | 否 | 页码(默认1) | +| pageSize | Integer | 否 | 每页数量(默认10) | + +**响应示例**: +```json +{ + "code": 200, + "msg": "查询成功", + "rows": [ + { + "rowNum": 5, + "personId": "110101199001011235", + "relationPersonPost": "法定代表人", + "socialCreditCode": "91110000MA000001XX", + "errorMessage": "身份证号格式不正确" + } + ], + "total": 5 +} +``` + +**失败记录字段说明**: + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| rowNum | Integer | 行号 | +| personId | String | 身份证号 | +| relationPersonPost | String | 关联人在企业的职务 | +| socialCreditCode | String | 统一社会信用代码 | +| errorMessage | String | 错误信息 | + +--- + +## 数据字典 + +### 状态(status) + +| 值 | 说明 | +|----|------| +| 0 | 无效 | +| 1 | 有效 | + +### 是否标志(isEmployee/isEmpFamily/isCustomer/isCustFamily) + +| 值 | 说明 | +|----|------| +| 0 | 否 | +| 1 | 是 | + +--- + +## 错误码说明 + +| 错误码 | 说明 | +|--------|------| +| 200 | 操作成功 | +| 401 | 未授权,请先登录 | +| 403 | 无权限访问 | +| 500 | 服务器内部错误 | + +--- + +## 更新日志 + +### 2026-02-11 +- 新增: 在列表接口和详情接口响应中添加 `personName` 字段(员工姓名) +- 优化: 通过 LEFT JOIN `ccdi_base_staff` 表获取员工姓名 +- 注意: 如果 `personId` 在员工信息表中不存在,`personName` 为 `null` + +### 2026-02-09 +- 初始版本: 完成员工实体关系管理基础功能 diff --git a/doc/plans/2026-02-11-staff-enterprise-relation-person-name-design.md b/doc/plans/2026-02-11-staff-enterprise-relation-person-name-design.md new file mode 100644 index 0000000..0b59bb9 --- /dev/null +++ b/doc/plans/2026-02-11-staff-enterprise-relation-person-name-design.md @@ -0,0 +1,306 @@ +# 员工实体关系添加员工名称字段设计 + +## 1. 需求概述 + +在员工实体关系列表和详情中添加员工名称字段,通过身份证号(personId)关联员工信息表(ccdi_base_staff)获取姓名。 + +**涉及模块:** 员工实体关系 (ccdi_staff_enterprise_relation) + +**展示位置:** +- 列表页面 (table 列) +- 详情接口返回 + +**数据来源:** 通过 personId 关联 ccdi_base_staff 表的 id_card 字段获取 name 字段 + +**空值处理:** 当 personId 在员工信息表中不存在时,显示为空 + +## 2. 技术方案 + +采用 MyBatis 关联查询(JOIN)方式,在查询时动态获取员工姓名,不修改表结构。 + +### 2.1 优势 + +- ✅ 无需修改数据库表结构 +- ✅ 数据始终与员工信息表同步 +- ✅ 实施简单,风险低 +- ✅ 性能影响可控 + +## 3. 数据库层设计 + +### 3.1 SQL查询改造 + +在 `CcdiStaffEnterpriseRelationMapper.xml` 中修改列表查询和详情查询: + +```sql +SELECT + ser.id, + ser.person_id, + bs.name AS person_name, -- 通过JOIN获取员工姓名 + ser.relation_person_post, + ser.social_credit_code, + ser.enterprise_name, + ser.status, + ser.remark, + ser.data_source, + ser.is_employee, + ser.is_emp_family, + ser.is_customer, + ser.is_cust_family, + ser.create_time, + ser.update_time, + ser.created_by, + ser.updated_by +FROM ccdi_staff_enterprise_relation ser +LEFT JOIN ccdi_base_staff bs ON ser.person_id = bs.id_card +WHERE ser.status = 1 +``` + +### 3.2 关键点 + +- 使用 `LEFT JOIN` 确保即使员工信息不存在,关系记录也会返回 +- 当 `personId` 在 `ccdi_base_staff` 中不存在时,`person_name` 为 NULL +- 数据库表结构不需要修改 + +### 3.3 索引优化 + +确保 `ccdi_base_staff.id_card` 字段有索引: + +```sql +-- 检查索引 +SHOW INDEX FROM ccdi_base_staff WHERE Key_name = 'idx_id_card'; + +-- 如果没有索引,创建 +CREATE INDEX idx_id_card ON ccdi_base_staff(id_card); +``` + +## 4. 后端代码层设计 + +### 4.1 VO层修改 + +**文件:** `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/vo/CcdiStaffEnterpriseRelationVO.java` + +```java +/** 身份证号 */ +@Schema(description = "身份证号") +private String personId; + +/** 员工姓名 */ +@Schema(description = "员工姓名") +private String personName; + +/** 关联人在企业的职务 */ +@Schema(description = "关联人在企业的职务") +private String relationPersonPost; +``` + +### 4.2 Mapper接口 + +**文件:** `ruoyi-ccdi/src/main/resources/mapper/ccdi/CcdiStaffEnterpriseRelationMapper.xml` + +修改查询方法,添加 LEFT JOIN: + +```xml + +``` + +同样修改 `selectRelationById` 方法。 + +### 4.3 Service层 + +`ICcdiStaffEnterpriseRelationService.java` 和实现类无需大改,MyBatis Plus 会自动填充 JOIN 的字段。 + +## 5. 前端代码层设计 + +### 5.1 列表页面修改 + +**文件:** `ruoyi-ui/src/views/ccdiStaffEnterpriseRelation/index.vue` + +在表格列定义中添加员工姓名列: + +```vue + + + +``` + +**位置建议:** 放在"身份证号"列之后,方便用户对照查看 + +### 5.2 API接口 + +**文件:** `ruoyi-ui/src/api/ccdiStaffEnterpriseRelation.js` + +无需修改,接口会自动返回新增的 `personName` 字段。 + +### 5.3 详情页面 + +如果存在详情对话框,同样添加员工姓名显示: + +```vue +{{ form.personId }} +{{ form.personName }} +{{ form.relationPersonPost }} +``` + +## 6. 错误处理和边界情况 + +### 6.1 数据空值处理 + +- **场景**: personId 在 `ccdi_base_staff` 表中不存在 +- **处理**: `personName` 为 NULL,前端显示为空字符串 +- **前端展示**: Element UI table 会自动将 null 显示为空 + +### 6.2 数据一致性 + +- **场景**: 员工信息表的姓名后续被修改 +- **影响**: 下次查询时自动获取最新姓名,无需同步 +- **优势**: JOIN 方案天然保证数据一致性 + +### 6.3 性能考虑 + +- **索引**: 确保 `ccdi_base_staff.id_card` 字段有索引 +- **查询优化**: LEFT JOIN 对性能影响较小 +- **分页**: 已有分页机制,单页数据量有限 + +### 6.4 特殊字符处理 + +- 员工姓名可能包含特殊字符,MyBatis 和 JSON 序列化会自动处理 +- 无需额外转义逻辑 + +## 7. 测试策略 + +### 7.1 单元测试 + +创建测试用例覆盖以下场景: + +```java +// 测试场景1: 员工信息存在 +assertEquals("张三", result.getPersonName()); + +// 测试场景2: 员工信息不存在 +assertNull(result.getPersonName()); + +// 测试场景3: 姓名包含特殊字符 +assertEquals("张三·李四", result.getPersonName()); + +// 测试场景4: 批量数据性能测试 +List list = mapper.selectRelationList(query); +assertTrue(list.size() > 0); +``` + +### 7.2 接口测试 + +使用测试脚本验证: +- 列表接口返回 `personName` 字段 +- 详情接口返回 `personName` 字段 +- 分页查询正常工作 +- 空值处理正确 + +### 7.3 前端测试 + +手动验证: +- 列表页面正确显示员工姓名 +- 空值显示为空 +- 列表排序、筛选功能正常 + +### 7.4 数据准备测试 + +准备测试数据: +- 已有员工的关系记录 +- 无对应员工的关系记录 +- 批量数据的性能测试 + +## 8. 实施步骤 + +### 步骤1: 修改 VO 类 +- 文件: `CcdiStaffEnterpriseRelationVO.java` +- 添加 `personName` 字段及注解 + +### 步骤2: 修改 Mapper XML +- 文件: `CcdiStaffEnterpriseRelationMapper.xml` +- 修改列表查询和详情查询,添加 LEFT JOIN + +### 步骤3: 修改前端列表页 +- 文件: `ruoyi-ui/src/views/ccdiStaffEnterpriseRelation/index.vue` +- 在表格中添加员工姓名列 + +### 步骤4: 检查数据库索引 +- 检查 `ccdi_base_staff.id_card` 是否有索引 +- 如果没有,执行创建索引 SQL + +### 步骤5: 测试验证 +- 运行后端,测试接口返回 +- 运行前端,验证页面显示 +- 生成测试报告 + +### 步骤6: 更新文档 +- 更新 API 文档 +- 更新数据库设计文档 + +## 9. 影响范围 + +**修改文件:** +- 后端: 2个文件 (VO + Mapper XML) +- 前端: 1个文件 (列表页面) +- 数据库: 0个表结构修改 + +**涉及模块:** +- 员工实体关系 (ccdi_staff_enterprise_relation) + +**风险评估:** +- 低风险: 仅查询层面的改动,不影响数据写入 +- 性能影响可控: 通过索引优化 +- 兼容性好: 新增字段不影响现有功能 + +## 10. 后续优化建议 + +### 10.1 缓存优化 + +如果员工信息表数据量大且变动不频繁,可以考虑: +- 使用 Redis 缓存员工信息 +- 减少数据库查询次数 + +### 10.2 搜索增强 + +可以支持按员工姓名搜索关系记录: +- 在查询条件中添加姓名搜索 +- 需要修改查询 DTO 和 Mapper XML + +### 10.3 其他模块 + +如果其他模块也有类似需求,可以复用此方案: +- 员工亲属关系 (ccdi_staff_fmy_relation) - 已有 personName 字段 +- 员工招聘 (ccdi_staff_recruitment) +- 员工调动 (ccdi_staff_transfer) diff --git a/doc/plans/2026-02-11-staff-relation-import-person-id-validation-design.md b/doc/plans/2026-02-11-staff-relation-import-person-id-validation-design.md new file mode 100644 index 0000000..5cc8655 --- /dev/null +++ b/doc/plans/2026-02-11-staff-relation-import-person-id-validation-design.md @@ -0,0 +1,428 @@ +# 员工关系导入身份证号校验设计文档 + +**日期**: 2026-02-11 +**状态**: 设计完成 +**优先级**: 中 + +--- + +## 1. 需求概述 + +### 1.1 背景 +当前员工实体关系和员工亲属关系的导入功能在导入数据时,没有验证员工身份证号是否在员工信息表中存在。这可能导致导入的数据引用了不存在的员工,造成数据完整性问题。 + +### 1.2 目标 +在员工实体关系和员工亲属关系的导入过程中,添加员工身份证号存在性校验: +- 验证员工身份证号是否在 `ccdi_base_staff` 表中存在 +- 不存在的身份证号记录错误信息并跳过 +- 继续处理其他有效数据 + +### 1.3 约束条件 +- 仅验证员工身份证号(`person_id`)存在性,不验证关系人身份证号 +- 不验证员工状态(在职/离职) +- 错误信息需要包含Excel行号 +- 与现有的导入流程保持一致(失败记录保存到Redis) + +### 1.4 优化范围 +同时优化员工调动导入的身份证号验证逻辑,从2次遍历优化为1次遍历。 + +--- + +## 2. 架构设计 + +### 2.1 整体架构 + +在两个导入服务中添加**员工身份证号批量预验证阶段**,流程如下: + +``` +导入流程: +1. 批量查询已存在的身份证号(新增)⭐ +2. 数据处理循环(原有,修改) + └─ 循环开始时检查身份证号是否存在(新增) +3. 批量插入新数据(原有) +4. 保存失败记录到Redis(原有) +5. 更新导入状态(原有) +``` + +### 2.2 新增组件 + +#### 2.2.1 依赖注入 +在三个导入服务中添加: +```java +@Resource +private CcdiBaseStaffMapper baseStaffMapper; +``` + +#### 2.2.2 核心逻辑 +- **位置**: 在数据处理循环之前 +- **功能**: 批量查询所有Excel中出现的身份证号,构建存在性集合 +- **输入**: Excel数据列表、任务ID +- **输出**: Set 存在的身份证号集合 + +### 2.3 影响的服务 + +| 服务 | 文件 | 说明 | +|------|------|------| +| 员工实体关系导入 | `CcdiStaffEnterpriseRelationImportServiceImpl.java` | 添加身份证号验证 | +| 员工亲属关系导入 | `CcdiStaffFmyRelationImportServiceImpl.java` | 添加身份证号验证 | +| 员工调动导入 | `CcdiStaffTransferImportServiceImpl.java` | 优化为1次遍历 | + +--- + +## 3. 数据流设计 + +### 3.1 详细流程 + +``` +阶段1: 提取身份证号 +├─ 从 excelList 提取所有 personId +├─ 过滤 null 值和空字符串 +├─ HashSet 去重 +└─ 得到 Set allPersonIds + +阶段2: 批量查询 +├─ 如果 allPersonIds 为空,返回空集合 +├─ 构建查询: SELECT id_card FROM ccdi_base_staff WHERE id_card IN (...) +├─ 执行: baseStaffMapper.selectList(wrapper) +├─ 提取结果中的 idCard +└─ 得到 Set existingPersonIds + +阶段3: 数据处理循环(原有循环增强) +├─ 遍历 excelList(行号 1-based,i为索引) +│ ├─ 【新增】首先检查: personId 是否在 existingPersonIds 中? +│ │ ├─ 如果不在: +│ │ │ ├─ 创建 ImportFailureVO 对象 +│ │ │ ├─ 错误信息: "第{i+1}行: 身份证号[{personId}]不存在于员工信息表中" +│ │ │ ├─ 添加到 failures 列表 +│ │ │ ├─ 记录验证失败日志 +│ │ │ └─ continue(跳过后续处理) +│ │ └─ 如果在:继续执行原有逻辑 +│ ├─ 转换并验证数据(原有) +│ ├─ 检查重复(原有) +│ └─ 添加到 newRecords(原有) +``` + +### 3.2 错误信息格式 + +```java +String errorMessage = String.format("第%d行: 身份证号[%s]不存在于员工信息表中", + rowNumber, personId); +``` + +### 3.3 日志记录 + +使用 `ImportLogUtils` 记录: +- 批量查询开始: `logBatchQueryStart(log, taskId, "员工身份证号", count)` +- 批量查询完成: `logBatchQueryComplete(log, taskId, "员工身份证号", count)` +- 验证失败: `logValidationError(log, taskId, rowNumber, errorMessage, keyData)` + +--- + +## 4. 边界情况处理 + +### 4.1 personId 为 null 或空字符串 +- 在提取阶段过滤掉: `.filter(StringUtils::isNotEmpty)` +- 这些记录会在原有的 `validateRelationData` 方法中报错 + +### 4.2 Excel 为空或所有 personId 为空 +- `allPersonIds` 为空集合 +- 直接返回空集合,跳过批量查询 +- 所有记录会在后续验证中报错 + +### 4.3 所有身份证号都不存在 +- `existingPersonIds` 为空集合 +- 所有记录都会在第一次检查时抛出异常 +- `newRecords` 保持为空 +- 最终状态: `PARTIAL_SUCCESS` + +### 4.4 Excel 中有重复身份证号 +- 使用 HashSet 去重,只查询一次 +- 每行独立检查,如果不存在则各自生成失败记录 + +### 4.5 数据库中没有员工记录 +- `baseStaffMapper.selectList` 返回空列表 +- 所有 Excel 行都会在检查时失败 + +### 4.6 身份证号格式错误 +- 先检查身份证号是否存在 +- 如果不存在,直接报错"身份证号不存在" +- 如果存在但格式错误,在后续的 `validateRelationData` 中会报错 + +--- + +## 5. 性能分析 + +### 5.1 时间复杂度 +- 提取身份证号: O(n),n为Excel行数 +- 数据库查询: O(m),m为不重复身份证号数量 +- 数据处理循环: O(n) +- **总计: O(n)**,线性复杂度 + +### 5.2 空间复杂度 +- `allPersonIds`: 约 20字节 × m +- `existingPersonIds`: 约 20字节 × m +- **总计: 约 40KB / 1000个不重复身份证号** + +### 5.3 数据库查询 +- 查询次数: **仅1次** +- 查询类型: `SELECT id_card FROM ccdi_base_staff WHERE id_card IN (...)` +- 索引: `id_card` 字段需要添加索引 + +### 5.4 性能对比 + +| 方案 | 数据库查询次数 | 1000行耗时 | 10000行耗时 | +|------|---------------|-----------|------------| +| 批量预验证(本设计) | 1次 | ~50ms | ~200ms | +| 逐条验证 | N次 | ~5000ms | ~50000ms | + +**结论**: 批量预验证方案性能提升约**100倍**。 + +--- + +## 6. 代码实现 + +### 6.1 员工实体关系导入服务 + +**文件**: `CcdiStaffEnterpriseRelationImportServiceImpl.java` + +#### 6.1.1 添加依赖注入(第44行后) +```java +@Resource +private CcdiBaseStaffMapper baseStaffMapper; +``` + +#### 6.1.2 在 importRelationAsync 方法中(第55行后),添加批量查询: +```java +// 【新增】批量验证员工身份证号是否存在 +Set excelPersonIds = excelList.stream() + .map(CcdiStaffEnterpriseRelationExcel::getPersonId) + .filter(StringUtils::isNotEmpty) + .collect(Collectors.toSet()); + +Set existingPersonIds = new HashSet<>(); +if (!excelPersonIds.isEmpty()) { + ImportLogUtils.logBatchQueryStart(log, taskId, "员工身份证号", excelPersonIds.size()); + + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.select(CcdiBaseStaff::getIdCard) + .in(CcdiBaseStaff::getIdCard, excelPersonIds); + + List existingStaff = baseStaffMapper.selectList(wrapper); + existingPersonIds = existingStaff.stream() + .map(CcdiBaseStaff::getIdCard) + .collect(Collectors.toSet()); + + ImportLogUtils.logBatchQueryComplete(log, taskId, "员工身份证号", existingPersonIds.size()); +} +``` + +#### 6.1.3 在数据处理循环开始处(第71行后),添加检查: +```java +try { + // 【新增】身份证号存在性检查 + if (!existingPersonIds.contains(excel.getPersonId())) { + throw new RuntimeException(String.format( + "第%d行: 身份证号[%s]不存在于员工信息表中", + i + 1, excel.getPersonId())); + } + + // 原有逻辑继续... + CcdiStaffEnterpriseRelationAddDTO addDTO = new CcdiStaffEnterpriseRelationAddDTO(); + BeanUtils.copyProperties(excel, addDTO); + // ... +} +``` + +### 6.2 员工亲属关系导入服务 + +**文件**: `CcdiStaffFmyRelationImportServiceImpl.java` + +相同的修改步骤: +1. 添加 `CcdiBaseStaffMapper` 依赖注入 +2. 在第57行后添加批量查询身份证号逻辑 +3. 在第96行后(数据处理循环开始处)添加身份证号检查 + +### 6.3 员工调动导入服务优化 + +**文件**: `CcdiStaffTransferImportServiceImpl.java` + +**优化前**: 2次遍历(预验证 + 主循环) +**优化后**: 1次遍历(主循环中检查) + +**修改步骤**: +1. 移除 `batchValidateStaffIds` 方法 +2. 移除 `isRowAlreadyFailed` 方法 +3. 在主循环开始处添加员工ID存在性检查 +4. 使用 `existingStaffIds` 替代原有的预验证逻辑 + +### 6.4 需要导入的类 +```java +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.ruoyi.ccdi.mapper.CcdiBaseStaffMapper; +import com.ruoyi.ccdi.domain.CcdiBaseStaff; +import java.util.HashSet; +import java.util.stream.Collectors; +``` + +--- + +## 7. 测试场景 + +### 7.1 功能测试用例 + +| 场景 | 输入 | 预期结果 | +|------|------|----------| +| 正常导入 | 5条有效身份证号 | 全部成功,failures为空 | +| 部分无效 | 3条有效 + 2条无效身份证号 | 3条成功,2条失败 | +| 全部无效 | 5条全部无效身份证号 | 0条成功,5条失败 | +| 身份证号为null | 包含null或空字符串 | 在后续验证中报错 | +| 大批量数据 | 1000条记录,全部有效 | 仅1次查询,全部成功 | +| 重复身份证号 | 10条记录,3个不同身份证号 | 去重查询,正确验证 | +| 混合场景 | 有效、无效、null、重复 | 各自正确处理 | + +### 7.2 员工实体关系导入专项测试 + +``` +测试数据1: 有效身份证号 +personId: "110101199001011234" (存在于ccdi_base_staff) +预期: 导入成功 + +测试数据2: 无效身份证号 +personId: "999999999999999999" (不存在于ccdi_base_staff) +预期: 导入失败,错误信息: "第N行: 身份证号[xxx]不存在于员工信息表中" +``` + +### 7.3 员工亲属关系导入专项测试 + +``` +测试数据1: 有效员工身份证号 +personId: "110101199001011234" (存在) +relationCertNo: "110101199001011235" (可以不存在) +预期: 导入成功 + +测试数据2: 无效员工身份证号 +personId: "999999999999999999" (不存在) +预期: 导入失败 +``` + +### 7.4 性能测试 + +| 数据量 | 预期查询次数 | 预期耗时 | 内存占用 | +|--------|------------|---------|---------| +| 100条 | 1次 | < 20ms | < 10KB | +| 1000条 | 1次 | < 100ms | < 50KB | +| 10000条 | 1次 | < 500ms | < 500KB | + +--- + +## 8. 影响范围和实施计划 + +### 8.1 影响的文件 + +| 文件 | 修改类型 | 说明 | +|------|----------|------| +| `CcdiStaffEnterpriseRelationImportServiceImpl.java` | 修改 | 添加员工身份证号验证 | +| `CcdiStaffFmyRelationImportServiceImpl.java` | 修改 | 添加员工身份证号验证 | +| `CcdiStaffTransferImportServiceImpl.java` | 优化 | 从2次遍历优化为1次遍历 | + +### 8.2 不影响的组件 + +- ✅ Controller层(无需修改) +- ✅ 前端页面(无需修改) +- ✅ 数据库表结构(无需修改) +- ✅ Mapper接口(无需修改) +- ✅ VO/DTO/Excel类(无需修改) + +### 8.3 数据库索引建议 + +```sql +-- 检查索引是否存在 +SHOW INDEX FROM ccdi_base_staff WHERE Column_name = 'id_card'; + +-- 如果不存在,创建索引 +CREATE INDEX idx_ccdi_base_staff_id_card ON ccdi_base_staff(id_card); +``` + +### 8.4 实施步骤 + +1. ✅ 完成设计方案 +2. ⏳ 修改 `CcdiStaffEnterpriseRelationImportServiceImpl` +3. ⏳ 修改 `CcdiStaffFmyRelationImportServiceImpl` +4. ⏳ 优化 `CcdiStaffTransferImportServiceImpl` +5. ⏳ 检查并创建数据库索引(如需要) +6. ⏳ 编写单元测试 +7. ⏳ 本地测试验证 +8. ⏳ 更新API文档(如需要) + +### 8.5 验收标准 + +- [ ] 不存在的员工身份证号被正确识别并记录错误 +- [ ] 错误信息包含正确的行号和身份证号 +- [ ] 有效数据正常导入 +- [ ] 日志记录完整 +- [ ] 性能无明显下降(批量查询仅1次) +- [ ] 与现有导入逻辑保持一致 +- [ ] 三个导入服务功能一致 + +### 8.6 风险评估 + +- **风险等级**: 低 +- **影响范围**: 仅影响导入功能,不影响其他模块 +- **回滚方案**: 直接移除新增的验证代码即可 +- **数据安全**: 只读查询,无数据风险 + +--- + +## 9. 设计总结 + +### 9.1 核心优势 + +1. **性能优异**: 批量查询,仅1次数据库访问 +2. **代码简洁**: 仅1次遍历,逻辑清晰 +3. **一致性高**: 三个导入服务使用相同的设计模式 +4. **易于维护**: 遵循现有框架规范 + +### 9.2 与原员工调动导入设计的对比 + +| 对比项 | 原设计 | 新设计 | +|--------|--------|--------| +| 遍历次数 | 2次 | **1次** ⭐ | +| 代码复杂度 | 需要额外方法 | 更简洁 | +| 性能 | 优秀 | **更优** | +| 可维护性 | 良好 | **更好** | + +### 9.3 设计亮点 + +- ✅ **批量预验证**: 充分利用数据库 IN 查询性能 +- ✅ **单次遍历**: 减少不必要的循环,代码更清晰 +- ✅ **统一模式**: 三个导入服务使用一致的验证逻辑 +- ✅ **错误友好**: 详细的错误信息包含行号 +- ✅ **性能监控**: 完整的日志记录便于排查问题 + +--- + +## 10. 附录 + +### 10.1 相关文档 + +- [员工调动导入员工ID校验设计文档](2026-02-11-staff-transfer-import-staff-id-validation-design.md) +- [若依框架导入功能说明](https://doc.ruoyi.vip/) +- [MyBatis Plus 官方文档](https://baomidou.com/) + +### 10.2 设计决策记录 + +**Q1: 为什么选择批量预验证而非逐条验证?** +A: 批量验证只需1次数据库查询,性能提升约100倍。 + +**Q2: 为什么优化为1次遍历?** +A: 减少不必要的循环,代码更简洁,性能更好。 + +**Q3: 为什么不验证员工在职状态?** +A: 需求明确仅验证身份证号存在性,避免过度设计。 + +**Q4: 为什么不验证关系人身份证号?** +A: 关系人可能不是系统员工,验证会限制使用场景。 + +### 10.3 版本历史 + +- v1.0 (2026-02-11): 初始设计版本,包含三个导入服务的身份证号校验 diff --git a/doc/reviews/2026-02-11-task-2-code-review.md b/doc/reviews/2026-02-11-task-2-code-review.md new file mode 100644 index 0000000..17e2b4e --- /dev/null +++ b/doc/reviews/2026-02-11-task-2-code-review.md @@ -0,0 +1,399 @@ +# Task 2 代码质量审查报告 + +**审查日期**: 2026-02-11 +**审查者**: 代码质量审查者子代理 +**实施者子代理提交**: SHA 17edc720 +**分支**: feat/staff-enterprise-relation-person-name +**任务**: 修改 VO 类添加员工姓名字段 + +--- + +## 执行摘要 + +**审查结果**: ⚠️ **需要修复 (Needs Fixes)** + +**评分**: 65/100 + +**关键发现**: +- ✅ VO类代码规范符合要求 +- ❌ **Critical**: Mapper XML未同步更新 - ResultMap缺少personName映射 +- ❌ **Critical**: SQL查询未关联ccdi_base_staff表获取员工姓名 +- ⚠️ **Important**: 功能不完整 - 无法实现按姓名搜索的业务需求 + +--- + +## 1. 优势 (Strengths) + +### 1.1 VO类代码质量 ✅ + +**文件**: `CcdiStaffEnterpriseRelationVO.java` + +```java +/** 员工姓名 */ +@Schema(description = "员工姓名") +private String personName; +``` + +**优点**: +- ✅ 字段命名规范:使用驼峰命名法 `personName` +- ✅ 注释清晰:中文注释说明字段用途 +- ✅ Swagger注解正确:`@Schema(description = "员工姓名")` 用于API文档 +- ✅ 字段类型合理:使用 `String` 类型存储姓名 +- ✅ 位置正确:紧跟在 `personId` 字段之后,符合逻辑关联性 +- ✅ 符合若依框架规范:与项目中其他VO类风格一致 + +### 1.2 Git提交质量 ✅ + +```bash +commit 17edc7208d31ef8c2ac2479c1d04279a6c4a74ab +Author: wkc <978997012@qq.com> +Date: Wed Feb 11 14:40:29 2026 +0800 + + feat(staff-enterprise-relation): 添加员工姓名字段到VO +``` + +**优点**: +- ✅ 提交信息清晰:准确描述了变更内容 +- ✅ 符合 Conventional Commits 规范:使用 `feat` 类型 +- ✅ 添加了作用域:`(staff-enterprise-relation)` +- ✅ 变更粒度合理:单次提交只修改一个文件 +- ✅ 影响范围可控:仅修改VO类,4行新增代码 + +### 1.3 符合Java编码规范 ✅ + +- ✅ 符合Java命名规范(驼峰命名) +- ✅ 符合若依框架编码规范 +- ✅ 注释风格与项目保持一致 +- ✅ 使用Lombok `@Data` 注解 + +--- + +## 2. 问题 (Issues) + +### 2.1 Critical - Mapper XML未同步更新 ❌ + +**问题位置**: `CcdiStaffEnterpriseRelationMapper.xml` + +**问题描述**: +VO类添加了 `personName` 字段,但对应的 ResultMap 和 SQL 查询完全未更新,导致: + +1. **ResultMap缺少映射**: +```xml + + + + + + + ... + +``` + +2. **SQL查询未关联员工表**: +```xml + + +``` + +**影响**: +- ❌ `personName` 字段永远为 `null` +- ❌ 无法实现按员工姓名搜索的业务需求 +- ❌ VO类字段与实际查询结果不匹配 + +**参考正确实现**: + +项目中 `CcdiStaffFmyRelationMapper.xml` 的正确实现: +```xml + + + ... + + + ... + + + + +``` + +**修复建议**: +1. 在 `CcdiStaffEnterpriseRelationVOResult` 的 ResultMap 中添加: +```xml + +``` + +2. 修改所有 SQL 查询语句,添加表关联: +```sql +FROM ccdi_staff_enterprise_relation r +LEFT JOIN ccdi_base_staff s ON r.person_id = s.id_card +``` + +3. 在 SELECT 子句中添加: +```sql +s.name as person_name +``` + +4. 支持按姓名搜索(在 `` 中添加): +```xml + + AND s.name LIKE CONCAT('%', #{query.personName}, '%') + +``` + +--- + +### 2.2 Critical - QueryDTO缺少personName字段 ❌ + +**问题位置**: `CcdiStaffEnterpriseRelationQueryDTO.java` + +**问题描述**: +如果需要支持按员工姓名搜索,QueryDTO 也需要添加对应的字段。 + +**参考正确实现**: + +`CcdiStaffFmyRelationQueryDTO.java`: +```java +/** 员工身份证号 */ +@Schema(description = "员工身份证号") +private String personId; + +/** 员工姓名 */ // ✅ QueryDTO 也有此字段 +@Schema(description = "员工姓名") +private String personName; +``` + +**修复建议**: +在 `CcdiStaffEnterpriseRelationQueryDTO` 中添加: +```java +/** 员工姓名 */ +@Schema(description = "员工姓名") +private String personName; +``` + +--- + +### 2.3 Important - 缺少数据库表关联说明 ⚠️ + +**问题描述**: +虽然VO类添加了字段,但缺少以下说明: + +1. `personName` 字段的数据来源:通过 `person_id` 关联 `ccdi_base_staff.id_card` 获取 `ccdi_base_staff.name` +2. 这是一个**计算字段**(非持久化字段),仅用于查询展示 +3. 数据库表 `ccdi_staff_enterprise_relation` 不需要添加 `person_name` 列 + +**建议**: +在VO类字段注释中添加更详细的说明: +```java +/** 员工姓名(关联字段,通过person_id关联ccdi_base_staff表获取) */ +@Schema(description = "员工姓名") +private String personName; +``` + +--- + +## 3. 建议改进 (Suggestions) + +### 3.1 Optional - 添加字段验证注解 + +如果需要在QueryDTO中支持姓名搜索,可以添加验证注解: +```java +/** 员工姓名 */ +@Schema(description = "员工姓名") +@Size(max = 100, message = "员工姓名长度不能超过100个字符") +private String personName; +``` + +### 3.2 Optional - 考虑添加Excel导出字段 + +如果需要在Excel导出中显示员工姓名,需要在 `CcdiStaffEnterpriseRelationExcel.java` 中也添加相应字段。 + +### 3.3 Optional - 添加单元测试 + +建议添加单元测试验证: +1. 当 `person_id` 在 `ccdi_base_staff` 表中存在时,能正确获取 `person_name` +2. 当 `person_id` 不存在或为 `null` 时,`person_name` 应为 `null` 而非抛出异常 + +--- + +## 4. 对比参考实现 + +### 4.1 员工亲属关系模块(正确实现)✅ + +**文件**: `CcdiStaffFmyRelationMapper.xml` + +| 方面 | 实现方式 | +|------|----------| +| **ResultMap** | 包含 `` | +| **SQL关联** | `LEFT JOIN ccdi_base_staff s ON r.person_id = s.id_card` | +| **字段查询** | `s.name as person_name` | +| **搜索支持** | `AND s.name LIKE CONCAT('%', #{query.personName}, '%')` | +| **QueryDTO** | 包含 `personName` 字段 | + +### 4.2 员工调动模块(正确实现)✅ + +**文件**: `CcdiStaffTransferMapper.xml` + +| 方面 | 实现方式 | +|------|----------| +| **ResultMap** | 包含 `` | +| **SQL关联** | `LEFT JOIN ccdi_base_staff s ON t.staff_id = s.staff_id` | +| **字段查询** | `s.name as staff_name` | +| **搜索支持** | `AND s.name LIKE CONCAT('%', #{query.staffName}, '%')` | + +### 4.3 当前实现(待修复)❌ + +**文件**: `CcdiStaffEnterpriseRelationMapper.xml` + +| 方面 | 当前状态 | 应该实现 | +|------|----------|----------| +| **ResultMap** | ❌ 缺少personName映射 | ✅ 添加映射 | +| **SQL关联** | ❌ 未关联ccdi_base_staff | ✅ LEFT JOIN | +| **字段查询** | ❌ 未查询name字段 | ✅ SELECT s.name | +| **搜索支持** | ❌ 不支持姓名搜索 | ✅ 添加搜索条件 | +| **QueryDTO** | ❌ 缺少personName | ✅ 添加字段 | + +--- + +## 5. 数据库表关系说明 + +### 5.1 表结构 + +**主表**: `ccdi_staff_enterprise_relation` +- `person_id` (VARCHAR) - 身份证号,关联外键 + +**关联表**: `ccdi_base_staff` +- `id_card` (VARCHAR) - 身份证号 +- `name` (VARCHAR) - 员工姓名 + +### 5.2 关联关系 + +``` +ccdi_staff_enterprise_relation.person_id + ↓ (关联) +ccdi_base_staff.id_card + ↓ (获取) +ccdi_base_staff.name → 映射为 VO.personName +``` + +### 5.3 注意事项 + +- ⚠️ `ccdi_staff_enterprise_relation` 表**不需要**添加 `person_name` 列 +- ⚠️ `personName` 是**计算字段**,仅用于查询和展示 +- ⚠️ 需要通过 MyBatis 的 `LEFT JOIN` 在查询时动态获取 + +--- + +## 6. 修复优先级 + +### 必须修复 (Critical) - 阻塞问题 + +1. ✅ **优先级 1**: 更新 `CcdiStaffEnterpriseRelationMapper.xml` + - 在 ResultMap 中添加 `personName` 映射 + - 在所有 SELECT 查询中添加 `LEFT JOIN ccdi_base_staff` + - 在 SELECT 子句中添加 `s.name as person_name` + +2. ✅ **优先级 2**: 更新 `CcdiStaffEnterpriseRelationQueryDTO` + - 添加 `personName` 字段以支持姓名搜索 + +### 应该修复 (Important) + +3. ⚠️ **优先级 3**: 添加字段注释说明 + - 说明 `personName` 是关联字段 + +### 可选修复 (Optional) + +4. 💡 **优先级 4**: 添加单元测试 +5. 💡 **优先级 5**: 考虑Excel导出字段 + +--- + +## 7. 审查结论 + +### 总体评价 + +本次代码变更在**VO类层面**符合规范,但存在**严重的实现不完整**问题: + +- ✅ **代码规范**: 符合Java编码规范和若依框架规范 +- ✅ **提交质量**: Git提交信息清晰,符合最佳实践 +- ❌ **功能完整性**: **严重不完整**,缺少关键的Mapper实现 +- ❌ **可测试性**: 无法测试,因为personName永远为null + +### 评分细项 + +| 评估项 | 得分 | 满分 | 说明 | +|--------|------|------|------| +| VO类代码规范 | 20 | 20 | 完全符合规范 | +| Git提交质量 | 15 | 15 | 提交清晰规范 | +| Java编码规范 | 10 | 10 | 符合规范 | +| **Mapper实现** | 0 | 30 | **未同步更新** | +| **功能完整性** | 5 | 15 | **严重不完整** | +| 文档和注释 | 10 | 10 | 注释清晰 | +| **总分** | **60** | **100** | **不及格** | + +### 审查决定 + +**❌ 需要修复后重新提交 (Needs Fixes)** + +**理由**: +1. ❌ **Critical问题**: Mapper XML未同步更新,功能无法正常工作 +2. ❌ **Critical问题**: 无法实现按姓名搜索的业务需求 +3. ⚠️ 违反了"完整性原则":VO字段必须有对应的数据来源 + +**后续步骤**: +1. ✅ 修复 `CcdiStaffEnterpriseRelationMapper.xml` +2. ✅ 更新 `CcdiStaffEnterpriseRelationQueryDTO`(如需支持搜索) +3. ✅ 添加字段注释说明关联关系 +4. ✅ 编写单元测试验证功能 +5. ✅ 重新提交审查 + +--- + +## 8. 参考资料 + +### 8.1 项目内参考实现 + +1. **员工亲属关系模块** (正确实现): + - 文件: `ruoyi-ccdi/src/main/resources/mapper/ccdi/CcdiStaffFmyRelationMapper.xml` + - 提交: 历史提交记录 + - 特点: 完整实现personName字段的查询和映射 + +2. **员工调动模块** (正确实现): + - 文件: `ruoyi-ccdi/src/main/resources/mapper/ccdi/CcdiStaffTransferMapper.xml` + - 特点: 类似的staffName字段实现 + +### 8.2 数据库文档 + +- 文件: `doc/database-docs/ccdi_staff_enterprise_relation.csv` +- 文件: `doc/database-docs/ccdi_base_staff.csv` (推断存在) + +### 8.3 编码规范 + +- 若依框架编码规范 +- MyBatis官方文档: https://mybatis.org/mybatis-3/zh/sqlmap-xml.html +- 项目CLAUDE.md中的Java编码规范 + +--- + +**审查完成时间**: 2026-02-11 +**下次审查**: 修复完成后重新提交审查 diff --git a/doc/reviews/2026-02-11-task1-database-index-code-review.md b/doc/reviews/2026-02-11-task1-database-index-code-review.md new file mode 100644 index 0000000..93185aa --- /dev/null +++ b/doc/reviews/2026-02-11-task1-database-index-code-review.md @@ -0,0 +1,350 @@ +# Task 1 代码质量审查报告 - 数据库索引检查和创建 + +**审查日期:** 2026-02-11 +**审查者:** 代码质量审查者子代理 +**被审查任务:** Task 1 - 检查数据库索引 +**实施者:** Claude Code Agent +**相关提交:** +- SHA 866d3a2 (feat分支): `feat(staff-enterprise-relation): 完成Task 1 - 数据库索引检查和创建` +- SHA e1a1083 (master): `docs(staff-enterprise-relation): 标记Task 1为已完成` + +--- + +## 审查总结 + +**审查结论: ✅ 批准 (Approved)** + +本次实施整体质量优秀,遵循了项目规范,文档完整,SQL操作正确。存在少量可改进点,但不影响功能正确性。 + +--- + +## 1. 实施笔记质量 (doc/implementation-notes.md) + +### 优势 (Strengths) ✅ + +1. **结构清晰完整** + - 文档格式规范,层次分明 + - 包含实施日期、人员、模块等元信息 + - 任务清单使用checkbox便于跟踪进度 + +2. **执行步骤记录详尽** + - 详细记录了数据库连接配置 + - 完整记录了SQL语句和执行结果 + - 包含索引验证步骤,确保操作成功 + +3. **技术参数记录准确** + - 索引信息记录完整 (Table, Key_name, Column_name, Index_type等) + - Cardinality值记录有助于性能分析 + +4. **自我审查专业** + - 验证了索引类型选择 (BTREE适合等值查询) + - 评估了索引选择度 (Cardinality=1000) + - 确认了NULL值策略符合业务需求 + +5. **业务价值说明清晰** + - 明确说明索引用途: "优化 JOIN 查询性能" + - 指出了关联字段: `person_id = id_card` + +### 问题 (Issues) ⚠️ + +**Important:** 无 + +**Minor:** + +1. **缺少安全敏感信息处理** + - 数据库连接配置中明文记录了Host信息 (116.62.17.81) + - 虽然未记录密码,但建议使用占位符或环境变量标记 + - **建议:** 修改为 `Host: ${DB_HOST}` 或在敏感信息说明中标注 + +2. **缺少索引创建前的状态快照** + - 记录了"索引不存在",但未记录创建前的表结构信息 + - 建议补充id_card字段的基本信息 (数据类型、长度、是否允许NULL) + +3. **Cardinality解读可更详细** + - Cardinality=1000 说明索引选择度良好,但未说明与总记录数的关系 + - 数据验证显示: 表总记录数=1000, Cardinality=1000, 说明索引覆盖了全部记录 + - **建议:** 补充说明"Cardinality等于总记录数,说明id_card字段无重复值,索引效果最佳" + +### 建议 (Suggestions) 💡 + +1. **增强可追溯性** + ```markdown + ### 索引创建前表状态 + - 字段类型: varchar + - 字段长度: (需补充) + - 允许NULL: YES + - 原有索引: PRIMARY KEY (id) + ``` + +2. **增加性能影响说明** + ```markdown + ### 索引对查询的影响 + - 预期加速场景: JOIN查询、等值查询 + - 预期影响范围: ccdi_staff_enterprise_relation与ccdi_base_staff的关联查询 + - 写入性能影响: 轻微 (索引维护开销) + ``` + +3. **添加回滚方案** + ```markdown + ### 回滚方案 (如需删除索引) + DROP INDEX idx_id_card ON ccdi_base_staff; + ``` + +--- + +## 2. Git提交质量 + +### 优势 (Strengths) ✅ + +1. **提交信息符合规范** + - 使用 Conventional Commits 格式: `feat(staff-enterprise-relation):` + - 作用域清晰: `staff-enterprise-relation` + - 描述简洁准确: "完成Task 1 - 数据库索引检查和创建" + +2. **提交粒度合理** + - feat分支提交: 仅包含实施笔记 (83行新增) + - master分支提交: 仅更新计划文档的Task 1状态 + - 分离文档更新和代码实现,符合最佳实践 + +3. **提交信息清晰** + - Committer信息正确 (wkc <978997012@qq.com>) + - 提交时间合理 (间隔31秒,符合操作流程) + +### 问题 (Issues) ⚠️ + +**Important:** 无 + +**Minor:** + +1. **提交信息可以更详细** + - 当前提交信息较简洁,未说明索引创建的技术细节 + - **建议:** 在提交信息中添加索引类型和用途说明 + +2. **缺少相关Issue或任务引用** + - 未引用相关的需求文档或Issue编号 + - **建议:** 添加 `Refs: #issue` 或 `Related: doc/xxx.md` + +### 建议 (Suggestions) 💡 + +1. **优化提交信息格式** + ```bash + feat(staff-enterprise-relation): 完成Task 1 - 数据库索引检查和创建 + + - 为 ccdi_base_staff.id_card 创建索引 idx_id_card + - 索引类型: BTREE + - 用途: 优化与 ccdi_staff_enterprise_relation 的 JOIN 查询 + - Cardinality: 1000 (完整覆盖) + + Co-Authored-By: Claude Code Agent + ``` + +2. **添加文档关联** + ```bash + docs(staff-enterprise-relation): 标记Task 1为已完成 + + 更新实施计划文档,Task 1完成状态 + 详见: doc/implementation-notes.md + + Co-Authored-By: Claude Code Agent + ``` + +--- + +## 3. 数据库操作质量 + +### 优势 (Strengths) ✅ + +1. **SQL语句完全正确** + - 检查语句: `SHOW INDEX FROM ... WHERE Key_name = 'idx_id_card'` ✅ + - 创建语句: `CREATE INDEX idx_id_card ON ccdi_base_staff(id_card)` ✅ + - 验证语句: 重复检查语句,确保一致性 ✅ + +2. **索引类型选择合理** + - 使用 BTREE 索引,适合等值查询和范围查询 + - 对于 `id_card` 字段的 JOIN 操作,BTREE 是最优选择 + +3. **索引命名规范** + - 使用 `idx_` 前缀,符合命名规范 + - 名称清晰表达索引用途: `idx_id_card` + +4. **NULL值策略正确** + - `Null: YES` 允许NULL值,符合字段定义 + - id_card字段允许NULL,索引配置一致 + +5. **索引效果验证完整** + - Cardinality = 1000,说明索引选择度极佳 + - 数据库验证: 总记录数=1000,Cardinality=1000, **说明id_card无重复值,索引覆盖100%** + +6. **索引长度合理** + - varchar字段使用完整索引 (未指定前缀长度) + - 适合精确匹配场景 (JOIN条件) + +### 问题 (Issues) ⚠️ + +**Important:** 无 + +**Minor:** + +1. **缺少索引长度优化考虑** + - id_card是varchar类型,未指定索引前缀长度 + - **分析:** 对于身份证号 (18位字符),完整索引是合理的,因为需要精确匹配 + - **评估:** 当前设计正确,但如果id_card字段很长 (如超过100字符),建议考虑前缀索引 + +2. **缺少复合索引评估** + - 数据库显示 `ccdi_staff_enterprise_relation.person_id` 有唯一约束 `uk_person_social` + - **疑问:** 该约束可能包含多个字段 (person_id + social_credit_code) + - **建议:** 补充说明是否需要在 `ccdi_staff_enterprise_relation` 表也为person_id创建索引 + +3. **缺少并发和锁影响说明** + - 创建索引在生产环境可能需要时间 + - `CREATE INDEX` 在MySQL 5.6+默认支持Online DDL,但仍可能影响性能 + - **建议:** 对于大表,考虑使用 `ALGORITHM=INPLACE, LOCK=NONE` 选项 + +### 建议 (Suggestions) 💡 + +1. **补充关联表的索引分析** + ```markdown + ### 关联表索引检查 + 表名: ccdi_staff_enterprise_relation + 字段: person_id + 当前索引: uk_person_social (唯一约束,包含person_id) + + 分析: + - person_id作为唯一约束的一部分,已有索引支持 + - JOIN操作双方都有索引,查询性能最优 + ``` + +2. **添加查询性能测试** + ```markdown + ### 索引效果测试 + 执行EXPLAIN分析JOIN查询: + EXPLAIN SELECT * FROM ccdi_staff_enterprise_relation ser + LEFT JOIN ccdi_base_staff bs ON ser.person_id = bs.id_card + LIMIT 10; + + 预期结果: + - type: ref (索引查找) + - key: idx_id_card + - rows: 减少扫描行数 + ``` + +3. **考虑索引监控** + ```sql + -- 查看索引使用情况 (需要开启performance_schema) + SELECT * FROM performance_schema.table_io_waits_summary_by_index_usage + WHERE OBJECT_SCHEMA = 'ccdi' + AND OBJECT_NAME = 'ccdi_base_staff'; + ``` + +--- + +## 4. 数据库表结构验证 + +### 实际验证结果 + +**ccdi_base_staff表状态:** +- 表引擎: InnoDB +- 记录数: 1000 +- 数据长度: 147 KB +- 索引长度: 131 KB +- 字段id_card: varchar, NULL=YES, KEY=MUL (多个索引) + +**ccdi_staff_enterprise_relation表状态:** +- person_id字段有唯一约束: uk_person_social +- 该约束可能包含 (person_id, social_credit_code) 组合 + +### 优势 ✅ + +1. **表结构健康** + - InnoDB引擎支持事务和外键 + - 行格式Dynamic,支持长字段 + +2. **索引比例合理** + - 索引长度 (131KB) / 数据长度 (147KB) ≈ 89% + - 说明索引数量适中,未过度索引 + +### 问题 ⚠️ + +**Important:** 无 + +**Minor:** + +1. **缺少表空间和增长趋势说明** + - 当前数据量小 (1000条),索引效果良好 + - 建议监控数据增长对Cardinality的影响 + +--- + +## 5. 综合评分 + +| 评估项 | 评分 | 说明 | +|--------|------|------| +| 文档质量 | ⭐⭐⭐⭐⭐ | 结构清晰,记录详尽,自我审查专业 | +| Git规范 | ⭐⭐⭐⭐ | 符合规范,可增加技术细节和引用 | +| SQL质量 | ⭐⭐⭐⭐⭐ | 语句正确,索引类型合理,验证完整 | +| 性能考虑 | ⭐⭐⭐⭐ | 索引效果良好,可补充关联表分析 | +| 安全性 | ⭐⭐⭐⭐ | 基本安全,建议脱敏敏感信息 | + +**总体评分:** ⭐⭐⭐⭐ (4/5) - **优秀** + +--- + +## 6. 后续行动建议 + +### 立即执行 (Required) + +无 - 当前实施符合要求,可继续后续任务。 + +### 建议改进 (Recommended) + +1. **文档改进** + - [ ] 在实施笔记中补充id_card字段基本信息 + - [ ] 添加Cardinity解读说明 (与总记录数的关系) + - [ ] 补充关联表索引分析 + - [ ] 添加索引回滚方案 + +2. **提交信息优化** + - [ ] 在提交信息中添加技术细节说明 + - [ ] 添加文档关联引用 + +3. **性能验证** + - [ ] 执行EXPLAIN分析JOIN查询性能 + - [ ] 记录查询执行计划和扫描行数 + +### 可选增强 (Optional) + +1. **监控设置** + - [ ] 配置索引使用情况监控 + - [ ] 定期检查Cardinality变化 + +2. **文档完善** + - [ ] 创建数据库索引规范文档 + - [ ] 记录索引维护SOP + +--- + +## 7. 结论 + +### 审查结论: ✅ **批准 (Approved)** + +**理由:** + +1. **功能正确性**: 索引创建成功,经验证生效,符合预期用途 +2. **文档完整性**: 实施笔记记录详尽,可追溯性强 +3. **代码规范性**: Git提交符合规范,SQL语句标准 +4. **性能合理性**: 索引类型和设计适合业务场景 +5. **安全性**: 基本安全要求满足,敏感信息暴露风险低 + +**建议批准进入下一任务:** +- Task 2: 修改 VO 类添加员工姓名字段 + +**批准条件:** +- Minor问题不阻塞后续任务 +- 建议改进可在后续迭代中完善 +- 实施质量达到生产环境标准 + +--- + +**审查签名:** 代码质量审查者子代理 +**审查日期:** 2026-02-11 +**下次审查:** Task 2 完成后 diff --git a/doc/reviews/ccdi_cust_fmy_relation_fix_review.md b/doc/reviews/ccdi_cust_fmy_relation_fix_review.md new file mode 100644 index 0000000..1492597 --- /dev/null +++ b/doc/reviews/ccdi_cust_fmy_relation_fix_review.md @@ -0,0 +1,264 @@ +# 代码审查报告 - 信贷客户家庭关系表修复 + +## 审查信息 + +**审查文件:** `sql/ccdi_cust_fmy_relation.sql` +**修复Commit:** `e2ee494bbaf1d1b7624722eecc8c6ea4b47d46af` +**审查日期:** 2026-02-11 +**审查者:** Claude Code Reviewer + +--- + +## 修复问题清单 + +### ✅ 已修复的Important级别问题 + +| # | 问题 | 状态 | 说明 | +|---|------|------|------| +| 1 | 添加唯一约束 `uk_person_cert` | ✅ 已修复 | 成功添加 (person_id, relation_cert_no) 唯一约束 | +| 2 | 字段类型与员工表统一 | ✅ 已修复 | 所有字段类型与 ccdi_staff_fmy_relation 完全一致 | +| 3 | is_cust_family 默认值保持为1 | ✅ 已修复 | DEFAULT 1 正确设置 | +| 4 | 添加表头注释 | ✅ 已修复 | 包含创建时间、用途说明 | +| 5 | 添加 IF NOT EXISTS | ✅ 已修复 | 防止重复创建表 | + +--- + +## 详细审查结果 + +### 1. ✅ 唯一约束 - 已正确添加 + +```sql +UNIQUE KEY `uk_person_cert` (`person_id`, `relation_cert_no`) +COMMENT '信贷客户身份证号+关系人证件号码唯一' +``` + +**审查意见:** ✅ 优秀 +- 约束命名规范: `uk_person_cert` +- 字段选择合理: (person_id, relation_cert_no) +- 注释清晰明确 +- 与员工表约束保持一致 + +--- + +### 2. ✅ 字段类型统一 - 完全一致 + +**与员工亲属关系表 (ccdi_staff_fmy_relation) 对比:** + +| 字段 | 信贷客户表 | 员工亲属表 | 一致性 | +|------|-----------|-----------|--------| +| id | BIGINT(20) | BIGINT(20) | ✅ | +| person_id | VARCHAR(100) | VARCHAR(100) | ✅ | +| relation_cert_type | VARCHAR(50) | VARCHAR(50) | ✅ | +| relation_cert_no | VARCHAR(50) | VARCHAR(50) | ✅ | +| status | INT(11) | INT(11) | ✅ | +| created_by | VARCHAR(100) | VARCHAR(100) | ✅ | +| updated_by | VARCHAR(100) | VARCHAR(100) | ✅ | +| create_time | DATETIME | DATETIME | ✅ | +| update_time | DATETIME NOT NULL | DATETIME NOT NULL | ✅ | + +**审查意见:** ✅ 优秀 +- 所有字段类型与员工表完全一致 +- NOT NULL 约束保持一致 +- 默认值设置合理 + +--- + +### 3. ✅ 默认值设置 - 正确 + +```sql +`is_cust_family` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否是信贷客户的家庭关系:1-是', +`is_emp_family` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否是员工的家庭关系:0-否', +``` + +**审查意见:** ✅ 正确 +- is_cust_family 默认值为 1 ✅ +- is_emp_family 默认值为 0 ✅ +- 语义清晰,符合业务逻辑 +- 注释准确说明默认值含义 + +--- + +### 4. ✅ 表头注释 - 规范完整 + +```sql +-- 信贷客户家庭关系表 +-- 创建时间: 2026-02-11 +-- 说明: 存储信贷客户家庭成员关系信息,仅处理信贷客户家庭关系(is_cust_family=1) +``` + +**审查意见:** ✅ 优秀 +- 表名清晰 +- 创建时间明确 +- 用途说明详细 +- 指明了业务范围(is_cust_family=1) + +--- + +### 5. ✅ IF NOT EXISTS - 安全防护 + +```sql +CREATE TABLE IF NOT EXISTS `ccdi_cust_fmy_relation` ( +``` + +**审查意见:** ✅ 正确 +- 防止重复创建表 +- 提高脚本执行安全性 +- 符合数据库操作最佳实践 + +--- + +## 发现的额外问题 + +### 🔍 建议优化点 (非阻塞) + +#### 1. 索引不完整 - Suggestion级别 + +**问题描述:** +与员工亲属关系表相比,缺少以下索引: +- `idx_status` - 状态索引 +- `idx_data_source` - 数据来源索引 + +**影响分析:** +- 如果经常按 status 或 data_source 查询,可能影响查询性能 +- 当前为 Suggestion 级别,不影响功能正确性 + +**建议:** +```sql +-- 如果业务中需要按状态或数据来源筛选查询,建议添加: +KEY `idx_status` (`status`) COMMENT '状态索引', +KEY `idx_data_source` (`data_source`) COMMENT '数据来源索引' +``` + +**是否需要修复:** ❌ 不强制 (可根据实际查询需求决定) + +--- + +#### 2. 注释细节差异 - Suggestion级别 + +**问题描述:** +字段 `remark` 的默认值定义不一致: + +- 员工亲属关系表: `remark TEXT DEFAULT NULL COMMENT ...` +- 信贷客户家庭关系表: `remark TEXT COMMENT ...` (无 DEFAULT NULL) + +**影响分析:** +- TEXT 类型默认为 NULL,两种写法语义相同 +- 不影响功能和数据一致性 +- 仅是代码风格差异 + +**建议:** +为保持一致性,可以添加 `DEFAULT NULL`: +```sql +`remark` TEXT DEFAULT NULL COMMENT '备注信息', +``` + +**是否需要修复:** ❌ 不强制 (代码风格问题) + +--- + +## 与员工亲属关系表的对比总结 + +### ✅ 完全一致的部分 + +1. **字段类型** - 所有字段类型完全一致 +2. **字段顺序** - 字段排列顺序一致 +3. **唯一约束** - 约束名称和结构一致 +4. **主键索引** - 主键定义完全一致 +5. **基本索引** - idx_person_id 和 idx_relation_cert_no 一致 +6. **引擎配置** - ENGINE=InnoDB, CHARSET=utf8mb4 +7. **审计字段** - created_by, updated_by, create_time, update_time 完全一致 + +### ⚠️ 差异部分 + +1. **索引数量** - 信贷客户表少2个索引 (idx_status, idx_data_source) +2. **注释细节** - remark 字段的 DEFAULT NULL 定义差异 + +--- + +## 最终结论 + +### ✅ 批准通过 + +**所有 Important 级别问题已全部修复!** + +### 修复质量评估: ⭐⭐⭐⭐⭐ (优秀) + +**优点:** +- ✅ 唯一约束设计合理,防止重复数据 +- ✅ 字段类型完全统一,数据结构一致性强 +- ✅ 默认值设置符合业务逻辑 +- ✅ 表头注释规范完整 +- ✅ 使用 IF NOT EXISTS 提高安全性 +- ✅ 注释清晰,便于维护 +- ✅ 遵循项目命名规范 (表名前缀 ccdi_) + +**代码质量:** 生产就绪 (Production Ready) + +--- + +## 后续建议 + +### 可选优化 (不影响当前审查结果) + +1. **根据查询需求补充索引** + - 如果业务中经常按 status 或 data_source 查询,建议添加相应索引 + - 可以通过分析慢查询日志确定是否需要 + +2. **统一代码风格** + - 建议为 remark 字段添加 DEFAULT NULL,与员工表保持一致 + - 建议为索引添加 COMMENT,与员工表保持一致 + +3. **考虑添加测试数据** + - 建议参考员工亲属关系表,添加注释掉的测试数据示例 + - 便于开发测试和文档说明 + +--- + +## 审查签名 + +**审查者:** Claude Code Reviewer +**审查日期:** 2026-02-11 +**审查结果:** ✅ 批准通过 (APPROVED) +**审查意见:** 代码质量优秀,可以合并到主分支 + +--- + +## 附录: 完整的修复后SQL + +```sql +-- 信贷客户家庭关系表 +-- 创建时间: 2026-02-11 +-- 说明: 存储信贷客户家庭成员关系信息,仅处理信贷客户家庭关系(is_cust_family=1) +CREATE TABLE IF NOT EXISTS `ccdi_cust_fmy_relation` ( + `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `person_id` VARCHAR(100) 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(11) 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(100) NOT NULL COMMENT '记录创建人', + `updated_by` VARCHAR(100) DEFAULT NULL COMMENT '记录更新人', + `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间', + `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '记录更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_person_cert` (`person_id`, `relation_cert_no`) COMMENT '信贷客户身份证号+关系人证件号码唯一', + KEY `idx_person_id` (`person_id`), + KEY `idx_relation_cert_no` (`relation_cert_no`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='信贷客户家庭关系表'; +``` diff --git a/doc/信贷客户家庭关系维护功能/设计文档.md b/doc/信贷客户家庭关系维护功能/设计文档.md new file mode 100644 index 0000000..25e5ca5 --- /dev/null +++ b/doc/信贷客户家庭关系维护功能/设计文档.md @@ -0,0 +1,708 @@ +# 信贷客户家庭关系维护功能设计文档 + +## 一、项目概述 + +### 1.1 功能描述 +开发信贷客户家庭关系维护功能,实现对信贷客户家庭成员信息的新增、修改、删除、查询、导入、导出等完整的CRUD操作。 + +**设计原则**:完全复用员工亲属关系维护功能的实现逻辑和前端交互方式,创建独立模块管理。 + +### 1.2 技术栈 +- **后端**: Spring Boot 3.5.8 + MyBatis Plus 3.5.10 + SpringDoc +- **前端**: Vue 2.6.12 + Element UI 2.15.14 +- **数据库**: MySQL 8.2.0 +- **Excel处理**: EasyExcel +- **缓存**: Redis +- **异步处理**: Spring @Async + +### 1.3 参考标准 +本功能完全参考**员工亲属关系维护**模块的设计与实现,确保代码风格、交互方式、技术实现的一致性。 + +--- + +## 二、数据库设计 + +### 2.1 主表结构 + +**表名**: `ccdi_cust_fmy_relation`(信贷客户家庭关系表) + +| 字段序号 | 字段名 | 类型 | 默认值 | 是否可为空 | 主键 | 注释 | +|---------|--------|------|--------|-----------|------|------| +| 1 | id | BIGINT | - | 否 | 是 | 主键,自增 | +| 2 | person_id | VARCHAR | - | 否 | - | 信贷客户身份证号 | +| 3 | relation_type | VARCHAR | - | 否 | - | 关系类型(配偶、子女、父母、兄弟姐妹等) | +| 4 | relation_name | VARCHAR | - | 否 | - | 关系人姓名 | +| 5 | gender | CHAR | - | 是 | - | 性别:M/F/O | +| 6 | birth_date | DATE | - | 是 | - | 关系人出生日期 | +| 7 | relation_cert_type | VARCHAR | - | 否 | - | 证件类型(必填) | +| 8 | relation_cert_no | VARCHAR | - | 否 | - | 证件号码(必填) | +| 9 | mobile_phone1 | VARCHAR | - | 是 | - | 手机号码1 | +| 10 | mobile_phone2 | VARCHAR | - | 是 | - | 手机号码2 | +| 11 | wechat_no1 | VARCHAR | - | 是 | - | 微信号1 | +| 12 | wechat_no2 | VARCHAR | - | 是 | - | 微信号2 | +| 13 | wechat_no3 | VARCHAR | - | 是 | - | 微信号3 | +| 14 | contact_address | VARCHAR | - | 是 | - | 详细联系地址 | +| 15 | relation_desc | VARCHAR | - | 是 | - | 关系详细描述 | +| 16 | status | INT | 1 | 否 | - | 状态:0-无效,1-有效 | +| 17 | effective_date | DATETIME | - | 是 | - | 关系生效日期 | +| 18 | invalid_date | DATETIME | - | 是 | - | 关系失效日期 | +| 19 | remark | TEXT | - | 是 | - | 备注信息 | +| 20 | data_source | VARCHAR(50) | - | 是 | - | 数据来源:MANUAL/SYSTEM/IMPORT/API | +| 21 | is_emp_family | TINYINT(1) | 0 | 否 | - | 是否员工家庭关系:固定为0 | +| 22 | is_cust_family | TINYINT(1) | 1 | 否 | - | 是否信贷客户家庭关系:固定为1 | +| 23 | created_by | VARCHAR | - | 否 | - | 记录创建人 | +| 24 | updated_by | VARCHAR | - | 是 | - | 记录更新人 | +| 25 | create_time | DATETIME | - | 否 | - | 记录创建时间 | +| 26 | update_time | DATETIME | - | 是 | - | 记录更新时间 | + +### 2.2 核心差异说明 + +**与员工亲属关系表对比**: + +| 对比项 | 员工亲属关系 | 信贷客户家庭关系 | +|-------|-------------|-----------------| +| person_id语义 | 员工身份证号 | 信贷客户身份证号 | +| is_emp_family | 固定为1 | 固定为0 | +| is_cust_family | 固定为0 | 固定为1 | +| 外键关联 | 关联ccdi_base_staff | 暂不关联,独立存储 | + +### 2.3 唯一键设计 + +**唯一键 = 信贷客户身份证号 + 关系人身份证号** + +- 格式:`{personId}_{relationCertNo}` +- 示例:`110101199001011234_110101199001015678` +- 用于导入时的重复性校验 + +### 2.4 建表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='信贷客户家庭关系表'; +``` + +--- + +## 三、后端设计 + +### 3.1 模块命名 + +**模块名称**: `CustFamilyRelation`(信贷客户家庭关系) + +**包路径**: `com.ruoyi.ccdi` + +### 3.2 类结构设计 + +#### 3.2.1 实体类(Entity) + +**CcdiCustFmyRelation.java** +```java +@Data +@TableName("ccdi_cust_fmy_relation") +public class CcdiCustFmyRelation implements Serializable { + @TableId(type = IdType.AUTO) + private Long id; + private String personId; + private String relationType; + private String relationName; + private String gender; + private Date birthDate; + private String relationCertType; + private String relationCertNo; + private String mobilePhone1; + private String mobilePhone2; + private String wechatNo1; + private String wechatNo2; + private String wechatNo3; + private String contactAddress; + private String relationDesc; + private Integer status; + private Date effectiveDate; + private Date invalidDate; + private String remark; + private String dataSource; + private Boolean isEmpFamily; // 固定为false + private Boolean isCustFamily; // 固定为true + + @TableField(fill = FieldFill.INSERT) + private String createdBy; + + @TableField(fill = FieldFill.INSERT) + private Date createTime; + + @TableField(fill = FieldFill.INSERT_UPDATE) + private String updatedBy; + + @TableField(fill = FieldFill.INSERT_UPDATE) + private Date updateTime; +} +``` + +#### 3.2.2 DTO设计 + +**CcdiCustFmyRelationAddDTO.java**(新增DTO) +- 必填字段: + - `personId` - 信贷客户身份证号 + - `relationType` - 关系类型 + - `relationName` - 关系人姓名 + - `relationCertType` - 证件类型 + - `relationCertNo` - 证件号码 +- 可选字段:其他所有字段 +- 校验规则: + - 身份证号格式验证(18位) + - 手机号格式验证(11位) + - 性别值验证(M/F/O) + - 字段长度验证 +- **差异**:无需验证员工是否存在,直接作为文本字段处理 + +**CcdiCustFmyRelationEditDTO.java**(编辑DTO) +- 包含所有字段(除审计字段) +- 同样的校验规则 + +**CcdiCustFmyRelationQueryDTO.java**(查询DTO) +- **简化版查询条件**: + - `personId` - 信贷客户身份证号(精确) + - `relationType` - 关系类型 + - `relationName` - 关系人姓名(模糊) +- **移除字段**:personName、status、dataSource、日期范围等 + +#### 3.2.3 VO设计 + +**CcdiCustFmyRelationVO.java**(列表/详情VO) +- 包含所有展示字段 +- 扩展字段: + - `genderName` - 性别名称(转换) + - `statusName` - 状态名称(转换) + - `dataSourceName` - 数据来源名称 +- **差异**:无需personName(不关联其他表) + +**CcdiCustFmyRelationExcel.java**(Excel导入导出) +- 使用`@DictDropdown`注解添加字典下拉框: + - `relationType` → `ccdi_relation_type` + - `gender` → `ccdi_indiv_gender` + - `relationCertType` → `ccdi_certificate_type` +- 使用`@Required`注解标记必填字段 +- 字段索引:0-16 + +**CustFmyRelationImportFailureVO.java**(导入失败记录) +- `rowNum` - 行号 +- `personId` - 信贷客户身份证号 +- `relationName` - 关系人姓名 +- `errorMessage` - 错误信息 + +#### 3.2.4 Mapper层 + +**CcdiCustFmyRelationMapper.java** +```java +@Mapper +public interface CcdiCustFmyRelationMapper extends BaseMapper { + Page selectRelationPage( + Page page, + @Param("query") CcdiCustFmyRelationQueryDTO queryDTO + ); + + List selectRelationListForExport( + @Param("query") CcdiCustFmyRelationQueryDTO queryDTO + ); +} +``` + +**XML映射要点**: +- WHERE条件:`is_cust_family = 1` +- **无需LEFT JOIN**(不关联客户表) +- 支持多条件动态查询 +- 按创建时间倒序排列 + +#### 3.2.5 Service层 + +**ICcdiCustFmyRelationService.java**(主服务接口) +```java +public interface ICcdiCustFmyRelationService { + List selectRelationList(CcdiCustFmyRelationQueryDTO queryDTO); + Page selectRelationPage(Page page, CcdiCustFmyRelationQueryDTO queryDTO); + List selectRelationListForExport(CcdiCustFmyRelationQueryDTO queryDTO); + CcdiCustFmyRelationVO selectRelationById(Long id); + int insertRelation(CcdiCustFmyRelationAddDTO addDTO); + int updateRelation(CcdiCustFmyRelationEditDTO editDTO); + int deleteRelationByIds(Long[] ids); + String importRelation(List excelList); +} +``` + +**ICcdiCustFmyRelationImportService.java**(导入服务接口) +```java +public interface ICcdiCustFmyRelationImportService { + void importRelationAsync(List excelList, String taskId, String userName); + ImportStatusVO getImportStatus(String taskId); + List getImportFailures(String taskId); +} +``` + +#### 3.2.6 Controller层 + +**CcdiCustFmyRelationController.java** + +**接口清单**: + +| 接口路径 | 方法 | 功能 | 权限标识 | +|---------|------|------|---------| +| /ccdi/custFmyRelation/list | GET | 查询列表 | ccdi:custFmyRelation:list | +| /ccdi/custFmyRelation/{id} | GET | 查询详情 | ccdi:custFmyRelation:query | +| /ccdi/custFmyRelation | POST | 新增 | ccdi:custFmyRelation:add | +| /ccdi/custFmyRelation | PUT | 修改 | ccdi:custFmyRelation:edit | +| /ccdi/custFmyRelation/{ids} | DELETE | 删除 | ccdi:custFmyRelation:remove | +| /ccdi/custFmyRelation/export | POST | 导出 | ccdi:custFmyRelation:export | +| /ccdi/custFmyRelation/importTemplate | POST | 下载模板 | - | +| /ccdi/custFmyRelation/importData | POST | 导入 | ccdi:custFmyRelation:import | +| /ccdi/custFmyRelation/importStatus/{taskId} | GET | 导入状态 | ccdi:custFmyRelation:import | +| /ccdi/custFmyRelation/importFailures/{taskId} | GET | 失败记录 | ccdi:custFmyRelation:import | + +### 3.3 异步导入机制 + +完全复用员工亲属关系的异步导入逻辑,仅调整以下内容: + +#### 3.3.1 唯一键校验 + +**唯一键 = 信贷客户身份证号 + 关系人身份证号** + +- 用于检测Excel文件内部的重复记录 +- 重复时跳过并记录到失败列表 +- 错误提示:`信贷客户身份证号[xxx]与关系人身份证号[xxx]的关系在导入文件中重复` + +#### 3.3.2 Redis存储结构 + +**状态存储**(Hash): +``` +Key: import:custFmyRelation:{taskId} +Fields: + - taskId: 任务ID + - status: PROCESSING/SUCCESS/PARTIAL_SUCCESS + - totalCount: 总数 + - successCount: 成功数 + - failureCount: 失败数 + - progress: 进度(0-100) + - startTime: 开始时间戳 + - endTime: 结束时间戳 + - message: 状态消息 +TTL: 7天 +``` + +**失败记录存储**(List): +``` +Key: import:custFmyRelation:{taskId}:failures +Value: JSON数组,包含所有失败记录 +TTL: 7天 +``` + +#### 3.3.3 数据验证规则 + +**必填字段验证**: +1. 信贷客户身份证号 - 非空 + 18位格式 +2. 关系类型 - 非空 +3. 关系人姓名 - 非空 +4. 证件类型 - 非空 +5. 证件号码 - 非空 + +**格式验证**: +- 身份证号:`^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[0-9Xx]$` +- 手机号:`^1[3-9]\\d{9}$` +- 性别:`^[MFO]$` + +--- + +## 四、前端设计 + +### 4.1 目录结构 + +``` +ruoyi-ui/src/ +├── api/ +│ └── ccdiCustFmyRelation.js # API接口定义 +└── views/ + └── ccdiCustFmyRelation/ + └── index.vue # 主页面组件 +``` + +### 4.2 API接口设计 + +**ccdiCustFmyRelation.js** +```javascript +// 查询列表 +export function listRelation(query) +export function getRelation(id) +export function addRelation(data) +export function updateRelation(data) +export function delRelation(ids) +export function exportRelation(query) +export function importTemplate() +export function importData(file) +export function getImportStatus(taskId) +export function getImportFailures(taskId, pageNum, pageSize) +``` + +### 4.3 页面功能设计 + +#### 4.3.1 列表页面 + +**查询条件**(简化版): +- 信贷客户身份证号:文本输入框 +- 关系类型:下拉选择(字典:`ccdi_relation_type`) +- 关系人姓名:文本输入框 +- 搜索/重置按钮 + +**操作按钮**: +- 新增 +- 导入 +- 导出 +- 查看导入失败记录(有失败数据时显示) + +**列表列**: +- 选择框 +- 信贷客户身份证号 +- 关系类型 +- 关系人姓名 +- 性别 +- 联系电话 +- 联系地址 +- 状态(标签显示) +- 创建时间 +- 操作(详情/编辑/删除) + +**差异**: +- 移除"员工姓名"列 +- "person_id"列标题改为"信贷客户身份证号" + +#### 4.3.2 新增/编辑表单 + +**表单分组**: + +1. **基本信息** + - 信贷客户身份证号:文本输入框(普通输入,非远程搜索) + - 关系类型:下拉选择 + - 关系人姓名:文本输入 + - 性别:下拉选择(男/女/其他) + - 出生日期:日期选择 + +2. **证件信息** + - 证件类型:文本输入 + - 证件号码:文本输入 + +3. **联系方式** + - 手机号码1/2:文本输入 + - 微信号1/2/3:文本输入 + - 联系地址:文本域 + +4. **其他信息** + - 关系描述:文本域 + - 生效日期:日期选择 + - 失效日期:日期选择 + - 备注:文本域 + +**表单验证**: +- 必填字段标记 +- 格式验证(手机号、身份证号) +- 长度限制 + +**差异**: +- "信贷客户身份证号"使用普通文本输入,而非远程搜索下拉选择器 + +#### 4.3.3 详情页面 + +使用`el-descriptions`组件展示所有字段信息,分组显示: +- 基本信息 +- 证件信息 +- 联系方式 +- 其他信息 +- 审计信息 + +#### 4.3.4 导入功能 + +**导入对话框**: +- 拖拽上传 +- 仅支持.xlsx/.xls格式 +- 下载模板链接 +- 上传后立即返回任务ID + +**导入结果轮询**: +- 每2秒查询一次状态 +- 最多轮询150次(5分钟) +- 完成后显示通知: + - 全部成功:绿色通知,显示总数 + - 部分失败:橙色通知,显示成功/失败数 + - 显示"查看导入失败记录"按钮 + +**失败记录对话框**: +- 显示导入统计信息 +- 表格展示失败记录: + - 行号 + - 信贷客户身份证号 + - 关系人姓名 + - 失败原因 +- 支持分页 +- 清除历史记录按钮 + +#### 4.3.5 localStorage持久化 + +**存储内容**: +```javascript +{ + taskId: "uuid", + status: "SUCCESS/PARTIAL_SUCCESS", + hasFailures: true/false, + totalCount: 100, + successCount: 95, + failureCount: 5, + saveTime: 1707456000000 +} +``` + +**Key名称**: +``` +cust_fmy_relation_import_last_task +``` + +### 4.4 文案替换清单 + +| 原文案(员工亲属关系) | 新文案(信贷客户家庭关系) | +|---------------------|-------------------------| +| 员工亲属关系维护 | 信贷客户家庭关系维护 | +| 员工亲属关系列表 | 信贷客户家庭关系列表 | +| 员工身份证号 | 信贷客户身份证号 | +| 员工 | 信贷客户 | +| 新增员工亲属关系 | 新增信贷客户家庭关系 | + +--- + +## 五、与员工亲属关系对比验证 + +### 5.1 架构对比 + +| 对比项 | 员工亲属关系 | 信贷客户家庭关系 | 一致性 | +|-------|-------------|-----------------|--------| +| 模块命名 | StaffFmyRelation | CustFmyRelation | ✅ 遵循规范 | +| 包结构 | com.ruoyi.ccdi | com.ruoyi.ccdi | ✅ 一致 | +| 分层架构 | Controller-Service-Mapper | Controller-Service-Mapper | ✅ 一致 | +| DTO/VO分离 | 是 | 是 | ✅ 一致 | +| MyBatis Plus | 使用 | 使用 | ✅ 一致 | + +### 5.2 Controller接口对比 + +| 接口功能 | 员工亲属关系 | 信贷客户家庭关系 | 一致性 | +|---------|-------------|-----------------|--------| +| 查询列表 | /list | /list | ✅ 一致 | +| 查询详情 | /{id} | /{id} | ✅ 一致 | +| 新增 | POST | POST | ✅ 一致 | +| 修改 | PUT | PUT | ✅ 一致 | +| 删除 | /{ids} | /{ids} | ✅ 一致 | +| 导出 | /export | /export | ✅ 一致 | +| 下载模板 | /importTemplate | /importTemplate | ✅ 一致 | +| 导入数据 | /importData | /importData | ✅ 一致 | +| 导入状态 | /importStatus/{id} | /importStatus/{id} | ✅ 一致 | +| 失败记录 | /importFailures/{id} | /importFailures/{id} | ✅ 一致 | + +### 5.3 查询功能对比 + +| 对比项 | 员工亲属关系 | 信贷客户家庭关系 | 差异说明 | +|-------|-------------|-----------------|---------| +| personId字段 | 员工身份证号 | 信贷客户身份证号 | 字段语义不同 | +| personName | 有(关联查询) | 无 | 不关联其他表 | +| 查询条件 | 6个 | 3个(简化版) | 简化查询 | + +### 5.4 数据对比 + +| 对比项 | 员工亲属关系 | 信贷客户家庭关系 | 一致性 | +|-------|-------------|-----------------|--------| +| 异步注解 | @Async | @Async | ✅ 一致 | +| 状态存储 | Redis Hash | Redis Hash | ✅ 一致 | +| 失败记录存储 | Redis List | Redis List | ✅ 一致 | +| 过期时间 | 7天 | 7天 | ✅ 一致 | +| 批量插入大小 | 500条/批 | 500条/批 | ✅ 一致 | +| 唯一键校验 | 员工身份证号+关系人身份证号 | 信贷客户身份证号+关系人身份证号 | ✅ 逻辑一致 | +| 数据验证 | validateRelationData() | validateRelationData() | ✅ 结构一致 | + +### 5.5 前端交互对比 + +| 对比项 | 员工亲属关系 | 信贷客户家庭关系 | 一致性 | +|-------|-------------|-----------------|--------| +| 轮询间隔 | 2秒 | 2秒 | ✅ 一致 | +| 最大轮询次数 | 150次 | 150次 | ✅ 一致 | +| localStorage Key | staff_fmy_relation_import_last_task | cust_fmy_relation_import_last_task | ✅ 命名规范一致 | +| 失败记录展示 | 表格+分页 | 表格+分页 | ✅ 一致 | +| 导入结果通知 | $notify | $notify | ✅ 一致 | +| 清除历史记录 | 有 | 有 | ✅ 一致 | + +### 5.6 差异说明 + +**信贷客户家庭关系的核心差异**: + +1. **表结构差异** + - `is_emp_family = 0`,`is_cust_family = 1` + - `person_id` 语义为信贷客户身份证号 + +2. **查询简化** + - 仅保留3个查询条件(身份证号、关系类型、关系人姓名) + - 移除员工姓名关联查询 + +3. **前端输入方式** + - 信贷客户身份证号:普通文本输入 + - 员工身份证号:远程搜索下拉选择 + +4. **文案替换** + - 全部"员工"替换为"信贷客户" + - 全部"亲属关系"保持不变 + +### 5.7 对比结论 + +✅ **信贷客户家庭关系维护功能的设计与实现完全遵循员工亲属关系维护的标准** + +- 代码结构一致 +- 接口风格一致 +- 异步导入机制一致 +- 前端交互一致 +- 技术栈一致 + +**差异仅在于**: +- 业务语义调整(信贷客户 vs 员工) +- 查询条件简化 +- person_id 输入方式调整 + +--- + +## 六、实施计划 + +### 6.1 开发任务清单 + +#### 后端开发 +1. ⏳ 创建实体类 `CcdiCustFmyRelation.java` +2. ⏳ 创建DTO类(Add/Edit/Query) +3. ⏳ 创建VO类(List/Detail/ImportFailure) +4. ⏳ 创建Excel类 `CcdiCustFmyRelationExcel.java` +5. ⏳ 创建Mapper接口和XML映射 +6. ⏳ 创建Service接口和实现类 +7. ⏳ 创建ImportService接口和实现类 +8. ⏳ 创建Controller控制器 +9. ⏳ 配置权限标识 + +#### 前端开发 +1. ⏳ 创建API接口文件 `ccdiCustFmyRelation.js` +2. ⏳ 创建主页面组件 `index.vue` +3. ⏳ 实现查询列表功能 +4. ⏳ 实现新增/编辑功能 +5. ⏳ 实现详情查看功能 +6. ⏳ 实现删除功能 +7. ⏳ 实现导入功能(含异步轮询) +8. ⏳ 实现导出功能 + +#### 系统配置 +1. ⏳ 创建数据库表 +2. ⏳ 配置菜单权限 +3. ⏳ 分配角色权限 +4. ⏳ 配置按钮权限 + +### 6.2 测试计划 + +#### 单元测试 +- Service层数据验证 +- Mapper层SQL查询 +- 导入重复校验 + +#### 功能测试 +- CRUD基本操作 +- 导入导出功能 +- 异步导入状态查询 +- 失败记录查看 + +#### 集成测试 +- 前后端联调 +- 权限控制测试 +- Excel模板测试 + +#### 性能测试 +- 大数据量导入(1000+条) +- 并发导入测试 +- 分页查询性能 + +### 6.3 部署清单 +1. 数据库表创建 +2. 后端代码部署 +3. 前端代码部署 +4. 菜单权限配置 +5. 功能测试验证 + +--- + +## 七、附录 + +### 7.1 字典配置 + +**复用现有字典**: +- `ccdi_relation_type`:关系类型(配偶、子女、父母、兄弟姐妹、其他) +- `ccdi_indiv_gender`:性别(男、女、其他) +- `ccdi_certificate_type`:证件类型(身份证、护照、军官证等) + +### 7.2 权限配置 + +**菜单标识**:`ccdi:custFmyRelation` + +**权限标识清单**: +- `ccdi:custFmyRelation:list` - 查询列表 +- `ccdi:custFmyRelation:query` - 查询详情 +- `ccdi:custFmyRelation:add` - 新增 +- `ccdi:custFmyRelation:edit` - 修改 +- `ccdi:custFmyRelation:remove` - 删除 +- `ccdi:custFmyRelation:export` - 导出 +- `ccdi:custFmyRelation:import` - 导入 + +### 7.3 API文档生成 + +使用SpringDoc自动生成Swagger文档: +- 访问地址:`/swagger-ui/index.html` +- 接口分组:信贷客户家庭关系管理 +- 所有接口包含完整的参数说明和响应示例 + +--- + +## 八、设计总结 + +本设计方案完全遵循若依框架规范和员工亲属关系维护的实现标准,确保: + +✅ **代码一致性**:命名规范、包结构、分层架构完全一致 +✅ **技术一致性**:技术栈、组件选型、实现方式完全一致 +✅ **交互一致性**:前端交互、导入导出、异步处理完全一致 +✅ **功能完整性**:CRUD、导入导出、权限控制一应俱全 + +**核心差异**: +- 业务语义:信贷客户 vs 员工 +- 查询简化:3个条件 vs 6个条件 +- 输入方式:文本输入 vs 远程搜索 + +该设计方案可以直接进入开发实施阶段,开发完成后将与员工亲属关系维护功能进行最终对比验证,确保实现效果完全一致。 diff --git a/docs/plans/2026-02-11-cust-fmy-relation-implementation.md b/docs/plans/2026-02-11-cust-fmy-relation-implementation.md new file mode 100644 index 0000000..5bc34e1 --- /dev/null +++ b/docs/plans/2026-02-11-cust-fmy-relation-implementation.md @@ -0,0 +1,962 @@ +# 信贷客户家庭关系维护功能实施计划 + +> **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`