企业微信API接口的Java SDK封装:可复用、可测试的工具类设计方法

企业微信API接口的Java SDK封装:可复用、可测试的工具类设计方法

直接在业务代码中拼接URL、手动处理Token、解析JSON响应,会导致重复逻辑多、难以维护和单元测试。本文基于wlkankan.cn.wecom.sdk包,设计模块化、依赖注入友好、支持Mock的企业微信SDK,提升开发效率与系统健壮性。

统一响应结构与异常定义

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

public class WeComResponse<T> {
    private int errcode;
    private String errmsg;
    private T data;

    public boolean isSuccess() {
        return errcode == 0;
    }

    // getters & setters
    public int getErrcode() { return errcode; }
    public void setErrcode(int errcode) { this.errcode = errcode; }
    public String getErrmsg() { return errmsg; }
    public void setErrmsg(String errmsg) { this.errmsg = errmsg; }
    public T getData() { return data; }
    public void setData(T data) { this.data = data; }
}
java 复制代码
package wlkankan.cn.wecom.sdk.exception;

public class WeComApiException extends RuntimeException {
    private final int errcode;

    public WeComApiException(int errcode, String message) {
        super(message);
        this.errcode = errcode;
    }

    public int getErrcode() { return errcode; }
}

Token管理器接口与实现

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

import org.springframework.stereotype.Component;

@Component
public class CorpAccessTokenManager implements AccessTokenProvider {
    private final TokenCache tokenCache = new TokenCache();

    @Override
    public String getAccessToken(String corpId, String secret) {
        String cacheKey = "wecom:token:" + corpId;
        String cached = tokenCache.get(cacheKey);
        if (cached != null) {
            return cached;
        }

        // 调用获取token接口
        String url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=" + corpId + "&corpsecret=" + secret;
        // 使用HttpClient或RestTemplate(此处简化)
        String rawResponse = "{\"access_token\":\"mock_token_123\",\"expires_in\":7200}";
        // 实际应解析JSON
        String token = "mock_token_123";
        tokenCache.put(cacheKey, token, 7000); // 提前200秒过期
        return token;
    }
}
java 复制代码
package wlkankan.cn.wecom.sdk.token;

public interface AccessTokenProvider {
    String getAccessToken(String corpId, String secret);
}
java 复制代码
package wlkankan.cn.wecom.sdk.token;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

public class TokenCache {
    private final ConcurrentHashMap<String, CacheEntry> cache = new ConcurrentHashMap<>();

    public void put(String key, String value, long ttlSeconds) {
        cache.put(key, new CacheEntry(value, System.currentTimeMillis() + ttlSeconds * 1000));
    }

    public String get(String key) {
        CacheEntry entry = cache.get(key);
        if (entry == null) return null;
        if (System.currentTimeMillis() > entry.expireAt) {
            cache.remove(key);
            return null;
        }
        return entry.value;
    }

    private static class CacheEntry {
        final String value;
        final long expireAt;
        CacheEntry(String value, long expireAt) {
            this.value = value;
            this.expireAt = expireAt;
        }
    }
}

HTTP客户端抽象

java 复制代码
package wlkankan.cn.wecom.sdk.http;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

@Component
public class WeComHttpClient {
    private final RestTemplate restTemplate = new RestTemplate();
    private final ObjectMapper objectMapper = new ObjectMapper();

    public <T> T post(String url, Object requestBody, Class<T> responseType) {
        try {
            String json = objectMapper.writeValueAsString(requestBody);
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_JSON);
            HttpEntity<String> entity = new HttpEntity<>(json, headers);
            return restTemplate.postForObject(url, entity, responseType);
        } catch (Exception e) {
            throw new RuntimeException("HTTP request failed", e);
        }
    }

    public <T> T get(String url, Class<T> responseType) {
        return restTemplate.getForObject(url, responseType);
    }
}

用户服务SDK封装

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

