package com.zsElectric.boot.common.util.electric; import cn.hutool.core.bean.BeanUtil; import com.google.gson.Gson; import com.google.gson.JsonObject; import com.zsElectric.boot.common.constant.ConnectivityConstants; import com.zsElectric.boot.common.util.AESCryptoUtils; import com.zsElectric.boot.common.util.HmacMD5Util; import com.zsElectric.boot.common.util.OkHttpUtil; import com.zsElectric.boot.common.util.SequenceGenUtil; import jakarta.annotation.Resource; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import java.time.Duration; import java.time.LocalDateTime; import java.util.Objects; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; /** * Token管理器 - 负责Token的获取、刷新、存储和有效性检查 */ @Slf4j @Component @RequiredArgsConstructor public class ElectricTokenManager { @Resource private OkHttpUtil okHttpUtil; private static final String TOKEN_KEY = "api:local:accessToken"; private static final String TOKEN_REFRESH_LOCK_KEY = "api:token:refresh:lock"; private static final int DEFAULT_TOKEN_EXPIRE_HOURS = 24; private final RedisTemplate redisTemplate; // 本地锁,防止单JVM内重复刷新Token[2](@ref) private final ReentrantLock localLock = new ReentrantLock(); /** * 获取有效的访问令牌(主入口方法) */ public String getValidAccessToken() { // 尝试从Redis获取已存储的Token[1](@ref) ApiToken apiToken = getStoredToken(); // 如果Token不存在或已过期,则刷新Token[1,3](@ref) if (Objects.isNull(apiToken) || !apiToken.isValid()) { apiToken = refreshAccessToken(); } return apiToken.getAccessToken(); } /** * 从Redis获取存储的Token */ private ApiToken getStoredToken() { try { Object tokenObj = redisTemplate.opsForValue().get(TOKEN_KEY); if (Objects.isNull(tokenObj)) { log.debug("Redis中未找到Token,tokenKey: {}", TOKEN_KEY); return null; } return (ApiToken) tokenObj; } catch (Exception e) { log.error("从Redis获取Token失败,tokenKey: {}", TOKEN_KEY, e); // 清除可能的错误数据 redisTemplate.delete(TOKEN_KEY); return null; } } /** * Token刷新相关方法 */ private ApiToken refreshAccessToken() { // 先尝试获取本地锁,减少分布式锁的竞争[2](@ref) if (!localLock.tryLock()) { log.debug("本地锁已被占用,等待其他线程刷新Token"); return waitAndRetrieve(); } try { // 获取分布式锁,防止集群环境下多个实例同时刷新 String lockKey = TOKEN_REFRESH_LOCK_KEY; Boolean lockAcquired = redisTemplate.opsForValue().setIfAbsent( lockKey, "LOCK", 30, TimeUnit.SECONDS); if (Boolean.FALSE.equals(lockAcquired)) { log.info("分布式锁已被占用,等待其他实例刷新Token"); return waitAndRetrieve(); } try { // 双重检查,防止重复刷新[2](@ref) ApiToken existingToken = getStoredToken(); if (Objects.nonNull(existingToken) && existingToken.isValid()) { log.info("其他线程已刷新Token,直接使用"); return existingToken; } log.info("开始刷新Token"); ApiToken newToken = fetchNewTokenFromRemote(); if (Objects.isNull(newToken) || Objects.isNull(newToken.getAccessToken()) || newToken.getAccessToken().trim().isEmpty()) { throw new RuntimeException("刷新Token失败,获取到的Token为空"); } // 设置Token时间信息并存储 setupAndStoreToken(TOKEN_KEY, newToken); log.info("Token刷新成功,tokenKey: {},过期时间: {}", TOKEN_KEY, newToken.getExpireTime()); return newToken; } finally { // 释放分布式锁 redisTemplate.delete(lockKey); } } finally { localLock.unlock(); } } /** * 等待并重新获取Token(用于锁竞争场景) */ private ApiToken waitAndRetrieve() { try { TimeUnit.MILLISECONDS.sleep(200); } catch (InterruptedException e) { Thread.currentThread().interrupt(); log.error("等待锁释放时被中断", e); } // 重试获取Token ApiToken existingToken = getStoredToken(); if (Objects.nonNull(existingToken) && existingToken.isValid()) { return existingToken; } // 如果仍然无效,递归刷新(会有锁控制) return refreshAccessToken(); } /** * 调用第三方接口获取新Token */ private ApiToken fetchNewTokenFromRemote() { try { //调用第三方API获取Token QueryTokenParms queryTokenParms = new QueryTokenParms(); queryTokenParms .setOperatorID(ConnectivityConstants.OPERATOR_ID) .setOperatorSecret(ConnectivityConstants.OPERATOR_SECRET); RequestParmsEntity requestParms = new RequestParmsEntity(); SequenceGenUtil.SequenceResult result = SequenceGenUtil.generate(); requestParms .setOperatorID(ConnectivityConstants.OPERATOR_ID) .setData(AESCryptoUtils.encrypt(new Gson().toJson(queryTokenParms), ConnectivityConstants.PLATFORM_DATA_SECRET, ConnectivityConstants.PLATFORM_DATA_SECRET_IV)) .setTimeStamp(result.getTimestamp()) .setSeq(result.getSequence()) .setSig(HmacMD5Util.genSign(requestParms.getOperatorID(), requestParms.getData(), requestParms.getTimeStamp(), requestParms.getSeq(), ConnectivityConstants.PLATFORM_SIG_SECRET)); JsonObject response = okHttpUtil.doPostForm(ConnectivityConstants.TEST_DOMAIN + ConnectivityConstants.QUERY_TOKEN, BeanUtil.beanToMap(requestParms), null); if (Objects.isNull(response)) { log.error("调用第三方接口获取Token失败"); return null; } Gson gson = new Gson(); ResponseParmsEntity responseParms = gson.fromJson(response, ResponseParmsEntity.class); String data = responseParms.getRet() + responseParms.getMsg() + responseParms.getData(); boolean verify = HmacMD5Util.verify(data, ConnectivityConstants.PLATFORM_SIG_SECRET, responseParms.getSig()); if (!verify) { log.error("第三方接口响应数据签名验证失败"); return null; } if (responseParms.getRet() != 0) { switch (responseParms.getRet()) { case -1: log.error("系统繁忙,此时请求方稍后重试"); break; case 4001: log.error("签名错误"); break; case 4002: log.error("Token错误"); break; case 4003: log.error("参数不合法,缺少必需的示例:OperatorID、Sig、TimeStamp、Data、Seq五个参数"); break; case 4004: log.error("请求的业务参数不合法,各接口定义自己的必须参数"); break; case 500: log.error("系统错误"); break; } } String decodeData = AESCryptoUtils.decrypt(responseParms.getData(), ConnectivityConstants.PLATFORM_DATA_SECRET, ConnectivityConstants.PLATFORM_DATA_SECRET_IV); QueryTokenResponseData responseData = gson.fromJson(decodeData, QueryTokenResponseData.class); if (responseData.getSuccStat() == 1) { //0-无,1-OperatorID无效 String failReason = ""; if (responseData.getFailReason() == 0) { failReason = "无"; } if (responseData.getFailReason() == 1) { failReason = "OperatorID无效"; } log.error("调用第三方接口获取Token失败,失败原因: {}", failReason); return null; } ApiToken apiToken = new ApiToken(); apiToken.setAccessToken(responseData.getAccessToken()); apiToken.setTokenAvailableTime(responseData.getTokenAvailableTime()); apiToken.setObtainTime(LocalDateTime.now()); apiToken.setExpireTime(apiToken.getObtainTime().plusSeconds(apiToken.getTokenAvailableTime())); return apiToken; } catch (Exception e) { log.error("获取新Token时发生异常,tokenKey: {}", TOKEN_KEY, e); throw new RuntimeException("调用第三方接口失败", e); } } /** * 设置Token信息并存储到Redis */ private void setupAndStoreToken(String tokenKey, ApiToken token) { LocalDateTime obtainTime = LocalDateTime.now(); token.setObtainTime(obtainTime); token.setExpireTime(obtainTime.plusSeconds(token.getTokenAvailableTime())); storeToken(tokenKey, token); } /** * 存储Token到Redis */ private void storeToken(String apiKey, ApiToken token) { long expireSeconds = Duration.between(LocalDateTime.now(), token.getExpireTime()).getSeconds(); // 设置Redis过期时间,取实际过期时间和默认过期时间的较小值 long redisExpireSeconds = Math.max(expireSeconds, 60); // 至少保留60秒 redisExpireSeconds = Math.min(redisExpireSeconds, DEFAULT_TOKEN_EXPIRE_HOURS * 3600L); redisTemplate.opsForValue().set(TOKEN_KEY, token, redisExpireSeconds, TimeUnit.SECONDS); log.debug("Token已存储到Redis,apiKey: {}, 过期时间: {}秒", apiKey, redisExpireSeconds); } }