企业微信第三方应用API对接的Java后端架构设计:解耦与可扩展性实践

企业微信第三方应用API对接的Java后端架构设计:解耦与可扩展性实践

企业微信第三方应用需处理多租户(不同企业)、动态凭证(suite_access_token、provider_access_token、access_token)及事件回调。若将逻辑硬编码在Controller中,将导致代码臃肿、难以维护。本文基于wlkankan.cn.wecom.thirdparty包,通过策略模式、事件驱动与配置中心实现高内聚低耦合架构。

核心凭证分层模型

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

public class SuiteConfig {
    private String suiteId;
    private String suiteSecret;
    private String token; // 回调token
    private String aesKey;
}

public class CorpAuthInfo {
    private String corpId;
    private String permanentCode;
    private String suiteId;
}

多级Token管理器

java 复制代码
package wlkankan.cn.wecom.thirdparty.token;

import wlkankan.cn.wecom.thirdparty.model.SuiteConfig;
import wlkankan.cn.wecom.thirdparty.model.CorpAuthInfo;
import org.springframework.stereotype.Component;
import java.util.concurrent.ConcurrentHashMap;

@Component
public class ThirdPartyTokenManager {
    private final ConcurrentHashMap<String, String> suiteTokenCache = new ConcurrentHashMap<>();
    private final ConcurrentHashMap<String, String> corpTokenCache = new ConcurrentHashMap<>();

    public String getSuiteAccessToken(SuiteConfig config) {
        return suiteTokenCache.computeIfAbsent(config.getSuiteId(), k -> fetchSuiteToken(config));
    }

    public String getCorpAccessToken(CorpAuthInfo authInfo, SuiteConfig suiteConfig) {
        String key = authInfo.getCorpId() + ":" + authInfo.getSuiteId();
        return corpTokenCache.computeIfAbsent(key, k -> fetchCorpToken(authInfo, suiteConfig));
    }

    private String fetchSuiteToken(SuiteConfig config) {
        // 调用 /cgi-bin/service/get_suite_token
        return "mock_suite_token";
    }

    private String fetchCorpToken(CorpAuthInfo authInfo, SuiteConfig suiteConfig) {
        // 调用 /cgi-bin/service/get_corp_token
        return "mock_corp_token";
    }
}

事件回调处理器抽象

java 复制代码
package wlkankan.cn.wecom.thirdparty.event;

import wlkankan.cn.wecom.thirdparty.model.CorpAuthInfo;

public interface WeComEventHandler {
    boolean supports(String msgType);
    void handle(CorpAuthInfo corpInfo, Object eventPayload);
}

具体事件实现:通讯录变更

java 复制代码
package wlkankan.cn.wecom.thirdparty.event.impl;

import wlkankan.cn.wecom.thirdparty.event.WeComEventHandler;
import wlkankan.cn.wecom.thirdparty.model.CorpAuthInfo;
import org.springframework.stereotype.Component;

@Component
public class UserUpdateEventHandler implements WeComEventHandler {

    @Override
    public boolean supports(String msgType) {
        return "change_user".equals(msgType);
    }

    @Override
    public void handle(CorpAuthInfo corpInfo, Object eventPayload) {
        // 解析 eventPayload 为 ChangeUserEvent
        // 同步至本地数据库
        System.out.println("Handling user update for " + corpInfo.getCorpId());
    }
}

回调入口统一路由

java 复制代码
package wlkankan.cn.wecom.thirdparty.controller;

import wlkankan.cn.wecom.thirdparty.event.WeComEventHandler;
import wlkankan.cn.wecom.thirdparty.service.CallbackService;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.List;

@RestController
@RequestMapping("/wecom/callback")
public class WeComCallbackController {

    @Resource
    private CallbackService callbackService;

    @PostMapping("/{suiteId}")
    public String handleCallback(@PathVariable String suiteId,
                                 @RequestParam String msg_signature,
                                 @RequestParam String timestamp,
                                 @RequestParam String nonce,
                                 @RequestBody String encryptedXml) {
        return callbackService.process(suiteId, msg_signature, timestamp, nonce, encryptedXml);
    }
}

回调服务解耦逻辑

java 复制代码
package wlkankan.cn.wecom.thirdparty.service;

