package com.usky.vpp.client; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.usky.vpp.config.VppUnProperties; import com.usky.vpp.config.VppUnPropertiesRegistry; import com.usky.vpp.config.VppUnTenantContext; import com.usky.vpp.util.VppUnMessageBuilder; import com.usky.vpp.util.VppUnPayloadHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import java.time.Instant; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.ReentrantLock; /** * UN Token 缓存(按租户维度,TokenRequest 获取,默认约 30 分钟过期) */ @Component public class VppUnTokenHolder { private static final Logger log = LoggerFactory.getLogger(VppUnTokenHolder.class); private final ConcurrentHashMap tokenStates = new ConcurrentHashMap<>(); private final ConcurrentHashMap locks = new ConcurrentHashMap<>(); @Autowired private VppUnPropertiesRegistry propertiesRegistry; @Autowired private VppUnHttpExecutor httpExecutor; @Autowired private ObjectMapper objectMapper; public String getToken() { Integer tenantId = VppUnTenantContext.resolveTenantId(); TokenState state = tokenStates.computeIfAbsent(tenantId, id -> createInitialState(id)); if (!state.needsRefresh()) { return state.token; } ReentrantLock lock = locks.computeIfAbsent(tenantId, id -> new ReentrantLock()); lock.lock(); try { state = tokenStates.computeIfAbsent(tenantId, id -> createInitialState(id)); if (!state.needsRefresh()) { return state.token; } refreshTokenInternal(tenantId); return state.token; } finally { lock.unlock(); } } public void invalidate() { Integer tenantId = VppUnTenantContext.resolveTenantId(); ReentrantLock lock = locks.computeIfAbsent(tenantId, id -> new ReentrantLock()); lock.lock(); try { tokenStates.remove(tenantId); } finally { lock.unlock(); } } public void applyTokenResponse(Map response) { Integer tenantId = VppUnTenantContext.resolveTenantId(); String newToken = VppUnPayloadHelper.getString(response, "token"); if (!StringUtils.hasText(newToken)) { throw new IllegalStateException("TokenResponse 缺少 token"); } VppUnProperties properties = propertiesRegistry.require(tenantId); int ttlMinutes = properties.getTokenTtlMinutes() != null ? properties.getTokenTtlMinutes() : 25; TokenState state = new TokenState(newToken, Instant.now().plusSeconds(ttlMinutes * 60L)); tokenStates.put(tenantId, state); propertiesRegistry.updateToken(tenantId, newToken); } private TokenState createInitialState(Integer tenantId) { VppUnProperties properties = propertiesRegistry.get(tenantId); if (properties != null && StringUtils.hasText(properties.getToken())) { int ttlMinutes = properties.getTokenTtlMinutes() != null ? properties.getTokenTtlMinutes() : 25; return new TokenState(properties.getToken(), Instant.now().plusSeconds(ttlMinutes * 60L)); } return new TokenState(null, Instant.EPOCH); } private void refreshTokenInternal(Integer tenantId) { VppUnProperties properties = propertiesRegistry.require(tenantId); Map request = VppUnMessageBuilder.buildTokenRequest(properties); String responseBody = httpExecutor.postRaw("TokenRequest", request, false); try { Map response = objectMapper.readValue(responseBody, new TypeReference>() { }); Integer code = VppUnPayloadHelper.getInteger(response, "code"); if (code != null && code != 200) { throw new IllegalStateException("TokenRequest 失败: " + response.get("description")); } applyTokenResponse(response); log.info("租户 {} UN Token 已刷新", tenantId); } catch (IllegalStateException ex) { throw ex; } catch (Exception ex) { throw new IllegalStateException("TokenResponse 解析失败", ex); } } private static final class TokenState { private final String token; private final Instant expiresAt; private TokenState(String token, Instant expiresAt) { this.token = token; this.expiresAt = expiresAt; } private boolean needsRefresh() { return !StringUtils.hasText(token) || Instant.now().isAfter(expiresAt); } } }