| 參數 | 說明 |
| signature | 微信加密簽名 |
| timestamp | 時間戳 |
| nonce | 隨機數 |
| echostr | 隨機字符串 |
驗證算法
- 將
token、timestamp、nonce 三個參數進行字典序排序。
- 將三個參數字符串拼接成一個字符串進行 SHA1 加密。
- 開發者獲得加密後的字符串與
signature 對比,相等則驗證通過,原樣返回 echostr。
Servlet 實現
@WebServlet("/wechat")
public class WechatServlet extends HttpServlet {
private static final String TOKEN = "your_token_here"; // 與公眾平台一致
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String signature = req.getParameter("signature");
String timestamp = req.getParameter("timestamp");
String nonce = req.getParameter("nonce");
String echostr = req.getParameter("echostr");
// 1. 排序
String[] arr = {TOKEN, timestamp, nonce};
Arrays.sort(arr);
String sortedStr = String.join("", arr);
// 2. SHA1
String sha1 = SHA1Util.encode(sortedStr);
// 3. 驗證
if (sha1.equals(signature)) {
resp.getWriter().print(echostr); // 關鍵:原樣返回
} else {
resp.getWriter().print("驗證失敗");
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
// 處理業務消息(文本、圖片、事件等),單獨篇幅講解
}
}
SHA1 工具類
public class SHA1Util {
public static String encode(String str) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-1");
byte[] digest = md.digest(str.getBytes(StandardCharsets.UTF_8));
StringBuilder hex = new StringBuilder();
for (byte b : digest) {
String h = Integer.toHexString(b & 0xFF);
if (h.length() == 1) hex.append('0');
hex.append(h);
}
return hex.toString();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
}
Access Token 管理機制
核心規則
- 唯一憑證:調用所有業務接口必須帶上
access_token。
- 有效期 7200 秒 (2 小時):過期需重新獲取。
- 每日調用上限 2000 次:不可高頻輪詢,必須緩存並主動刷新。
- 重複獲取導致舊 token 失效:集群環境必須集中管理。
獲取接口
GET https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
// 返回: {"access_token":"ACCESS_TOKEN","expires_in":7200}
單機版管理方案:啟動 Servlet + 後台線程
@WebServlet(value = "/init", loadOnStartup = 0)
public class AccessTokenServlet extends HttpServlet {
@Override
public void init() {
String appId = getInitParameter("appid");
String appSecret = getInitParameter("appsecret");
new Thread(new TokenRefresher(appId, appSecret), "Wechat-Token-Refresher").start();
}
}
class TokenRefresher implements Runnable {
private final String appId, appSecret;
// volatile 保證可見性
private volatile AccessTokenHolder holder = new AccessTokenHolder();
TokenRefresher(String appId, String appSecret) { this.appId = appId; this.appSecret = appSecret; }
@Override public void run() {
while (true) {
try {
AccessToken token = fetchToken();
if (token != null) {
holder = new AccessTokenHolder(token.getAccessToken(), System.currentTimeMillis() + token.getExpiresIn() * 1000L);
// 成功:休眠 7000 秒 (略小於 7200,留緩衝)
Thread.sleep(7000_000L);
} else {
Thread.sleep(3_000L); // 失敗快速重試
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); break;
} catch (Exception e) {
log.error("Token 刷新異常", e);
try { Thread.sleep(10_000L); } catch (InterruptedException ie) { break; }
}
}
}
private AccessToken fetchToken() {
String url = String.format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s", appId, appSecret);
String json = HttpUtil.get(url); // 封裝 HttpsURLConnection
return JSON.parseObject(json, AccessToken.class); // Fastjson/Gson
}
// 業務調用獲取 token
static String getAccessToken() {
return INSTANCE.holder.getToken();
}
}
class AccessTokenHolder {
final String token;
final long expireTime; // 毫秒時間戳
AccessTokenHolder() { this(null, 0); }
AccessTokenHolder(String token, long expireTime) { this.token = token; this.expireTime = expireTime; }
String getToken() { return (token != null && System.currentTimeMillis() < expireTime) ? token : null; }
}
web.xml 配置 (非 Spring Boot)
<servlet>
<servlet-name>initToken</servlet-name>
<servlet-class>com.xxx.AccessTokenServlet</servlet-class>
<init-param><param-name>appid</param-name><param-value>你的AppID</param-value></init-param>
<init-param><param-name>appsecret</param-name><param-value>你的AppSecret</param-value></init-param>
<load-on-startup>0</load-on-startup>
</servlet>
集群/分佈式環境升級方案
| 方案 | 關鍵點 |
| Redis 集中存儲 | Key: wechat:access_token:{appid},Value: token,TTL 7000s。雙重檢查鎖獲取:無則單節點請求微信並回填。 |
| 分佈式鎖 | Redisson RLock 保證集群僅一節點刷新。 |
| 定時任務 | XXL-Job 或 Spring @Scheduled 定時刷新,比守護線程更可控。 |
HTTPS 請求工具類
public class HttpUtil {
public static String get(String url) {
try {
URL u = new URL(url);
HttpsURLConnection conn = (HttpsURLConnection) u.openConnection();
// 信任所有證書 (生產環境請配置正規 CA 憑證)
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(null, new TrustManager[]{new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; }
public void checkClientTrusted(X509Certificate[] c, String a) {}
public void checkServerTrusted(X509Certificate[] c, String a) {}
}}, null);
conn.setSSLSocketFactory(ctx.getSocketFactory());
conn.setHostnameVerifier((h, s) -> true);
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
try (BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
return br.lines().collect(Collectors.joining("\n"));
}
} catch (Exception e) {
throw new RuntimeException("HTTPS GET 失敗: " + url, e);
}
}
}
測試驗證
- 配置公眾平台 URL:
https://your.domain/wechat,Token 一致。
- 提交 → 顯示 "配置成功"。
- 控制台輸出 Token 刷新日誌。
- JSP/Controller 調用
TokenRefresher.getAccessToken() 獲取有效 token。
總結
微信公眾號開發基石在於:接入驗證邏輯正確 (SHA1+排序+原樣返回 echostr) 以及 Access Token 綠色管理 (緩存+定時刷新+集群互斥)。單機用 Servlet+守護線程快速起步,生產必上 Redis+分佈式鎖。掌握這兩點,後續菜單管理、素材上傳、模板消息、網頁授權等高級接口調用才有可靠基礎。
- 上一篇文章: Android 自定義 View 進階:組合控件 ImageButton 實現與 ViewPager 指示器封裝
- 下一篇文章: Jackson 核心功能詳解:對象序列化、反序列化與泛型容器 TypeReference 實戰
Copyright ©
程式碼之家 All Rights Reserved