微信開發中,errcode: 40097, errmsg: "invalid args" 是高頻報錯,官方文檔說明極少。本文記錄一次真實排查過程:定時任務首次獲取 jsapi_ticket 成功,兩小時後周期性失效,根因是 URL 字符串拼接導致 access_token 重複參數。
access_token/jsapi_ticket 過期週期),功能失效wx.config({debug: true}) 報錯:invalid signaturejsapi_ticket 過期!@Override
public void run() {
while (true) {
try {
// 1. 獲取 access_token
HttpsURLConnection conn = HttpUtil.initHttpsConnection(accessTokenUrl, "GET");
String result = HttpUtil.getHttpsContent(conn, "utf-8");
JSONObject json = JSON.parseObject(result);
if (json != null) {
accessToken = new AccessToken(json.getString("access_token"), json.getLong("expires_in"));
// 2. 獲取 jsapi_ticket —— 關鍵錯誤點
jsapiTicketUrl += accessToken.getAccess_token() + "&type=jsapi"; // ❌ 直接修改類屬性!
conn = HttpUtil.initHttpsConnection(jsapiTicketUrl, "GET");
result = HttpUtil.getHttpsContent(conn, "utf-8");
// ... 解析 ticket
}
} catch (IOException e) { e.printStackTrace(); }
// 休眠邏輯
Thread.sleep(accessToken != null ? (accessToken.getExpire_in() - 200) * 1000 : 60_000);
}
}
jsapiTicketUrl = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token="
// 拼接後 → "https://...?access_token=TOKEN1&type=jsapi" ✅ 正確
// jsapiTicketUrl 已變為上次拼接後的完整 URL
jsapiTicketUrl += accessToken.getAccess_token() + "&type=jsapi";
// 變成 → "https://...?access_token=TOKEN1&type=jsapiTOKEN2&type=jsapi" ❌ 參數重複!
GET /cgi-bin/ticket/getticket?
access_token=TOKEN1&type=jsapiTOKEN2&type=jsapi
// 解析出兩個 access_token 參數 → 40097 invalid args
String ticketUrl = jsapiTicketUrl + accessToken.getAccess_token() + "&type=jsapi";
conn = HttpUtil.initHttpsConnection(ticketUrl, "GET");
@Component
public class WxTokenRefresher implements Runnable {
private static final String TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s";
private static final String TICKET_URL = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=%s&type=jsapi";
private volatile String accessToken;
private volatile String jsapiTicket;
private volatile long tokenExpireTime;
private volatile long ticketExpireTime;
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
refreshIfNeeded();
} catch (Exception e) {
log.error("微信憑證刷新異常", e);
}
sleepUntilNextRefresh();
}
}
private void refreshIfNeeded() {
long now = System.currentTimeMillis();
// 提前 3 分鐘刷新
if (now >= tokenExpireTime - 3 * 60 * 1000) {
String url = String.format(TOKEN_URL, appId, appSecret);
JSONObject resp = HttpUtil.getJson(url);
if ("ok".equals(resp.getString("errmsg"))) {
accessToken = resp.getString("access_token");
tokenExpireTime = now + resp.getLong("expires_in") * 1000;
}
}
if (now >= ticketExpireTime - 3 * 60 * 1000 && accessToken != null) {
String url = String.format(TICKET_URL, accessToken); // ✅ 每次新構建
JSONObject resp = HttpUtil.getJson(url);
if ("ok".equals(resp.getString("errmsg"))) {
jsapiTicket = resp.getString("ticket");
ticketExpireTime = now + resp.getLong("expires_in") * 1000;
}
}
}
}
String.format、UriComponentsBuilder、局部變量volatile 保證可見性Redis 集中存儲 access_token/jsapi_ticket,多實例共享Redisson 分布式鎖,防止並發重複刷新一個 += 操作導致的 URL 參數重複,觸發微信 40097 錯誤,癱瘓 JSAPI 功能兩小時。這警示我們:可變狀態是並發與定時任務的大敵,字符串拼接必須無副作用,關鍵憑證管理應下沉到基礎設施層統一治理。