企业微信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测试,显著提升企业微信对接代码的可维护性与可靠性。