微信公眾平臺開發實戰Java版之如何獲取公眾號的access_token以及快取access_token
一、access_token簡介
為了使第三方開發者能夠為使用者提供更多更有價值的個性化服務,微信公眾平臺 開放了許多介面,包括自定義選單介面、客服介面、獲取使用者資訊介面、使用者分組介面、群發介面等,
開發者在呼叫這些介面時,都需要傳入一個相同的引數 access_token,它是公眾賬號的全域性唯一票據,它是介面訪問憑證。
access_token是公眾號的全域性唯一票據,公眾號呼叫各介面時都需使用access_token。開發者需要進行妥善儲存。
access_token的儲存至少要保留512個字元空間。
access_token的有效期目前為2個小時,需定時重新整理,重複獲取將導致上次獲取的 access_token失效。
如果第三方不使用中控伺服器,而是選擇各個業務邏輯點各自去重新整理access_token,那麼就可能會產生衝突,導致服務不穩定。
公眾號可以使用AppID和AppSecret呼叫本介面來獲取access_token。AppID和AppSecret可在微信公眾平臺官網-開發者中心頁中獲得(需要已經成為開發者,且帳號沒有異常狀態)。注意呼叫所有微信介面時均需使用https協議。
access_token的有效期是7200秒(兩小時),在有效期內,可以一直使用,只有當access_token過期時,才需要再次呼叫介面 獲取access_token。在理想情況下,一個7x24小時執行的系統,每天只需要獲取12次access_token,即每2小時獲取一次。如果在 有效期內,再次獲取access_token,那麼上一次獲取的access_token將失效。
目前,獲取access_token介面的呼叫頻率限制為2000次/天,如果每次傳送客服訊息、獲取使用者資訊、群發訊息之前都要先呼叫獲取 access_token介面得到介面訪問憑證,這顯然是不合理的,一方面會更耗時(多了一次介面呼叫操作),另一方面2000次/天的呼叫限制恐怕也不 夠用。因此,在實際應用中,我們需要將獲取到的access_token儲存起來,然後定期呼叫access_token介面更新它,以保證隨時取出的 access_token都是有效的。
我們先看看官方的說明:
介面呼叫請求說明
引數說明
引數 | 是否必須 | 說明 |
---|---|---|
grant_type | 是 | 獲取access_token填寫client_credential |
appid | 是 | 第三方使用者唯一憑證 |
secret | 是 | 第三方使用者唯一憑證金鑰,即appsecret |
返回說明
正常情況下,微信會返回下述JSON資料包給公眾號:
{"access_token":"ACCESS_TOKEN","expires_in":7200}
引數 | 說明 |
---|---|
access_token | 獲取到的憑證 |
expires_in | 憑證有效時間,單位:秒 |
錯誤時微信會返回錯誤碼等資訊,JSON資料包示例如下(該示例為AppID無效錯誤):
{"errcode":40013,"errmsg":"invalid appid"}
二、封裝基本類
封裝一下token類:
package com.souvc.weixin.pojo; /** * 類名: Token </br> * 描述: 憑證 </br> * 開發人員: souvc </br> * 建立時間: 2015-9-30 </br> * 釋出版本:V1.0 </br> */ public class Token { // 介面訪問憑證 private String accessToken; // 憑證有效期,單位:秒 private int expiresIn; public String getAccessToken() { return accessToken; } public void setAccessToken(String accessToken) { this.accessToken = accessToken; } public int getExpiresIn() { return expiresIn; } public void setExpiresIn(int expiresIn) { this.expiresIn = expiresIn; } }
View Code
三、獲取token。
1.問題:如何通過獲取token?
解決方案:(1)直接通過瀏覽器訪問。(2)編寫程式,模擬https連線,獲得token。
解決詳細步驟如下:
(2)如何在程式中模擬傳送https請求,並且獲取到token呢?
對於https請求,我們需要一個證書信任管理器,這個管理器類需要自己定義,但需要實現X509TrustManager介面,
首先定義一個MyX509TrustManager 類。
package com.souvc.weixin.util; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import javax.net.ssl.X509TrustManager; /** * 類名: MyX509TrustManager </br> * 描述: 信任管理器 </br> * 開發人員: souvc </br> * 建立時間: 2015-9-30 </br> * 釋出版本:V1.0 </br> */ public class MyX509TrustManager implements X509TrustManager { // 檢查客戶端證書 public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { } // 檢查伺服器端證書 public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { } // 返回受信任的X509證書陣列 public X509Certificate[] getAcceptedIssuers() { return null; } }
View Code
建立一個token測試類:
package com.souvc.weixin.test; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URL; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import com.souvc.weixin.util.MyX509TrustManager; public class TokenTest { public static void main(String[] args) throws Exception { //修改appID,secret String tokenUrl="https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET"; //建立連線 URL url = new URL(tokenUrl); HttpsURLConnection httpUrlConn = (HttpsURLConnection) url.openConnection(); // 建立SSLContext物件,並使用我們指定的信任管理器初始化 TrustManager[] tm = { new MyX509TrustManager() }; SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE"); sslContext.init(null, tm, new java.security.SecureRandom()); // 從上述SSLContext物件中得到SSLSocketFactory物件 SSLSocketFactory ssf = sslContext.getSocketFactory(); httpUrlConn.setSSLSocketFactory(ssf); httpUrlConn.setDoOutput(true); httpUrlConn.setDoInput(true); // 設定請求方式(GET/POST) httpUrlConn.setRequestMethod("GET"); // 取得輸入流 InputStream inputStream = httpUrlConn.getInputStream(); InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8"); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); //讀取響應內容 StringBuffer buffer = new StringBuffer(); String str = null; while ((str = bufferedReader.readLine()) != null) { buffer.append(str); } bufferedReader.close(); inputStreamReader.close(); // 釋放資源 inputStream.close(); httpUrlConn.disconnect(); //輸出返回結果 System.out.println(buffer); } }
View Code
微信伺服器返回的結果:
{"access_token":"E3kRcQTati3QBPz97ou7zG0NXFrZFbA5No_hs5FNUZ62ROT0jr0txWr-gG1w-t06kk0zBW0kFmJiicJAydFyHNZhIh2uqIw4B5t85huRLs4","expires_in":7200}
2.問題:微信伺服器返回的是json資料,如何從json裡面解析出來的值?
方案:可以通過一款開源的json開發工具包json-lib,將他轉換為java物件。
詳細實現方法:
首先引入jar包
jar包下載: http://yunpan.cn/cH9UCqsQ9XVdE 訪問密碼 2711
封裝一個通用的工具類 CommonUtil ,用於專門獲取token:
package com.souvc.weixin.util; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.ConnectException; import java.net.URL; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import net.sf.json.JSONException; import net.sf.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.souvc.weixin.pojo.Token; /** * 類名: CommonUtil </br> * 描述: 通用工具類 </br> * 開發人員: souvc </br> * 建立時間: 2015-9-30 </br> * 釋出版本:V1.0 </br> */ public class CommonUtil { private static Logger log = LoggerFactory.getLogger(CommonUtil.class); // 憑證獲取(GET) public final static String token_url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET"; /** * 傳送https請求 * * @param requestUrl 請求地址 * @param requestMethod 請求方式(GET、POST) * @param outputStr 提交的資料 * @return JSONObject(通過JSONObject.get(key)的方式獲取json物件的屬性值) */ public static JSONObject httpsRequest(String requestUrl, String requestMethod, String outputStr) { JSONObject jsonObject = null; try { // 建立SSLContext物件,並使用我們指定的信任管理器初始化 TrustManager[] tm = { new MyX509TrustManager() }; SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE"); sslContext.init(null, tm, new java.security.SecureRandom()); // 從上述SSLContext物件中得到SSLSocketFactory物件 SSLSocketFactory ssf = sslContext.getSocketFactory(); URL url = new URL(requestUrl); HttpsURLConnection conn = (HttpsURLConnection) url.openConnection(); conn.setSSLSocketFactory(ssf); conn.setDoOutput(true); conn.setDoInput(true); conn.setUseCaches(false); // 設定請求方式(GET/POST) conn.setRequestMethod(requestMethod); // 當outputStr不為null時向輸出流寫資料 if (null != outputStr) { OutputStream outputStream = conn.getOutputStream(); // 注意編碼格式 outputStream.write(outputStr.getBytes("UTF-8")); outputStream.close(); } // 從輸入流讀取返回內容 InputStream inputStream = conn.getInputStream(); InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8"); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); String str = null; StringBuffer buffer = new StringBuffer(); while ((str = bufferedReader.readLine()) != null) { buffer.append(str); } // 釋放資源 bufferedReader.close(); inputStreamReader.close(); inputStream.close(); inputStream = null; conn.disconnect(); jsonObject = JSONObject.fromObject(buffer.toString()); } catch (ConnectException ce) { log.error("連線超時:{}", ce); } catch (Exception e) { log.error("https請求異常:{}", e); } return jsonObject; } /** * 獲取介面訪問憑證 * * @param appid 憑證 * @param appsecret 金鑰 * @return */ public static Token getToken(String appid, String appsecret) { Token token = null; String requestUrl = token_url.replace("APPID", appid).replace("APPSECRET", appsecret); // 發起GET請求獲取憑證 JSONObject jsonObject = httpsRequest(requestUrl, "GET", null); if (null != jsonObject) { try { token = new Token(); token.setAccessToken(jsonObject.getString("access_token")); token.setExpiresIn(jsonObject.getInt("expires_in")); } catch (JSONException e) { token = null; // 獲取token失敗 log.error("獲取token失敗 errcode:{} errmsg:{}", jsonObject.getInt("errcode"), jsonObject.getString("errmsg")); } } return token; } }
View Code
修改Token測試類:
package com.souvc.weixin.test; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URL; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import org.junit.Test; import com.souvc.weixin.pojo.Token; import com.souvc.weixin.util.CommonUtil; import com.souvc.weixin.util.MyX509TrustManager; public class TokenTest { @Test public void testGetToken1() throws Exception { String tokenUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=appID&secret=appsecret"; // 建立連線 URL url = new URL(tokenUrl); HttpsURLConnection httpUrlConn = (HttpsURLConnection) url.openConnection(); // 建立SSLContext物件,並使用我們指定的信任管理器初始化 TrustManager[] tm = { new MyX509TrustManager() }; SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE"); sslContext.init(null, tm, new java.security.SecureRandom()); // 從上述SSLContext物件中得到SSLSocketFactory物件 SSLSocketFactory ssf = sslContext.getSocketFactory(); httpUrlConn.setSSLSocketFactory(ssf); httpUrlConn.setDoOutput(true); httpUrlConn.setDoInput(true); // 設定請求方式(GET/POST) httpUrlConn.setRequestMethod("GET"); // 取得輸入流 InputStream inputStream = httpUrlConn.getInputStream(); InputStreamReader inputStreamReader = new InputStreamReader( inputStream, "utf-8"); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); // 讀取響應內容 StringBuffer buffer = new StringBuffer(); String str = null; while ((str = bufferedReader.readLine()) != null) { buffer.append(str); } bufferedReader.close(); inputStreamReader.close(); // 釋放資源 inputStream.close(); httpUrlConn.disconnect(); // 輸出返回結果 System.out.println(buffer); } @Test public void testGetToken2() { Token token = CommonUtil.getToken("appID","appsecret"); System.out.println("access_token:"+token.getAccessToken()); System.out.println("expires_in:"+token.getExpiresIn()); } }
View Code
控制檯輸出效果如下,說明我們獲取到了access_token和expires_in:
access_token:2amR6pr1eN-BuSBgho-nzo5tofxJ6BdEnRJQ87Zs5bj4ny4CGB8w-1D3YtjG2PzmEvVm1INrsVg-5BjyHCkWmBKsLPDSF3r_bdaPxMpKtbw
expires_in:7200
四、快取access_token。
問題:問題又來來,可是我們如何把這個值快取起來呢,並且需要2倆小時獲取一次?
方案:
(1)可以通過快取框架把該值通過key-values形式快取在記憶體中 。
(2)可以把該值存入資料庫中,需要的時候就去提取。
使用資料庫的話,大概思路就是這樣的。第一次使用將其access_token儲存起來,下次需要access_token則將其查出。若是失效則,重新建立並更新資料庫.若是沒有失效,則直接使用。
建立資料庫 souvc
create database souvc ;
建立資料庫表 t_token 來存放token值
CREATE TABLE `t_token` ( `id` int(11) NOT NULL AUTO_INCREMENT, `access_token` varchar(1024) NOT NULL, `expires_in` int(11) NOT NULL, `createTime` datetime NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8
現在我們通過jdbc來讀取這個token值。
匯入jdbc的jar包
http://yunpan.cn/cH9IRaKemL3II 訪問密碼 2ffa
匯入mysql 的jar包
http://yunpan.cn/cmv3BGe9CkUr3 訪問密碼 3601
新建一個數據庫配置檔案db.properties
#Oracle