# 转运部同步功能说明 ## 功能概述 在现有的分公司同步功能基础上,新增转运部同步功能。转运部是总公司(ID=101)下的一个部门,需要同步转运部及其子部门数据。 ## 同步逻辑 ### 1. 分公司同步 - **父部门**: 若依科技(ID=100) - **数据源**: SQL Server - `uv_department` 视图 - **查询条件**: `a.departmentName = N'合作单位'` - **名称格式**: `湛江--护士` → 分公司:`湛江分公司`,子部门:`护士` - **特点**: 需要解析"--"分隔符来创建分公司和子部门 ### 2. 转运部同步(新增) - **父部门**: 总公司(ID=101) - **数据源**: SQL Server - `uv_department` 视图 - **查询条件**: `a.departmentName = N'转运部'` - **名称格式**: 直接使用 `departmentName`,无需解析 - **特点**: 直接创建转运部的子部门,不需要解析"--"格式 ## 实现架构 ### 职责分离设计 ``` Controller (REST API) ↓ Service (同步逻辑 - MySQL) ↓ DataService (数据查询 - SQL Server) ↓ Mapper (SQL 执行) ``` ### 核心类说明 #### 1. DepartmentSyncMapper.xml **文件路径**: `ruoyi-system/src/main/resources/mapper/system/DepartmentSyncMapper.xml` 新增查询方法: ```xml ``` **SQL 优化要点**: - 使用 `TOP 500` 限制返回数据量 - 使用 `WITH (NOLOCK)` 提高查询性能 - 中文字符串使用 `N` 前缀确保正确匹配 - 使用 `` 包裹 SQL 避免 XML 特殊字符问题 #### 2. DepartmentSyncMapper.java **文件路径**: `ruoyi-system/src/main/java/com/ruoyi/system/mapper/DepartmentSyncMapper.java` 新增方法: ```java /** * 查询转运部下的所有子部门 * * @return 转运部子部门列表 */ List selectTransportDepartments(); ``` **特点**: - 接口级别标注 `@DataSource(DataSourceType.SQLSERVER)` - 自动切换到 SQL Server 数据源 #### 3. IDepartmentSyncDataService.java **文件路径**: `ruoyi-system/src/main/java/com/ruoyi/system/service/IDepartmentSyncDataService.java` 新增方法: ```java /** * 从 SQL Server 查询转运部下的所有子部门数据 * * 数据源:SQL Server (uv_department 视图) * * @return 转运部子部门列表 */ List getTransportDepartments(); ``` #### 4. DepartmentSyncDataServiceImpl.java **文件路径**: `ruoyi-system/src/main/java/com/ruoyi/system/service/impl/DepartmentSyncDataServiceImpl.java` 新增实现: ```java @Override public List getTransportDepartments() { try { log.info("开始从 SQL Server 查询转运部子部门数据..."); 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); } } ``` **特点**: - 类级别标注 `@DataSource(DataSourceType.SQLSERVER)` - 只负责查询 SQL Server 数据,不涉及 MySQL 操作 #### 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); ``` #### 6. DepartmentSyncServiceImpl.java **文件路径**: `ruoyi-system/src/main/java/com/ruoyi/system/service/impl/DepartmentSyncServiceImpl.java` 新增实现(核心同步逻辑): ```java @Override @Transactional public AjaxResult syncTransportDepartments(List transportDepts) { // 1. 检查总公司(ID=101)是否存在 SysDept headOffice = sysDeptMapper.selectDeptById(101L); if (headOffice == null) { return AjaxResult.error("总公司(ID=101)不存在,请先创建总公司"); } // 2. 确保"转运部"存在 SysDept existingTransport = sysDeptMapper.checkDeptNameUnique("转运部", 101L); if (existingTransport == null) { // 创建转运部 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(); } else { transportDeptId = existingTransport.getDeptId(); } // 3. 创建或更新转运部子部门 for (DepartmentSyncDTO dto : transportDepts) { String deptName = dto.getDepartmentName(); // 检查是否已存在(通过 departmentId + parentId) SysDept existingDept = sysDeptMapper.selectDeptByDepartmentIdAndParentId( dto.getDepartmentId(), transportDeptId); if (existingDept != null) { // 更新已存在的部门 existingDept.setDeptName(deptName); existingDept.setUpdateBy("sync"); sysDeptMapper.updateDept(existingDept); } else { // 检查是否存在同名部门 SysDept sameName = sysDeptMapper.checkDeptNameUnique(deptName, transportDeptId); if (sameName != null) { // 更新同名部门的 departmentId sameName.setDepartmentId(dto.getDepartmentId()); sameName.setUpdateBy("sync"); sysDeptMapper.updateDept(sameName); } 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); } } } } ``` **特点**: - 只涉及 MySQL 数据库操作 - 使用 `@Transactional` 保证事务一致性 - 支持幂等性设计(可重复执行) #### 7. DepartmentSyncController.java **文件路径**: `ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/DepartmentSyncController.java` 新增接口: ```java /** * 同步转运部和子部门数据(使用外部传入的数据源) * * @param transportDepts 转运部子部门数据列表 * @return 同步结果 */ @PreAuthorize("@ss.hasPermi('system:dept:sync')") @PostMapping("/transport/data") public AjaxResult syncTransportDepartmentsWithData(@RequestBody List transportDepts) { return departmentSyncService.syncTransportDepartments(transportDepts); } ``` ## REST API 接口 ### 1. 同步分公司(外部数据源) - **URL**: `POST /system/dept/sync/branch/data` - **权限**: `system:dept:sync` - **请求体**: `List` - **说明**: 接收外部传入的分公司数据进行同步 ### 2. 同步转运部(外部数据源) - **URL**: `POST /system/dept/sync/transport/data` - **权限**: `system:dept:sync` - **请求体**: `List` - **说明**: 接收外部传入的转运部子部门数据进行同步 ### 3. 同步 OA 用户(外部数据源) - **URL**: `POST /system/dept/sync/user/data` - **权限**: `system:user:sync` - **请求体**: `List` - **说明**: 接收外部传入的 OA 用户数据进行同步 ## 数据流程图 ``` ┌─────────────────────────────────────────────────────────┐ │ 外部系统/定时任务 │ └─────────────────────────────────────────────────────────┘ │ │ 调用 REST API ▼ ┌─────────────────────────────────────────────────────────┐ │ DepartmentSyncController │ │ • /branch/data (分公司同步) │ │ • /transport/data (转运部同步) ← 新增 │ │ • /user/data (用户同步) │ └─────────────────────────────────────────────────────────┘ │ │ 传递 DTO 列表 ▼ ┌─────────────────────────────────────────────────────────┐ │ DepartmentSyncServiceImpl (MySQL) │ │ • syncBranchDepartments() │ │ • syncTransportDepartments() ← 新增 │ └─────────────────────────────────────────────────────────┘ │ │ 写入 MySQL ▼ ┌─────────────────────────────────────────────────────────┐ │ SysDeptMapper │ │ • 查询已存在部门 │ │ • 插入新部门 │ │ • 更新部门信息 │ └─────────────────────────────────────────────────────────┘ ``` ## 数据查询流程 ``` ┌─────────────────────────────────────────────────────────┐ │ 外部系统需要先调用 DataService │ └─────────────────────────────────────────────────────────┘ │ │ 调用数据查询服务 ▼ ┌─────────────────────────────────────────────────────────┐ │ DepartmentSyncDataServiceImpl (SQL Server) │ │ • getBranchDepartments() (查询分公司) │ │ • getTransportDepartments() (查询转运部) ← 新增 │ └─────────────────────────────────────────────────────────┘ │ │ 切换到 SQL Server ▼ ┌─────────────────────────────────────────────────────────┐ │ DepartmentSyncMapper │ │ • selectBranchDepartments() │ │ • selectTransportDepartments() ← 新增 │ └─────────────────────────────────────────────────────────┘ │ │ 查询 SQL Server ▼ ┌─────────────────────────────────────────────────────────┐ │ SQL Server - uv_department │ └─────────────────────────────────────────────────────────┘ ``` ## 部门层级结构 ### 分公司结构 ``` 若依科技 (ID=100) ├── 湛江分公司 │ ├── 护士 │ ├── 医生 │ └── ... ├── 广州分公司 │ ├── 护士 │ ├── 医生 │ └── ... └── ... ``` ### 转运部结构(新增) ``` 总公司 (ID=101) └── 转运部 ├── 转运队 ├── 调度中心 ├── 设备维护 └── ... ``` ## 幂等性设计 同步操作支持重复执行,通过以下方式保证幂等性: 1. **部门唯一标识**: 使用 `departmentId`(SQL Server 部门 ID)作为唯一标识 2. **查询优先**: 先查询是否已存在,再决定插入或更新 3. **更新兼容**: 已存在的数据会被更新,不会重复插入 ## 事务管理 - 所有同步方法都标注 `@Transactional` - 确保同步过程中出现异常时自动回滚 - 保证数据一致性 ## 数据源切换机制 ### 智能数据源切换(已修复) 通过修改 `DataSourceAspect.java`,实现了智能数据源切换: 1. **记录旧数据源**: 在切换前记录当前数据源状态 2. **条件性切换**: 只有数据源确实需要变化时才切换 3. **智能恢复**: 只有本次调用改变了数据源时才恢复 详见:`多数据源切换问题修复说明.md` ## 完整调用示例 假设需要同步转运部数据,可以这样调用: ### 方式 1: 分步调用(推荐) ```java // 步骤 1: 调用 DataService 查询 SQL Server 数据 List transportDepts = departmentSyncDataService.getTransportDepartments(); // 步骤 2: 调用 SyncService 将数据同步到 MySQL AjaxResult result = departmentSyncService.syncTransportDepartments(transportDepts); ``` ### 方式 2: REST API 调用 ```bash # 步骤 1: 查询 SQL Server 数据(需要自行实现获取接口) curl -X GET http://localhost:8080/system/dept/sync/transport/query # 步骤 2: 同步数据到 MySQL curl -X POST http://localhost:8080/system/dept/sync/transport/data \ -H "Content-Type: application/json" \ -d '[ { "departmentId": 1001, "departmentName": "转运队", "parentId": 100, "parentName": "转运部" }, { "departmentId": 1002, "departmentName": "调度中心", "parentId": 100, "parentName": "转运部" } ]' ``` ## 注意事项 1. **总公司依赖**: 同步转运部前必须确保总公司(ID=101)已存在 2. **权限控制**: 所有同步接口都需要 `system:dept:sync` 权限 3. **数据源隔离**: DataService 只访问 SQL Server,SyncService 只访问 MySQL 4. **XML 特殊字符**: 所有 SQL 语句都使用 `` 包裹 5. **性能优化**: SQL Server 查询使用 `NOLOCK` 提示和 `TOP` 限制 ## 相关文档 - `部门同步服务重构说明.md` - 分公司同步架构说明 - `用户同步服务重构说明.md` - 用户同步架构说明 - `多数据源切换问题修复说明.md` - 数据源切换问题修复 - `SQL Server数据源查询优化说明.md` - SQL Server 查询优化 ## 更新日志 **2025-10-18** - 新增转运部同步功能 - 在 `DepartmentSyncMapper.xml` 中添加 `selectTransportDepartments` 查询 - 在 `IDepartmentSyncDataService` 和实现类中添加 `getTransportDepartments()` 方法 - 在 `IDepartmentSyncService` 和实现类中添加 `syncTransportDepartments()` 方法 - 在 `DepartmentSyncController` 中添加 `/transport/data` 接口 - 创建本说明文档