企业微信外部联系人同步的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模块实现了对企业微信外部联系人变更的精准捕获,避免全量拉取开销,同时兼顾新增、更新与删除场景,保障本地数据与企业微信端最终一致。

相关推荐
一定要AK2 小时前
Spring 入门核心笔记
java·笔记·spring
A__tao2 小时前
Elasticsearch Mapping 一键生成 Java 实体类(支持嵌套 + 自动过滤注释)
java·python·elasticsearch
KevinCyao2 小时前
java视频短信接口怎么调用?SpringBoot集成视频短信及回调处理Demo
java·spring boot·音视频
科技小花2 小时前
数据治理平台架构演进观察:AI原生设计如何重构企业数据管理范式
数据库·重构·架构·数据治理·ai-native·ai原生
2501_948114242 小时前
2026年大模型API聚合平台技术评测:企业级接入层的治理演进与星链4SAPI架构观察
大数据·人工智能·gpt·架构·claude
迷藏4942 小时前
**发散创新:基于Rust实现的开源合规权限管理框架设计与实践**在现代软件架构中,**权限控制(RBAC)** 已成为保障
java·开发语言·python·rust·开源
FserSuN3 小时前
LangChain DeepAgent 多 Agent 架构原理学习
架构·langchain
坏孩子的诺亚方舟3 小时前
RTL设计师攻略0_架构与微架构
架构·cpu·面试攻略
智星云算力3 小时前
本地GPU与租用GPU混合部署:混合算力架构搭建指南
人工智能·架构·gpu算力·智星云·gpu租用
wuxinyan1233 小时前
Java面试题47:一文深入了解Nginx
java·nginx·面试题