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

用户同步服务重构说明

重构概述

将用户同步功能重构为**数据查询**和**数据同步**两个独立的服务,与部门同步保持一致的架构风格,实现职责分离和灵活性提升。


架构变化

重构前

┌─────────────────────────────────────────┐
│   UserSyncServiceImpl                   │
│                                         │
│   ┌─────────────────────────────────┐  │
│   │ syncOaUsers()                   │  │
│   │  ├─ 查询 SQL Server             │  │
│   │  ├─ 查询 MySQL 部门             │  │
│   │  └─ 写入 MySQL 用户             │  │
│   └─────────────────────────────────┘  │
└─────────────────────────────────────────┘

问题
- ❌ 数据查询和同步逻辑耦合
- ❌ 无法使用外部数据源
- ❌ 难以扩展其他数据来源

重构后

┌──────────────────────────────────────────────────────────┐
│  UserSyncDataServiceImpl (新增)                          │
│  职责:从 SQL Server 查询用户数据                         │
│                                                           │
│  ├─ getOaUsers()                                         │
│      └─ 返回 List<UserSyncDTO>                           │
└──────────────────────────────────────────────────────────┘
                              │
                              v
┌──────────────────────────────────────────────────────────┐
│  UserSyncServiceImpl (重构)                              │
│  职责:接收数据并写入 MySQL                               │
│                                                           │
│  ├─ syncOaUsers()                                        │
│  │   └─ 调用 UserSyncDataService 获取数据                │
│  │                                                        │
│  └─ syncOaUsers(List<UserSyncDTO>)                       │
│      └─ 接收外部传入的数据进行同步                        │
└──────────────────────────────────────────────────────────┘

核心组件

1. IUserSyncDataService(新增)

文件路径: ruoyi-system/src/main/java/com/ruoyi/system/service/IUserSyncDataService.java

职责: 定义从 SQL Server 查询用户数据的接口

public interface IUserSyncDataService {
    /**
     * 从 SQL Server 查询 OA 用户数据
     * 
     * @return OA 用户列表
     */
    List<UserSyncDTO> getOaUsers();
}

2. UserSyncDataServiceImpl(新增)

文件路径: ruoyi-system/src/main/java/com/ruoyi/system/service/impl/UserSyncDataServiceImpl.java

职责: 专门负责从 SQL Server 查询用户数据

关键特性:
```java
@Service
public class UserSyncDataServiceImpl implements IUserSyncDataService {
@Autowired
private UserSyncMapper userSyncMapper; // 带 @DataSource 注解

@Override
public List<UserSyncDTO> getOaUsers() {
    log.info("开始从 SQL Server 查询 OA 用户数据...");

    // 自动切换到 SQL Server 数据源
    List<UserSyncDTO> oaUsers = userSyncMapper.selectOaUsers();

    log.info("从 SQL Server 查询到 {} 条 OA 用户数据", oaUsers.size());
    return oaUsers;
}

}
```

数据流:
UserSyncDataServiceImpl ↓ UserSyncMapper (@DataSource(SQLSERVER)) ↓ SQL Server 数据库 (OA_User) ↓ 返回 List<UserSyncDTO>


3. IUserSyncService(重构)

文件路径: ruoyi-system/src/main/java/com/ruoyi/system/service/IUserSyncService.java

变化: 新增重载方法,支持外部数据源

public interface IUserSyncService {
    /**
     * 同步OA用户数据(从 SQL Server 查询并同步到 MySQL)
     */
    AjaxResult syncOaUsers();
    
    /**
     * 同步OA用户数据(使用外部传入的数据源)
     * 
     * @param oaUsers 外部传入的OA用户数据列表
     */
    AjaxResult syncOaUsers(List<UserSyncDTO> oaUsers);
}

4. UserSyncServiceImpl(重构)

文件路径: ruoyi-system/src/main/java/com/ruoyi/system/service/impl/UserSyncServiceImpl.java

变化:
1. 移除对 UserSyncMapper 的直接依赖
2. 注入 IUserSyncDataService 获取数据
3. 新增 syncOaUsers(List<UserSyncDTO>) 方法

重构后的代码:

@Service
public class UserSyncServiceImpl implements IUserSyncService {
    
    @Autowired
    private IUserSyncDataService userSyncDataService; // 数据查询服务
    
    @Autowired
    private SysUserMapper sysUserMapper; // MySQL 用户操作
    
    @Autowired
    private SysDeptMapper sysDeptMapper; // MySQL 部门查询
    
