Java开发公众号系列教程(二):公众号开发全局缓存access_token和jsapi_ticket

2023-10-06 23:50

本文主要是介绍Java开发公众号系列教程(二):公众号开发全局缓存access_token和jsapi_ticket,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

上篇文章给大家分享了Java实现微信公众号调用微信拍照接口和打开本地相册上传图片的实战案例详解,近期收到很多开发者朋友通过笔者微信的咨询和反馈,表示很专业,很全面,很详细,十足的干货,足金足两,很受益。广大开发者朋友的持续支持和好评,让笔者有了更饱满的技术创作精神,那么今天就再次给大家分享一篇今天精心整理的干货《获取公众号的access_token和jsapi_ticket以及全局缓存公众号accessToken和jsapi_ticket》

        首先先了解一下微信access_token和jsapi_ticket是什么,啥时用,怎么用等问题。

       为了使第三方开发者能够为用户提供更多更有价值的个性化服务,微信公众平台开放了许多接口,包括自定义菜单接口、客服接口、获取用户信息接口、用户分组接口、群发接口,调用摄像头接口等,

开发者在调用这些接口时,都需要传入一个相同的参数 access_token,它是公众众号的全局唯一票据,它是接口访问凭证。

能看到这里相信读者已经知道在调用微信JS-SDK的所有场景下,都需要一个很重要的参数签名(signature) ,而这个参数要使用权限签名算法生成,生成签名要用到access_token和jsapi_ticket。所以签名之前必须先了解一下jsapi_ticket,jsapi_ticket是公众号用于调用微信JS接口的临时票据。正常情况下,jsapi_ticket的有效期为7200秒,通过access_token来获取。由于获取jsapi_ticket的api调用次数非常有限,频繁刷新jsapi_ticket会导致api调用受限,影响自身业务,开发者必须在自己的服务全局缓存jsapi_ticket

一、获取access_token(通过下面2种方式都可以获取)

1、开发者拥有认证过的微信公众号通过下面方式获取

登陆微信公众平台点选左侧的开发者中心,申请成为开发者。

成功后可以看到开发者中心界面,其中有AppId与AppSecret。目前AppSecret是部分隐藏的,如果要查看完整的版本需要绑定手机并扫二维码才能看到。

为了不暴露自己的AppId和AppSecret,我们要去向微信服务器获取一个access_token 使用GET方法访问下面的接口获取,详细代码见下文工具类: 

https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET

首先封装一个AccessToken实体类,包含2个属性具体代码如下:

package com.huaqi.payment.domain;
import java.io.Serializable;/*** create by guo bin hui in 2018-09-30* AccessToken实体类用于组装返回结果*/
public class AccessToken  implements Serializable {private String token;private Integer expiresIn;public String getToken() {return token;}public void setToken(String token) {this.token = token;}public Integer getExpiresIn() {return expiresIn;}public void setExpiresIn(Integer expiresIn) {this.expiresIn = expiresIn;}
}
public static AccessToken getToken()throws IOException{AccessToken token =  new AccessToken();String url = Constants.GET_ACCESS_TOKEN.replace("APPID",Constants.appID).replace("APPSECRET",Constants.secret);JSONObject jsonObj = doGetStr(url);if(!StringUtils.isEmpty(jsonObj)){token.setToken(jsonObj.getString("access_token"));token.setExpiresIn(jsonObj.getInt("expires_in"));}return token;}

调用上述微信接口所需参数说明

正常情况下,访问这个接口微信会返回下述JSON数据包给公众号:

{"access_token":"ACCESS_TOKEN","expires_in":7200}

2、开发者如果没有微信公众号通过下面方式获取

(1)、首先注册一个微信公众号测试账号,具体步骤自行百度,此处省略

(2)、使用网页调试工具生成:地址 https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141013

上图填写完后点击检查问题,则可以生成access_token

二、获取jsapi_ticket

首先封装一个JSTicket实体类,用于组装微信服务器返回的JSON数据包

package com.huaqi.payment.domain;
import java.io.Serializable;public class JSTicket implements Serializable {private String ticket;private Integer expiresIn;private Integer errCode;private String errMsg;public String getTicket() {return ticket;}public void setTicket(String ticket) {this.ticket = ticket;}public Integer getExpiresIn() {return expiresIn;}public void setExpiresIn(Integer expiresIn) {this.expiresIn = expiresIn;}public Integer getErrCode() {return errCode;}public void setErrCode(Integer errCode) {this.errCode = errCode;}public String getErrMsg() {return errMsg;}public void setErrMsg(String errMsg) {this.errMsg = errMsg;}
}

获取到access_token后通过下面方法获取ticket(然后才会有微信JS-SDK接口签名权限)

    public static JSTicket getJsTicket(String access_token) throws IOException{JSTicket ticketObj = new JSTicket();String url = Constants.TICKET_CREATE_URL.replace("ACCESS_TOKEN",access_token);JSONObject jsonObj = doGetStr(url);if(!StringUtils.isEmpty(jsonObj)){ticketObj.setTicket(jsonObj.getString("ticket"));ticketObj.setErrCode(jsonObj.getInt("errcode"));ticketObj.setErrMsg(jsonObj.getString("errmsg"));ticketObj.setExpiresIn(jsonObj.getInt("expires_in"));}return ticketObj;}

下面是上述用到的具体的工具类:包括其他参数的获取,SHA1加密,文件流读写操作等等。

