企业微信外部联系人同步的CDC(变更数据捕获)架构与Java实现

企业微信外部联系人同步的CDC(变更数据捕获)架构与Java实现

企业微信通过/cgi-bin/externalcontact/get_follow_user_list/cgi-bin/externalcontact/list等接口提供外部联系人数据,但全量拉取效率低下且易触发限流。更优方案是采用CDC(Change Data Capture)思想:仅捕获新增、更新或删除的联系人,并实时同步至本地业务系统。本文基于wlkankan.cn.cdc包,设计一套轻量级Java CDC架构,结合企业微信的cursor机制与本地状态追踪,实现高效增量同步。

企业微信外部联系人变更标识

企业微信在list接口中支持cursor参数,返回下一页游标及has_next标志。更重要的是,每条联系人记录包含update_time(Unix时间戳),可用于判断是否变更。我们以此构建增量同步依据。

java 复制代码
package wlkankan.cn.model;

public class ExternalContact {
    private String externalUserId;
    private String name;
    private String position;
    private Long updateTime; // 关键字段:用于CDC
    private String followUserId;

    // getters/setters
}

本地状态存储:记录最后同步时间戳

为每个跟进员工(follow_user_id)维护最后成功同步的最大update_time

java 复制代码
package wlkankan.cn.cdc.storage;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class SyncStateStore {
    // followUserId -> lastSyncUpdateTime
    private static final Map<String, Long> LAST_SYNC_TIME = new ConcurrentHashMap<>();

    public static void updateLastSyncTime(String followUserId, long updateTime) {
        LAST_SYNC_TIME.merge(followUserId, updateTime, Math::max);
    }

    public static long getLastSyncTime(String followUserId) {
        return LAST_SYNC_TIME.getOrDefault(followUserId, 0L);
    }

    // 实际应持久化到DB
    public static void persistToDatabase() {
        // INSERT INTO sync_state (follow_user_id, last_update_time) ...
        JdbcSyncStateDao.batchUpdate(LAST_SYNC_TIME);
    }
}

CDC同步核心逻辑

每次同步时,先获取所有跟进人列表,再逐个拉取其外部联系人,并过滤出update_time > last_sync_time的记录:

java 复制代码
package wlkankan.cn.cdc.service;

import wlkankan.cn.cdc.storage.SyncStateStore;
import wlkankan.cn.client.WeComApiClient;
import wlkankan.cn.model.ExternalContact;
import java.util.ArrayList;
import java.util.List;

public class ExternalContactCdcService {

    public void syncAllFollowUsers() {
        List<String> followUsers = WeComApiClient.getFollowUserList();
        for (String userId : followUsers) {
            syncForUser(userId);
        }
        SyncStateStore.persistToDatabase();
    }

    private void syncForUser(String followUserId) {
        long lastSync = SyncStateStore.getLastSyncTime(followUserId);
        String cursor = null;
        List<ExternalContact> changes = new ArrayList<>();

        do {
            var response = WeComApiClient.listExternalContacts(followUserId, cursor);
            for (ExternalContact contact : response.getContacts()) {
                if (contact.getUpdateTime() > lastSync) {
                    changes.add(contact);
                    // 更新本地最大时间戳
                    SyncStateStore.updateLastSyncTime(followUserId, contact.getUpdateTime());
                }
            }
            cursor = response.getNextCursor();
        } while (response.hasNext());

        if (!changes.isEmpty()) {
            handleChanges(followUserId, changes);
        }
    }

    private void handleChanges(String followUserId, List<ExternalContact> changes) {
        // 写入业务库(UPSERT)
        ExternalContactDao.upsertBatch(changes);
        // 发送变更事件(如Kafka)
        ChangeEventPublisher.publish(followUserId, changes);
    }
}

处理联系人删除场景

企业微信未直接提供"删除"事件,但可通过全量对比标记失效识别。推荐方案:定期全量校验,发现本地存在但API无返回的记录即为删除。

java 复制代码
package wlkankan.cn.cdc.service;

import wlkankan.cn.model.ExternalContact;
import java.util.Set;
import java.util.stream.Collectors;

public class DeletionDetector {

    public void detectDeletions(String followUserId) {
        Set<String> localIds = ExternalContactDao.getExternalUserIdsByFollower(followUserId);
        Set<String> remoteIds = fetchAllRemoteIds(followUserId);

        localIds.removeAll(remoteIds);
        if (!localIds.isEmpty()) {
            // 标记为已删除
            ExternalContactDao.markAsDeleted(localIds);
            ChangeEventPublisher.publishDeletion(followUserId, new ArrayList<>(localIds));
        }
    }

    private Set<String> fetchAllRemoteIds(String followUserId) {
        String cursor = null;
        Set<String> ids = new java.util.HashSet<>();
        do {
            var resp = WeComApiClient.listExternalContacts(followUserId, cursor);
            ids.addAll(resp.getContacts().stream()
                .map(ExternalContact::getExternalUserId)
                .collect(Collectors.toSet()));
            cursor = resp.getNextCursor();
        } while (resp.hasNext());
        return ids;
    }
}

调度与容错

使用Spring Scheduler定期触发CDC任务,并加入重试机制:

java 复制代码
package wlkankan.cn.cdc.scheduler;

import wlkankan.cn.cdc.service.ExternalContactCdcService;
import wlkankan.cn.cdc.service.DeletionDetector;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class CdcScheduler {

    private final ExternalContactCdcService cdcService = new ExternalContactCdcService();
    private final DeletionDetector deletionDetector = new DeletionDetector();

    // 每5分钟增量同步
    @Scheduled(fixedDelay = 300_000)
    public void incrementalSync() {
        try {
            cdcService.syncAllFollowUsers();
        } catch (Exception e) {
            AlertService.notify("CDC incremental sync failed: " + e.getMessage());
        }
    }

    // 每24小时检测删除
    @Scheduled(cron = "0 0 2 * * ?")
    public void detectDeletions() {
        try {
            List<String> users = WeComApiClient.getFollowUserList();
            for (String user : users) {
                deletionDetector.detectDeletions(user);
            }
        } catch (Exception e) {
            AlertService.notify("Deletion detection failed: " + e.getMessage());
        }
    }
}

该CDC架构通过wlkankan.cn.cdc模块实现了对企业微信外部联系人变更的精准捕获,避免全量拉取开销,同时兼顾新增、更新与删除场景,保障本地数据与企业微信端最终一致。

相关推荐
hello 早上好2 小时前
01_ JVM 核心架构详解
jvm·架构
小肖爱笑不爱笑2 小时前
登录认证-会话技术、JWT令牌、过滤器Filter、拦截器Interceptor
java·开发语言·过滤器·拦截器·登录认证
码农三叔2 小时前
(4-2)机械传动系统与关节设计: 减速器与传动机构
人工智能·架构·机器人·人形机器人
程序猿阿伟2 小时前
《突破训练瓶颈:参数服务器替代架构效率优化指南》
运维·服务器·架构
一条代码鱼2 小时前
修复Nacos namespaces未授权访问漏洞【原理扫描】
java·运维·spring cloud
努力成为包租婆3 小时前
uniapp--原生插件开发
java·数据库·uni-app
海南java第二人4 小时前
Spring MVC核心流程深度解析:从请求到响应的完美掌控
java·springmvc
未来之窗软件服务4 小时前
幽冥大陆(一百10)PHP打造Java的Jar安全——东方仙盟筑基期
java·php·phar·仙盟创梦ide·东方仙盟