使用Redis的seltIfAbsent【线程安全的原子操作,不可分割,整体执行】
获取Token时,判断需要获取新Token【此处:token为空或token的有效期小于10s】,即需要刷新Token:
先获取一个刷新Token的锁
获取成功:
先获取Token,再检查是否需要刷新Token,有可能在获取锁的时候,Token刷新已经成功,这时候就不需要再次刷新Token;
删除存储Token RedisKey;
获取Token【获取成功后,会存储到Redis】;
删除锁;
获取失败,说明有现成在执行获取Token方法;
获取Token,判断是否需要刷新,
是说明需要等待其他线程获取Token完成【每次判断完之后睡眠200ms,再重新获取token,在执行循环判断是否需要刷新,执行上述逻辑】【如果循环了10次并且还未获取到正常Token,就抛出异常】
否说明已经获取完成,直接返回即可;
获取Token以及判断是否需要刷新方法
private String getOutlookToken() {
ValueOperations<String, String> ops = redisTemplate.opsForValue();
String token = ops.get(OUTLOOK_REDIS_KEY);
if (needRefresh(token, OUTLOOK_REDIS_KEY)) {
return lockAndRefreshOutlookToken(ops);
}
if (StrUtil.isBlank(token)) {
throw new BusinessException(ErrorCodeMsgEnums.SYSTEM_ERROR, "获取token异常,请稍后再试");
}
return token;
}
private boolean needRefresh(String token, String redisKey) {
if (StrUtil.isBlank(token)) {
return true;
}
Long expire = redisTemplate.getExpire(redisKey, TimeUnit.SECONDS);
return (expire == null || expire < 10L);
}
刷新Token方法
private String lockAndRefreshOutlookToken(ValueOperations<String, String> ops) {
// 获取锁
Boolean locked =
ops.setIfAbsent(
OUTLOOK_REDIS_LOCK_KEY,
LocalDateTime.now().format(DatePattern.NORM_DATETIME_MS_FORMATTER),
20L,
TimeUnit.SECONDS);
String token;
// 获取到锁
if (Boolean.TRUE.equals(locked)) {
try {
// 再次检查是否需要刷新token
token = ops.get(OUTLOOK_REDIS_KEY);
if (needRefresh(token, OUTLOOK_REDIS_KEY)) {
redisTemplate.delete(OUTLOOK_REDIS_KEY);
token = outlookLogin();
}
} finally {
redisTemplate.delete(OUTLOOK_REDIS_LOCK_KEY);
}
} else {
// 获取锁失败,尝试 10次 获取token
int maxRetries = 10;
int retryCount = 0;
token = ops.get(OUTLOOK_REDIS_KEY);
while (needRefresh(token, OUTLOOK_REDIS_KEY)) {
if (retryCount > maxRetries) {
throw new BusinessException(ErrorCodeMsgEnums.SYSTEM_ERROR, "获取token失败,请稍后再试(最大次数)");
}
ThreadUtil.sleep(200);
token = ops.get(OUTLOOK_REDIS_KEY);
retryCount++;
}
}
return token;
}