微信公眾號入門筆記(四)獲取access_token
作者:zhutulang
以下是微信公眾平臺開發者文件中擷取的內容:
access_token是公眾號的全域性唯一票據,公眾號呼叫各介面時都需使用access_token。開發者需要進行妥善儲存。access_token的儲存至少要保留512個字元空間。access_token的有效期目前為2個小時,需定時重新整理,重複獲取將導致上次獲取的access_token失效。
介面呼叫請求說明
http請求方式: GET
引數說明
引數 |
是否必須 |
說明 |
grant_type |
是 |
獲取access_token填寫client_credential |
appid |
是 |
第三方使用者唯一憑證 |
secret |
是 |
第三方使用者唯一憑證金鑰,即appsecret |
返回說明
正常情況下,微信會返回下述JSON資料包給公眾號:
{"access_token":"ACCESS_TOKEN","expires_in":7200}
引數 |
說明 |
access_token |
獲取到的憑證 |
expires_in |
憑證有效時間,單位:秒 |
那麼,從以上的說明中我們知道:
(1)我們需要以get方式傳送https請求。
(2)appid和secret 可以從我們的公眾號後臺檢視。
(3)目前,access_token的有效期目前為2個小時,我們需要提供一個定時重新整理機制。並且最好能有一個強制重新整理的機制。
一、如何傳送https請求
對於第一點,用HttpClient包傳送https請求,核心思路就是忽略校驗過程,程式碼參考自:
SSLClient 類如下:
package com.dongliushui.util; importjava.security.cert.CertificateException; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import org.apache.http.conn.ClientConnectionManager; import org.apache.http.conn.scheme.Scheme; importorg.apache.http.conn.scheme.SchemeRegistry; importorg.apache.http.conn.ssl.SSLSocketFactory; importorg.apache.http.impl.client.DefaultHttpClient; /** *@ClassName: SSLClient *@Description: 用於進行Https請求的HttpClient *@author (程式碼來源):http://blog.csdn.net/rongyongfeikai2/article/details/41659353 *@date 2016年1月8日 *@version V1.0 */ public class SSLClient extendsDefaultHttpClient { publicSSLClient() throws Exception{ super(); SSLContext ctx = SSLContext.getInstance("TLS"); X509TrustManager tm = new X509TrustManager() { @Override publicvoid checkClientTrusted( java.security.cert.X509Certificate[]chain, String authType) throwsCertificateException { //TODO Auto-generated method stub } @Override publicvoid checkServerTrusted( java.security.cert.X509Certificate[]chain, String authType) throwsCertificateException { //TODO Auto-generated method stub } @Override publicjava.security.cert.X509Certificate[] getAcceptedIssuers() { //TODO Auto-generated method stub returnnull; } }; ctx.init(null, new TrustManager[]{tm}, null); SSLSocketFactory ssf = newSSLSocketFactory(ctx,SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); ClientConnectionManager ccm = this.getConnectionManager(); SchemeRegistry sr = ccm.getSchemeRegistry(); sr.register(new Scheme("https", 443, ssf)); } }
HttpUtil 類如下:
package com.dongliushui.util;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import net.sf.json.JSONObject;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
importorg.apache.http.client.entity.UrlEncodedFormEntity;
importorg.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
importorg.apache.http.impl.client.DefaultHttpClient;
importorg.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
/**
*@ClassName: HttpUtil
* @Description:Http請求工具類
*@author zhutulang
*@date 2016年1月8日
*@version V1.0
*/
public class HttpUtil {
/**
* <p>Title: doHttpsPost</p>
* <p>Description: 傳送https 形式的post請求</p>
* @param url 請求url
* @param contentType
* @param paramMap 引數map
* @return
* @author zhutulang
* @version 1.0
*/
publicstatic byte[] doHttpsPostJson(String url, String contentType, Map<String,String> paramMap){
returnpostJson(1, url, contentType, paramMap);
}
/**
* <p>Title: doHttpsPost</p>
* <p>Description: 傳送http 形式的post請求</p>
* @param url 請求url
* @param contentType
* @param paramMap 引數map
* @return
* @author zhutulang
* @version 1.0
*/
publicstatic byte[] doPostJson(String url, String contentType, Map<String,String> paramMap){
return postJson(0, url, contentType,paramMap);
}
/**
* <p>Title: doHttpsGet</p>
* <p>Description: 傳送https 形式的get請求</p>
* @param url 請求url
* @param contentType
* @return
* @author zhutulang
* @version 1.0
*/
publicstatic byte[] doHttpsGet(String url, String contentType){
returnget(1, url, contentType);
}
/**
* <p>Title: doGet</p>
* <p>Description: 傳送http 形式的gett請求</p>
* @param url 請求url
* @param contentType
* @return
* @author zhutulang
* @version 1.0
*/
publicstatic byte[] doGet(String url, String contentType){
returnget(0, url, contentType);
}
/**
* <p>Title: post</p>
* <p>Description: 傳送post請求,表單提交引數</p>
* @param type 0:普通post請求 1:https形式的post請求
* @param url 請求url
* @param contentType
* @param paramMap 引數map
* @return
* @author zhutulang
* @version 1.0
*/
privatestatic byte[] postCommon(int type, String url, String contentType,Map<String, String> paramMap){
//響應內容
byte[] bs = null;
HttpClient httpClient = null;
HttpPost httpPost = null;
try {
if(type == 0){
//建立傳送 http 請求的httpClient例項
httpClient= new DefaultHttpClient();
}else if(type == 1){
//建立傳送 https 請求的httpClient例項
httpClient= new SSLClient();
}
// 建立HttpPost
httpPost = new HttpPost(url);
httpPost.setHeader("content-type", contentType);
//設定引數
List<NameValuePair> list = newArrayList<NameValuePair>();
if(paramMap != null){
Iterator<Entry<String, String>>iterator = paramMap.entrySet().iterator();
while(iterator.hasNext()){
Entry<String,String> elem =(Entry<String, String>) iterator.next();
list.add(newBasicNameValuePair(elem.getKey(),elem.getValue()));
}
if(list.size() > 0){
UrlEncodedFormEntity entity = newUrlEncodedFormEntity(list,"UTF-8");
httpPost.setEntity(entity);
}
}
// 執行POST請求
HttpResponse response =httpClient.execute(httpPost);
// 獲取響應實體
HttpEntity entity = response.getEntity();
if(entity != null){
bs = EntityUtils.toByteArray(entity);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 關閉連線,釋放資源
httpClient.getConnectionManager().shutdown();
httpPost = null;
httpClient = null;
}
return bs;
}
/**
* <p>Title: post</p>
* <p>Description: 傳送post請求,json方式提交引數</p>
* @param type 0:普通post請求 1:https形式的post請求
* @param url 請求url
* @param contentType
* @param paramMap 引數map
* @return
* @author zhutulang
* @version 1.0
*/
privatestatic byte[] postJson(int type, String url, String contentType, Map<String,String> paramMap){
//響應內容
byte[] bs = null;
HttpClient httpClient = null;
HttpPost httpPost = null;
try {
if(type == 0){
//建立傳送 http 請求的httpClient例項
httpClient= new DefaultHttpClient();
}else if(type == 1){
//建立傳送 https 請求的httpClient例項
httpClient= new SSLClient();
}
// 建立HttpPost
httpPost = new HttpPost(url);
httpPost.setHeader("content-type", contentType);
if(paramMap != null){
Iterator<Entry<String, String>>iterator = paramMap.entrySet().iterator();
// 接收引數json列表
JSONObject jsonParam = newJSONObject();
while(iterator.hasNext()){
Entry<String,String> elem =(Entry<String, String>) iterator.next();
jsonParam.put(elem.getKey(),elem.getValue());
}
if(jsonParam.size() > 0){
StringEntity entity = newStringEntity(jsonParam.toString(),"UTF-8");
entity.setContentEncoding("UTF-8");
entity.setContentType("application/json");
httpPost.setEntity(entity);
}
}
// 執行POST請求
HttpResponse response =httpClient.execute(httpPost);
// 獲取響應實體
HttpEntity entity = response.getEntity();
if(entity != null){
bs = EntityUtils.toByteArray(entity);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 關閉連線,釋放資源
httpClient.getConnectionManager().shutdown();
httpPost = null;
httpClient = null;
}
return bs;
}
/**
* <p>Title: get</p>
* <p>Description: 傳送get請求</p>
* @param type 0:普通get請求 1:https形式的get請求
* @param url 請求url
* @param contentType
* @return
* @author zhutulang
* @version 1.0
*/
privatestatic byte[] get(int type, String url, String contentType){
//響應內容
byte[] bs = null;
HttpClient httpClient = null;
HttpGet httpGet = null;
try {
if(type == 0){
//建立傳送 http 請求的httpClient例項
httpClient= new DefaultHttpClient();
}else if(type == 1){
//建立傳送 https 請求的httpClient例項
httpClient= new SSLClient();
}
// 建立HttpPost
httpGet = new HttpGet(url);
httpGet.setHeader("content-type", contentType);
// 執行POST請求
HttpResponse response =httpClient.execute(httpGet);
// 獲取響應實體
HttpEntity entity = response.getEntity();
if(entity != null){
bs = EntityUtils.toByteArray(entity);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 關閉連線,釋放資源
httpClient.getConnectionManager().shutdown();
httpGet = null;
httpClient = null;
}
return bs;
}
}
二、如何定時重新整理access_token
在叢集環境中,這個問題可能會比較複雜。我們可能需要考慮到在叢集中各個機器的任務排程協調,對於獲取到的access_token,我們可能會考慮將它儲存在資料庫中,或者統一的快取模組中,比如redis中。對於單伺服器環境,我們大可以直接將其儲存在記憶體中。
定時任務我們經常會用到quartz框架。不過spring也提供有任務排程的模組,我習慣用@Scheduled註解。至於它的使用,大家可自行百度。
以下程式碼中形如@Value("#{weixinProperties['AppId']}")
是通過spring讀取配置檔案,如果沒見過這樣做的朋友也可以自行去查詢相關資料。
相關的配置放在一個名為weixin.properties的配置檔案中:
#weixin properties
# 你自己的appid和appsecret
AppId=XXXXXXXXX
AppSecret=XXXXXXXXXXXXXXXXXXX
#get access_token urlget
get_access_token_url=https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
#batchget_material urlpost
batchget_material_url=https://api.weixin.qq.com/cgi-bin/material/batchget_material?access_token=ACCESS_TOKEN
Spring配置檔案中:
<!-- weixin.properties 配置檔案 -->
<bean id="weixinProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<property name="locations">
<list>
<value>classpath*:weixin.properties</value>
</list>
</property>
</bean>
<bean id="propertyConfigurer"class="org.springframework.beans.factory.config.PreferencesPlaceholderConfigurer">
<property name="properties" ref="weixinProperties" />
</bean>
AccessTokenTaker 程式碼如下:
package com.dongliushui.quartz;
importjava.io.UnsupportedEncodingException;
import org.apache.log4j.Logger;
importorg.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
importorg.springframework.stereotype.Component;
import com.dongliushui.util.HttpUtil;
/**
*@ClassName: AccessTokenTaker
*@Description: 獲取access_token
*@author zhutulang
*@date 2016年1月10日
*@version V1.0
*/
@Component
public class AccessTokenTaker {
@Value("#{weixinProperties['AppId']}")
private String appId;
@Value("#{weixinProperties['AppSecret']}")
private String appSecret;
@Value("#{weixinProperties['get_access_token_url']}")
private String getAccessTokenUrl;
/**
* access_token
*/
privatestatic String ACCESS_TOKEN = null;
/**
* 上次更新access_token時間
*/
privatestatic LongLAST_ACCESS_TOKEN_UPDATE_TIME = null;
privatestatic Logger log = Logger.getLogger(AccessTokenTaker.class);
/**
* <p>Title: get</p>
* <p>Description: 每隔一個小時去獲取一次access_token</p>
* @author zhutulang
* @version 1.0
*/
@Scheduled(fixedRate=3600000)
privatevoid getTask(){
get();
}
/**
* <p>Title: getFromCache</p>
* <p>Description: 從快取中獲取access_token</p>
* @return
* @author zhutulang
* @version 1.0
*/
public String getFromCache(){
returnACCESS_TOKEN;
}
/**
* <p>Title: getNew</p>
* <p>Description: 強制更新、獲取access_token</p>
* <p>如果發現現在的時間戳和上次更新的時間戳間隔小於5分鐘,那麼不更新</p>
* @return
* @author zhutulang
* @version 1.0
*/
publicsynchronized String getNew(){
longtimeNow = System.currentTimeMillis();
if(LAST_ACCESS_TOKEN_UPDATE_TIME== null){
get();
}elseif(timeNow - LAST_ACCESS_TOKEN_UPDATE_TIME < 300000){
//如果是5分鐘以內
returnACCESS_TOKEN;
}else{
get();
}
returnACCESS_TOKEN;
}
/**
* <p>Title: get</p>
* <p>Description: 呼叫獲取access_token介面</p>
* @author zhutulang
* @version 1.0
*/
synchronized void get(){
Stringurl = getAccessTokenUrl.replace("APPID",appId).replace("APPSECRET", appSecret);
StringcontentType = "application/json";
byte[]bytes = HttpUtil.doHttpsGet(url, contentType);
try{
StringaccessToken = new String(bytes, "UTF-8");
longtimeNow = System.currentTimeMillis();
ACCESS_TOKEN= accessToken;
LAST_ACCESS_TOKEN_UPDATE_TIME= timeNow;
log.info("執行獲取access_token任務,access_token="+ACCESS_TOKEN);
log.info("時間戳="+LAST_ACCESS_TOKEN_UPDATE_TIME);
}catch (UnsupportedEncodingException e) {
//TODO Auto-generated catch block
e.printStackTrace();
}
}
publicString getAppId() {
returnappId;
}
publicvoid setAppId(String appId) {
this.appId= appId;
}
publicString getAppSecret() {
returnappSecret;
}
publicvoid setAppSecret(String appSecret) {
this.appSecret= appSecret;
}
publicString getGetAccessTokenUrl() {
returngetAccessTokenUrl;
}
publicvoid setGetAccessTokenUrl(String getAccessTokenUrl) {
this.getAccessTokenUrl= getAccessTokenUrl;
}
}
其它相關程式碼可檢視: