PersonnelSyncTimer.java 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. package com.malk.timer;
  2. import com.malk.service.personnel.PersonnelSyncService;
  3. import lombok.extern.slf4j.Slf4j;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
  6. import org.springframework.context.annotation.Configuration;
  7. import org.springframework.scheduling.annotation.EnableScheduling;
  8. import org.springframework.scheduling.annotation.Scheduled;
  9. import java.util.concurrent.atomic.AtomicBoolean;
  10. import java.util.Map;
  11. /**
  12. * 钉钉 -> 宜搭 人员档案 定时全量增量同步
  13. * 工作日 (MON-FRI) 每天 05:30 / 13:15 / 18:30 各一次 fullSync (limit 取配置 personnel-sync.limitFirstN, 生产为 0 即真·全量)
  14. * 共 3 次/工作日; 周末不跑
  15. */
  16. @Slf4j
  17. @Configuration
  18. @EnableScheduling
  19. @ConditionalOnProperty(prefix = "enable", name = "scheduling", havingValue = "true", matchIfMissing = false)
  20. public class PersonnelSyncTimer {
  21. @Autowired
  22. private PersonnelSyncService personnelSyncService;
  23. // fixme: 防止上一轮未完成时下一轮重入 (两轮并发会使 QPS 翻倍)
  24. private final AtomicBoolean running = new AtomicBoolean(false);
  25. // fixme: 钉钉整点 QPS 高峰偶发 subcode=90002 限流 → 首次失败后再重试 2 次, 间隔 60s 让钉钉冷却; 仍失败放弃等下一次 cron
  26. private static final int MAX_ATTEMPTS = 3;
  27. private static final long RETRY_DELAY_MS = 60_000L;
  28. /** 工作日 05:30 / 13:15 / 18:30 各一次全量同步(单方法挂 3 条 cron,离散时点无法用单条 cron 表达) */
  29. @Scheduled(cron = "0 30 5 ? * MON-FRI")
  30. @Scheduled(cron = "0 15 13 ? * MON-FRI")
  31. @Scheduled(cron = "0 30 18 ? * MON-FRI")
  32. public void scheduledFullSync() {
  33. runFullSync("05:30/13:15/18:30");
  34. }
  35. private void runFullSync(String tag) {
  36. if (!running.compareAndSet(false, true)) {
  37. log.warn("[PersonnelSync] 上次定时同步尚未结束,跳过本次触发 tag={}", tag);
  38. return;
  39. }
  40. log.info("[PersonnelSync] 定时同步任务开始 tag={}", tag);
  41. try {
  42. for (int attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
  43. try {
  44. java.util.Map<String, Object> stats = personnelSyncService.fullSync(null);
  45. if (hasSyncFailures(stats)) {
  46. log.warn("[PersonnelSync] 定时同步存在未完成记录 tag={} attempt={}/{} {}",
  47. tag, attempt, MAX_ATTEMPTS, stats);
  48. if (attempt < MAX_ATTEMPTS) {
  49. if (!waitBeforeRetry(tag)) return;
  50. continue;
  51. }
  52. log.error("[PersonnelSync] 定时同步连续 {} 次仍有未完成记录,等待下一次 cron tag={} {}",
  53. MAX_ATTEMPTS, tag, stats);
  54. return;
  55. }
  56. log.info("[PersonnelSync] 定时同步任务完成 tag={} attempt={}/{} {}", tag, attempt, MAX_ATTEMPTS, stats);
  57. return;
  58. } catch (Exception e) {
  59. log.error("[PersonnelSync] 定时同步任务失败 tag={} attempt={}/{}", tag, attempt, MAX_ATTEMPTS, e);
  60. if (attempt < MAX_ATTEMPTS) {
  61. if (!waitBeforeRetry(tag)) return;
  62. }
  63. }
  64. }
  65. log.error("[PersonnelSync] 定时同步任务连续 {} 次失败,放弃本轮等待下一次 cron 触发 tag={}", MAX_ATTEMPTS, tag);
  66. } finally {
  67. running.set(false);
  68. }
  69. }
  70. static boolean hasSyncFailures(Map<String, Object> stats) {
  71. return stats == null
  72. || count(stats, "failed") > 0
  73. || count(stats, "managerLookupFailed") > 0;
  74. }
  75. private static int count(Map<String, Object> stats, String key) {
  76. if (stats == null || !(stats.get(key) instanceof Number)) {
  77. return 0;
  78. }
  79. return ((Number) stats.get(key)).intValue();
  80. }
  81. private static boolean waitBeforeRetry(String tag) {
  82. try {
  83. Thread.sleep(RETRY_DELAY_MS);
  84. return true;
  85. } catch (InterruptedException ie) {
  86. Thread.currentThread().interrupt();
  87. log.warn("[PersonnelSync] 重试等待被中断,放弃本轮 tag={}", tag);
  88. return false;
  89. }
  90. }
  91. }