首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

微信 JS-SDK 錯誤碼 40097 徹底排查:access_token 拼接導致 jsapi_ticket 獲取失敗

     編輯:Java 綜合教學

前言

微信開發中,errcode: 40097, errmsg: "invalid args" 是高頻報錯,官方文檔說明極少。本文記錄一次真實排查過程:定時任務首次獲取 jsapi_ticket 成功,兩小時後周期性失效,根因是 URL 字符串拼接導致 access_token 重複參數。

一、現象描述

  • 應用啟動後,微信 JSAPI(拍照、掃碼等)正常工作
  • 約 2 小時後(access_token/jsapi_ticket 過期週期),功能失效
  • 開啟 wx.config({debug: true}) 報錯:invalid signature
  • 官方調試工具驗證提示:jsapi_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

四、修復方案

核心原則:不修改類屬性 URL,使用局部變量拼接

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;
            }
        }
    }
}

五、核心經驗總結

  1. 首次成功、後續失敗 → 參數構建邏輯有狀態污染:檢查是否修改了共享變量
  2. URL 拼接必須用不可變模式:String.format、UriComponentsBuilder、局部變量
  3. 定時任務需冪等、可重入:網絡異常不影響下次執行,使用 volatile 保證可見性
  4. 提前刷新機制:在過期前 N 分鐘刷新,避免邊界競態
  5. 40097 核心含義:請求參數格式錯誤(重複、缺失、類型不匹配),優先排查 URL/Body 組裝邏輯

六、擴展:微信憑證管理最佳實踐

  • 使用 Redis 集中存儲 access_token/jsapi_ticket,多實例共享
  • 引入 Redisson 分布式鎖,防止並發重複刷新
  • 監控指標:刷新成功率、耗時、剩餘有效期,接入告警
  • 單元測試覆蓋:URL 構建、過期邊界、異常重試

總結

一個 += 操作導致的 URL 參數重複,觸發微信 40097 錯誤,癱瘓 JSAPI 功能兩小時。這警示我們:可變狀態是並發與定時任務的大敵,字符串拼接必須無副作用,關鍵憑證管理應下沉到基礎設施層統一治理。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved