docs(staff-enterprise-relation): 更新API文档,添加员工姓名字段说明
- 新增员工实体关系管理API文档 - 在列表接口和详情接口响应中添加personName字段 - 说明personName通过LEFT JOIN ccdi_base_staff表获取 - 如果personId在员工信息表中不存在,personName为null
This commit is contained in:
@@ -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
|
||||
<select id="selectRelationList" resultType="CcdiStaffEnterpriseRelationVO">
|
||||
SELECT
|
||||
ser.id,
|
||||
ser.person_id,
|
||||
bs.name AS person_name,
|
||||
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>
|
||||
<if test="personId != null and personId != ''">
|
||||
AND ser.person_id LIKE CONCAT('%', #{personId}, '%')
|
||||
</if>
|
||||
<if test="enterpriseName != null and enterpriseName != ''">
|
||||
AND ser.enterprise_name LIKE CONCAT('%', #{enterpriseName}, '%')
|
||||
</if>
|
||||
<if test="status != null">
|
||||
AND ser.status = #{status}
|
||||
</if>
|
||||
</where>
|
||||
ORDER BY ser.create_time DESC
|
||||
</select>
|
||||
```
|
||||
|
||||
同样修改 `selectRelationById` 方法。
|
||||
|
||||
### 4.3 Service层
|
||||
|
||||
`ICcdiStaffEnterpriseRelationService.java` 和实现类无需大改,MyBatis Plus 会自动填充 JOIN 的字段。
|
||||
|
||||
## 5. 前端代码层设计
|
||||
|
||||
### 5.1 列表页面修改
|
||||
|
||||
**文件:** `ruoyi-ui/src/views/ccdiStaffEnterpriseRelation/index.vue`
|
||||
|
||||
在表格列定义中添加员工姓名列:
|
||||
|
||||
```vue
|
||||
<el-table-column label="身份证号" align="center" prop="personId" width="180" />
|
||||
<el-table-column label="员工姓名" align="center" prop="personName" width="100" />
|
||||
<el-table-column label="职务" align="center" prop="relationPersonPost" width="120" />
|
||||
```
|
||||
|
||||
**位置建议:** 放在"身份证号"列之后,方便用户对照查看
|
||||
|
||||
### 5.2 API接口
|
||||
|
||||
**文件:** `ruoyi-ui/src/api/ccdiStaffEnterpriseRelation.js`
|
||||
|
||||
无需修改,接口会自动返回新增的 `personName` 字段。
|
||||
|
||||
### 5.3 详情页面
|
||||
|
||||
如果存在详情对话框,同样添加员工姓名显示:
|
||||
|
||||
```vue
|
||||
<el-form-item label="身份证号">{{ form.personId }}</el-form-item>
|
||||
<el-form-item label="员工姓名">{{ form.personName }}</el-form-item>
|
||||
<el-form-item label="职务">{{ form.relationPersonPost }}</el-form-item>
|
||||
```
|
||||
|
||||
## 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<CcdiStaffEnterpriseRelationVO> 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)
|
||||
@@ -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<String> 存在的身份证号集合
|
||||
|
||||
### 2.3 影响的服务
|
||||
|
||||
| 服务 | 文件 | 说明 |
|
||||
|------|------|------|
|
||||
| 员工实体关系导入 | `CcdiStaffEnterpriseRelationImportServiceImpl.java` | 添加身份证号验证 |
|
||||
| 员工亲属关系导入 | `CcdiStaffFmyRelationImportServiceImpl.java` | 添加身份证号验证 |
|
||||
| 员工调动导入 | `CcdiStaffTransferImportServiceImpl.java` | 优化为1次遍历 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 数据流设计
|
||||
|
||||
### 3.1 详细流程
|
||||
|
||||
```
|
||||
阶段1: 提取身份证号
|
||||
├─ 从 excelList 提取所有 personId
|
||||
├─ 过滤 null 值和空字符串
|
||||
├─ HashSet 去重
|
||||
└─ 得到 Set<String> allPersonIds
|
||||
|
||||
阶段2: 批量查询
|
||||
├─ 如果 allPersonIds 为空,返回空集合
|
||||
├─ 构建查询: SELECT id_card FROM ccdi_base_staff WHERE id_card IN (...)
|
||||
├─ 执行: baseStaffMapper.selectList(wrapper)
|
||||
├─ 提取结果中的 idCard
|
||||
└─ 得到 Set<String> 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<String> excelPersonIds = excelList.stream()
|
||||
.map(CcdiStaffEnterpriseRelationExcel::getPersonId)
|
||||
.filter(StringUtils::isNotEmpty)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
Set<String> existingPersonIds = new HashSet<>();
|
||||
if (!excelPersonIds.isEmpty()) {
|
||||
ImportLogUtils.logBatchQueryStart(log, taskId, "员工身份证号", excelPersonIds.size());
|
||||
|
||||
LambdaQueryWrapper<CcdiBaseStaff> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.select(CcdiBaseStaff::getIdCard)
|
||||
.in(CcdiBaseStaff::getIdCard, excelPersonIds);
|
||||
|
||||
List<CcdiBaseStaff> 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): 初始设计版本,包含三个导入服务的身份证号校验
|
||||
Reference in New Issue
Block a user