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

Java 微信開發 API 實戰(三):access_token 獲取、緩存與自動刷新機制

     編輯:Java 相關知識

access_token 核心作用

  • 公眾號**全局唯一憑證**,調用所有高級介面 (自定義菜單、素材管理、用戶管理等) 必須攜帶
  • 有效期 **7200 秒 (2 小時)**,過期失效
  • 每日調用上限 **2000 次**,頻繁獲取會導致舊 token 失效
  • 存儲空間需 ≥ 512 字符

獲取 access_token (HTTP GET)

https://api.weixin.qq.com/cgi-bin/token?
    grant_type=client_credential
    &appid=APPID
    &secret=APPSECRET

成功響應

{"access_token":"ACCESS_TOKEN","expires_in":7200}

失敗響應

{"errcode":40013,"errmsg":"invalid appid"}

Java 實現:獲取並解析

public class WeixinApiTest {
    private static final String APPID = "wx889b****b3666b0b8";
    private static final String SECRET = "6da7676***f0a9f15fbf06027856bb";
    private static final String TOKEN_URL = 
        "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s";
    
    public String getAccessToken() throws IOException {
        String url = String.format(TOKEN_URL, APPID, SECRET);
        HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
        conn.setRequestMethod("GET");
        
        try (InputStream is = conn.getInputStream();
             BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"))) {
            
            StringBuilder sb = new StringBuilder();
            String line;
            while ((line = br.readLine()) != null) sb.append(line);
            
            // Gson 解析
            JsonObject obj = JsonParser.parseString(sb.toString()).getAsJsonObject();
            if (obj.has("access_token")) {
                return obj.get("access_token").getAsString();
            } else {
                throw new RuntimeException("獲取失敗: " + obj.get("errmsg").getAsString());
            }
        }
    }
}

本地緩存策略:文件存儲 + 時間戳驗證

核心邏輯

  1. 啟動時讀取本地文件 access_token.cache
  2. 解析 JSON 獲取 token 與 timestamp
  3. 若 當前時間 - timestamp < 7100 秒 (預留 100 秒緩衝),直接返回緩存 token
  4. 否則調用微信介面獲取新 token,覆蓋寫入文件

完整實現

public class AccessTokenManager {
    private static final long EXPIRE_MS = 7100 * 1000L; // 緩衝 100 秒
    private static final File CACHE_FILE = new File("access_token.cache");
    private static final Gson GSON = new Gson();
    private static final String APPID = "...";
    private static final String SECRET = "...";
    
    private static final Lock LOCK = new ReentrantLock();
    
    public static String getAccessToken() {
        // 1. 嘗試讀取緩存
        String cached = readCache();
        if (cached != null && !isExpired(cached)) {
            return cached;
        }
        
        // 2. 雙重檢查鎖獲取新 token
        LOCK.lock();
        try {
            // 再次檢查 (其他線程可能已更新)
            cached = readCache();
            if (cached != null && !isExpired(cached)) return cached;
            
            // 遠程獲取
            String newToken = fetchFromWeixin();
            writeCache(newToken);
            return newToken;
        } finally {
            LOCK.unlock();
        }
    }
    
    private static String readCache() {
        if (!CACHE_FILE.exists() || CACHE_FILE.length() == 0) return null;
        try (FileReader fr = new FileReader(CACHE_FILE)) {
            TokenCache cache = GSON.fromJson(fr, TokenCache.class);
            return cache.token;
        } catch (Exception e) { return null; }
    }
    
    private static boolean isExpired(String token) {
        try (FileReader fr = new FileReader(CACHE_FILE)) {
            TokenCache cache = GSON.fromJson(fr, TokenCache.class);
            return System.currentTimeMillis() - cache.timestamp > EXPIRE_MS;
        } catch (Exception e) { return true; }
    }
    
    private static String fetchFromWeixin() {
        // 呼叫上述 HTTP GET 邏輯
        return new WeixinApiTest().getAccessToken();
    }
    
    private static void writeCache(String token) {
        TokenCache cache = new TokenCache(token, System.currentTimeMillis());
        try (FileWriter fw = new FileWriter(CACHE_FILE)) {
            GSON.toJson(cache, fw);
        } catch (IOException e) { /* 記錄日誌 */ }
    }
    
    static class TokenCache {
        String token;
        long timestamp;
        TokenCache(String token, long timestamp) {
            this.token = token; this.timestamp = timestamp;
        }
    }
}

生產級升級:中控服務器架構

微信官方建議:集群/多實例部署時,由**專門的中控服務器**統一獲取、緩存、分發 access_token,業務服務器從中控獲取。

架構優勢

  • 避免多實例並發衝突導致 token 互踢
  • 集中監控 token 狀態、異常告警
  • 支援 Redis/資料庫替換文件存儲,適應分佈式部署
  • 可增加定時刷新任務 (如每 1.5 小時主動刷新)

Spring Boot 整合示例 (Redis)

@Component
public class WeixinTokenService {
    @Autowired private StringRedisTemplate redis;
    @Value("${weixin.appid}") String appid;
    @Value("${weixin.secret}") String secret;
    
    private static final String REDIS_KEY = "weixin:access_token";
    private static final long TTL_SECONDS = 7100;
    
    public String getAccessToken() {
        String token = redis.opsForValue().get(REDIS_KEY);
        if (StringUtils.hasText(token)) return token;
        
        return refreshToken();
    }
    
    private String refreshToken() {
        String url = String.format(TOKEN_URL, appid, secret);
        String result = restTemplate.getForObject(url, String.class);
        JsonObject obj = JsonParser.parseString(result).getAsJsonObject();
        
        if (obj.has("access_token")) {
            String token = obj.get("access_token").getAsString();
            redis.opsForValue().set(REDIS_KEY, token, TTL_SECONDS, TimeUnit.SECONDS);
            return token;
        }
        throw new RuntimeException("Token 刷新失敗: " + obj.get("errmsg"));
    }
    
    @Scheduled(fixedRate = 7100 * 1000) // 定時預熱
    public void scheduledRefresh() {
        refreshToken();
    }
}

測試驗證

@Test
public void testTokenLifecycle() {
    // 1. 首次獲取
    String token1 = AccessTokenManager.getAccessToken();
    assertNotNull(token1);
    
    // 2. 緩存命中
    String token2 = AccessTokenManager.getAccessToken();
    assertEquals(token1, token2);
    
    // 3. 模擬過期 (修改文件時間戳或等待)
    // 4. 自動刷新
    String token3 = AccessTokenManager.getAccessToken();
    assertNotEquals(token1, token3); // 應獲取新 token
}

注意事項清單

  • ✅ AppID/Secret 機密性:配置文件加密、環境變量注入、不提交代碼倉庫
  • ✅ 異常重試:網絡抖動時指數退避重試 3 次
  • ✅ 監控告警:Token 獲取失敗、緩存未命中率、API 配額使用率
  • ✅ 多環境隔離:開發/測試/生產使用不同公眾號,配置分離
  • ✅ 兼容性:access_token 可能擴展字段,解析時忽略未知字段
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved