2025-10-18
在现有的分公司同步功能基础上,增加转运部同步功能。转运部是总公司(ID=101)下的一个部门,需要同步转运部及其所有子部门。
| 项目 | 分公司同步 | 转运部同步 |
|---|---|---|
| 父部门 | 若依科技(ID=100) | 总公司(ID=101) |
| SQL Server 查询条件 | a.departmentName = N'合作单位' |
a.departmentName = N'转运部' |
| 名称格式 | 湛江--护士(需要解析"--") |
直接使用 departmentName |
| 同步逻辑 | 创建分公司 + 子部门(两层) | 创建转运部 + 子部门(两层) |
文件路径: ruoyi-system/src/main/resources/mapper/system/DepartmentSyncMapper.xml
修改内容: 新增查询方法 selectTransportDepartments
<!-- 查询转运部下的所有子部门 -->
<select id="selectTransportDepartments" resultMap="DepartmentSyncResult">
<![CDATA[
SELECT TOP 500
b.departmentID,
b.departmentName,
b.parentID,
a.departmentName AS parentName
FROM uv_department a WITH (NOLOCK)
INNER JOIN uv_department b WITH (NOLOCK) ON a.departmentID = b.parentID
WHERE a.departmentName = N'转运部'
ORDER BY b.departmentName
]]>
</select>
关键点:
- 使用 CDATA 包裹 SQL 避免 XML 特殊字符问题
- 使用 TOP 500 限制返回数据量
- 使用 WITH (NOLOCK) 提高并发性能
- 中文字符串使用 N 前缀
文件路径: ruoyi-system/src/main/java/com/ruoyi/system/mapper/DepartmentSyncMapper.java
修改内容: 新增方法声明
/**
* 查询转运部下的所有子部门
*
* @return 转运部子部门列表
*/
List<DepartmentSyncDTO> selectTransportDepartments();
修改位置: 第 23-28 行(在 selectBranchDepartments() 方法后添加)
文件路径: ruoyi-system/src/main/java/com/ruoyi/system/service/IDepartmentSyncDataService.java
修改内容: 新增方法声明
/**
* 从 SQL Server 查询转运部下的所有子部门数据
*
* 数据源:SQL Server (uv_department 视图)
*
* @return 转运部子部门列表
*/
List<DepartmentSyncDTO> getTransportDepartments();
修改位置: 第 24-32 行(在 getBranchDepartments() 方法后添加)
文件路径: ruoyi-system/src/main/java/com/ruoyi/system/service/impl/DepartmentSyncDataServiceImpl.java
修改内容: 新增方法实现
/**
* 从 SQL Server 查询转运部下的所有子部门数据
*
* 注意:DepartmentSyncMapper 上有 @DataSource(DataSourceType.SQLSERVER) 注解
* 此方法会自动切换到 SQL Server 数据源执行查询
*
* @return 转运部子部门列表
*/
@Override
public List<DepartmentSyncDTO> getTransportDepartments()
{
try
{
log.info("开始从 SQL Server 查询转运部子部门数据...");
// 调用 Mapper 查询 SQL Server 数据
// @DataSource 注解会自动切换数据源
List<DepartmentSyncDTO> 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() 方法后、类结束前添加)
文件路径: ruoyi-system/src/main/java/com/ruoyi/system/service/IDepartmentSyncService.java
修改内容: 新增方法声明
/**
* 同步转运部和子部门数据(使用外部传入的数据源)
*
* 同步逻辑:
* 1. 确保总公司(ID=101)下存在"转运部"
* 2. 创建转运部的子部门(直接创建,无需解析"--"格式)
*
* @param transportDepts 外部传入的转运部子部门数据列表
* @return 同步结果
*/
AjaxResult syncTransportDepartments(List<DepartmentSyncDTO> transportDepts);
修改位置: 第 29-40 行(在 syncBranchDepartments() 方法后添加)
文件路径: ruoyi-system/src/main/java/com/ruoyi/system/service/impl/DepartmentSyncServiceImpl.java
修改内容: 新增方法实现(核心同步逻辑)
完整方法: 第 189-324 行
关键逻辑:
检查总公司是否存在(第 197-202 行)java SysDept headOffice = sysDeptMapper.selectDeptById(101L); if (headOffice == null) { log.error("总公司(ID=101)不存在,无法同步转运部"); return AjaxResult.error("总公司(ID=101)不存在,请先创建总公司"); }
确保转运部存在(第 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);
}
```
同步子部门(第 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 保证事务一致性
- 详细的日志记录
文件路径: ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/DepartmentSyncController.java
修改内容: 新增 REST API 接口
/**
* 同步转运部和子部门数据(使用外部传入的数据源)
*
* 同步逻辑:
* 1. 确保总公司(ID=101)下存在"转运部"
* 2. 创建转运部的子部门
*
* @param transportDepts 转运部子部门数据列表
* @return 同步结果
*/
@PreAuthorize("@ss.hasPermi('system:dept:sync')")
@PostMapping("/transport/data")
public AjaxResult syncTransportDepartmentsWithData(@RequestBody List<DepartmentSyncDTO> transportDepts)
{
return departmentSyncService.syncTransportDepartments(transportDepts);
}
修改位置: 第 50-64 行(在 /branch/data 接口后添加)
接口信息:
- URL: POST /system/dept/sync/transport/data
- 权限: system:dept:sync
- 请求体: List<DepartmentSyncDTO>
- 响应: AjaxResult
文件路径: 转运部同步功能说明.md
内容包括:
- 功能概述
- 同步逻辑说明
- 实现架构
- 核心类详细说明
- REST API 接口
- 数据流程图
- 部门层级结构
- 幂等性设计
- 事务管理
- 数据源切换机制
- 完整调用示例
- 注意事项
文件路径: 转运部同步功能测试指南.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+ 行 |
TOP 500 限制返回数据量WITH (NOLOCK) 提高并发性能N 前缀确保正确匹配<![CDATA[...]]> 包裹 SQL 避免 XML 解析问题@DataSource(DataSourceType.SQLSERVER) 查询 SQL ServerdepartmentId 作为唯一标识@Transactional所有修改的文件均通过编译检查,无语法错误。
请参考 转运部同步功能测试指南.md 进行以下测试:
确保 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());
```
mvn clean package确保需要调用同步接口的用户拥有 system:dept:sync 权限。
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": "转运部"
}
]'
// 步骤 1: 查询 SQL Server 数据
List<DepartmentSyncDTO> transportDepts = departmentSyncDataService.getTransportDepartments();
// 步骤 2: 同步到 MySQL
AjaxResult result = departmentSyncService.syncTransportDepartments(transportDepts);
可以添加一个 GET 接口直接返回 SQL Server 的转运部数据:java @GetMapping("/transport/query") public AjaxResult queryTransportDepartments() { List<DepartmentSyncDTO> transportDepts = departmentSyncDataService.getTransportDepartments(); return AjaxResult.success(transportDepts); }
像分公司同步一样,添加一个不需要传参的同步方法:
```java
// 接口
AjaxResult syncTransportDepartments();
// 实现
@Override
@Transactional
public AjaxResult syncTransportDepartments()
{
List transportDepts = departmentSyncDataService.getTransportDepartments();
return syncTransportDepartments(transportDepts);
}
// Controller
@PostMapping("/transport")
public AjaxResult syncTransportDepartments()
{
return departmentSyncService.syncTransportDepartments();
}
```
添加一个接口同时同步分公司和转运部:
```java
@PostMapping("/all")
public AjaxResult syncAllDepartments()
{
// 同步分公司
AjaxResult branchResult = syncBranchDepartments();
// 同步转运部
AjaxResult transportResult = syncTransportDepartments();
// 合并结果返回
return AjaxResult.success("全部同步完成",
Map.of("branch", branchResult, "transport", transportResult));
}
```