# 转运部同步功能 - 代码修改清单
## 修改时间
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 接口
- ✅ 编写功能说明文档
- ✅ 编写测试指南文档
- ✅ 编写修改清单文档