package com.huaqi.payment.util;import com.huaqi.payment.domain.AccessToken;
import com.huaqi.payment.domain.JSTicket;
import net.sf.json.JSONObject;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.util.StringUtils;import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;import java.text.SimpleDateFormat;
import java.util.Date;public class WeiXinUtil {private static String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=APPSECRET&code=CODE&grant_type=authorization_code";public static AccessToken getToken()throws IOException{AccessToken token =  new AccessToken();String url = Constants.GET_ACCESS_TOKEN.replace("APPID",Constants.appID).replace("APPSECRET",Constants.secret);JSONObject jsonObj = doGetStr(url);if(!StringUtils.isEmpty(jsonObj)){token.setToken(jsonObj.getString("access_token"));token.setExpiresIn(jsonObj.getInt("expires_in"));}return token;}public static JSTicket getJsTicket(String access_token) throws IOException{JSTicket ticketObj = new JSTicket();String url = Constants.TICKET_CREATE_URL.replace("ACCESS_TOKEN",access_token);JSONObject jsonObj = doGetStr(url);if(!StringUtils.isEmpty(jsonObj)){ticketObj.setTicket(jsonObj.getString("ticket"));ticketObj.setErrCode(jsonObj.getInt("errcode"));ticketObj.setErrMsg(jsonObj.getString("errmsg"));ticketObj.setExpiresIn(jsonObj.getInt("expires_in"));}return ticketObj;}/*** 获取时间戳(秒)*/public static String getTimestamp() {return String.valueOf(System.currentTimeMillis() / 1000);}/*** 获取当前时间 yyyyMMddHHmmss*/public static String getCurrTime() {Date now = new Date();SimpleDateFormat outFormat = new SimpleDateFormat("yyyyMMddHHmmss");String s = outFormat.format(now);return s;}/*** 生成随机字符串*/public static String getNonceStr() {String currTime = getCurrTime();String strTime = currTime.substring(8, currTime.length());String strRandom = buildRandom(4) + "";return strTime + strRandom;}/*** 取出一个指定长度大小的随机正整数.* @param length int设定所取出随机数的长度。length小于11* @return int 返回生成的随机数。*/public static int buildRandom(int length) {int num = 1;double random = Math.random();if (random < 0.1) {random = random + 0.1;}for (int i = 0; i < length; i++) {num = num * 10;}return (int) ((random * num));}/*** 保存图片至服务器* @param mediaId* @return 文件名*/public static String saveImageToDisk(String mediaId)throws IOException{String filename = "";InputStream inputStream = getMediaStream(mediaId);byte[] data = new byte[1024];int len ;FileOutputStream fileOutputStream = null;try {//服务器存图路径String path = Constants.UPLOAD_PATH;filename = System.currentTimeMillis() + getNonceStr() + ".jpg";fileOutputStream = new FileOutputStream(path + File.separator+ filename);while ((len = inputStream.read(data)) != -1) {fileOutputStream.write(data, 0, len);}} catch (IOException e) {e.printStackTrace();} finally {if (inputStream != null) {try {inputStream.close();} catch (IOException e) {e.printStackTrace();}}if (fileOutputStream != null) {try {fileOutputStream.close();} catch (IOException e) {e.printStackTrace();}}}return filename;}/*** 获取临时素材*/private static InputStream getMediaStream(String mediaId)throws IOException {String url = "https://api.weixin.qq.com/cgi-bin/media/get";String access_token = getAccessToken();String params = "access_token=" + access_token + "&media_id=" + mediaId;InputStream is = null;try {String urlNameString = url + "?" + params;URL urlGet = new URL(urlNameString);HttpURLConnection http = (HttpURLConnection) urlGet.openConnection();http.setRequestMethod("GET"); // 必须是get方式请求http.setRequestProperty("Content-Type","application/x-www-form-urlencoded");http.setDoOutput(true);http.setDoInput(true);http.connect();// 获取文件转化为byte流is = http.getInputStream();} catch (Exception e) {e.printStackTrace();}return is;}public static JSONObject doGetStr(String url) throws IOException{HttpClient httpClient = new DefaultHttpClient();HttpGet  httpGet = new HttpGet(url);//HttpGet使用Get方式发送请求URLJSONObject jsonObj = null;HttpResponse  res = httpClient.execute(httpGet);//使用httpClient从Client执行httpGet的请求HttpEntity  entity = res.getEntity();//从HttpResponse中获取结果if(!StringUtils.isEmpty(entity)){String result =   EntityUtils.toString(entity,"utf-8");jsonObj = JSONObject.fromObject(result);//字符串类型转换为JSON对象}return jsonObj;}public static JSONObject doPostStr(String url,String outStr) throws IOException{HttpClient httpClient = new DefaultHttpClient();HttpPost httpPost = new HttpPost(url);//HttpGet使用Post方式发送请求URLJSONObject jsonObj = null;httpPost.setEntity(new StringEntity(outStr,"utf-8"));//使用setEntity方法,将传进来的参数放进请求HttpResponse  res = httpClient.execute(httpPost);HttpEntity  entity = res.getEntity();//从HttpResponse中获取结果if(!StringUtils.isEmpty(entity)){String result =   EntityUtils.toString(entity,"utf-8");jsonObj = JSONObject.fromObject(result);//字符串类型转换为JSON对象}return jsonObj;}
}

通过以上方法我们已经获取到了access_token和jsapi_ticket,这2个参数的有限期只有7200秒,接下来笔者为大家详解在项目中如何全局缓存这2个参数

三、全局缓存公众号accessToken和jsapi_ticket

1、目前常规的实现思路有三种方式

(1)、通过数据库保存
      做法是获取access_token的时候把当前系统时间和access_token保存到数据表中,当再次获取时,查询上次获取的时间与当前系统时间比较,看看时间是否大于2个小时(7200s)。如果超过这个时间限制,再获取一个access_token,然后更新数据表的accessToken和getTime。
       表名:t_access_token
       票据:access_token
       获取时间:getTime
(2)、通过物理磁盘创建txt文件保存
      1、创建access_token.txt文件
      2、读取get_time
      3、读取txt文件判断时间是否超过2个小时
      4、超过则覆盖重写access_token.txt文件内容

(3)、通过servlet启动线程,让线程定时执行获取

2、具体的实现方式

这里我们主要讲一下上述1中的(2)和(3)这二种方式

方式一、通过物理磁盘创建txt文件保存。这里依赖google的Gson jar包

