junboV2/docs/plans/2026-02-07-organization-import-verification-design.md

18 KiB
Raw Blame History

组织机构导入验证设计方案

日期: 2026-02-07 状态: 设计完成 目标: 确保 org.yml 数据完整、准确地导入到 SQLite支持幂等性和完整性验证

一、背景与目标

问题描述

项目完成了从 YAML/Excel 到 SQLite 的数据模型迁移,但缺乏完善的验证机制,无法确保:

  1. 数量完整性 - YAML 中的数据是否全部导入
  2. 关系正确性 - 时间范围内的关系状态是否正确
  3. 数据去重 - 多次导入不会产生重复数据
  4. 原子性 - 导入过程遇到错误能正确回滚

验证目标

  • 数量验证: 员工、部门、关系数量与 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