下面是一份 "可直接复制进项目用"的通用 Token 管理工具 ,逐行详细注释 + 完整使用示例 + 设计说明 。
不依赖企业微信,任何第三方 Token 都能用。
我会按 「是什么 → 怎么用 → 为什么这样设计」 的顺序来讲,保证你以后回头看也能秒懂。
一、整体使用方式(先给你一个全景)
你最终在业务代码里只需要这样用:
arduino
String token = tokenManager.getToken(
"wx:login",
() -> new DefaultAccessToken(fetchTokenFromRemote(), 7200)
);
你只关心两件事:
- key 是什么
- 怎么获取新 token
其余全部交给 TokenManager
二、核心接口与类(逐个讲)
1️⃣ AccessToken 接口(Token 抽象)
csharp
/**
* 通用 AccessToken 抽象
*
* 代表"一个有生命周期的 token"
* 不关心 token 从哪里来
*/
public interface AccessToken {
/**
* @return 实际可用于请求的 token 字符串
*/
String getToken();
/**
* @return 当前 token 是否已过期
*/
boolean isExpired();
}
设计目的
- 解耦平台
- JWT / OAuth / 内部 token 都可以实现它
2️⃣ DefaultAccessToken(默认实现)
arduino
/**
* 默认 Token 实现
*
* 使用"绝对过期时间"判断是否失效
*/
public class DefaultAccessToken implements AccessToken {
/** token 字符串 */
private final String token;
/** token 过期时间点(毫秒时间戳) */
private final long expireAt;
/**
* @param token token 字符串
* @param expiresInSeconds 过期秒数(通常接口返回)
*/
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;
}
}
为什么要提前 5 分钟过期?
-
防止:
- 网络延迟
- 服务器时间偏差
- 请求刚发出 token 就失效
3️⃣ TokenProvider(获取 Token 的策略)
csharp
/**
* Token 提供器
*
* 由业务方实现"如何获取一个新的 token"
*/
@FunctionalInterface
public interface TokenProvider {
/**
* 获取新的 token
*
* @return 新的 AccessToken
*/
AccessToken refresh();
}
为什么用接口?
-
TokenManager 不依赖任何 SDK / HTTP / 平台
-
你可以:
- HTTP 调用
- SDK 调用
- 数据库
- 本地生成
4️⃣ TokenManager(核心)
typescript
@Component
public class TokenManager {
/** token 缓存池 */
private final Map<String, AccessToken> tokenCache = new ConcurrentHashMap<>();
/** 每个 key 一把锁,避免并发刷新 */
private final Map<String, Object> lockMap = new ConcurrentHashMap<>();
/**
* 获取 token(通用方法)
*
* @param key token 唯一标识(非常重要)
* @param tokenProvider token 刷新逻辑
* @return 可用 token 字符串
*/
public String getToken(String key, TokenProvider tokenProvider) {
AccessToken token = tokenCache.get(key);
// token 不存在 或 已过期
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();
}
/**
* 主动清除 token(例如配置变更)
*/
public void invalidate(String key) {
tokenCache.remove(key);
lockMap.remove(key);
}
}
三、详细使用示例(你照抄就能用)
示例 1️⃣:第三方 OAuth Token(通用)
ini
String token = tokenManager.getToken(
"oauth:system",
() -> {
OAuthDTO dto = oauthClient.getToken();
return new DefaultAccessToken(dto.getToken(), dto.getExpireIn());
}
);
示例 2️⃣:企业微信 / 钉钉 / 飞书(完全通用)
ini
String token = tokenManager.getToken(
"wx:addressBook",
() -> {
WxTokenDTO dto = callWxTokenApi();
return new DefaultAccessToken(dto.getAccess_token(), dto.getExpires_in());
}
);
示例 3️⃣:JWT(本地生成)
arduino
String token = tokenManager.getToken(
"jwt:internal",
() -> new DefaultAccessToken(jwtService.generate(), 3600)
);
示例 4️⃣:自研系统登录
arduino
String token = tokenManager.getToken(
"internal:api",
() -> new DefaultAccessToken(loginAndGetToken(), 7200)
);
四、key 的设计规范(非常重要)
推荐格式:
makefile
平台:用途
例如:
wx:loginwx:userding:tokenaliyun:raminternal:api
❌ 不推荐:
- 使用 corpSecret
- 使用 URL
- 使用随机字符串
五、常见问题(你以后一定会遇到)
Q1:多个线程同时获取 token 会不会重复请求?
👉 不会
- 每个 key 独立锁
- 双重检查
Q2:token 过期了会不会阻塞请求?
👉 只会阻塞 第一次刷新
- 其余线程复用新 token
Q3:能不能换成 Redis?
👉 可以,而且 不用改使用方式
只需替换:
javascript
Map<String, AccessToken> tokenCache
六、推荐的项目结构
markdown
infrastructure/
└── token/
├── AccessToken.java
├── DefaultAccessToken.java
├── TokenProvider.java
└── TokenManager.java
七、设计总结(你可以写进技术文档)
TokenManager 负责:
- 缓存
- 过期判断
- 并发控制
TokenProvider 负责:
- 如何获取 token
二者解耦,支持任意平台、任意 Token 类型。