VppUnTokenHolder.java 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. package com.usky.vpp.client;
  2. import com.fasterxml.jackson.core.type.TypeReference;
  3. import com.fasterxml.jackson.databind.ObjectMapper;
  4. import com.usky.vpp.config.VppUnProperties;
  5. import com.usky.vpp.config.VppUnPropertiesRegistry;
  6. import com.usky.vpp.config.VppUnTenantContext;
  7. import com.usky.vpp.util.VppUnMessageBuilder;
  8. import com.usky.vpp.util.VppUnPayloadHelper;
  9. import org.slf4j.Logger;
  10. import org.slf4j.LoggerFactory;
  11. import org.springframework.beans.factory.annotation.Autowired;
  12. import org.springframework.stereotype.Component;
  13. import org.springframework.util.StringUtils;
  14. import java.time.Instant;
  15. import java.util.Map;
  16. import java.util.concurrent.ConcurrentHashMap;
  17. import java.util.concurrent.locks.ReentrantLock;
  18. /**
  19. * UN Token 缓存(按租户维度,TokenRequest 获取,默认约 30 分钟过期)
  20. */
  21. @Component
  22. public class VppUnTokenHolder {
  23. private static final Logger log = LoggerFactory.getLogger(VppUnTokenHolder.class);
  24. private final ConcurrentHashMap<Integer, TokenState> tokenStates = new ConcurrentHashMap<>();
  25. private final ConcurrentHashMap<Integer, ReentrantLock> locks = new ConcurrentHashMap<>();
  26. @Autowired
  27. private VppUnPropertiesRegistry propertiesRegistry;
  28. @Autowired
  29. private VppUnHttpExecutor httpExecutor;
  30. @Autowired
  31. private ObjectMapper objectMapper;
  32. public String getToken() {
  33. Integer tenantId = VppUnTenantContext.resolveTenantId();
  34. TokenState state = tokenStates.computeIfAbsent(tenantId, id -> createInitialState(id));
  35. if (!state.needsRefresh()) {
  36. return state.token;
  37. }
  38. ReentrantLock lock = locks.computeIfAbsent(tenantId, id -> new ReentrantLock());
  39. lock.lock();
  40. try {
  41. state = tokenStates.computeIfAbsent(tenantId, id -> createInitialState(id));
  42. if (!state.needsRefresh()) {
  43. return state.token;
  44. }
  45. refreshTokenInternal(tenantId);
  46. return state.token;
  47. } finally {
  48. lock.unlock();
  49. }
  50. }
  51. public void invalidate() {
  52. Integer tenantId = VppUnTenantContext.resolveTenantId();
  53. ReentrantLock lock = locks.computeIfAbsent(tenantId, id -> new ReentrantLock());
  54. lock.lock();
  55. try {
  56. tokenStates.remove(tenantId);
  57. } finally {
  58. lock.unlock();
  59. }
  60. }
  61. public void applyTokenResponse(Map<String, Object> response) {
  62. Integer tenantId = VppUnTenantContext.resolveTenantId();
  63. String newToken = VppUnPayloadHelper.getString(response, "token");
  64. if (!StringUtils.hasText(newToken)) {
  65. throw new IllegalStateException("TokenResponse 缺少 token");
  66. }
  67. VppUnProperties properties = propertiesRegistry.require(tenantId);
  68. int ttlMinutes = properties.getTokenTtlMinutes() != null ? properties.getTokenTtlMinutes() : 25;
  69. TokenState state = new TokenState(newToken, Instant.now().plusSeconds(ttlMinutes * 60L));
  70. tokenStates.put(tenantId, state);
  71. propertiesRegistry.updateToken(tenantId, newToken);
  72. }
  73. private TokenState createInitialState(Integer tenantId) {
  74. VppUnProperties properties = propertiesRegistry.get(tenantId);
  75. if (properties != null && StringUtils.hasText(properties.getToken())) {
  76. int ttlMinutes = properties.getTokenTtlMinutes() != null ? properties.getTokenTtlMinutes() : 25;
  77. return new TokenState(properties.getToken(), Instant.now().plusSeconds(ttlMinutes * 60L));
  78. }
  79. return new TokenState(null, Instant.EPOCH);
  80. }
  81. private void refreshTokenInternal(Integer tenantId) {
  82. VppUnProperties properties = propertiesRegistry.require(tenantId);
  83. Map<String, Object> request = VppUnMessageBuilder.buildTokenRequest(properties);
  84. String responseBody = httpExecutor.postRaw("TokenRequest", request, false);
  85. try {
  86. Map<String, Object> response = objectMapper.readValue(responseBody, new TypeReference<Map<String, Object>>() {
  87. });
  88. Integer code = VppUnPayloadHelper.getInteger(response, "code");
  89. if (code != null && code != 200) {
  90. throw new IllegalStateException("TokenRequest 失败: " + response.get("description"));
  91. }
  92. applyTokenResponse(response);
  93. log.info("租户 {} UN Token 已刷新", tenantId);
  94. } catch (IllegalStateException ex) {
  95. throw ex;
  96. } catch (Exception ex) {
  97. throw new IllegalStateException("TokenResponse 解析失败", ex);
  98. }
  99. }
  100. private static final class TokenState {
  101. private final String token;
  102. private final Instant expiresAt;
  103. private TokenState(String token, Instant expiresAt) {
  104. this.token = token;
  105. this.expiresAt = expiresAt;
  106. }
  107. private boolean needsRefresh() {
  108. return !StringUtils.hasText(token) || Instant.now().isAfter(expiresAt);
  109. }
  110. }
  111. }