获取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的思路(目前我是在项目中定义了一个Map集合,后期可能会保存到数据库 或者 缓存到Redis中):
1,首先定义一个存放token的Map集合
// 缓存添加的时间 public static String cacheAddTime = null; // token缓存 public static Map<String, Token> TOKEN_TICKET_CACHE = new HashMap<String, Token>(); // token对应的key private static final String TOKEN = "token";
2.首先,用户在调用需要access_token接口的时候,先查询Map里保存access_token的值是否存在。
3.如果access_token存在的话,判断此access_token是否有效。如果有效的话,直接返回此值。
// 从Map缓存中读取token private static Token getTokenTicket(String key) { if (TOKEN_TICKET_CACHE != null && TOKEN_TICKET_CACHE.get(key) != null) { return TOKEN_TICKET_CACHE.get(key); } return null; }
4.如果没有效,则调用获取access_token的接口,再次获取,并且更改Map中已经存在的access_token值 和 时间。
5.接第一步骤,如果access_token不存在,则调用获取access_token的接口,将获取到的数据保存在Map里。
// 更新Map缓存中token private static void updateToken(String key, Token accessTocken) { if (TOKEN_TICKET_CACHE != null && TOKEN_TICKET_CACHE.get(key) != null) { TOKEN_TICKET_CACHE.remove(key);// 从缓存中删除" + key + "成功 } TOKEN_TICKET_CACHE.put(key, accessTocken); // 更新缓存修改的时间 cacheAddTime = String.valueOf(accessTocken.getAddTime()); }
正常情况下,微信会返回下述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 /** 22 * 公众平台通用接口工具类 23 * 24 * @author Administrator 25 * 26 */ 27 public class WechatConfig { 28 // 获取access_token的接口地址(GET) 限2000(次/天) 29 public final static String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET"; 30 // 创建菜单 31 public static final String MENU_CREATE_URL = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN"; 32 // 查询自定义菜单 33 public static final String MENU_GET_URL = "https://api.weixin.qq.com/cgi-bin/menu/get?access_token=ACCESS_TOKEN"; 34 // 删除自定义菜单 35 public static final String MENU_DELTE_URL = "https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=ACCESS_TOKEN"; 36 // 获取jsapi_ticket的接口地址(GET) 限2000(次/天) 37 public static final String JSAPI_TICKET_URL = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi"; 38 // 发送模板消息 39 public static final String SEND_MESSAGE = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=ACCESS_TOKEN"; 40 41 // 微信appid 42 public final static String APPID = ConfigUtil.getProperty("wx_appid"); 43 // 微信wx_secret 44 public final static String SECERT = ConfigUtil.getProperty("wx_secret"); 45 // 缓存添加的时间 46 public static String cacheAddTime = null; 47 // token,ticket缓存 48 public static Map<String, Token> TOKEN_TICKET_CACHE = new HashMap<String, Token>(); 49 // token对应的key 50 private static final String TOKEN = "token"; 51 // ticket对应的key 52 private static final String TICKET = "ticket"; 53 54 public static void main(String[] args) { 55 System.out.println(getToken(APPID,SECERT)); 56 } 57 /** 58 * 获得Token 59 * @param appId 60 * @param secret 61 * @return 62 */ 63 public static String getToken(String appId, String secret) { 64 Token accessTocken = getToken(appId, secret, System.currentTimeMillis() / 1000); 65 return accessTocken.getAccessToken(); 66 } 67 68 /** 69 * 获取access_token 70 * 71 * @param appid 72 * 凭证 73 * @param appsecret 74 * 密钥 75 * @return 76 */ 77 public static Token getToken(String appid, String appsecret, long currentTime) { 78 Token tockenCache = getTokenTicket(TOKEN); 79 Token Token = null; 80 // 缓存存在并且没过期 81 if (tockenCache != null && (currentTime - tockenCache.getAddTime() <= tockenCache.getExpiresIn())) { 82 return tockenCache; 83 } else { 84 // 缓存中token不存在或已过期 85 String requestUrl = ACCESS_TOKEN_URL.replace("APPID", appid).replace("APPSECRET", appsecret); 86 JSONObject jsonObject = httpRequest(requestUrl, "GET", null); 87 // 如果请求成功 88 if (null != jsonObject) { 89 Token = new Token(); 90 Token.setAccessToken(jsonObject.getString("access_token")); 91 // 正常过期时间是7200秒,此处设置3600秒读取一次 92 //一天有获取2000次的限制 ,设置1小时获取一次AccessToken防止超出请求限制 93 Token.setExpiresIn(jsonObject.getIntValue("expires_in") / 2); 94 Token.setAddTime(currentTime); 95 updateToken(TOKEN, Token); 96 } 97 } 98 return Token; 99 } 100 101 /** 102 * 发起https请求并获取结果 103 * 104 * @param requestUrl 105 * 请求地址 106 * @param requestMethod 107 * 请求方式(GET、POST) 108 * @param outputStr 109 * 提交的数据 110 * @return JSONObject(通过JSONObject.get(key)的方式获取json对象的属性值) 111 */ 112 private static JSONObject httpRequest(String requestUrl, String requestMethod, String outputStr) { 113 JSONObject jsonObject = null; 114 StringBuffer buffer = new StringBuffer(); 115 try { 116 // 创建SSLContext对象,并使用我们指定的信任管理器初始化 117 TrustManager[] tm = { new MyX509TrustManager() }; 118 SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE"); 119 sslContext.init(null, tm, new java.security.SecureRandom()); 120 // 从上述SSLContext对象中得到SSLSocketFactory对象 121 SSLSocketFactory ssf = sslContext.getSocketFactory(); 122 123 URL url = new URL(requestUrl); 124 HttpsURLConnection httpUrlConn = (HttpsURLConnection) url.openConnection(); 125 httpUrlConn.setSSLSocketFactory(ssf); 126 127 httpUrlConn.setDoOutput(true); 128 httpUrlConn.setDoInput(true); 129 httpUrlConn.setUseCaches(false); 130 // 设置请求方式(GET/POST) 131 httpUrlConn.setRequestMethod(requestMethod); 132 133 if ("GET".equalsIgnoreCase(requestMethod)) 134 httpUrlConn.connect(); 135 136 // 当有数据需要提交时 137 if (null != outputStr) { 138 OutputStream outputStream = httpUrlConn.getOutputStream(); 139 // 注意编码格式,防止中文乱码 140 outputStream.write(outputStr.getBytes("UTF-8")); 141 outputStream.close(); 142 } 143 // 将返回的输入流转换成字符串 144 InputStream inputStream = httpUrlConn.getInputStream(); 145 InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8"); 146 BufferedReader bufferedReader = new BufferedReader(inputStreamReader); 147 148 String str = null; 149 while ((str = bufferedReader.readLine()) != null) { 150 buffer.append(str); 151 } 152 bufferedReader.close(); 153 inputStreamReader.close(); 154 // 释放资源 155 inputStream.close(); 156 inputStream = null; 157 httpUrlConn.disconnect(); 158 jsonObject = JSONObject.parseObject(buffer.toString()); 159 // jsonObject = JSONObject.fromObject(buffer.toString()); 160 } catch (ConnectException ce) { 161 System.out.println("Weixin server connection timed out."); 162 } catch (Exception e) { 163 System.out.println("https request error:{}" + e.getMessage()); 164 } 165 return jsonObject; 166 } 167 168 // 从缓存中读取token或者ticket 169 private static Token getTokenTicket(String key) { 170 if (TOKEN_TICKET_CACHE != null && TOKEN_TICKET_CACHE.get(key) != null) { 171 return TOKEN_TICKET_CACHE.get(key); 172 } 173 return null; 174 } 175 176 // 更新缓存中token或者ticket 177 private static void updateToken(String key, Token accessTocken) { 178 if (TOKEN_TICKET_CACHE != null && TOKEN_TICKET_CACHE.get(key) != null) { 179 TOKEN_TICKET_CACHE.remove(key);// 从缓存中删除" + key + "成功 180 } 181 TOKEN_TICKET_CACHE.put(key, accessTocken); 182 // 更新缓存修改的时间 183 cacheAddTime = String.valueOf(accessTocken.getAddTime()); 184 } 185 }
运行里面的main方法查看获取的值即可
好了,到此获取access_token的方法基本结束了
有什么问题请指正^.^
如果在操作过程中有问题,欢迎随时讨论^.^
其他文章关联
(一)Java开发微信公众号(一)---初识微信公众号以及环境搭建
(二)Java开发微信公众号(二)---开启开发者模式,接入微信公众平台开发
(三)Java开发微信公众号(三)---微信服务器请求消息,响应消息,事件消息以及工具处理类的封装