编辑 | blame | 历史 | 原始文档

转运部同步功能说明

功能概述

在现有的分公司同步功能基础上,新增转运部同步功能。转运部是总公司(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 <!-- 查询转运部下的所有子部门 --> <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>

SQL 优化要点:
- 使用 TOP 500 限制返回数据量
- 使用 WITH (NOLOCK) 提高查询性能
- 中文字符串使用 N 前缀确保正确匹配
- 使用 <![CDATA[...]]> 包裹 SQL 避免 XML 特殊字符问题

2. DepartmentSyncMapper.java

文件路径: ruoyi-system/src/main/java/com/ruoyi/system/mapper/DepartmentSyncMapper.java

新增方法:
java /** * 查询转运部下的所有子部门 * * @return 转运部子部门列表 */ List<DepartmentSyncDTO> 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<DepartmentSyncDTO> 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<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);
}

}
```

特点:
- 类级别标注 @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<DepartmentSyncDTO> 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<DepartmentSyncDTO> transportDepts) { return departmentSyncService.syncTransportDepartments(transportDepts); }

REST API 接口

1. 同步分公司(外部数据源)

  • URL: POST /system/dept/sync/branch/data
  • 权限: system:dept:sync
  • 请求体: List<DepartmentSyncDTO>
  • 说明: 接收外部传入的分公司数据进行同步

2. 同步转运部(外部数据源)

  • URL: POST /system/dept/sync/transport/data
  • 权限: system:dept:sync
  • 请求体: List<DepartmentSyncDTO>
  • 说明: 接收外部传入的转运部子部门数据进行同步

3. 同步 OA 用户(外部数据源)

  • URL: POST /system/dept/sync/user/data
  • 权限: system:user:sync
  • 请求体: List<UserSyncDTO>
  • 说明: 接收外部传入的 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: 分步调用(推荐)

// 步骤 1: 调用 DataService 查询 SQL Server 数据
List<DepartmentSyncDTO> transportDepts = departmentSyncDataService.getTransportDepartments();

// 步骤 2: 调用 SyncService 将数据同步到 MySQL
AjaxResult result = departmentSyncService.syncTransportDepartments(transportDepts);

方式 2: REST API 调用

# 步骤 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 语句都使用 <![CDATA[...]]> 包裹
  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 接口
- 创建本说明文档