import wlkankan.cn.wecom.thirdparty.event.WeComEventHandler;
import wlkankan.cn.wecom.thirdparty.model.CorpAuthInfo;
import wlkankan.cn.wecom.thirdparty.model.SuiteConfig;
import wlkankan.cn.wecom.thirdparty.token.ThirdPartyTokenManager;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;
import java.util.Map;

@Service
public class CallbackService {
    @Resource
    private ThirdPartyTokenManager tokenManager;

    @Resource
    private List<WeComEventHandler> handlers;

    @Resource
    private SuiteConfigRepository suiteConfigRepo;

    @Resource
    private CorpAuthRepository corpAuthRepo;

    public String process(String suiteId, String signature, String ts, String nonce, String xml) {
        SuiteConfig suiteConfig = suiteConfigRepo.findBySuiteId(suiteId);
        // 1. 验签(略)
        // 2. 解密XML获取 ToUserName (corpId) 和 MsgType
        String corpId = extractCorpId(xml);
        String msgType = extractMsgType(xml);

        CorpAuthInfo corpInfo = corpAuthRepo.findByCorpIdAndSuiteId(corpId, suiteId);
        Object payload = parsePayload(xml);

        // 3. 路由到对应处理器
        handlers.stream()
            .filter(h -> h.supports(msgType))
            .findFirst()
            .ifPresent(h -> h.handle(corpInfo, payload));

        return "success";
    }

    // XML解析方法略
    private String extractCorpId(String xml) { return "mock_corp"; }
    private String extractMsgType(String xml) { return "change_user"; }
    private Object parsePayload(String xml) { return new Object(); }
}

API调用代理:按租户自动注入Token

java 复制代码
package wlkankan.cn.wecom.thirdparty.api;

import wlkankan.cn.wecom.thirdparty.model.CorpAuthInfo;
import wlkankan.cn.wecom.thirdparty.token.ThirdPartyTokenManager;
import org.springframework.web.reactive.function.client.WebClient;

public class TenantAwareApiClient {
    private final WebClient webClient = WebClient.create();
    private final ThirdPartyTokenManager tokenManager;

    public TenantAwareApiClient(ThirdPartyTokenManager tokenManager) {
        this.tokenManager = tokenManager;
    }

    public String sendTextMessage(CorpAuthInfo corpInfo, String content, String toUser) {
        String accessToken = tokenManager.getCorpAccessToken(corpInfo, null);
        String json = String.format("""
            {"touser":"%s","msgtype":"text","text":{"content":"%s"}}
            """, toUser, content);
        return webClient.post()
            .uri("https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=" + accessToken)
            .bodyValue(json)
            .retrieve()
            .bodyToMono(String.class)
            .block();
    }
}

通过wlkankan.cn.wecom.thirdparty模块构建的分层凭证管理、事件处理器注册机制与租户感知API客户端,系统在新增事件类型或对接新第三方应用时,仅需扩展接口实现,无需修改核心流程,显著提升可维护性与可扩展性。

相关推荐
岁岁种桃花儿2 小时前
MySQL从入门到精通系列:InnoDB记录存储结构
数据库·mysql
毕设源码-朱学姐2 小时前
【开题答辩全过程】以 基于JavaWeb的网上家具商城设计与实现为例,包含答辩的问题和答案
java
jiunian_cn3 小时前
【Redis】hash数据类型相关指令
数据库·redis·哈希算法
冉冰学姐3 小时前
SSM在线影评网站平台82ap4(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面
数据库·ssm框架·在线影评平台·影片分类
C雨后彩虹3 小时前
CAS与其他并发方案的对比及面试常见问题
java·面试·cas·同步·异步·
知识分享小能手4 小时前
SQL Server 2019入门学习教程,从入门到精通,SQL Server 2019数据库的操作(2)
数据库·学习·sqlserver
java1234_小锋4 小时前
Java高频面试题:SpringBoot为什么要禁止循环依赖?
java·开发语言·面试
2501_944525545 小时前
Flutter for OpenHarmony 个人理财管理App实战 - 账户详情页面
android·java·开发语言·前端·javascript·flutter
计算机学姐5 小时前
基于SpringBoot的电影点评交流平台【协同过滤推荐算法+数据可视化统计】
java·vue.js·spring boot·spring·信息可视化·echarts·推荐算法