    // 方法 1: 内部查询 + 同步
    @Override
    @Transactional
    public AjaxResult syncOaUsers() {
        // 调用数据查询服务获取 SQL Server 数据
        List<UserSyncDTO> oaUsers = userSyncDataService.getOaUsers();
        
        if (oaUsers == null || oaUsers.isEmpty()) {
            return AjaxResult.warn("未获取到需要同步的用户数据");
        }
        
        // 调用重载方法执行同步
        return syncOaUsers(oaUsers);
    }
    
    // 方法 2: 外部数据源 + 同步(新增)
    @Override
    @Transactional
    public AjaxResult syncOaUsers(List<UserSyncDTO> oaUsers) {
        if (oaUsers == null || oaUsers.isEmpty()) {
            return AjaxResult.warn("传入的用户数据为空");
        }
        
        log.info("开始同步 {} 条OA用户数据到 MySQL 数据库...", oaUsers.size());
        
        // 同步逻辑(只涉及 MySQL 操作)
        // 1. 查询 MySQL 部门信息
        // 2. 创建或更新用户
        // ...
    }
}

5. DepartmentSyncController(更新)

文件路径: ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/DepartmentSyncController.java

变化: 新增用户同步的外部数据源接口

@RestController
@RequestMapping("/system/dept/sync")
public class DepartmentSyncController {
    
    @Autowired
    private IDepartmentSyncService departmentSyncService;
    
    @Autowired
    private IUserSyncService userSyncService;
    
    /**
     * 接口 1: 同步部门数据(从 SQL Server 查询并同步)
     */
    @PostMapping("/branch")
    public AjaxResult syncBranchDepartments() {
        return departmentSyncService.syncBranchDepartments();
    }
    
    /**
     * 接口 2: 同步部门数据(使用外部传入的数据源)
     */
    @PostMapping("/branch/data")
    public AjaxResult syncBranchDepartmentsWithData(@RequestBody List<DepartmentSyncDTO> branchDepts) {
        return departmentSyncService.syncBranchDepartments(branchDepts);
    }
    
    /**
     * 接口 3: 同步用户数据(从 SQL Server 查询并同步)
     */
    @PostMapping("/user")
    public AjaxResult syncOaUsers() {
        return userSyncService.syncOaUsers();
    }
    
    /**
     * 接口 4: 同步用户数据(使用外部传入的数据源)⭐ 新增
     */
    @PostMapping("/user/data")
    public AjaxResult syncOaUsersWithData(@RequestBody List<UserSyncDTO> oaUsers) {
        return userSyncService.syncOaUsers(oaUsers);
    }
}

使用场景

场景 1: 常规同步(从 SQL Server 查询)

# 同步用户数据
POST http://localhost:8080/system/dept/sync/user

执行流程:
1. Controller 调用 userSyncService.syncOaUsers()
2. Service 调用 userSyncDataService.getOaUsers()
3. 自动切换到 SQL Server 查询数据
4. 将数据写入 MySQL


场景 2: 使用外部数据源

POST http://localhost:8080/system/dept/sync/user/data
Content-Type: application/json

[
  {
    "oaUserId": 1001,
    "userName": "zhangsan",
    "nickName": "张三",
    "departmentId": 2001,
    "sex": "0",
    "email": "zhangsan@example.com",
    "phonenumber": "13800138000"
  },
  {
    "oaUserId": 1002,
    "userName": "lisi",
    "nickName": "李四",
    "departmentId": 2002,
    "sex": "1",
    "email": "lisi@example.com",
    "phonenumber": "13900139000"
  }
]

执行流程:
1. Controller 接收 JSON 数据并转换为 List<UserSyncDTO>
2. Controller 调用 userSyncService.syncOaUsers(oaUsers)
3. Service 查询 MySQL 部门信息
4. Service 创建或更新用户到 MySQL


场景 3: 批量同步部门和用户

@Service
public class BatchSyncService {
    
    @Autowired
    private IDepartmentSyncDataService departmentSyncDataService;
    
    @Autowired
    private IUserSyncDataService userSyncDataService;
    
    @Autowired
    private IDepartmentSyncService departmentSyncService;
    
    @Autowired
    private IUserSyncService userSyncService;
    
