企业微信第三方应用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客户端,系统在新增事件类型或对接新第三方应用时,仅需扩展接口实现,无需修改核心流程,显著提升可维护性与可扩展性。

相关推荐
Decay__1 小时前
05_MySQL 常用 SQL 大全 —— 诊断、管理、优化辅助 SQL 与三大系统库速查
运维·数据库·sql·mysql·架构
学长毕设4 小时前
基于SpringBoot的实验室资产管理系统
java·spring boot·后端
zzu123zsw5 小时前
从臃肿的模板调用到声明式契约:深入 UniHttp 动态代理与多渠道生命周期扩展
java·spring boot·microsoft·unihttp
小徐不徐说6 小时前
C++ 接口与插件设计:从工厂注册到 Qt 动态插件
数据库·c++·qt·系统架构·程序设计
源码集结号6 小时前
云诊所门诊系统源码,基于SpringBoot+Vue.js架构,支持多租户设计与Docker容器化部署
java·spring boot·源码·医院信息系统·门诊系统·诊所系统
论文复现现场6 小时前
WRF、FEA 能用 L40S 加速吗?求解器检查、断点恢复与长期运行
java·数据库·算法·wrf·有限元分析·gpu计算·断点续算
啃瓜子的松鼠7 小时前
泛微OAe9-后端二开异步调用
java
Decay__7 小时前
04_MySQL DBA 日常运维手册 —— 会话管理、锁与阻塞、事务监控、慢查询与日志巡检
运维·数据库·sql·mysql·架构
秋天的怀念_David7 小时前
第二十一夜 · 数字黑洞与西西弗斯的惩罚
java·程序人生·算法
外收内放8 小时前
Python基础语法练习题(73)
java·前端·python