18 KiB
18 KiB
组织机构导入验证设计方案
日期: 2026-02-07 状态: 设计完成 目标: 确保 org.yml 数据完整、准确地导入到 SQLite,支持幂等性和完整性验证
一、背景与目标
问题描述
项目完成了从 YAML/Excel 到 SQLite 的数据模型迁移,但缺乏完善的验证机制,无法确保:
- 数量完整性 - YAML 中的数据是否全部导入
- 关系正确性 - 时间范围内的关系状态是否正确
- 数据去重 - 多次导入不会产生重复数据
- 原子性 - 导入过程遇到错误能正确回滚
验证目标
- ✅ 数量验证: 员工、部门、关系数量与 YAML 一致
- ✅ 关系验证: 所有时间点的组织关系正确无误
- ✅ 幂等性: 多次导入不会产生重复数据
- ✅ 冲突检测: 数据不一致时报错而非静默覆盖
- ✅ 可视化报告: 提供 Markdown + JSON 双格式报告
二、整体架构
核心组件
┌─────────────────────────────────────────────────────────────┐
│ org.yml │
└────────────────────────┬────────────────────────────────────┘
│
▼
┌───────────────────────────────┐
│ OrganizationImportService │ 【增强幂等性】
│ - 唯一性检查 │
│ - 冲突检测 │
│ - 事务保证 │
└───────────────┬───────────────┘
│
▼
┌───────────────┐
│ SQLite 数据库 │
└───────┬───────┘
│
▼
┌───────────────────────────────┐
│ OrganizationVerifier │ 【新增验证器】
│ - verifyEmployees() │
│ - verifyDepartments() │
│ - verifyDepartmentRelations()│
│ - verifyEmployeeDeptRelations()│
│ - verifyAtDate() │
└───────────────┬───────────────┘
│
▼
┌───────────────────────────────┐
│ VerificationReport │ 【新增报告类】
│ - CategoryResult × 4 │
│ - Summary │
└───────────────┬───────────────┘
│
▼
┌───────────────────────────────┐
│ ReportGenerator │ 【新增生成器】
│ - generateMarkdown() │
│ - generateJson() │
└───────────────────────────────┘
三、幂等性保证设计
3.1 唯一性判断规则
员工(EmployeeEntity)
唯一性标识: 姓名 + 所有别名
判断逻辑: 如果姓名或任一别名匹配已有员工,视为同一人
示例:
YAML: "潘力;潘总"
数据库已有: name="潘力", aliases=["潘总"]
结果: 视为同一人,跳过
部门(DepartmentEntity)
唯一性标识: 部门名称
判断逻辑: 部门名称完全匹配
示例:
YAML: "产品研发中心"
数据库已有: name="产品研发中心"
结果: 视为同一部门,跳过
部门关系(DepartmentRelationEntity)
唯一性标识: 父部门 + 子部门 + 开始日期
判断逻辑: 三者都匹配视为同一关系
示例:
YAML: "产品研发中心;测试部;2012-06-01"
数据库已有: parent="产品研发中心", child="测试部", startDate="2012-06-01"
结果: 视为同一关系
冲突检测:
- 如果唯一性标识相同,但 endDate 不同 → 报错(数据冲突)
人员部门关系(EmployeeDepartmentRelationEntity)
唯一性标识: 员工 + 部门 + 开始日期
判断逻辑: 三者都匹配视为同一关系
冲突检测:
- 如果唯一性标识相同,但 endDate 不同 → 报错(数据冲突)
3.2 导入策略
对于每条记录:
1. 查询是否已存在(根据唯一性规则)
2. 如果存在且完全一致:
✅ 跳过(记录到 skipped 计数)
3. 如果存在但数据不一致:
❌ 报错(记录到 conflicts 列表)
❌ 事务回滚,停止导入
4. 如果不存在:
✅ 插入新记录(记录到 inserted 计数)
遇到任何冲突立即停止,保证数据一致性
3.3 ImportResult 扩展
@Data
@Builder
public class ImportResult {
private boolean success; // 是否成功
private int insertedCount; // 新插入的记录数
private int skippedCount; // 跳过的记录数(完全相同)
private List<DataConflict> conflicts; // 数据冲突列表
private List<String> errors; // 其他错误信息
}
@Data
@Builder
public class DataConflict {
private String type; // 冲突类型: "DepartmentRelation" / "EmployeeDepartmentRelation"
private String key; // 唯一标识: "产品研发中心 -> 测试部, 2012-06-01"
private String yamlValue; // YAML 中的值
private String dbValue; // 数据库中的值
private String field; // 冲突字段: "endDate"
}
四、完整时间线验证设计
4.1 时间点提取策略
从 YAML 和数据库中提取所有关键时间点
关键时间点包括:
1. 所有关系的开始日期(startDate)
2. 所有关系的结束日期(endDate)
3. 开始日期的前一天(验证关系未生效)
4. 结束日期的后一天(验证关系已结束)
示例:
关系1: 产品研发中心 -> 测试部, 2012-06-01 ~ 无结束
提取时间点:
- 2012-05-31 (前一天,关系应不存在)
- 2012-06-01 (开始日期,关系应存在)
关系2: 产品研发中心 -> 大数据平台开发部, 2012-01-01 ~ 2023-12-17
提取时间点:
- 2011-12-31 (前一天,关系应不存在)
- 2012-01-01 (开始,关系应存在)
- 2023-12-17 (结束,关系应存在)
- 2023-12-18 (后一天,关系应不存在)
4.2 验证逻辑
对于每个时间点 T:
1. 查询 YAML: 哪些关系在 T 应该有效
规则: startDate <= T && (endDate == null || endDate >= T)
2. 查询数据库: 哪些关系在 T 实际有效
使用同样的查询规则
3. 对比结果:
- 缺失 (Missing): YAML 有但数据库没有
- 多余 (Extra): 数据库有但 YAML 没有
- 匹配 (Match): 两边都有且一致
4. 记录差异到报告
4.3 验证覆盖范围
验证项目:
✅ 部门关系在所有关键时间点的正确性
✅ 人员部门关系在所有关键时间点的正确性
✅ 关系的开始日期和结束日期准确性
✅ 边界条件(前一天、后一天)的正确性
五、验证报告设计
5.1 报告数据结构
@Data
@Builder
public class VerificationReport {
private LocalDateTime verificationTime; // 验证时间
private boolean overallSuccess; // 总体是否通过
// 四大类验证结果
private CategoryResult employees;
private CategoryResult departments;
private CategoryResult departmentRelations;
private CategoryResult employeeDepartmentRelations;
// 汇总统计
private Summary summary;
}
@Data
@Builder
public class CategoryResult {
private String category; // 类别名称
private boolean passed; // 是否通过
// 数量统计
private int yamlCount; // YAML 中的数量
private int dbCount; // 数据库中的数量
private boolean countMatch; // 数量是否匹配
// 差异明细
private List<String> missing; // 数据库缺失的记录
private List<String> extra; // 数据库多余的记录
private List<FieldMismatch> mismatches; // 字段不匹配
// 时间点验证结果(仅关系类)
private List<TimePointIssue> timePointIssues;
}
@Data
@Builder
public class FieldMismatch {
private String recordKey; // 记录标识
private String field; // 字段名
private String yamlValue; // YAML 中的值
private String dbValue; // 数据库中的值
}
@Data
@Builder
public class TimePointIssue {
private LocalDate date; // 时间点
private String issueType; // "MISSING" / "EXTRA"
private String relation; // 关系描述
}
@Data
@Builder
public class Summary {
private int totalChecks; // 总检查项
private int passedChecks; // 通过的检查
private int failedChecks; // 失败的检查
private List<String> criticalIssues; // 关键问题列表
}
5.2 Markdown 报告格式
# 组织机构导入验证报告
**验证时间**: 2026-02-07 15:30:00
**总体结果**: ✅ 通过 / ❌ 失败
---
## 一、员工验证
- **YAML 数量**: 200
- **数据库数量**: 200
- **状态**: ✅ 通过
---
## 二、部门验证
- **YAML 数量**: 40
- **数据库数量**: 40
- **状态**: ✅ 通过
---
## 三、部门关系验证
- **YAML 数量**: 95
- **数据库数量**: 94
- **状态**: ❌ 失败
### 缺失记录
- 敏捷团队 -> 风暴队, 2025-11-17 ~ null
### 时间点验证问题
- **2025-11-17**: ❌ 缺少关系 "敏捷团队 -> 风暴队"
- **2025-11-18**: ❌ 缺少关系 "敏捷团队 -> 风暴队"
---
## 四、人员部门关系验证
- **YAML 数量**: 880
- **数据库数量**: 880
- **状态**: ✅ 通过
---
## 汇总统计
- **总检查项**: 1215
- **通过**: 1214
- **失败**: 1
- **通过率**: 99.92%
### 关键问题
1. 部门关系缺失: "敏捷团队 -> 风暴队, 2025-11-17 ~ null"
5.3 JSON 报告格式
{
"verificationTime": "2026-02-07T15:30:00",
"overallSuccess": false,
"employees": {
"category": "员工",
"passed": true,
"yamlCount": 200,
"dbCount": 200,
"countMatch": true,
"missing": [],
"extra": [],
"mismatches": []
},
"departmentRelations": {
"category": "部门关系",
"passed": false,
"yamlCount": 95,
"dbCount": 94,
"countMatch": false,
"missing": ["敏捷团队 -> 风暴队, 2025-11-17 ~ null"],
"extra": [],
"mismatches": [],
"timePointIssues": [
{
"date": "2025-11-17",
"issueType": "MISSING",
"relation": "敏捷团队 -> 风暴队"
}
]
},
"summary": {
"totalChecks": 1215,
"passedChecks": 1214,
"failedChecks": 1,
"criticalIssues": [
"部门关系缺失: 敏捷团队 -> 风暴队, 2025-11-17 ~ null"
]
}
}
六、测试策略
6.1 测试用例
1. 基础导入测试
@Test
void shouldImportOrganizationDataSuccessfully() {
// 清空数据库
cleanDatabase();
// 执行导入
ImportResult result = importService.importFromYaml("org.yml");
// 验证结果
assertThat(result.isSuccess()).isTrue();
assertThat(result.getInsertedCount()).isGreaterThan(0);
assertThat(result.getConflicts()).isEmpty();
}
2. 幂等性测试
@Test
void shouldBeIdempotentWhenImportingTwice() {
// 第一次导入
ImportResult result1 = importService.importFromYaml("org.yml");
int firstInserted = result1.getInsertedCount();
// 第二次导入(相同数据)
ImportResult result2 = importService.importFromYaml("org.yml");
// 验证幂等性
assertThat(result2.getInsertedCount()).isEqualTo(0);
assertThat(result2.getSkippedCount()).isEqualTo(firstInserted);
assertThat(result2.isSuccess()).isTrue();
// 验证数据库记录总数不变
long totalEmployees = employeeRepository.count();
assertThat(totalEmployees).isEqualTo(200); // 假设 YAML 有 200 个员工
}
3. 数据冲突检测测试
@Test
void shouldDetectConflictWhenEndDateDiffers() {
// 导入原始数据
importService.importFromYaml("org.yml");
// 修改某个关系的结束日期
String modifiedYaml = modifyRelationEndDate("org.yml",
"产品研发中心;测试部;2012-06-01",
"2023-12-31"); // 原本无结束日期
// 再次导入
ImportResult result = importService.importFromYaml(modifiedYaml);
// 验证冲突检测
assertThat(result.isSuccess()).isFalse();
assertThat(result.getConflicts()).hasSize(1);
DataConflict conflict = result.getConflicts().get(0);
assertThat(conflict.getType()).isEqualTo("DepartmentRelation");
assertThat(conflict.getField()).isEqualTo("endDate");
assertThat(conflict.getYamlValue()).isEqualTo("2023-12-31");
assertThat(conflict.getDbValue()).isEqualTo("null");
}
4. 完整性验证测试
@Test
void shouldVerifyAllDataCorrectness() {
// 导入数据
ImportResult importResult = importService.importFromYaml("org.yml");
assertThat(importResult.isSuccess()).isTrue();
// 执行验证
VerificationReport report = verifier.verify("org.yml");
// 验证四大类
assertThat(report.getEmployees().isPassed()).isTrue();
assertThat(report.getDepartments().isPassed()).isTrue();
assertThat(report.getDepartmentRelations().isPassed()).isTrue();
assertThat(report.getEmployeeDepartmentRelations().isPassed()).isTrue();
// 验证总体结果
assertThat(report.isOverallSuccess()).isTrue();
// 生成报告文件
reportGenerator.generateMarkdown(report, "target/verification-report.md");
reportGenerator.generateJson(report, "target/verification-report.json");
}
5. 时间点验证测试
@Test
void shouldVerifyRelationsAtAllCriticalTimepoints() {
// 导入数据
importService.importFromYaml("org.yml");
// 加载 YAML 数据
OrganizationData yamlData = yamlDataLoader.load("org.yml");
// 提取所有关键时间点
Set<LocalDate> criticalDates = timelineExtractor.extractCriticalDates(yamlData);
// 对每个时间点验证
List<String> failures = new ArrayList<>();
for (LocalDate date : criticalDates) {
TimePointVerification result = verifier.verifyAtDate(date, yamlData);
if (!result.isPassed()) {
failures.add(String.format("%s: %s", date, result.getIssues()));
}
}
// 验证所有时间点都通过
assertThat(failures)
.withFailMessage("以下时间点验证失败:\n" + String.join("\n", failures))
.isEmpty();
}
6.2 测试数据准备
@SpringBootTest
@Transactional
@TestPropertySource(properties = {
"spring.datasource.url=jdbc:sqlite::memory:",
"spring.jpa.hibernate.ddl-auto=create-drop"
})
class OrganizationImportTest {
@Autowired
private OrganizationImportService importService;
@Autowired
private OrganizationVerifier verifier;
@Autowired
private ReportGenerator reportGenerator;
@BeforeEach
void setup() {
// 每个测试前清空数据库
cleanDatabase();
}
private void cleanDatabase() {
employeeDepartmentRelationRepository.deleteAll();
departmentRelationRepository.deleteAll();
employeeRepository.deleteAll();
departmentRepository.deleteAll();
}
}
七、实现计划
阶段 1: 增强导入服务
- 实现唯一性检查逻辑
- 实现数据冲突检测
- 扩展 ImportResult 类
- 添加事务注解
阶段 2: 创建验证器
- 创建 OrganizationVerifier 类
- 实现四大类验证方法
- 实现时间点提取逻辑
- 实现时间点验证方法
阶段 3: 创建报告组件
- 创建报告数据类
- 创建 ReportGenerator 类
- 实现 Markdown 生成
- 实现 JSON 生成
阶段 4: 编写测试
- 基础导入测试
- 幂等性测试
- 冲突检测测试
- 完整性验证测试
- 时间点验证测试
八、使用流程
开发阶段
# 1. 修改 org.yml
# 2. 运行测试
./gradlew test --tests OrganizationImportTest
# 3. 查看报告
cat build/reports/verification-report.md
手动验证
@SpringBootTest
public class ManualVerification {
@Autowired
private OrganizationImportService importService;
@Autowired
private OrganizationVerifier verifier;
@Autowired
private ReportGenerator reportGenerator;
@Test
void manualVerify() {
// 1. 导入
ImportResult importResult = importService.importFromYaml("org.yml");
System.out.println("导入结果: " + importResult);
// 2. 验证
VerificationReport report = verifier.verify("org.yml");
// 3. 生成报告
reportGenerator.generateMarkdown(report, "verification-report.md");
reportGenerator.generateJson(report, "verification-report.json");
// 4. 检查结果
if (!report.isOverallSuccess()) {
System.err.println("验证失败,请查看报告: verification-report.md");
}
}
}
九、成功标准
✅ 功能完整性
- 支持 4 类数据验证(员工、部门、部门关系、人员部门关系)
- 支持完整时间线验证(所有关键时间点)
- 支持幂等性导入(多次导入不重复)
- 支持冲突检测(数据不一致时报错)
✅ 报告质量
- Markdown 报告清晰易读
- JSON 报告结构完整
- 差异明细准确定位问题
✅ 测试覆盖
- 单元测试覆盖所有核心逻辑
- 集成测试验证端到端流程
- 所有测试通过
✅ 代码质量
- 符合项目编码规范
- 有完善的注释和文档
- 通过代码审查
文档版本: 1.0 最后更新: 2026-02-07 维护者: User & Claude