|
@@ -0,0 +1,196 @@
|
|
|
+package com.fs.erp.service.impl;
|
|
|
+
|
|
|
+import cn.hutool.core.bean.BeanUtil;
|
|
|
+import com.alibaba.fastjson.JSON;
|
|
|
+import com.alibaba.fastjson.TypeReference;
|
|
|
+import com.fs.erp.dto.GetInitTokenRequestDTO;
|
|
|
+import com.fs.erp.dto.GetInitTokenResponseDTO;
|
|
|
+import com.fs.erp.dto.RefreshTokenRequestDTO;
|
|
|
+import com.fs.erp.http.JstErpHttpService;
|
|
|
+import com.fs.erp.utils.SignUtil;
|
|
|
+import org.apache.http.protocol.HttpService;
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.beans.factory.annotation.Value;
|
|
|
+import org.springframework.data.redis.core.StringRedisTemplate;
|
|
|
+import org.springframework.scheduling.annotation.Scheduled;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+
|
|
|
+import javax.annotation.PostConstruct;
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
+
|
|
|
+@Service
|
|
|
+public class JstTokenService {
|
|
|
+ private static final Logger log = LoggerFactory.getLogger(JstTokenService.class);
|
|
|
+
|
|
|
+ private static final String JST_TOKEN_KEY = "jst:access_token";
|
|
|
+ private static final String JST_REFRESH_TOKEN_KEY = "jst:refresh_token";
|
|
|
+ private static final String JST_TOKEN_EXPIRE_KEY = "jst:token_expire";
|
|
|
+
|
|
|
+ @Value("${jst.app_key}")
|
|
|
+ private String appKey;
|
|
|
+
|
|
|
+ @Value("${jst.app_secret}")
|
|
|
+ private String appSecret;
|
|
|
+
|
|
|
+ @Value("${jst.authorization_code}")
|
|
|
+ private String authorizationCode;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private JstErpHttpService httpService;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private StringRedisTemplate redisTemplate;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取访问令牌,如果缓存中存在有效令牌则直接返回,否则重新获取
|
|
|
+ * @return 有效的访问令牌
|
|
|
+ */
|
|
|
+ public String getAccessToken() {
|
|
|
+ String accessToken = redisTemplate.opsForValue().get(JST_TOKEN_KEY);
|
|
|
+
|
|
|
+ if (accessToken != null && !accessToken.isEmpty()) {
|
|
|
+ log.debug("从缓存中获取到有效的access_token");
|
|
|
+ return accessToken;
|
|
|
+ }
|
|
|
+
|
|
|
+ log.info("缓存中没有有效的access_token,重新获取");
|
|
|
+ return refreshAccessToken();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 强制刷新访问令牌
|
|
|
+ * @return 新的访问令牌
|
|
|
+ */
|
|
|
+ public String refreshAccessToken() {
|
|
|
+ // 检查是否有刷新令牌
|
|
|
+ String refreshToken = redisTemplate.opsForValue().get(JST_REFRESH_TOKEN_KEY);
|
|
|
+
|
|
|
+ if (refreshToken != null && !refreshToken.isEmpty()) {
|
|
|
+ log.info("使用refresh_token刷新令牌");
|
|
|
+ try {
|
|
|
+ return refreshTokenWithApi(refreshToken);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("使用refresh_token刷新失败,尝试重新初始化", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果没有刷新令牌或刷新失败,则重新初始化
|
|
|
+ return initToken();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 初始化令牌
|
|
|
+ * @return 新的访问令牌
|
|
|
+ */
|
|
|
+ private String initToken() {
|
|
|
+ GetInitTokenRequestDTO requestDTO = new GetInitTokenRequestDTO();
|
|
|
+ requestDTO.setApp_key(appKey);
|
|
|
+ requestDTO.setGrant_type("authorization_code");
|
|
|
+ requestDTO.setCharset("utf-8");
|
|
|
+ requestDTO.setCode(authorizationCode);
|
|
|
+ requestDTO.setTimestamp(String.valueOf(System.currentTimeMillis() / 1000));
|
|
|
+
|
|
|
+ // 计算签名
|
|
|
+ requestDTO.setSign(SignUtil.getSignNew(appSecret, requestDTO));
|
|
|
+
|
|
|
+ log.info("初始化token请求参数: {}", JSON.toJSONString(requestDTO));
|
|
|
+
|
|
|
+ GetInitTokenResponseDTO response = httpService.getInitToken(requestDTO);
|
|
|
+ if (response == null || response.getAccess_token() == null) {
|
|
|
+ throw new RuntimeException("初始化token失败: " + (response != null ? JSON.toJSONString(response) : "响应为空"));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 缓存token信息
|
|
|
+ storeTokenInfo(response);
|
|
|
+
|
|
|
+ log.info("初始化token成功,token将在{}秒后过期", response.getExpires_in());
|
|
|
+ return response.getAccess_token();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 使用刷新令牌API刷新令牌
|
|
|
+ * @param refreshToken 刷新令牌
|
|
|
+ * @return 新的访问令牌
|
|
|
+ */
|
|
|
+ private String refreshTokenWithApi(String refreshToken) {
|
|
|
+ RefreshTokenRequestDTO requestDTO = new RefreshTokenRequestDTO();
|
|
|
+ requestDTO.setApp_key(appKey);
|
|
|
+ requestDTO.setGrant_type("refresh_token");
|
|
|
+ requestDTO.setCharset("utf-8");
|
|
|
+ requestDTO.setRefresh_token(refreshToken);
|
|
|
+ requestDTO.setTimestamp(String.valueOf(System.currentTimeMillis() / 1000));
|
|
|
+
|
|
|
+ // 计算签名
|
|
|
+ requestDTO.setSign(SignUtil.getSignNew(appSecret, requestDTO));
|
|
|
+
|
|
|
+ log.info("刷新token请求参数: {}", JSON.toJSONString(requestDTO));
|
|
|
+
|
|
|
+ GetInitTokenResponseDTO response = httpService.refreshToken(requestDTO);
|
|
|
+ if (response == null || response.getAccess_token() == null) {
|
|
|
+ throw new RuntimeException("刷新token失败: " + (response != null ? JSON.toJSONString(response) : "响应为空"));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 缓存token信息
|
|
|
+ storeTokenInfo(response);
|
|
|
+
|
|
|
+ log.info("刷新token成功,token将在{}秒后过期", response.getExpires_in());
|
|
|
+ return response.getAccess_token();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 将令牌信息存储到Redis
|
|
|
+ * @param tokenInfo 令牌信息
|
|
|
+ */
|
|
|
+ private void storeTokenInfo(GetInitTokenResponseDTO tokenInfo) {
|
|
|
+ // 默认30天过期时间,转换为秒
|
|
|
+ long expiresIn = tokenInfo.getExpires_in() != null ?
|
|
|
+ Long.parseLong(String.valueOf(tokenInfo.getExpires_in())) : 30 * 24 * 60 * 60;
|
|
|
+
|
|
|
+ // 存储令牌,设置比官方少一天的过期时间,以确保安全
|
|
|
+ redisTemplate.opsForValue().set(JST_TOKEN_KEY, tokenInfo.getAccess_token(), expiresIn - 86400, TimeUnit.SECONDS);
|
|
|
+
|
|
|
+ // 存储刷新令牌,有效期与访问令牌相同
|
|
|
+ if (tokenInfo.getRefresh_token() != null) {
|
|
|
+ redisTemplate.opsForValue().set(JST_REFRESH_TOKEN_KEY, tokenInfo.getRefresh_token(), expiresIn, TimeUnit.SECONDS);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 存储过期时间戳
|
|
|
+ long expireAt = System.currentTimeMillis() + (expiresIn * 1000);
|
|
|
+ redisTemplate.opsForValue().set(JST_TOKEN_EXPIRE_KEY, String.valueOf(expireAt), expiresIn, TimeUnit.SECONDS);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 定时任务,每20天刷新一次令牌
|
|
|
+ * 根据文档说明,刷新token必须在有效期小于15天时进行,所以这里设置20天刷新一次
|
|
|
+ */
|
|
|
+ @Scheduled(fixedRate = 20 * 24 * 60 * 60 * 1000)
|
|
|
+ public void scheduleTokenRefresh() {
|
|
|
+ log.info("定时任务:刷新聚水潭API令牌");
|
|
|
+ try {
|
|
|
+ refreshAccessToken();
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("定时刷新令牌失败", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 启动时初始化令牌
|
|
|
+ */
|
|
|
+ @PostConstruct
|
|
|
+ public void init() {
|
|
|
+ try {
|
|
|
+ // 检查Redis中是否已有有效token
|
|
|
+ String accessToken = redisTemplate.opsForValue().get(JST_TOKEN_KEY);
|
|
|
+ if (accessToken == null || accessToken.isEmpty()) {
|
|
|
+ log.info("系统启动,初始化聚水潭API令牌");
|
|
|
+ initToken();
|
|
|
+ } else {
|
|
|
+ log.info("系统启动,发现缓存中已有有效的聚水潭API令牌");
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("初始化聚水潭API令牌失败", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|