微信access_token、jsapi_ticket中控服务器

一:做微信开发的过程中,肯定少不了和access_token打交道

首先要区分 网页授权access_token基础支持的access_token的不同


a.网页授权access_token 是一次性的,只能获取到一个微信用户信息,是与微信用户一对一的关系,获取次数没有限制。

b.基础支持的access_token的是有时间限制的:7200s,在有效期内就可以使用access_token和openId 获取微信用户信息。

二:如下是微信官方对access_token和jsapi_ticket的描述

1)获取access_token

access_token是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token。开发者需要进行妥善保存。

access_token的存储至少要保留512个字符空间。access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access_token失效。

公众平台的API调用所需的access_token的使用及生成方式说明:

1、建议公众号开发者使用中控服务器统一获取和刷新Access_token,其他业务逻辑服务器所使用的access_token均来自于该中控服务器,不应该各自去刷新,否则容易造成冲突,导致access_token覆盖而影响业务;
2、目前Access_token的有效期通过返回的expire_in来传达,目前是7200秒之内的值。中控服务器需要根据这个有效时间提前去刷新新access_token。在刷新过程中,中控服务器可对外继续输出的老access_token,此时公众平台后台会保证在5分钟内,新老access_token都可用,这保证了第三方业务的平滑过渡;
3、Access_token的有效时间可能会在未来有调整,所以中控服务器不仅需要内部定时主动刷新,还需要提供被动刷新access_token的接口,这样便于业务服务器在API调用获知access_token已超时的情况下,可以触发access_token的刷新流程。


公众号可以使用AppID和AppSecret调用本接口来获取access_token。AppID和AppSecret可在“微信公众平台-开发-基本配置”页中获得(需要已经成为开发者,且帐号没有异常状态)。调用接口时,请登录“微信公众平台-开发-基本配置”提前将服务器IP地址添加到IP白名单中,点击查看设置方法,否则将无法调用成功。

2)jsapi_ticket

生成签名之前必须先了解一下jsapi_ticket,jsapi_ticket是公众号用于调用微信JS接口的临时票据。正常情况下,jsapi_ticket的有效期为7200秒,通过access_token来获取。由于获取jsapi_ticket的api调用次数非常有限,频繁刷新jsapi_ticket会导致api调用受限,影响自身业务,开发者必须在自己的服务全局缓存jsapi_ticket 。

1.参考以下文档获取access_token(有效期7200秒,开发者必须在自己的服务全局缓存access_token):../15/54ce45d8d30b6bf6758f68d2e95bc627.html

2.用第一步拿到的access_token 采用http GET方式请求获得jsapi_ticket(有效期7200秒,开发者必须在自己的服务全局缓存jsapi_ticket):https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi

:access_token中控器

access_token每天有使用次数限制,所以客户服务器不能每次是都去请求一个新的access_token,每次请求之后,access_token都有一个过期时间。因此微信平台建议你使用一个中控服务器来定时刷新token,取得之后存起来不用再去请求token,因为access_token请求有次数限制。 这样处理只有有两个好处:

  1. 保证access_token每日都不会超出访问限制,保证服务的正常。
  2. 提高服务的性能,不用每次发送业务请求之前都先发送一次access_token获取请求。
access_token保存在内存中这种模式效率最高,但无法支持集群模式,对后续系统扩容影响较大,所以推荐存放在redis、传统数据库中。

1)注册监听器

public class TokenListener implements ServletContextListener{
	
    private static Logger log = Logger.getLogger(TokenListener.class);
    
	private Timer timer = null;

	@Override
	public void contextInitialized(ServletContextEvent arg0) {
		log.info("accessToken监听器启动..........");
		timer = new Timer(true);//Timer是调度控制器,TimerTask是可调度的任务
		//注册定时任务
		registeAccessTokenTimer();
		//注册jsapi_ticket定时器
		registeJsApiTicketTimer();
	}
	
	@Override
	public void contextDestroyed(ServletContextEvent arg0) {
		timer.cancel();
	}
	
	/**
	 * 注册accessToken定时器
	 */
	private void registeAccessTokenTimer(){
		AccessTokenTimer accessTokenTimer = new AccessTokenTimer();
		timer.schedule(accessTokenTimer, AccessTokenTimer.DELAY,AccessTokenTimer.PERIOD);
		log.info("accessToken定时器注册成功,执行间隔为" + AccessTokenTimer.PERIOD);
	}
	
	/**
	 * 注册jsapi_ticket定时器
	 */
	private void registeJsApiTicketTimer(){
		JsApiTicketTimer jsApiTicketTimer = new JsApiTicketTimer();
		timer.schedule(jsApiTicketTimer, JsApiTicketTimer.DELAY,JsApiTicketTimer.PERIOD);
		log.info("jsapi_ticket定时器注册成功,执行间隔为" + JsApiTicketTimer.PERIOD);
	}
	
}
2)定时器,7000s刷新一下
public class AccessTokenTimer extends TimerTask{
	
	private static Logger logger = Logger.getLogger(AccessTokenTimer.class);
	
	//accessToken有效期7200秒,提前200秒请求新的token
	public static final long PERIOD = 7000 * 1000;
	public static final long DELAY = 0; //此任务的延迟时间为0,即立即执行

	@Override
	public void run() {
		logger.info("accessToken 定时任务启动,获取新的accessToken");
		//得到新的access token
		AccessToken accessToken = new AccessToken();
		//获取成功之后持久化accessToken
		if(accessToken.request()){
			AccessTokenServer accessTokenServer = new AccessTokenServer();
			CustomerServer customerServer = (CustomerServer)accessTokenServer.customerServer();
			customerServer.save(accessToken);
		}
	}

}
3)保存到数据库
public class CustomerAccessTokenServer extends CustomerServer {

	private BaseDaoImpl baseDaoImpl = SpringContextHolder.getBean("baseDaoImpl");;

	protected String query() {
		String accessToken = null;
		// 执行数据库操作
		String sql = "select cfgValue from x_cfg where cfgKey = 'access_token'";
		List> list = (List>) baseDaoImpl.queryBySql(sql);
		if (list != null && list.size() > 0) {
			accessToken = list.get(0).get("cfgValue").toString();
		}
		return accessToken;
	}

	public boolean save(Token token) {
		String sql0 = "select count(id) countNum from x_cfg where cfgKey = 'access_token'";
		String sql = "";
		List> list = (List>) baseDaoImpl.queryBySql(sql0);
		if (list != null && list.size() > 0 && list.get(0).get("countNum") > 0) {
			sql = "update x_cfg set cfgValue='" + token.getToken() + "',update_time=NOW() where cfgKey= 'access_token'";
		} else {
			sql = "insert into x_cfg(cfgKey,cfgValue,create_time,comments) values('access_token','" + token.getToken() + "',NOW(),'access_token')";
		}
		try {
			baseDaoImpl.executeSQL(sql);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return true;
	}

}
4)启动后
2017-12-09 23:29:52,796 INFO [com.xyx.test.TokenListener] - 
2017-12-09 23:29:52,796 INFO [com.xyx.test.TokenListener] - 
2017-12-09 23:29:52,796 INFO [com.xyx.test.timer.AccessTokenTimer] - 
2017-12-09 23:29:52,796 INFO [com.xyx.test.AccessToken] - <创建获取access_token url>
2017-12-09 23:29:52,796 INFO [com.xyx.test.TokenListener] - 
2017-12-09 23:29:53,688 INFO [com.xyx.test.token.Token] - 
2017-12-09 23:29:53,688 INFO [com.xyx.dao.impl.BaseDaoImpl] - 
2017-12-09 23:29:53,736 INFO [com.xyx.test.Ticket] - <获取ticket,ticket类型jsapi>
2017-12-09 23:29:53,814 INFO [com.xyx.test.Token] - 
2017-12-09 23:29:53,814 INFO [com.xyx.dao.impl.BaseDaoImpl] -