Java开发微信公众号(五)---微信开发中如何获取access_token以及缓存access_token
获取access_token是微信api最重要的一个部分,因为调用其他api很多都需要用到access_token。比如自定义菜单接口、客服接口、获取用户信息接口、用户分组接口、群发接口等在请求的时候都需要用到access_token。
(一)access_token的介绍
access_token是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token。开发者需要进行妥善保存。access_token的存储至少要保留512个字符空间。access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access_token失效。
公众号可以使用AppID和AppSecret调用本接口来获取access_token。AppID和AppSecret可在微信公众平台官网-开发者中心页中获得。且一个公众号每天获取access_token的次数上限为2000次。详情请查看微信开发文档“获取access_token”;
通过开发者文档我们可以发现access_token的有效期限是2个小时,刷新的时候会导致上次获取的access_token失效,而且一个公众号每天获取access_token的次数上限为2000次。如果我们不做控制,而是选择各个业务逻辑点各自去刷新access_token,那么就可能会产生冲突,导致服务不稳定。所以我们需要有一个地方来保存我们获取到的access_token 和获取时间,每次有请求的时候先到这个地方查看是否已有 access_token,并判断这个access_token是否在有效期内,在的话直接返回,反之则重新调用接口获取access_token,同时在我们保存access_token的地方更新token和时间。
先和大家说一下我保存access_token的思路(目前先实现token的获取,access_token的缓存和更新后期会加上):
1.首先定义一个存放token的数据库表
2.首先,用户在调用需要access_token接口的时候,先查询数据库里保存access_token的值是否存在。
3.如果access_token存在的话,判断此access_token是否有效。如果有效的话,直接返回此值。
4.如果没有效,则调用获取access_token的接口,再次获取,并且更改数据库表中已经存在的access_token值 和 时间。
5.接第一步骤,如果access_token不存在,则调用获取access_token的接口,将获取到的数据保存在数据库表里。
正常情况下,微信会返回下述JSON数据包给公众号:
{"access_token":"ACCESS_TOKEN","expires_in":7200}
错误时微信会返回错误码等信息,JSON数据包示例如下(该示例为AppID无效错误):
{"errcode":40013,"errmsg":"invalid appid"}
封装access_token类 Token
1 package com.webchat.util.weixin.model; 2 3 /** 4 * token凭证 签名 5 * 6 * @author Administrator 7 * 8 */ 9 public class Token { 10 //接口访问凭证 11 private String accessToken; 12 //有效期限 13 private int expiresIn; 14 //获取token的最新时间 15 private long addTime; 16 //签名 17 private String ticket; 18 public String getAccessToken() { 19 return accessToken; 20 } 21 public void setAccessToken(String accessToken) { 22 this.accessToken = accessToken; 23 } 24 public int getExpiresIn() { 25 return expiresIn; 26 } 27 public void setExpiresIn(int expiresIn) { 28 this.expiresIn = expiresIn; 29 } 30 public long getAddTime() { 31 return addTime; 32 } 33 public void setAddTime(long addTime) { 34 this.addTime = addTime; 35 } 36 public String getTicket() { 37 return ticket; 38 } 39 public void setTicket(String ticket) { 40 this.ticket = ticket; 41 } 42 }
接口调用请求地址
https请求方式: 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 |
对于https请求,我们需要一个证书信任管理器,这个管理器类需要自己定义,但需要实现X509TrustManager接口,首先定义一个MyX509TrustManager 类。
1 package com.webchat.util.weixin.utils; 2 3 import java.security.cert.CertificateException; 4 import java.security.cert.X509Certificate; 5 6 import javax.net.ssl.X509TrustManager; 7 8 public class MyX509TrustManager implements X509TrustManager{ 9 10 public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { 11 // TODO Auto-generated method stub 12 13 } 14 15 public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { 16 // TODO Auto-generated method stub 17 18 } 19 20 public X509Certificate[] getAcceptedIssuers() { 21 // TODO Auto-generated method stub 22 return null; 23 } 24 25 }
定义一个weixin.properties 文件
#wei xin pei zhi wx_token=mywebchat wx_appid=wx6138e8XXXXXXX wx_secret=4364283f8XXXXXXXXXXXX
读取配置文件的工具类
1 package com.webchat.util.weixin; 2 3 import java.io.IOException; 4 import java.util.Properties; 5 6 import org.apache.log4j.Logger; 7 import org.springframework.core.io.ClassPathResource; 8 import org.springframework.core.io.Resource; 9 import org.springframework.core.io.support.PropertiesLoaderUtils; 10 11 /** 12 * 读取配置文件工具类 13 * @author Administrator 14 * 15 */ 16 public class ConfigUtil { 17 18 private static final Logger LOG = Logger.getLogger(ConfigUtil.class); 19 20 private static Properties config = null; 21 22 /** 23 * 返回weixin.properties配置信息 24 * @param key key值 25 * @return value值 26 */ 27 public static String getProperty(String key) { 28 if (config == null) { 29 synchronized (ConfigUtil.class) { 30 if (null == config) { 31 try { 32 Resource resource = new ClassPathResource("static/weixin/weixin.properties"); 33 config = PropertiesLoaderUtils.loadProperties(resource); 34 } catch (IOException e) { 35 LOG.error(e.getMessage(), e); 36 } 37 } 38 } 39 } 40 41 return config.getProperty(key); 42 } 43 }
获取access_token的工具类:WechatConfig
1 package com.webchat.util.weixin; 2 3 import java.io.BufferedReader; 4 import java.io.InputStream; 5 import java.io.InputStreamReader; 6 import java.io.OutputStream; 7 import java.net.ConnectException; 8 import java.net.URL; 9 import java.util.HashMap; 10 import java.util.Map; 11 12 import javax.net.ssl.HttpsURLConnection; 13 import javax.net.ssl.SSLContext; 14 import javax.net.ssl.SSLSocketFactory; 15 import javax.net.ssl.TrustManager; 16 17 import com.alibaba.fastjson.JSONObject; 18 import com.webchat.util.weixin.model.Token; 19 import com.webchat.util.weixin.utils.MyX509TrustManager; 20 21 public class WebChatConfig { 22 // 获取access_token的接口地址(GET) 限2000(次/天) 23 public final static String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET"; 24 // 创建菜单 25 public static final String MENU_CREATE_URL = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN"; 26 // 查询自定义菜单 27 public static final String MENU_GET_URL = "https://api.weixin.qq.com/cgi-bin/menu/get?access_token=ACCESS_TOKEN"; 28 // 删除自定义菜单 29 public static final String MENU_DELTE_URL = "https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=ACCESS_TOKEN"; 30 // 获取jsapi_ticket的接口地址(GET) 限2000(次/天) 31 public static final String JSAPI_TICKET_URL = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi"; 32 // 发送模板消息 33 public static final String SEND_MESSAGE = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=ACCESS_TOKEN"; 34 35 // 微信appid 36 public final static String APPID = ConfigUtil.getProperty("wx_appid"); 37 // 微信wx_secret 38 public final static String SECERT = ConfigUtil.getProperty("wx_secret"); 39 40 41 42 public static void main(String[] args) { 43 System.out.println(getToken(APPID, SECERT)); 44 } 45 46 /** 47 * 获得Token 48 * 49 * @param appId 50 * @param secret 51 * @return 52 */ 53 public static String getToken(String appId, String secret) { 54 Token accessTocken = getToken(appId, secret, System.currentTimeMillis() / 1000); 55 return accessTocken.getAccessToken(); 56 } 57 58 /** 59 * 获取access_token 60 * 61 * @param appid 62 * 凭证 63 * @param appsecret 64 * 密钥 65 * @return 66 */ 67 public static Token getToken(String appid, String appsecret, long currentTime) { 68 Token Token = null; 69 // 调用接口获取token 70 String requestUrl = ACCESS_TOKEN_URL.replace("APPID", appid).replace("APPSECRET", appsecret); 71 JSONObject jsonObject = httpRequest(requestUrl, "GET", null); 72 // 如果请求成功 73 if (null != jsonObject) { 74 Token = new Token(); 75 Token.setAccessToken(jsonObject.getString("access_token")); 76 // 正常过期时间是7200秒,此处设置3600秒读取一次 77 // 一天有获取2000次的限制 ,设置1小时获取一次AccessToken防止超出请求限制 78 Token.setExpiresIn(jsonObject.getIntValue("expires_in") / 2); 79 Token.setAddTime(currentTime); 80 } 81 return Token; 82 } 83 84 /** 85 * 发起https请求并获取结果 86 * 87 * @param requestUrl 88 * 请求地址 89 * @param requestMethod 90 * 请求方式(GET、POST) 91 * @param outputStr 92 * 提交的数据 93 * @return JSONObject(通过JSONObject.get(key)的方式获取json对象的属性值) 94 */ 95 private static JSONObject httpRequest(String requestUrl, String requestMethod, String outputStr) { 96 JSONObject jsonObject = null; 97 StringBuffer buffer = new StringBuffer(); 98 try { 99 // 创建SSLContext对象,并使用我们指定的信任管理器初始化 100 TrustManager[] tm = { new MyX509TrustManager() }; 101 SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE"); 102 sslContext.init(null, tm, new java.security.SecureRandom()); 103 // 从上述SSLContext对象中得到SSLSocketFactory对象 104 SSLSocketFactory ssf = sslContext.getSocketFactory(); 105 106 URL url = new URL(requestUrl); 107 HttpsURLConnection httpUrlConn = (HttpsURLConnection) url.openConnection(); 108 httpUrlConn.setSSLSocketFactory(ssf); 109 110 httpUrlConn.setDoOutput(true); 111 httpUrlConn.setDoInput(true); 112 httpUrlConn.setUseCaches(false); 113 // 设置请求方式(GET/POST) 114 httpUrlConn.setRequestMethod(requestMethod); 115 116 if ("GET".equalsIgnoreCase(requestMethod)) 117 httpUrlConn.connect(); 118 119 // 当有数据需要提交时 120 if (null != outputStr) { 121 OutputStream outputStream = httpUrlConn.getOutputStream(); 122 // 注意编码格式,防止中文乱码 123 outputStream.write(outputStr.getBytes("UTF-8")); 124 outputStream.close(); 125 } 126 // 将返回的输入流转换成字符串 127 InputStream inputStream = httpUrlConn.getInputStream(); 128 InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8"); 129 BufferedReader bufferedReader = new BufferedReader(inputStreamReader); 130 131 String str = null; 132 while ((str = bufferedReader.readLine()) != null) { 133 buffer.append(str); 134 } 135 bufferedReader.close(); 136 inputStreamReader.close(); 137 // 释放资源 138 inputStream.close(); 139 inputStream = null; 140 httpUrlConn.disconnect(); 141 jsonObject = JSONObject.parseObject(buffer.toString()); 142 // jsonObject = JSONObject.fromObject(buffer.toString()); 143 } catch (ConnectException ce) { 144 System.out.println("Weixin server connection timed out."); 145 } catch (Exception e) { 146 System.out.println("https request error:{}" + e.getMessage()); 147 } 148 return jsonObject; 149 } 150 }
运行里面的main方法查看获取的值即可
好了,到此获取access_token的方法基本结束了
有什么问题请指正^.^
如果在操作过程中有问题,欢迎随时讨论^.^
其他文章关联
(一)Java开发微信公众号(一)---初识微信公众号以及环境搭建
(二)Java开发微信公众号(二)---开启开发者模式,接入微信公众平台开发
(三)Java开发微信公众号(三)---微信服务器请求消息,响应消息,事件消息以及工具处理类的封装
(四)Java开发微信公众号(四)---微信服务器post消息体的接收及消息的处理
(五)Java开发微信公众号(五)---微信开发中如何获取access_token以及缓存access_token