dependencies {compile("org.springframework:spring-webmvc:${springVersion}","javax.servlet:servlet-api:2.5","com.alipay.sdk:alipay-sdk-java:3.1.0","org.apache.commons:commons-collections4:4.1","dom4j:dom4j:1.6.1","commons-codec:commons-codec:1.9","commons-httpclient:commons-httpclient:3.0.1","net.sf.json-lib:json-lib:2.4:jdk15",'mysql:mysql-connector-java:5.1.38','org.mybatis:mybatis-spring:1.2.2','org.mybatis:mybatis:3.2.8',"com.alibaba:druid:1.1.9","net.sf.ehcache:ehcache-core:2.6.11","org.springframework:spring-context-support:4.2.3.RELEASE","org.springframework:spring-jdbc:3.0.5.RELEASE","com.alibaba:fastjson:1.2.4","javax.servlet:jstl:1.2","org.apache.httpcomponents:httpclient:4.3.6","org.apache.httpcomponents:httpcore:4.4.6","com.google.code.gson:gson:2.7")
}

如果是maven项目安装下面方式依赖

<dependency>  <groupId>com.google.code.gson</groupId>  <artifactId>gson</artifactId>  <version>2.7</version>  
</dependency> 

 具体的代码实现如下:主要思路就是通过IO流的读写操作,为了测试,写一个main方法可以测试一下

package com.huaqi.payment.util;
import com.google.gson.Gson;
import com.huaqi.payment.domain.AccessToken;import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;/**** 微信获取AccessToken并本地保存*/
public class WxAccessToken {private static final long MAX_TIME = 7000 * 1000;// 微信允许最长Access_token有效时间为7200秒,这里设置为7000秒public static AccessToken access_token_obj=null;/*** 获取Access_token 保存并且只保存2小时Access_token。如果超过两个小时重新获取;如果没有超过两个小时,直接获取* 思路:将获取到的Access_token和当前时间存储到file里,* 取出时判断当前时间和存储里面的记录的时间的时间差,如果大于MAX_TIME,重新获取,并且将获取到的存储到file替换原来的内容* 如果小于MAX_TIME,直接获取。*/public static String getSavedAccessToken() throws IOException {Gson gson = new Gson();String mAccess_token = null;// 需要获取的Access_token;System.out.println(System.getProperty("user.di"));File file = new File(Constants.LOCAL_ACCESS_TOKEN_path);// Access_token保存的位置// 如果文件不存在,创建if (!file.exists())file.createNewFile();// 如果文件大小等于0,说明第一次使用,存入Access_tokenif (file.length() == 0) {access_token_obj = WeiXinUtil.getToken();String token = access_token_obj.getToken();FileOutputStream fos = new FileOutputStream(Constants.LOCAL_ACCESS_TOKEN_path, false);// 不允许追加AccessToken at = new AccessToken();at.setToken(token);at.setExpiresIn(Integer.valueOf(System.currentTimeMillis()/1000+""));String json = gson.toJson(at);fos.write(json.getBytes());fos.close();} else {// 读取文件内容FileInputStream fis = new FileInputStream(file);byte[] b = new byte[2048];int len = fis.read(b);String mJsonAccess_token = new String(b, 0, len);// 读取到的文件内容AccessToken access_token = gson.fromJson(mJsonAccess_token, new AccessToken().getClass());if (access_token.getExpiresIn() != null) {long lastSaveTime = Long.parseLong(access_token.getExpiresIn()*1000+"");long nowTime = System.currentTimeMillis();long remianTime = nowTime - lastSaveTime;if (remianTime < MAX_TIME) {AccessToken access = gson.fromJson(mJsonAccess_token, new AccessToken().getClass());mAccess_token = access.getToken();} else {access_token_obj = WeiXinUtil.getToken();FileOutputStream fos = new FileOutputStream(file, false);// 不允许追加AccessToken newToken = new AccessToken();newToken.setToken(access_token_obj.getToken());newToken.setExpiresIn(Integer.valueOf(System.currentTimeMillis()/1000+""));String json = gson.toJson(newToken);fos.write((json).getBytes());fos.close();}}}return mAccess_token;}public static void main(String args[]) throws IOException {WxAccessToken.getSavedAccessToken();}
}

