- 新增员工实体关系管理API文档 - 在列表接口和详情接口响应中添加personName字段 - 说明personName通过LEFT JOIN ccdi_base_staff表获取 - 如果personId在员工信息表中不存在,personName为null
14 KiB
14 KiB
员工关系导入身份证号校验设计文档
日期: 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 依赖注入
在三个导入服务中添加:
@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<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 错误信息格式
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字节 × mexistingPersonIds: 约 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行后)
@Resource
private CcdiBaseStaffMapper baseStaffMapper;
6.1.2 在 importRelationAsync 方法中(第55行后),添加批量查询:
// 【新增】批量验证员工身份证号是否存在
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行后),添加检查:
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
相同的修改步骤:
- 添加
CcdiBaseStaffMapper依赖注入 - 在第57行后添加批量查询身份证号逻辑
- 在第96行后(数据处理循环开始处)添加身份证号检查
6.3 员工调动导入服务优化
文件: CcdiStaffTransferImportServiceImpl.java
优化前: 2次遍历(预验证 + 主循环) 优化后: 1次遍历(主循环中检查)
修改步骤:
- 移除
batchValidateStaffIds方法 - 移除
isRowAlreadyFailed方法 - 在主循环开始处添加员工ID存在性检查
- 使用
existingStaffIds替代原有的预验证逻辑
6.4 需要导入的类
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 数据库索引建议
-- 检查索引是否存在
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 实施步骤
- ✅ 完成设计方案
- ⏳ 修改
CcdiStaffEnterpriseRelationImportServiceImpl - ⏳ 修改
CcdiStaffFmyRelationImportServiceImpl - ⏳ 优化
CcdiStaffTransferImportServiceImpl - ⏳ 检查并创建数据库索引(如需要)
- ⏳ 编写单元测试
- ⏳ 本地测试验证
- ⏳ 更新API文档(如需要)
8.5 验收标准
- 不存在的员工身份证号被正确识别并记录错误
- 错误信息包含正确的行号和身份证号
- 有效数据正常导入
- 日志记录完整
- 性能无明显下降(批量查询仅1次)
- 与现有导入逻辑保持一致
- 三个导入服务功能一致
8.6 风险评估
- 风险等级: 低
- 影响范围: 仅影响导入功能,不影响其他模块
- 回滚方案: 直接移除新增的验证代码即可
- 数据安全: 只读查询,无数据风险
9. 设计总结
9.1 核心优势
- 性能优异: 批量查询,仅1次数据库访问
- 代码简洁: 仅1次遍历,逻辑清晰
- 一致性高: 三个导入服务使用相同的设计模式
- 易于维护: 遵循现有框架规范
9.2 与原员工调动导入设计的对比
| 对比项 | 原设计 | 新设计 |
|---|---|---|
| 遍历次数 | 2次 | 1次 ⭐ |
| 代码复杂度 | 需要额外方法 | 更简洁 |
| 性能 | 优秀 | 更优 |
| 可维护性 | 良好 | 更好 |
9.3 设计亮点
- ✅ 批量预验证: 充分利用数据库 IN 查询性能
- ✅ 单次遍历: 减少不必要的循环,代码更清晰
- ✅ 统一模式: 三个导入服务使用一致的验证逻辑
- ✅ 错误友好: 详细的错误信息包含行号
- ✅ 性能监控: 完整的日志记录便于排查问题
10. 附录
10.1 相关文档
10.2 设计决策记录
Q1: 为什么选择批量预验证而非逐条验证? A: 批量验证只需1次数据库查询,性能提升约100倍。
Q2: 为什么优化为1次遍历? A: 减少不必要的循环,代码更简洁,性能更好。
Q3: 为什么不验证员工在职状态? A: 需求明确仅验证身份证号存在性,避免过度设计。
Q4: 为什么不验证关系人身份证号? A: 关系人可能不是系统员工,验证会限制使用场景。
10.3 版本历史
- v1.0 (2026-02-11): 初始设计版本,包含三个导入服务的身份证号校验