    public void batchSyncAll() {
        // 1. 从 SQL Server 查询部门数据
        List<DepartmentSyncDTO> deptData = departmentSyncDataService.getBranchDepartments();
        
        // 2. 同步部门到 MySQL
        departmentSyncService.syncBranchDepartments(deptData);
        
        // 3. 从 SQL Server 查询用户数据
        List<UserSyncDTO> userData = userSyncDataService.getOaUsers();
        
        // 4. 同步用户到 MySQL(确保部门已同步)
        userSyncService.syncOaUsers(userData);
    }
}

场景 4: 数据预处理后同步

@Service
public class PreprocessSyncService {
    
    @Autowired
    private IUserSyncDataService userSyncDataService;
    
    @Autowired
    private IUserSyncService userSyncService;
    
    public void syncWithPreprocess() {
        // 1. 从 SQL Server 查询数据
        List<UserSyncDTO> rawData = userSyncDataService.getOaUsers();
        
        // 2. 数据预处理
        List<UserSyncDTO> processedData = rawData.stream()
            .filter(user -> StringUtils.isNotEmpty(user.getEmail())) // 只同步有邮箱的用户
            .peek(user -> {
                // 统一邮箱域名
                if (!user.getEmail().contains("@company.com")) {
                    user.setEmail(user.getUserName() + "@company.com");
                }
            })
            .collect(Collectors.toList());
        
        // 3. 同步处理后的数据
        userSyncService.syncOaUsers(processedData);
    }
}

接口对比

接口 路径 数据来源 适用场景
部门同步 1 POST /system/dept/sync/branch 内部查询 SQL Server 常规定时同步
部门同步 2 POST /system/dept/sync/branch/data 外部传入 JSON 手动数据导入、数据预处理
用户同步 1 POST /system/dept/sync/user 内部查询 SQL Server 常规定时同步
用户同步 2 POST /system/dept/sync/user/data 外部传入 JSON 手动数据导入、数据预处理 ⭐

定时任务更新

定时任务可以继续使用原有方式,无需修改:

@Component("oaSyncTask")
public class OaSyncTask {
    
    @Autowired
    private IDepartmentSyncService departmentSyncService;
    
    @Autowired
    private IUserSyncService userSyncService;
    
    public void syncOaData() {
        // 第一步:同步部门
        AjaxResult deptResult = departmentSyncService.syncBranchDepartments();
        
        if (deptResult.get("code").equals(200)) {
            // 第二步:同步用户(只有部门同步成功才执行)
            AjaxResult userResult = userSyncService.syncOaUsers();
            log.info("用户同步结果: {}", userResult.get("msg"));
        }
    }
}

文件清单

新增文件 ✨

  • IUserSyncDataService.java - 用户数据查询服务接口
  • UserSyncDataServiceImpl.java - 用户数据查询服务实现
  • 用户同步服务重构说明.md - 本文档

修改文件 🔧

  • IUserSyncService.java - 新增重载方法
  • UserSyncServiceImpl.java - 重构实现
  • DepartmentSyncController.java - 新增 /user/data 接口

无需修改

  • UserSyncMapper.java - Mapper 接口不变
  • UserSyncMapper.xml - SQL 映射不变
  • OaSyncTask.java - 定时任务不变

优势总结

1. 与部门同步保持一致 ✅

  • 相同的架构风格
  • 相同的职责分离模式
  • 相同的扩展方式

2. 职责分离 ✅

  • UserSyncDataService: 只负责查询数据
  • UserSyncService: 只负责同步数据

3. 灵活性提升 ✅

  • 支持从 SQL Server 查询
  • 支持外部数据源
  • 支持数据预处理
  • 支持多源数据整合

4. 易于扩展 ✅

// 扩展:支持其他数据源
public interface IUserSyncDataService {
    List<UserSyncDTO> getOaUsers();              // SQL Server
    List<UserSyncDTO> getOaUsersFromLDAP();      // LDAP
    List<UserSyncDTO> getOaUsersFromAPI();       // HTTP API
}

5. 易于测试 ✅

@Test
public void testSyncWithMockData() {
    // 创建测试数据
    List<UserSyncDTO> testData = createTestUserData();
    
    // 测试同步逻辑
    AjaxResult result = userSyncService.syncOaUsers(testData);
    
    // 验证结果
    assertEquals(200, result.get("code"));
}

总结

通过本次重构,用户同步功能与部门同步功能保持了一致的架构风格:

职责分离: 数据查询和同步逻辑解耦
灵活性提升: 支持多种数据来源
易于扩展: 可轻松添加新的数据源
向后兼容: 原有代码无需修改
易于测试: 可使用 Mock 数据进行单元测试
架构一致: 与部门同步保持相同模式


相关文档