使用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;
  }