下面是一套 真正通用、可复用、与具体平台完全解耦的 Token 管理工具设计,满足:
- ✅ 不绑定企业微信
- ✅ 支持 任意第三方(OAuth / 内部系统 / API Token)
- ✅ 自动缓存
- ✅ 自动过期刷新
- ✅ 并发安全
- ✅ 可 Spring 注入
- ✅ 你现在的企业微信 / 以后阿里云 / 钉钉 / 自研系统都能用
一、设计目标(一句话)
TokenManager 只负责:缓存 + 过期判断 + 刷新调度至于"怎么获取 Token",交给调用方提供
👉 这就是策略模式 + 缓存池
二、核心抽象(这是关键)
1️⃣ 通用 Token 接口(不是企业微信)
plain
public interface AccessToken {
/** 获取 token 字符串 */
String getToken();
/** 是否已过期 */
boolean isExpired();
}
2️⃣ 通用 Token 刷新器(最重要)
plain
@FunctionalInterface
public interface TokenProvider {
/**
* 获取新的 token
* 由调用方实现(HTTP / SDK / DB / 本地算法 都行)
*/
AccessToken refresh();
}
⚠️ 注意:TokenManager 完全不知道 token 从哪来
三、通用 TokenManager(核心实现)✅
plain
@Component
public class TokenManager {
/** Token 缓存池 */
private final Map<String, AccessToken> tokenCache = new ConcurrentHashMap<>();
/** 锁池(避免全局锁) */
private final Map<String, Object> lockMap = new ConcurrentHashMap<>();
/**
* 获取 token(通用)
*
* @param key token 唯一标识(非常重要)
* @param tokenProvider token 刷新逻辑
*/
public String getToken(String key, TokenProvider tokenProvider) {
AccessToken token = tokenCache.get(key);
if (token == null || token.isExpired()) {
// 每个 key 一把锁
Object lock = lockMap.computeIfAbsent(key, k -> new Object());
synchronized (lock) {
token = tokenCache.get(key);
if (token == null || token.isExpired()) {
token = tokenProvider.refresh();
tokenCache.put(key, token);
}
}
}
return token.getToken();
}
/** 主动清除(可选) */
public void invalidate(String key) {
tokenCache.remove(key);
lockMap.remove(key);
}
}
📌 这是一个"平台无关"的基础设施类
四、一个通用 Token 实现(可直接用)
plain
public class DefaultAccessToken implements AccessToken {
private final String token;
private final long expireAt;
public DefaultAccessToken(String token, long expiresInSeconds) {
this.token = token;
// 提前 5 分钟过期
this.expireAt = System.currentTimeMillis() + (expiresInSeconds - 300) * 1000;
}
@Override
public String getToken() {
return token;
}
@Override
public boolean isExpired() {
return System.currentTimeMillis() >= expireAt;
}
}
五、使用示例(重点:看有多通用)
✅ 1️⃣ 企业微信(你现在的需求)
plain
String token = tokenManager.getToken(
"wx:login",
() -> {
WxTokenDTO dto = callWxApi();
return new DefaultAccessToken(dto.getAccess_token(), dto.getExpires_in());
}
);
✅ 2️⃣ 钉钉 Token
plain
String token = tokenManager.getToken(
"dingding:token",
() -> {
DingTokenDTO dto = dingApi.getToken();
return new DefaultAccessToken(dto.getAccessToken(), dto.getExpireIn());
}
);
✅ 3️⃣ 阿里云 RAM Token
plain
String token = tokenManager.getToken(
"aliyun:ram",
() -> new DefaultAccessToken(fetchAliyunToken(), 3600)
);
✅ 4️⃣ 自研系统 API Token
plain
String token = tokenManager.getToken(
"internal:api",
() -> new DefaultAccessToken(loginAndGetToken(), 7200)
);
✅ 5️⃣ 本地算法生成 Token(JWT)
plain
String token = tokenManager.getToken(
"jwt:system",
() -> new DefaultAccessToken(jwtService.generate(), 1800)
);
六、为什么这个方案是"工程级"的?
| 点 | 说明 |
|---|---|
| ✅ 完全解耦 | TokenManager 不依赖任何平台 |
| ✅ 策略模式 | TokenProvider 决定"怎么拿 token" |
| ✅ 高并发安全 | 每个 key 独立锁 |
| ✅ 可扩展 | 新平台不改一行管理代码 |
| ✅ 可测试 | TokenProvider 可 mock |
| ✅ 可缓存 | Map / Redis 都能替换 |
七、进阶(如果你后面会分布式)
如果你以后是 多节点部署,只需要把:
plain
Map<String, AccessToken> tokenCache
换成:
- Redis
- Caffeine
- Guava Cache
👉 TokenManager 设计完全不用变
八、你现在项目中可以这样分层(建议)
plain
infrastructure/
└── token/
├── TokenManager.java
├── AccessToken.java
├── TokenProvider.java
└── DefaultAccessToken.java
企业微信、钉钉、阿里云都只是 TokenProvider 的实现