|
@@ -65,9 +65,9 @@ public class WechatService implements WechatServiceInterface {
|
|
|
|
|
|
|
|
private final RestTemplate restTemplate = new RestTemplate();
|
|
private final RestTemplate restTemplate = new RestTemplate();
|
|
|
|
|
|
|
|
- // ===== access_token 缓存 =====
|
|
|
|
|
- private volatile String cachedAccessToken;
|
|
|
|
|
- private volatile long tokenExpireTime;
|
|
|
|
|
|
|
+ // ===== stable access_token 缓存(独立缓存,用于避免"not latest"冲突)=====
|
|
|
|
|
+ private volatile String cachedStableAccessToken;
|
|
|
|
|
+ private volatile long stableTokenExpireTime;
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 判断是否测试模式
|
|
* 判断是否测试模式
|
|
@@ -213,6 +213,7 @@ public class WechatService implements WechatServiceInterface {
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 获取access_token(带缓存,TTL 7000秒,提前200秒刷新)
|
|
* 获取access_token(带缓存,TTL 7000秒,提前200秒刷新)
|
|
|
|
|
+ * 默认使用stable_token API,避免多实例/多调用导致的"not latest"冲突
|
|
|
*/
|
|
*/
|
|
|
public String getAccessToken() {
|
|
public String getAccessToken() {
|
|
|
// 测试模式:每次都重新获取
|
|
// 测试模式:每次都重新获取
|
|
@@ -220,23 +221,61 @@ public class WechatService implements WechatServiceInterface {
|
|
|
return doGetAccessToken();
|
|
return doGetAccessToken();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ return getStableAccessToken();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取stable access_token(带缓存)
|
|
|
|
|
+ * 使用 /cgi-bin/stable_token 接口,token有效期7200秒,可被所有实例共享使用
|
|
|
|
|
+ * 彻底解决"access_token is invalid or not latest"错误
|
|
|
|
|
+ */
|
|
|
|
|
+ private String getStableAccessToken() {
|
|
|
long now = System.currentTimeMillis();
|
|
long now = System.currentTimeMillis();
|
|
|
- if (cachedAccessToken != null && now < tokenExpireTime) {
|
|
|
|
|
- return cachedAccessToken;
|
|
|
|
|
|
|
+ if (cachedStableAccessToken != null && now < stableTokenExpireTime) {
|
|
|
|
|
+ return cachedStableAccessToken;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
synchronized (this) {
|
|
synchronized (this) {
|
|
|
- if (cachedAccessToken != null && now < tokenExpireTime) {
|
|
|
|
|
- return cachedAccessToken;
|
|
|
|
|
|
|
+ if (cachedStableAccessToken != null && now < stableTokenExpireTime) {
|
|
|
|
|
+ return cachedStableAccessToken;
|
|
|
}
|
|
}
|
|
|
- String token = doGetAccessToken();
|
|
|
|
|
- // 微信access_token有效期7200秒,提前200秒过期缓存
|
|
|
|
|
- cachedAccessToken = token;
|
|
|
|
|
- tokenExpireTime = now + 7000 * 1000;
|
|
|
|
|
|
|
+ String token = doGetStableAccessToken();
|
|
|
|
|
+ cachedStableAccessToken = token;
|
|
|
|
|
+ stableTokenExpireTime = now + 7000 * 1000;
|
|
|
return token;
|
|
return token;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ private String doGetStableAccessToken() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ String url = "https://api.weixin.qq.com/cgi-bin/stable_token";
|
|
|
|
|
+
|
|
|
|
|
+ JSONObject requestBody = new JSONObject();
|
|
|
|
|
+ requestBody.put("grant_type", "client_credential");
|
|
|
|
|
+ requestBody.put("appid", appid);
|
|
|
|
|
+ requestBody.put("secret", secret);
|
|
|
|
|
+
|
|
|
|
|
+ org.springframework.http.HttpHeaders headers = new org.springframework.http.HttpHeaders();
|
|
|
|
|
+ headers.setContentType(org.springframework.http.MediaType.APPLICATION_JSON);
|
|
|
|
|
+ org.springframework.http.HttpEntity<String> entity = new org.springframework.http.HttpEntity<>(
|
|
|
|
|
+ requestBody.toJSONString(), headers);
|
|
|
|
|
+
|
|
|
|
|
+ ResponseEntity<String> response = restTemplate.exchange(url,
|
|
|
|
|
+ org.springframework.http.HttpMethod.POST, entity, String.class);
|
|
|
|
|
+ JSONObject json = JSON.parseObject(response.getBody());
|
|
|
|
|
+
|
|
|
|
|
+ if (json.containsKey("access_token")) {
|
|
|
|
|
+ return json.getString("access_token");
|
|
|
|
|
+ } else {
|
|
|
|
|
+ log.error("获取stable access_token失败: {}", json.toJSONString());
|
|
|
|
|
+ throw new RuntimeException("获取access_token失败");
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("获取stable access_token异常", e);
|
|
|
|
|
+ throw new RuntimeException("获取access_token异常");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
private String doGetAccessToken() {
|
|
private String doGetAccessToken() {
|
|
|
try {
|
|
try {
|
|
|
String url = String.format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s",
|
|
String url = String.format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s",
|