作者:zhutulang
以下是微信公众平台开发者文档中截取的内容:
access_token是公众号的全局唯一票据,公众号调用各接口时都需使用access_token。开发者需要进行妥善保存。access_token的存储至少要保留512个字符空间。access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access_token失效。
接口调用请求说明
http请求方式: GET
https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
参数说明
参数 |
是否必须 |
说明 |
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请求,核心思路就是忽略校验过程,代码参考自:
http://blog.csdn.net/rongyongfeikai2/article/details/41659353
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; } }
其它相关代码可查看:
http://download.csdn.net/detail/zhutulang/9423587