通用 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 类型。

相关推荐
小毅&Nora11 分钟前
【后端】【SpringBoot】① 源码解析:从启动到优雅关闭
spring boot·后端·优雅关闭
嘻哈baby14 分钟前
从TIME_WAIT爆炸到端口耗尽:Linux短连接服务排查与优化
后端
wszy180927 分钟前
外部链接跳转:从 App 打开浏览器的正确姿势
java·javascript·react native·react.js·harmonyos
开心就好202531 分钟前
iOS应用性能监控全面解析:CPU、内存、FPS、卡顿与内存泄漏检测
后端
期待のcode32 分钟前
认识Java虚拟机
java·开发语言·jvm
raining_peidx36 分钟前
xxljob源码
java·开发语言
肥猪猪爸37 分钟前
双重检查锁(DCL)与 volatile 的关键作用
java·开发语言·单例模式
yaoxin5211231 小时前
289. Java Stream API - 从字符串的字符创建 Stream
java·开发语言
浮游本尊1 小时前
Java学习第35天 - 分布式系统深入与大数据处理
java
2301_780669861 小时前
Set集合、HashSet集合的底层原理
java