# 转运部同步功能 - 代码修改清单 ## 修改时间 2025-10-18 ## 需求说明 在现有的分公司同步功能基础上,增加转运部同步功能。转运部是总公司(ID=101)下的一个部门,需要同步转运部及其所有子部门。 ## 关键差异 | 项目 | 分公司同步 | 转运部同步 | |------|-----------|-----------| | 父部门 | 若依科技(ID=100) | 总公司(ID=101) | | SQL Server 查询条件 | `a.departmentName = N'合作单位'` | `a.departmentName = N'转运部'` | | 名称格式 | `湛江--护士`(需要解析"--") | 直接使用 `departmentName` | | 同步逻辑 | 创建分公司 + 子部门(两层) | 创建转运部 + 子部门(两层) | ## 修改文件列表 ### 1. DepartmentSyncMapper.xml ✅ **文件路径**: `ruoyi-system/src/main/resources/mapper/system/DepartmentSyncMapper.xml` **修改内容**: 新增查询方法 `selectTransportDepartments` ```xml ``` **关键点**: - 使用 `CDATA` 包裹 SQL 避免 XML 特殊字符问题 - 使用 `TOP 500` 限制返回数据量 - 使用 `WITH (NOLOCK)` 提高并发性能 - 中文字符串使用 `N` 前缀 --- ### 2. DepartmentSyncMapper.java ✅ **文件路径**: `ruoyi-system/src/main/java/com/ruoyi/system/mapper/DepartmentSyncMapper.java` **修改内容**: 新增方法声明 ```java /** * 查询转运部下的所有子部门 * * @return 转运部子部门列表 */ List selectTransportDepartments(); ``` **修改位置**: 第 23-28 行(在 `selectBranchDepartments()` 方法后添加) --- ### 3. IDepartmentSyncDataService.java ✅ **文件路径**: `ruoyi-system/src/main/java/com/ruoyi/system/service/IDepartmentSyncDataService.java` **修改内容**: 新增方法声明 ```java /** * 从 SQL Server 查询转运部下的所有子部门数据 * * 数据源:SQL Server (uv_department 视图) * * @return 转运部子部门列表 */ List getTransportDepartments(); ``` **修改位置**: 第 24-32 行(在 `getBranchDepartments()` 方法后添加) --- ### 4. DepartmentSyncDataServiceImpl.java ✅ **文件路径**: `ruoyi-system/src/main/java/com/ruoyi/system/service/impl/DepartmentSyncDataServiceImpl.java` **修改内容**: 新增方法实现 ```java /** * 从 SQL Server 查询转运部下的所有子部门数据 * * 注意:DepartmentSyncMapper 上有 @DataSource(DataSourceType.SQLSERVER) 注解 * 此方法会自动切换到 SQL Server 数据源执行查询 * * @return 转运部子部门列表 */ @Override public List getTransportDepartments() { try { log.info("开始从 SQL Server 查询转运部子部门数据..."); // 调用 Mapper 查询 SQL Server 数据 // @DataSource 注解会自动切换数据源 List transportDepts = departmentSyncMapper.selectTransportDepartments(); if (transportDepts == null || transportDepts.isEmpty()) { log.warn("未从 SQL Server 查询到转运部子部门数据"); return transportDepts; } log.info("从 SQL Server 查询到 {} 条转运部子部门数据", transportDepts.size()); return transportDepts; } catch (Exception e) { log.error("从 SQL Server 查询转运部子部门数据失败", e); throw new RuntimeException("查询 SQL Server 数据失败: " + e.getMessage(), e); } } ``` **修改位置**: 第 67-102 行(在 `getBranchDepartments()` 方法后、类结束前添加) --- ### 5. IDepartmentSyncService.java ✅ **文件路径**: `ruoyi-system/src/main/java/com/ruoyi/system/service/IDepartmentSyncService.java` **修改内容**: 新增方法声明 ```java /** * 同步转运部和子部门数据(使用外部传入的数据源) * * 同步逻辑: * 1. 确保总公司(ID=101)下存在"转运部" * 2. 创建转运部的子部门(直接创建,无需解析"--"格式) * * @param transportDepts 外部传入的转运部子部门数据列表 * @return 同步结果 */ AjaxResult syncTransportDepartments(List transportDepts); ``` **修改位置**: 第 29-40 行(在 `syncBranchDepartments()` 方法后添加) --- ### 6. DepartmentSyncServiceImpl.java ✅ **文件路径**: `ruoyi-system/src/main/java/com/ruoyi/system/service/impl/DepartmentSyncServiceImpl.java` **修改内容**: 新增方法实现(核心同步逻辑) **完整方法**: 第 189-324 行 **关键逻辑**: 1. **检查总公司是否存在**(第 197-202 行) ```java SysDept headOffice = sysDeptMapper.selectDeptById(101L); if (headOffice == null) { log.error("总公司(ID=101)不存在,无法同步转运部"); return AjaxResult.error("总公司(ID=101)不存在,请先创建总公司"); } ``` 2. **确保转运部存在**(第 204-227 行) ```java SysDept existingTransport = sysDeptMapper.checkDeptNameUnique("转运部", 101L); if (existingTransport != null) { transportDeptId = existingTransport.getDeptId(); log.info("转运部已存在: ID={}", transportDeptId); } else { // 创建新的转运部 SysDept newTransport = new SysDept(); newTransport.setParentId(101L); newTransport.setDeptName("转运部"); newTransport.setAncestors("0,101"); newTransport.setOrderNum(1); newTransport.setStatus("0"); newTransport.setCreateBy("sync"); sysDeptMapper.insertDept(newTransport); transportDeptId = newTransport.getDeptId(); log.info("创建新转运部: ID={}", transportDeptId); } ``` 3. **同步子部门**(第 229-293 行) ```java for (DepartmentSyncDTO dto : transportDepts) { String deptName = dto.getDepartmentName(); // 先根据departmentId查询是否已存在 SysDept existingDept = sysDeptMapper.selectDeptByDepartmentIdAndParentId( dto.getDepartmentId(), transportDeptId); if (existingDept != null) { // 更新已存在的部门 existingDept.setDeptName(deptName); existingDept.setUpdateBy("sync"); sysDeptMapper.updateDept(existingDept); updatedDept++; } else { // 检查是否存在同名部门 SysDept sameName = sysDeptMapper.checkDeptNameUnique(deptName, transportDeptId); if (sameName != null) { // 更新同名部门的 departmentId sameName.setDepartmentId(dto.getDepartmentId()); sameName.setUpdateBy("sync"); sysDeptMapper.updateDept(sameName); updatedDept++; } else { // 创建新部门 SysDept newDept = new SysDept(); newDept.setParentId(transportDeptId); newDept.setDeptName(deptName); newDept.setAncestors("0,101," + transportDeptId); newDept.setOrderNum(1); newDept.setStatus("0"); newDept.setDepartmentId(dto.getDepartmentId()); newDept.setCreateBy("sync"); sysDeptMapper.insertDept(newDept); createdDept++; } } } ``` **设计特点**: - 支持幂等性(可重复执行) - 自动判断创建还是更新 - 使用 `@Transactional` 保证事务一致性 - 详细的日志记录 --- ### 7. DepartmentSyncController.java ✅ **文件路径**: `ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/DepartmentSyncController.java` **修改内容**: 新增 REST API 接口 ```java /** * 同步转运部和子部门数据(使用外部传入的数据源) * * 同步逻辑: * 1. 确保总公司(ID=101)下存在"转运部" * 2. 创建转运部的子部门 * * @param transportDepts 转运部子部门数据列表 * @return 同步结果 */ @PreAuthorize("@ss.hasPermi('system:dept:sync')") @PostMapping("/transport/data") public AjaxResult syncTransportDepartmentsWithData(@RequestBody List transportDepts) { return departmentSyncService.syncTransportDepartments(transportDepts); } ``` **修改位置**: 第 50-64 行(在 `/branch/data` 接口后添加) **接口信息**: - URL: `POST /system/dept/sync/transport/data` - 权限: `system:dept:sync` - 请求体: `List` - 响应: `AjaxResult` --- ## 新增文档 ### 1. 转运部同步功能说明.md ✅ **文件路径**: `转运部同步功能说明.md` **内容包括**: - 功能概述 - 同步逻辑说明 - 实现架构 - 核心类详细说明 - REST API 接口 - 数据流程图 - 部门层级结构 - 幂等性设计 - 事务管理 - 数据源切换机制 - 完整调用示例 - 注意事项 ### 2. 转运部同步功能测试指南.md ✅ **文件路径**: `转运部同步功能测试指南.md` **内容包括**: - 测试环境准备 - 测试步骤 - 测试场景(5个场景) - 完整测试流程(Java 代码) - 日志监控 - 常见问题排查 - 性能测试 - 回滚测试数据 - 测试清单 --- ## 代码统计 | 文件类型 | 新增文件 | 修改文件 | 总计 | |---------|---------|---------|------| | Mapper XML | 0 | 1 | 1 | | Java 接口 | 0 | 3 | 3 | | Java 实现类 | 0 | 2 | 2 | | Controller | 0 | 1 | 1 | | 文档 | 3 | 0 | 3 | | **总计** | **3** | **7** | **10** | ## 新增代码行数统计 | 文件 | 新增行数 | |------|---------| | DepartmentSyncMapper.xml | 12 行 | | DepartmentSyncMapper.java | 7 行 | | IDepartmentSyncDataService.java | 9 行 | | DepartmentSyncDataServiceImpl.java | 35 行 | | IDepartmentSyncService.java | 12 行 | | DepartmentSyncServiceImpl.java | 138 行 | | DepartmentSyncController.java | 17 行 | | **代码总计** | **230 行** | | 转运部同步功能说明.md | 455 行 | | 转运部同步功能测试指南.md | 411 行 | | 转运部同步功能-修改清单.md | 本文档 | | **文档总计** | **866+ 行** | --- ## 技术要点 ### 1. SQL Server 查询优化 - ✅ 使用 `TOP 500` 限制返回数据量 - ✅ 使用 `WITH (NOLOCK)` 提高并发性能 - ✅ 中文字符串使用 `N` 前缀确保正确匹配 - ✅ 使用 `` 包裹 SQL 避免 XML 解析问题 ### 2. 多数据源架构 - ✅ DataService 标注 `@DataSource(DataSourceType.SQLSERVER)` 查询 SQL Server - ✅ SyncService 使用默认数据源操作 MySQL - ✅ 智能数据源切换(详见:多数据源切换问题修复说明.md) ### 3. 幂等性设计 - ✅ 使用 `departmentId` 作为唯一标识 - ✅ 先查询后决定插入或更新 - ✅ 支持重复执行不会产生重复数据 ### 4. 事务管理 - ✅ 所有同步方法标注 `@Transactional` - ✅ 异常时自动回滚 - ✅ 保证数据一致性 ### 5. 职责分离 - ✅ DataService: 专门负责查询 SQL Server - ✅ SyncService: 专门负责同步到 MySQL - ✅ Controller: 提供 REST API 接口 --- ## 测试验证 ### 编译检查 ✅ 所有修改的文件均通过编译检查,无语法错误。 ### 功能测试 请参考 `转运部同步功能测试指南.md` 进行以下测试: - [ ] 测试 1: 从 SQL Server 查询转运部数据 - [ ] 测试 2: 通过 REST API 同步转运部 - [ ] 测试 3: 验证 MySQL 中的同步结果 - [ ] 场景 1: 首次同步 - [ ] 场景 2: 重复同步(幂等性) - [ ] 场景 3: 部分更新 - [ ] 场景 4: 同名部门处理 - [ ] 场景 5: 总公司不存在错误处理 --- ## 部署说明 ### 1. 数据库准备 确保 MySQL 中存在总公司: ```sql -- 检查总公司是否存在 SELECT * FROM sys_dept WHERE dept_id = 101; -- 如果不存在,创建总公司 INSERT INTO sys_dept (dept_id, parent_id, ancestors, dept_name, order_num, status, create_by, create_time) VALUES (101, 0, '0', '总公司', 1, '0', 'admin', NOW()); ``` ### 2. 代码部署 1. 编译项目:`mvn clean package` 2. 部署到服务器 3. 重启应用 ### 3. 权限配置 确保需要调用同步接口的用户拥有 `system:dept:sync` 权限。 --- ## 调用示例 ### REST API 调用 ```bash curl -X POST http://localhost:8080/system/dept/sync/transport/data \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_TOKEN" \ -d '[ { "departmentId": 1001, "departmentName": "转运队", "parentId": 100, "parentName": "转运部" }, { "departmentId": 1002, "departmentName": "调度中心", "parentId": 100, "parentName": "转运部" } ]' ``` ### Java 代码调用 ```java // 步骤 1: 查询 SQL Server 数据 List transportDepts = departmentSyncDataService.getTransportDepartments(); // 步骤 2: 同步到 MySQL AjaxResult result = departmentSyncService.syncTransportDepartments(transportDepts); ``` --- ## 后续优化建议 ### 1. 添加查询接口 可以添加一个 GET 接口直接返回 SQL Server 的转运部数据: ```java @GetMapping("/transport/query") public AjaxResult queryTransportDepartments() { List transportDepts = departmentSyncDataService.getTransportDepartments(); return AjaxResult.success(transportDepts); } ``` ### 2. 添加内部查询模式 像分公司同步一样,添加一个不需要传参的同步方法: ```java // 接口 AjaxResult syncTransportDepartments(); // 实现 @Override @Transactional public AjaxResult syncTransportDepartments() { List transportDepts = departmentSyncDataService.getTransportDepartments(); return syncTransportDepartments(transportDepts); } // Controller @PostMapping("/transport") public AjaxResult syncTransportDepartments() { return departmentSyncService.syncTransportDepartments(); } ``` ### 3. 批量同步 添加一个接口同时同步分公司和转运部: ```java @PostMapping("/all") public AjaxResult syncAllDepartments() { // 同步分公司 AjaxResult branchResult = syncBranchDepartments(); // 同步转运部 AjaxResult transportResult = syncTransportDepartments(); // 合并结果返回 return AjaxResult.success("全部同步完成", Map.of("branch", branchResult, "transport", transportResult)); } ``` --- ## 相关文档链接 1. [转运部同步功能说明.md](转运部同步功能说明.md) - 功能详细说明 2. [转运部同步功能测试指南.md](转运部同步功能测试指南.md) - 测试指南 3. [部门同步服务重构说明.md](部门同步服务重构说明.md) - 分公司同步架构 4. [用户同步服务重构说明.md](用户同步服务重构说明.md) - 用户同步架构 5. [多数据源切换问题修复说明.md](多数据源切换问题修复说明.md) - 数据源切换机制 6. [SQL Server数据源查询优化说明.md](SQL Server数据源查询优化说明.md) - SQL 优化 --- ## 版本信息 - **功能版本**: v1.0 - **开发日期**: 2025-10-18 - **开发人员**: AI Assistant - **审核状态**: 待测试 - **框架版本**: RuoYi-Vue (Spring Boot 2.5.15) --- ## 更新日志 ### v1.0 (2025-10-18) - ✅ 新增转运部数据查询功能 - ✅ 新增转运部同步功能 - ✅ 新增 REST API 接口 - ✅ 编写功能说明文档 - ✅ 编写测试指南文档 - ✅ 编写修改清单文档