最终缓存到本地磁盘的access_token.txt内容格式如下:

{"token":"14__rU4rUKeTPSqv6Y4l1r1gnR9M3rl07RG-5Q_D0os6TmOP8204OlwSjmGSKP7KuRVffopvrY9t3vi78GWUsC2wd8jG1CdfTrZQbUirxiMp3KJxh3zhYjVABD_ixd6pqd8v5GNEkWPM6tFzYxTGEOhABAVTD","expiresIn":1538225055}

在项目中要用到access_token的地方就可以引用这个工具类,动态获取access_token,根据动态获取的access_token生成的jsapi_ticket自然也是动态的,有效期2个小时。

方式二、通过servlet启动线程,让线程定时执行获取

(1).写一个线程,在线程中实现定时获取accessToken。代码如下:

package com.huaqi.payment.util;import com.huaqi.payment.domain.AccessToken;import java.io.*;public class DynamicTokenThread  implements Runnable {private static AccessToken access_token = null;@Overridepublic void run() {while(true){try {//调用工具类获取access_token(每日最多获取2000次,每次获取的有效期为7200秒)access_token = WeiXinUtil.getToken();System.out.println(System.getProperty("user.di"));OutputStream  os = new FileOutputStream(Constants.LOCAL_ACCESS_TOKEN_path,false);byte[] data = access_token.getToken().getBytes();
//              byte[] data  = access_token.getToken().getBytes();os.write(data, 0, data.length);    //3、写入文件os.flush();    //将存储在管道中的数据强制刷新出去if (null != access_token) {//7000秒之后重新进行获取Thread.sleep((access_token.getExpiresIn() - 200) * 1000);} else {//获取失败时,60秒之后尝试重新获取Thread.sleep(60 * 1000);}}catch (InterruptedException e) {e.printStackTrace();}catch (FileNotFoundException e) {e.printStackTrace();System.out.println("文件没有找到!");}catch (IOException e){e.printStackTrace();System.out.println("写入文件失败!");}}}
}

  (2). 写一个servlet去开启这个线程。

package com.huaqi.payment.server;import com.huaqi.payment.util.DynamicTokenThread;import javax.servlet.http.HttpServlet;
/*** 编写servlet并在servlet初始化时启动该线程**/
public class GetAccessTokenServlet extends HttpServlet {@Overridepublic void init(){new Thread(new DynamicTokenThread()).start();//启动动态获取access_token的线程}
}

3. 在web.xml中去配置servlet,设置成启动tomcat时就启动该线程,然后每隔一定时间就会去执行一次该线程(自动获取一次请求动态得到access_token)

<servlet><servlet-name>getAccessTokenServlet</servlet-name><servlet-class>com.huaqi.payment.server.GetAccessTokenServlet</servlet-class><load-on-startup>1</load-on-startup>
</servlet><servlet-mapping><servlet-name>getAccessTokenServlet</servlet-name><url-pattern>/*</url-pattern>
</servlet-mapping>

以上内堪称最全面的Java获取公众号的access_token和jsapi_ticket以及全局缓存公众号accessToken和jsapi_ticket案例详解,更多技术干货敬请持续关注博主。欢迎广大开发者朋友一起学习交流,联系笔者电话(同微信):18629374628

 

 

 

这篇关于Java开发公众号系列教程(二):公众号开发全局缓存access_token和jsapi_ticket的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/154712

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

Hadoop企业开发案例调优场景

需求 (1)需求:从1G数据中,统计每个单词出现次数。服务器3台,每台配置4G内存,4核CPU,4线程。 (2)需求分析: 1G / 128m = 8个MapTask;1个ReduceTask;1个mrAppMaster 平均每个节点运行10个 / 3台 ≈ 3个任务(4    3    3) HDFS参数调优 (1)修改:hadoop-env.sh export HDFS_NAMENOD

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory