通用 Token 管理工具(详细注释 + 完整使用示例 + 设计说明)

下面是一份 "可直接复制进项目用"的通用 Token 管理工具逐行详细注释 + 完整使用示例 + 设计说明

不依赖企业微信,任何第三方 Token 都能用

我会按 「是什么 → 怎么用 → 为什么这样设计」 的顺序来讲,保证你以后回头看也能秒懂。


一、整体使用方式(先给你一个全景)

你最终在业务代码里只需要这样用:

arduino 复制代码
String token = tokenManager.getToken(
    "wx:login",
    () -> new DefaultAccessToken(fetchTokenFromRemote(), 7200)
);

你只关心两件事:

  1. key 是什么
  2. 怎么获取新 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:login
  • wx:user
  • ding:token
  • aliyun:ram
  • internal: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 类型。

相关推荐
TH_12 小时前
28、powershell快速删除 node_modules
java
用户47949283569152 小时前
并发编程里的"堵车"与"红绿灯":死锁、活锁与两种锁策略(乐观锁、悲观锁)
前端·后端
一 乐2 小时前
智慧医药|基于springboot + vue智慧医药系统(源码+数据库+文档)
java·前端·数据库·vue.js·spring boot·后端
Tony Bai2 小时前
Goroutine “气泡”宇宙——Go 并发模型的新维度
开发语言·后端·golang
王中阳Go2 小时前
攻克制造业知识检索难题:我们如何用Go+AI打造高可用RAG系统,将查询效率提升600%
人工智能·后端·go
游浪踏2 小时前
006_prompt
后端·openai
雨中飘荡的记忆2 小时前
享元模式深度解析:看Java如何优雅节省内存
java·设计模式
悟空码字2 小时前
SpringBoot接口防抖大作战,拒绝“手抖”重复提交!
java·spring boot·后端
编程大师哥2 小时前
Boost C++
java·c++·php