|
|
@@ -0,0 +1,372 @@
|
|
|
+package com.jzg.quotation.summary.utils;
|
|
|
+
|
|
|
+import com.alibaba.fastjson2.JSON;
|
|
|
+import com.alibaba.fastjson2.JSONArray;
|
|
|
+import com.alibaba.fastjson2.JSONObject;
|
|
|
+import com.jzg.commons.exception.SystemException;
|
|
|
+import lombok.Data;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.redisson.api.RBucket;
|
|
|
+import org.redisson.api.RedissonClient;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.beans.factory.annotation.Value;
|
|
|
+import org.springframework.http.*;
|
|
|
+import org.springframework.stereotype.Component;
|
|
|
+import org.springframework.web.client.RestTemplate;
|
|
|
+
|
|
|
+import java.time.Duration;
|
|
|
+import java.util.List;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 本地生活权益认证工具类
|
|
|
+ * 用于请求获取 token 并自动缓存到 Redis,支持过期自动刷新
|
|
|
+ *
|
|
|
+ * @author wh
|
|
|
+ * @date 2025-03-16
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+@Component
|
|
|
+public class LacalLiveTokenUtil {
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private RedissonClient redissonClient;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private RestTemplate restTemplate;
|
|
|
+
|
|
|
+ @Value("${localLive.base-url}")
|
|
|
+ private String baseUrl;
|
|
|
+
|
|
|
+ @Value("${localLive.app-key}")
|
|
|
+ private String appKey;
|
|
|
+
|
|
|
+ @Value("${localLive.app-secret}")
|
|
|
+ private String appSecret;
|
|
|
+
|
|
|
+ private static final String TOKEN_PREFIX = "localLive:token:";
|
|
|
+ /**
|
|
|
+ * 获取 token 的 URL
|
|
|
+ */
|
|
|
+ private static final String TOKEN_URL = "/channel/api/rightsGrant/login";
|
|
|
+ /**
|
|
|
+ * 权益发放 URL
|
|
|
+ */
|
|
|
+ private static final String GRANT_URL = "/channel/api/rightsGrant/apiGrant";
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Token 响应实体类
|
|
|
+ */
|
|
|
+ @Data
|
|
|
+ public static class TokenResponse {
|
|
|
+ private Integer code;
|
|
|
+ private String message;
|
|
|
+ private ResultDTO result;
|
|
|
+ private Boolean success;
|
|
|
+ private Long timestamp;
|
|
|
+
|
|
|
+ @Data
|
|
|
+ public static class ResultDTO {
|
|
|
+ private String expireTimeAt;
|
|
|
+ private String token;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Token 请求参数
|
|
|
+ */
|
|
|
+ @Data
|
|
|
+ public static class TokenRequest {
|
|
|
+ private String appKey;
|
|
|
+ private String appSecret;
|
|
|
+
|
|
|
+ public TokenRequest(String appKey, String appSecret) {
|
|
|
+ this.appKey = appKey;
|
|
|
+ this.appSecret = appSecret;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 权益发放请求参数
|
|
|
+ */
|
|
|
+ @Data
|
|
|
+ public static class RightsGrantRequest {
|
|
|
+ private ReceiverDTO receiverDTO;
|
|
|
+ private String remark;
|
|
|
+ private List<RightsItemDTO> rightsList;
|
|
|
+
|
|
|
+ @Data
|
|
|
+ public static class ReceiverDTO {
|
|
|
+ private String phone;
|
|
|
+ private String plateNo;
|
|
|
+ private String policyNo;
|
|
|
+ private Integer receiverAmountLimit;
|
|
|
+ private String receiverName;
|
|
|
+ private String remark;
|
|
|
+ private String vin;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Data
|
|
|
+ public static class RightsItemDTO {
|
|
|
+ private String rightsContentId;
|
|
|
+ private Integer rightsCount;
|
|
|
+ private Integer rightsType;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 权益发放响应实体类
|
|
|
+ */
|
|
|
+ @Data
|
|
|
+ public static class RightsGrantResponse {
|
|
|
+ private Integer code;
|
|
|
+ private String message;
|
|
|
+ private String result;
|
|
|
+ private Boolean success;
|
|
|
+ private Long timestamp;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取 Token(带自动缓存和刷新)
|
|
|
+ *
|
|
|
+ * @return token
|
|
|
+ */
|
|
|
+ public String getToken() {
|
|
|
+ return getToken(baseUrl , appKey, appSecret, TOKEN_PREFIX);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取 Token(带自动缓存和刷新,自定义参数)
|
|
|
+ *
|
|
|
+ * @param requestUrl 请求 URL
|
|
|
+ * @param appKey appKey
|
|
|
+ * @param appSecret appSecret
|
|
|
+ * @param redisKeyPrefix Redis key 前缀
|
|
|
+ * @return token
|
|
|
+ */
|
|
|
+ public String getToken(String requestUrl, String appKey, String appSecret, String redisKeyPrefix) {
|
|
|
+ String redisKey = redisKeyPrefix + appKey;
|
|
|
+
|
|
|
+ // 尝试从 Redis 获取 token
|
|
|
+ RBucket<String> bucket = redissonClient.getBucket(redisKey);
|
|
|
+ String cachedToken = bucket.get();
|
|
|
+
|
|
|
+ if (cachedToken != null && !cachedToken.isEmpty()) {
|
|
|
+ log.info("从 Redis 缓存中获取 token: {}", redisKey);
|
|
|
+ return cachedToken;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Redis 中没有,调用 HTTP 接口获取
|
|
|
+ log.info("Redis 缓存未命中,调用 HTTP 接口获取 token: {}", redisKey);
|
|
|
+ TokenResponse response = requestToken(requestUrl+TOKEN_URL, appKey, appSecret);
|
|
|
+
|
|
|
+ // 验证响应
|
|
|
+ if (response == null || !Boolean.TRUE.equals(response.getSuccess())
|
|
|
+ || response.getCode() != 200 || response.getResult() == null) {
|
|
|
+ String errorMsg = response != null ? response.getMessage() : "HTTP 请求失败";
|
|
|
+ log.error("获取 token 失败:{}", errorMsg);
|
|
|
+ throw new SystemException("获取 token 失败:" + errorMsg);
|
|
|
+ }
|
|
|
+
|
|
|
+ String token = response.getResult().getToken();
|
|
|
+ String expireTimeAt = response.getResult().getExpireTimeAt();
|
|
|
+
|
|
|
+ if (token == null || token.isEmpty()) {
|
|
|
+ log.error("获取 token 失败:返回的 token 为空");
|
|
|
+ throw new SystemException("获取 token 失败:返回的 token 为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算过期时间并存储到 Redis
|
|
|
+ Duration ttl = calculateTTL(expireTimeAt);
|
|
|
+ bucket.set(token, ttl);
|
|
|
+
|
|
|
+ log.info("成功获取并缓存 token: {}, 过期时间:{}秒", redisKey, ttl.getSeconds());
|
|
|
+
|
|
|
+ return token;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 请求 Token 接口
|
|
|
+ *
|
|
|
+ * @param requestUrl 请求 URL
|
|
|
+ * @param appKey appKey
|
|
|
+ * @param appSecret appSecret
|
|
|
+ * @return TokenResponse
|
|
|
+ */
|
|
|
+ private TokenResponse requestToken(String requestUrl, String appKey, String appSecret) {
|
|
|
+ try {
|
|
|
+ // 构建请求头
|
|
|
+ HttpHeaders headers = new HttpHeaders();
|
|
|
+ headers.setContentType(MediaType.APPLICATION_JSON);
|
|
|
+
|
|
|
+ // 构建请求体
|
|
|
+ TokenRequest request = new TokenRequest(appKey, appSecret);
|
|
|
+ String requestBody = JSON.toJSONString(request);
|
|
|
+
|
|
|
+ HttpEntity<String> entity = new HttpEntity<>(requestBody, headers);
|
|
|
+
|
|
|
+ log.info("请求 token 接口:{}, 请求参数:{}", requestUrl, requestBody);
|
|
|
+
|
|
|
+ // 发送 POST 请求
|
|
|
+ ResponseEntity<String> responseEntity = restTemplate.postForEntity(
|
|
|
+ requestUrl, entity, String.class);
|
|
|
+
|
|
|
+ String responseBody = responseEntity.getBody();
|
|
|
+ log.info("token 接口响应:{}", responseBody);
|
|
|
+
|
|
|
+ // 解析响应
|
|
|
+ return JSON.parseObject(responseBody, TokenResponse.class);
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("请求 token 接口异常:{}", requestUrl, e);
|
|
|
+ throw new SystemException("请求 token 接口异常:" + e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算 TTL(单位:秒)
|
|
|
+ *
|
|
|
+ * @param expireTimeAt 过期时间字符串(格式:yyyy-MM-dd HH:mm:ss 或时间戳)
|
|
|
+ * @return TTL 时长
|
|
|
+ */
|
|
|
+ private Duration calculateTTL(String expireTimeAt) {
|
|
|
+ if (expireTimeAt == null || expireTimeAt.isEmpty()) {
|
|
|
+ // 默认 2 小时,提前 2 小时过期
|
|
|
+ return Duration.ofHours(2);
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ // 尝试解析为时间戳
|
|
|
+ long timestamp = Long.parseLong(expireTimeAt);
|
|
|
+ long now = System.currentTimeMillis();
|
|
|
+ long ttlMillis = timestamp - now;
|
|
|
+
|
|
|
+ if (ttlMillis > 0) {
|
|
|
+ // 提前 2 小时(7200 秒)过期
|
|
|
+ long advanceExpirySeconds = Math.max(0, ttlMillis / 1000 - 7200);
|
|
|
+ return Duration.ofSeconds(advanceExpirySeconds);
|
|
|
+ }
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ // 不是时间戳,尝试解析日期字符串
|
|
|
+ try {
|
|
|
+ java.time.LocalDateTime expireTime = java.time.LocalDateTime.parse(
|
|
|
+ expireTimeAt.replace(" ", "T"),
|
|
|
+ java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME
|
|
|
+ );
|
|
|
+ java.time.LocalDateTime now = java.time.LocalDateTime.now();
|
|
|
+ long ttlSeconds = java.time.Duration.between(now, expireTime).getSeconds();
|
|
|
+
|
|
|
+ if (ttlSeconds > 0) {
|
|
|
+ // 提前 2 小时(7200 秒)过期
|
|
|
+ long advanceExpirySeconds = Math.max(0, ttlSeconds - 7200);
|
|
|
+ return Duration.ofSeconds(advanceExpirySeconds);
|
|
|
+ }
|
|
|
+ } catch (Exception ex) {
|
|
|
+ log.warn("解析过期时间失败,使用默认过期时间:{}", expireTimeAt);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 默认提前 2 小时过期
|
|
|
+ return Duration.ofHours(2);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 手动删除缓存的 token
|
|
|
+ *
|
|
|
+ * @param redisKey Redis key
|
|
|
+ */
|
|
|
+ public void deleteToken(String redisKey) {
|
|
|
+ RBucket<String> bucket = redissonClient.getBucket(redisKey);
|
|
|
+ bucket.delete();
|
|
|
+ log.info("已删除缓存 token: {}", redisKey);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 强制刷新 token
|
|
|
+ */
|
|
|
+ public void refreshToken() {
|
|
|
+ String redisKey = TOKEN_PREFIX + appKey;
|
|
|
+ deleteToken(redisKey);
|
|
|
+ log.info("已清除 token 缓存,下次调用将重新获取");
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 权益发放接口
|
|
|
+ *
|
|
|
+ * @param request 权益发放请求参数
|
|
|
+ * @return 权益发放响应
|
|
|
+ */
|
|
|
+ public RightsGrantResponse apiGrant(RightsGrantRequest request) {
|
|
|
+ // 先获取 token
|
|
|
+ String token = getToken();
|
|
|
+
|
|
|
+ // 构建请求 URL
|
|
|
+ String requestUrl = baseUrl;
|
|
|
+ requestUrl += GRANT_URL;
|
|
|
+
|
|
|
+ log.info("调用权益发放接口:{}, 请求参数:{}", requestUrl, JSON.toJSONString(request));
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 构建请求头
|
|
|
+ HttpHeaders headers = new HttpHeaders();
|
|
|
+ headers.setContentType(MediaType.APPLICATION_JSON);
|
|
|
+ headers.set("Authorization", token);
|
|
|
+
|
|
|
+ // 构建请求体
|
|
|
+ String requestBody = JSON.toJSONString(request);
|
|
|
+ HttpEntity<String> entity = new HttpEntity<>(requestBody, headers);
|
|
|
+
|
|
|
+ // 发送 POST 请求
|
|
|
+ ResponseEntity<String> responseEntity = restTemplate.exchange(
|
|
|
+ requestUrl,
|
|
|
+ HttpMethod.POST,
|
|
|
+ entity,
|
|
|
+ String.class
|
|
|
+ );
|
|
|
+
|
|
|
+ String responseBody = responseEntity.getBody();
|
|
|
+ log.info("权益发放接口响应:{}", responseBody);
|
|
|
+
|
|
|
+ // 解析响应
|
|
|
+ RightsGrantResponse response = JSON.parseObject(responseBody, RightsGrantResponse.class);
|
|
|
+
|
|
|
+ // 验证响应
|
|
|
+ if (response == null || !Boolean.TRUE.equals(response.getSuccess())
|
|
|
+ || response.getCode() != 200) {
|
|
|
+ String errorMsg = response != null ? response.getMessage() : "HTTP 请求失败";
|
|
|
+ log.error("权益发放失败:{}", errorMsg);
|
|
|
+ throw new SystemException("权益发放失败:" + errorMsg);
|
|
|
+ }
|
|
|
+
|
|
|
+ return response;
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("调用权益发放接口异常:{}", requestUrl, e);
|
|
|
+ throw new SystemException("调用权益发放接口异常:" + e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 权益发放接口(简化版,直接传入 JSON 对象)
|
|
|
+ *
|
|
|
+ * @param receiverDTO 接收人信息
|
|
|
+ * @param rightsList 权益列表
|
|
|
+ * @param remark 备注
|
|
|
+ * @return 权益发放响应
|
|
|
+ */
|
|
|
+ public RightsGrantResponse apiGrant(JSONObject receiverDTO, JSONArray rightsList, String remark) {
|
|
|
+ RightsGrantRequest request = new RightsGrantRequest();
|
|
|
+ request.setReceiverDTO(receiverDTO.toJavaObject(RightsGrantRequest.ReceiverDTO.class));
|
|
|
+ request.setRightsList(rightsList.toJavaList(RightsGrantRequest.RightsItemDTO.class));
|
|
|
+ request.setRemark(remark);
|
|
|
+ return apiGrant(request);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+}
|