You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
109 lines
3.8 KiB
109 lines
3.8 KiB
|
1 month ago
|
package com.project.ding.utils;
|
||
|
|
|
||
|
|
|
||
|
|
import com.project.ding.domain.dto.UserDTO;
|
||
|
|
import com.project.ding.domain.entity.SyncLogEntity;
|
||
|
|
import com.project.ding.domain.entity.UserEntity;
|
||
|
|
import com.project.ding.domain.service.UserBaseService;
|
||
|
|
import com.project.ding.mapper.SyncLogMapper;
|
||
|
|
import jakarta.annotation.Resource;
|
||
|
|
import lombok.extern.slf4j.Slf4j;
|
||
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
||
|
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||
|
|
import org.springframework.stereotype.Component;
|
||
|
|
|
||
|
|
import java.util.Date;
|
||
|
|
import java.util.List;
|
||
|
|
import java.util.concurrent.CompletableFuture;
|
||
|
|
import java.util.concurrent.ExecutorService;
|
||
|
|
import java.util.concurrent.TimeUnit;
|
||
|
|
import java.util.stream.Collectors;
|
||
|
|
|
||
|
|
@Component
|
||
|
|
@Slf4j
|
||
|
|
public class DingUserSyncUtil {
|
||
|
|
@Resource(name = "dingUserSyncExecutor")
|
||
|
|
private ExecutorService executor;
|
||
|
|
|
||
|
|
@Autowired
|
||
|
|
private StringRedisTemplate redisTemplate;
|
||
|
|
|
||
|
|
@Autowired
|
||
|
|
private UserBaseService userBaseService;
|
||
|
|
|
||
|
|
@Autowired
|
||
|
|
private DingUtil dingUtil;
|
||
|
|
|
||
|
|
@Autowired
|
||
|
|
private SyncLogMapper syncLogMapper;
|
||
|
|
|
||
|
|
private static final String LOCK_KEY = "lock:ding:user_sync";
|
||
|
|
private static final String LAST_SYNC_KEY = "cache:ding:last_sync_time";
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 触发同步任务
|
||
|
|
* @param force 是否强制触发(跳过120分钟冷却检查)
|
||
|
|
*/
|
||
|
|
public String triggerSync(boolean force) {
|
||
|
|
// 1. 抢占 Redis 锁(防止多个实例同时跑全量)
|
||
|
|
Boolean acquired = redisTemplate.opsForValue().setIfAbsent(LOCK_KEY, "true", 1, TimeUnit.HOURS);
|
||
|
|
if (Boolean.FALSE.equals(acquired)) {
|
||
|
|
return "同步任务已在运行中,请勿重复触发";
|
||
|
|
}
|
||
|
|
|
||
|
|
// 2. 提交异步任务(不使用 @Async)
|
||
|
|
CompletableFuture.runAsync(() -> {
|
||
|
|
try {
|
||
|
|
// 3. 冷却检查
|
||
|
|
if (!force) {
|
||
|
|
String lastSync = redisTemplate.opsForValue().get(LAST_SYNC_KEY);
|
||
|
|
if (lastSync != null && (System.currentTimeMillis() - Long.parseLong(lastSync) < 120 * 60 * 1000)) {
|
||
|
|
log.info("处于冷却期,跳过全量同步");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
log.info(">>> 开始钉钉用户全量同步 (force={})", force);
|
||
|
|
runActualSyncTask();
|
||
|
|
|
||
|
|
} catch (Exception e) {
|
||
|
|
log.error("全量同步发生严重异常", e);
|
||
|
|
} finally {
|
||
|
|
// 4. 释放锁
|
||
|
|
redisTemplate.delete(LOCK_KEY);
|
||
|
|
}
|
||
|
|
}, executor);
|
||
|
|
|
||
|
|
return "同步任务已启动,请查看系统日志或更新时间";
|
||
|
|
}
|
||
|
|
private void runActualSyncTask() {
|
||
|
|
// 插入一条同步日志
|
||
|
|
SyncLogEntity syncLogEntity = new SyncLogEntity();
|
||
|
|
syncLogEntity.setStartTime(new Date());
|
||
|
|
syncLogEntity.setStatus(0);
|
||
|
|
syncLogMapper.insert(syncLogEntity);
|
||
|
|
|
||
|
|
// 开始同步
|
||
|
|
try {
|
||
|
|
List<UserDTO> allUserDTOList = dingUtil.getAllUserDTO();
|
||
|
|
userBaseService.saveOrUpdateBatch(allUserDTOList.stream()
|
||
|
|
.map(dto -> dto.toEntity(UserEntity::new))
|
||
|
|
.collect(Collectors.toList()) , 300);
|
||
|
|
syncLogEntity.setStatus(1);
|
||
|
|
syncLogEntity.setEndTime(new Date());
|
||
|
|
syncLogMapper.updateById(syncLogEntity);
|
||
|
|
|
||
|
|
redisTemplate.opsForValue().set(LAST_SYNC_KEY, String.valueOf(System.currentTimeMillis()));
|
||
|
|
log.info(">>> 全量同步完成");
|
||
|
|
} catch (Exception e) {
|
||
|
|
// 同步失败更新
|
||
|
|
syncLogEntity.setEndTime(new Date());
|
||
|
|
syncLogEntity.setStatus(2); // 失败
|
||
|
|
syncLogEntity.setErrorMsg(e.getMessage());
|
||
|
|
syncLogMapper.updateById(syncLogEntity);
|
||
|
|
log.error(">>> 全量同步过程报错", e);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|