import wlkankan.cn.wecom.sdk.model.WeComResponse;
import wlkankan.cn.wecom.sdk.token.AccessTokenProvider;
import wlkankan.cn.wecom.sdk.http.WeComHttpClient;
import wlkankan.cn.wecom.sdk.exception.WeComApiException;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class UserService {
    @Resource
    private AccessTokenProvider tokenProvider;

    @Resource
    private WeComHttpClient httpClient;

    public WeComResponse<UserDetail> getUser(String corpId, String secret, String userId) {
        String token = tokenProvider.getAccessToken(corpId, secret);
        String url = "https://qyapi.weixin.qq.com/cgi-bin/user/get?access_token=" + token + "&userid=" + userId;
        WeComResponse<UserDetail> resp = httpClient.get(url, WeComResponse.class);
        if (!resp.isSuccess()) {
            throw new WeComApiException(resp.getErrcode(), resp.getErrmsg());
        }
        return resp;
    }

    public static class UserDetail {
        private String userid;
        private String name;
        private String mobile;
        // getters/setters
        public String getUserid() { return userid; }
        public void setUserid(String userid) { this.userid = userid; }
        public String getName() { return name; }
        public void setName(String name) { this.name = name; }
        public String getMobile() { return mobile; }
        public void setMobile(String mobile) { this.mobile = mobile; }
    }
}

支持Mock的单元测试

java 复制代码
package wlkankan.cn.wecom.sdk.test;

import wlkankan.cn.wecom.sdk.service.UserService;
import wlkankan.cn.wecom.sdk.token.AccessTokenProvider;
import wlkankan.cn.wecom.sdk.http.WeComHttpClient;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

public class UserServiceTest {

    @Test
    public void testGetUserSuccess() {
        AccessTokenProvider mockTokenProvider = Mockito.mock(AccessTokenProvider.class);
        WeComHttpClient mockClient = Mockito.mock(WeComHttpClient.class);

        when(mockTokenProvider.getAccessToken("corp123", "secret456"))
            .thenReturn("mock_token");

        UserService.UserDetail detail = new UserService.UserDetail();
        detail.setUserid("zhangsan");
        detail.setName("张三");

        UserService.WeComResponse<UserService.UserDetail> resp = new UserService.WeComResponse<>();
        resp.setErrcode(0);
        resp.setErrmsg("ok");
        resp.setData(detail);

        when(mockClient.get(anyString(), eq(UserService.WeComResponse.class)))
            .thenReturn(resp);

        UserService service = new UserService();
        // 注入Mock(实际项目可用Spring Test)
        service.tokenProvider = mockTokenProvider;
        service.httpClient = mockClient;

        UserService.WeComResponse<UserService.UserDetail> result = service.getUser("corp123", "secret456", "zhangsan");
        assertTrue(result.isSuccess());
        assertEquals("张三", result.getData().getName());
    }
}

配置类集成

java 复制代码
package wlkankan.cn.wecom.sdk.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(basePackages = "wlkankan.cn.wecom.sdk")
public class WeComSdkAutoConfiguration {
    // 自动装配所有SDK组件
}

通过wlkankan.cn.wecom.sdk模块封装的SDK,业务层只需注入UserService等服务类,无需关心Token刷新、HTTP调用、错误重试等细节。接口设计清晰,依赖可替换,便于Mock测试,显著提升企业微信对接代码的可维护性与可靠性。

相关推荐
lhldsg11 小时前
AI零售系统实战指南:从架构设计到落地部署全解析
java·人工智能·小程序·uni-app·零售
Interview Aid11211 小时前
TikTok OA 四题分享|半小时内 AC,题目基本都是实现题
java·开发语言·算法·面试·职场和发展
mldong12 小时前
换了工作流引擎,前端一行代码没改
java·架构
CoderIsArt19 小时前
C#中UI 线程与 Dispatcher
开发语言·ui·c#
微尘寒风19 小时前
【Git】的安装和使用
java·git
y = xⁿ20 小时前
DeepSeek Harness 学习日记:关于Agent接口,工具调用的底层实现
android·java·学习
侧耳倾听11120 小时前
jwt使用简介
java·jwt
顶点多余21 小时前
那些在算法中适合巩固的知识点---1
java·前端·算法
AI人工智能+电脑小能手21 小时前
大白话说Java设计模式-46-责任链模式(业务实战篇)
java·设计模式·责任链模式·风控校验·servlet filter·spring interceptor·链式处理
jay神21 小时前
【计算机毕业设计】基于SpringBoot的程序教学辅助系统
java·前端·vue.js·spring boot·后端·毕业设计·课程设计