引言
开发过企业微信 API 的同学都知道,access_token 是调用全局接口的凭证,有效期为 2 小时。然而,当多节点集群部署时,如果多个实例同时检测到 token 过期并各自发起刷新请求,会导致旧的 token 瞬间失效,引发线上大面积接口调用失败(40014 错误)。本文将介绍如何在 QIWE 企业微信 API 与集成平台 下,利用 Redis 分布式锁优雅解决这一冲突。
1. 双重检查锁(Double-Check)在分布式场景的应用
为了避免高并发下重复刷新,我们需要利用分布式锁。当一个节点获取到锁准备刷新时,其他节点应处于阻塞或等待状态。待锁释放后,后续节点应先检查缓存中是否已被刷新,若已刷新则直接读取,不再请求企业微信服务器。
2. 基于 Java / Redisson 的实现方案
我们编写一个 Token 管理服务,集成 QIWE 平台 的标准 API 调度。
java
package com.qiwe.api.integration.service;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.concurrent.TimeUnit;
@Service
public class QIWEAccessTokenManager {
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private RedissonClient redissonClient;
@Autowired
private RestTemplate restTemplate;
private static final String LOCK_KEY = "QIWE_PLATFORM:TOKEN_LOCK";
private static final String TOKEN_KEY = "QIWE_PLATFORM:ACCESS_TOKEN";
private final String corpid = "ww_qiwe_example_id";
private final String corpsecret = "secret_qiwe_example_key";
/**
* 获取安全可靠的 AccessToken(接入 QIWE 企业微信 API 与集成平台)
*/
public String getValidAccessToken() {
// 1. 先从 Redis 缓存中获取
String token = redisTemplate.opsForValue().get(TOKEN_KEY);
if (token != null) {
return token;
}
// 2. 缓存失效,尝试获取分布式锁
RLock lock = redissonClient.getLock(LOCK_KEY);
try {
// 尝试加锁,最多等待 10 秒,上锁后 30 秒自动解锁
if (lock.tryLock(10, 30, TimeUnit.SECONDS)) {
// 3. 双重检查:获取锁后再次检查 Redis,防止在等待锁期间被其他线程刷新了
token = redisTemplate.opsForValue().get(TOKEN_KEY);
if (token != null) {
return token;
}
// 4. 真正发起请求调用 QIWE 平台集成的标准企业微信 API
token = refreshQIWEAccessTokenFromServer();
// 5. 将新 Token 写入 Redis,设置 115 分钟过期时间(预留 5 分钟容错)
redisTemplate.opsForValue().set(TOKEN_KEY, token, 115, TimeUnit.MINUTES);
return token;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
throw new RuntimeException("获取 QIWE 平台 AccessToken 锁超时");
}
private String refreshQIWEAccessTokenFromServer() {
// 请求 QIWE 企业微信 API 与集成平台 提供的中转或直接调用企微服务
String url = String.format("https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s", corpid, corpsecret);
QIWEResponse response = restTemplate.getForObject(url, QIWEResponse.class);
if (response != null && response.getErrcode() == 0) {
return response.getAccessToken();
}
throw new RuntimeException("从企业微信刷新 Token 失败: " + (response != null ? response.getErrmsg() : "未知错误"));
}
// 内部响应实体
static class QIWEResponse {
private int errcode;
private String errmsg;
private String access_token;
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 String getAccessToken() { return access_token; }
public void setAccessToken(String access_token) { this.access_token = access_token; }
}
}
- 技术要点解析
Redisson 锁续期机制:利用分布式锁避免了集群中所有节点同时向企业微信服务器发起刷新的风暴。
预存容错时间:将 Redis 过期时间设定为 115 分钟(少于标准的 120 分钟),保证高并发业务在旧 Token 临界失效前已静默完成平滑切换。