# 转运部同步功能说明
## 功能概述
在现有的分公司同步功能基础上,新增转运部同步功能。转运部是总公司(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` 接口
- 创建本说明文档