本文为原创,原始地址为http://www.cnblogs.com/fengzheng/p/5027630.html

上一篇说了微信开发的准备工作,准备工作完成之后,就要开始步入正题了。其实微信公众号开发,说白了,就是要构造和发送http或https的请求组成,并根据请求的返回数据做逻辑处理。

今天就来说一说微信开发第一步,公众号接入以及access_token的管理。

微信公众号接入

在微信公众号开发手册上,关于公众号接入这一节内容还是写的比较详细的,文档中说接入公众号需要3个步骤,分别是:

1、填写服务器配置
2、验证服务器地址的有效性
3、依据接口文档实现业务逻辑

其实,第3步已经不能算做公众号接入的步骤,而是接入之后,开发人员可以根据微信公众号提供的接口所能做的一些开发。

第1步中服务器配置包含服务器地址(URL)、Token和EncodingAESKey。

服务器地址即公众号后台提供业务逻辑的入口地址,目前只支持80端口,之后包括接入验证以及任何其它的操作的请求(例如消息的发送、菜单管理、素材管理等)都要从这个地址进入。接入验证和其它请求的区别就是,接入验证时是get请求,其它时候是post请求;

Token可由开发者可以任意填写,用作生成签名(该Token会和接口URL中包含的Token进行比对,从而验证安全性);

EncodingAESKey由开发者手动填写或随机生成,将用作消息体加解密密钥。本例中全部以未加密的明文消息方式,不涉及此配置项。

第2步,验证服务器地址的有效性,当点击“提交”按钮后,微信服务器将发送一个http的get请求到刚刚填写的服务器地址,并且携带四个参数:

接到请求后,我们需要做如下三步,若确认此次GET请求来自微信服务器,原样返回echostr参数内容,则接入生效,否则接入失败。

1. 将token、timestamp、nonce三个参数进行字典序排序
2. 将三个参数字符串拼接成一个字符串进行sha1加密
3. 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信

代码会说话,以下是我定义的一个入口servlevt,在其中的doGet方法中定义校验方法:

    //token
private final String token = "fengzheng"; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("开始签名校验");
String signature = request.getParameter("signature");
String timestamp = request.getParameter("timestamp");
String nonce = request.getParameter("nonce");
String echostr = request.getParameter("echostr"); ArrayList<String> array = new ArrayList<String>();
array.add(signature);
array.add(timestamp);
array.add(nonce); //排序
String sortString = sort(token, timestamp, nonce);
//加密
String mytoken = Decript.SHA1(sortString);
//校验签名
if (mytoken != null && mytoken != "" && mytoken.equals(signature)) {
System.out.println("签名校验通过。");
response.getWriter().println(echostr); //如果检验成功输出echostr,微信服务器接收到此输出,才会确认检验完成。
} else {
System.out.println("签名校验失败。");
}
} /**
* 排序方法
* @param token
* @param timestamp
* @param nonce
* @return
*/
public static String sort(String token, String timestamp, String nonce) {
String[] strArray = { token, timestamp, nonce };
Arrays.sort(strArray); StringBuilder sbuilder = new StringBuilder();
for (String str : strArray) {
sbuilder.append(str);
} return sbuilder.toString();
}

以下代码是加密的方法:

public class Decript {

    public static String SHA1(String decript) {
try {
MessageDigest digest = MessageDigest
.getInstance("SHA-1");
digest.update(decript.getBytes());
byte messageDigest[] = digest.digest();
// Create Hex String
StringBuffer hexString = new StringBuffer();
// 字节数组转换为 十六进制 数
for (int i = 0; i < messageDigest.length; i++) {
String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
if (shaHex.length() < 2) {
hexString.append(0);
}
hexString.append(shaHex);
}
return hexString.toString(); } catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return "";
}
}

servlet映射的xml如下:

 <servlet>
<servlet-name>Start</servlet-name>
<servlet-class>org.fengzheng.wechat.Start</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Start</servlet-name>
<url-pattern>/wechat</url-pattern>
</servlet-mapping>

  

我这里用的是IntelliJ IDEA+tomcat7.0开发,直接启动项目,然后用ngrok将本地8080端口映射到外网。进入微信测试公众号管理界面,在接口配置信息中填入映射的外网地址和token

点击提交按钮,页面会提示配置成功,

会到IDE,看到控制台中输出了信息

  

access_token管理

在将access_token之前,还有两个重要参数需要知晓,这两个参数分别是appID和appsecret,这是在申请公众号的时候自动分配给公众号的,相当于公众号的身份标示,在很多接口中需要这两个参数,接下来在请求access_token的时候就需要这两个参数。

公众号接入成功之后,接下来就要实现相应的逻辑了。在使用微信公众号接口中,发现有许多请求都需要access_token。access_token是公众号的全局唯一凭证,公众号调用各接口时都需使用access_token。开发者需要进行妥善保存。access_token的存储至少要保留512个字符空间。access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access_token失效。并且每天调用获取access_token接口的上限是2000次。

总结以上说明,access_token需要做到以下两点:

1.因为access_token有2个小时的时效性,要有一个机制保证最长2个小时重新获取一次;

2.因为接口调用上限每天2000次,所以不能调用太频繁;

就此,这里采用的方案是这样的,定义一个默认启动的servlet,在init方法中启动一个Thread,这个进程中定义一个无限循环的方法,用来获取access_token,当获取成功后,此进程休眠7000秒,否则休眠3秒钟继续获取。流程图如下:

下面正式开始在工程中实现以上思路,因为返回的数据都是json格式,这里会用到阿里的fastjson库,为构造请求和处理请求后的数据序列化和反序列化提供支持。后续的其它接口也会用到。

1.定义一个AccessToken实体

public class AccessToken {
public String getAccessToken() {
return accessToken;
} public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
} public int getExpiresin() {
return expiresin;
} public void setExpiresin(int expiresin) {
this.expiresin = expiresin;
} private String accessToken; private int expiresin;
}

2.定义一个默认启动的servlet,在init方法中启动一个Thread,并在web.xml中将这个servlet设置为默认自启动的。

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException; /**
* Created by huzhicheng on 2015/12/8.
*/
@WebServlet(name = "AccessTokenServlet")
public class AccessTokenServlet extends HttpServlet { public void init() throws ServletException {
TokenThread.appId = getInitParameter("appid"); //获取servlet初始参数appid和appsecret
TokenThread.appSecret = getInitParameter("appsecret");
System.out.println("appid:"+TokenThread.appId);
System.out.println("appSecret:"+TokenThread.appSecret);
new Thread(new TokenThread()).start(); //启动进程
} protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { }
}

在web.xml中设置servlet自启动,并设置初始化参数appid和appsecret

<servlet>
<servlet-name>initAccessTokenServlet</servlet-name>
<servlet-class>
org.fengzheng.wechat.accesstoken.AccessTokenServlet
</servlet-class>
<init-param>
<param-name>appid</param-name>
<param-value>your appid</param-value>
</init-param>
<init-param>
<param-name>appsecret</param-name>
<param-value>your appsecret</param-value>
</init-param>
<load-on-startup>0</load-on-startup>
</servlet>

3.定义Thread类,在此类中调用access_token获取接口,并将得到的数据抽象到静态实体,以便在其它地方使用。接口地址为https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET,其中grant_type固定写为client_credential即可。此请求为https的get请求,返回的数据格式为{"access_token":"ACCESS_TOKEN","expires_in":7200}。

进程类实现如下:

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.fengzheng.wechat.common.NetWorkHelper; /**
* Created by huzhicheng on 2015/11/5.
*/
public class TokenThread implements Runnable {
public static String appId = ""; public static String appSecret= "";

  //注意是静态的
public static AccessToken accessToken = null; public void run(){
while (true){
try{
accessToken = this.getAccessToken();
if(null!=accessToken){
System.out.println(accessToken.getAccessToken());
Thread.sleep(7000 * 1000); //获取到access_token 休眠7000秒 }else{
Thread.sleep(1000*3); //获取的access_token为空 休眠3秒
}
}catch(Exception e){
System.out.println("发生异常:"+e.getMessage());
e.printStackTrace();
try{
Thread.sleep(1000*10); //发生异常休眠1秒
}catch (Exception e1){ }
}
}
} /**
* 获取access_token
* @return
*/
private AccessToken getAccessToken(){
NetWorkHelper netHelper = new NetWorkHelper();
String Url = String.format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s",this.appId,this.appSecret);
String result = netHelper.getHttpsResponse(Url,"");
System.out.println(result);
//response.getWriter().println(result);
JSONObject json = JSON.parseObject(result);
AccessToken token = new AccessToken();
token.setAccessToken(json.getString("access_token"));
token.setExpiresin(json.getInteger("expires_in"));
return token;
}
}

其中NetWorkHelper中getHttpsResponse方法是请求一个https地址,参数requestMethod为字符串“GET”或者“POST”,传null或者“”默认为get方式。

实现如下:

public String getHttpsResponse(String hsUrl,String requestMethod) {
URL url;
InputStream is = null;
String resultData = "";
try {
url = new URL(hsUrl);
HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
TrustManager[] tm = {xtm}; SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(null, tm, null); con.setSSLSocketFactory(ctx.getSocketFactory());
con.setHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String arg0, SSLSession arg1) {
return true;
}
}); con.setDoInput(true); //允许输入流,即允许下载 //在android中必须将此项设置为false
con.setDoOutput(false); //允许输出流,即允许上传
con.setUseCaches(false); //不使用缓冲
if(null!=requestMethod && !requestMethod.equals("")) {
con.setRequestMethod(requestMethod); //使用指定的方式
}
else{
con.setRequestMethod("GET"); //使用get请求
}
is = con.getInputStream(); //获取输入流,此时才真正建立链接
InputStreamReader isr = new InputStreamReader(is);
BufferedReader bufferReader = new BufferedReader(isr);
String inputLine = "";
while ((inputLine = bufferReader.readLine()) != null) {
resultData += inputLine + "\n";
}
System.out.println(resultData); Certificate[] certs = con.getServerCertificates(); int certNum = 1; for (Certificate cert : certs) {
X509Certificate xcert = (X509Certificate) cert;
} } catch (Exception e) {
e.printStackTrace();
}
return resultData;
} X509TrustManager xtm = new X509TrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() {
// TODO Auto-generated method stub
return null;
} @Override
public void checkServerTrusted(X509Certificate[] arg0, String arg1)
throws CertificateException {
// TODO Auto-generated method stub } @Override
public void checkClientTrusted(X509Certificate[] arg0, String arg1)
throws CertificateException {
// TODO Auto-generated method stub }
};

至此代码实现完毕,将项目部署,看到控制台输出如下:

  

为方面看效果,可以把休眠时间设置短一点,比如30秒获取一次,然后将access_token输出。下面做一个测试jsp页面,并把休眠时间设置为30秒,这样过30秒刷新页面,就可以看到变化,顺便演示一下在其它地方如何拿到access_token

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="org.fengzheng.wechat.accesstoken.TokenThread" %>
<html>
<head>
<title></title>
</head>
<body>
access_token为:<%=TokenThread.accessToken.getAccessToken()%>
</body>
</html>

这样在浏览器上浏览这个页面,显示效果如下:

30秒后刷新,这个值发生了变化:

  

代码已上传至github,仓库会随时更新,目前只有本篇所讲的代码。

欢迎关注公众号「gushidefengzheng」古时的风筝

还可以加入 Java 微信讨论群(如果二维码过期:请加微信:fengdezitai001 ,备注:cnblogs):

用java开发微信公众号:公众号接入和access_token管理(二)的更多相关文章

  1. Java开发微信公众号(五)---微信开发中如何获取access_token以及缓存access_token

    获取access_token是微信api最重要的一个部分,因为调用其他api很多都需要用到access_token.比如自定义菜单接口.客服接口.获取用户信息接口.用户分组接口.群发接口等在请求的时候 ...

  2. Java开发微信公众号(四)---微信服务器post消息体的接收及消息的处理

    在前几节文章中我们讲述了微信公众号环境的搭建.如何接入微信公众平台.以及微信服务器请求消息,响应消息,事件消息以及工具处理类的封装:接下来我们重点说一下-微信服务器post消息体的接收及消息的处理,这 ...

  3. Java开发微信公众号(三)---微信服务器请求消息,响应消息,事件消息以及工具处理类的封装

    在前面几篇文章我们讲了微信公众号环境的配置 和微信公众号服务的接入,接下来我们来说一下微信服务器请求消息,响应消息以及事件消息的相关内容,首先我们来分析一下消息类型和返回xml格式及实体类的封装. ( ...

  4. Java开发微信公众号(二)---开启开发者模式,接入微信公众平台开发

    接入微信公众平台开发,开发者需要按照如下步骤完成: 1.填写服务器配置 2.验证服务器地址的有效性 3.依据接口文档实现业务逻辑 资料准备: 1.一个可以访问的外网,即80的访问端口,因为微信公众号接 ...

  5. Java开发微信公众号(一)---初识微信公众号以及环境搭建

    ps:1.开发语言使用Java springMvc+Mybaits+spring maven实现 2.使用微信接口测试账号进行本地测试 https://mp.weixin.qq.com/debug/c ...

  6. 用java开发微信公众号:测试公众号与本地测试环境搭建(一)

    本文为原创,原始地址为:http://www.cnblogs.com/fengzheng/p/5023678.html 俗话说,工欲善其事,必先利其器.要做微信公众号开发,两样东西不可少,那就是要有一 ...

  7. JAVA开发微信支付-公众号支付/微信浏览器支付(JSAPI)

    写这篇文章的目的有2个,一是自己的项目刚开发完微信支付功能,趁热回个炉温习一下,二也是帮助像我这样对微信支付不熟悉,反复看了多天文档还是一知半解,原理都没摸清,更不要说实现了.本以为网上的微信开发教程 ...

  8. 使用Java开发微信公众平台(二)——消息的接收与响应

    上一篇文章(http://www.jerehedu.com/fenxiang/171807_for_detail.htm )中,我们学习了使用Java语言开发微信公众平台的第一部分——环境搭建与开发接 ...

  9. 使用Java开发微信公众平台(四)——消息的接收与响应

    上一篇文章(http://www.jerehedu.com/fenxiang/171807_for_detail.htm )中,我们学习了使用Java语言开发微信公众平台的第一部分——环境搭建与开发接 ...

随机推荐

  1. ASP.NET Core 之 Identity 入门(一)

    前言 在 ASP.NET Core 中,仍然沿用了 ASP.NET里面的 Identity 组件库,负责对用户的身份进行认证,总体来说的话,没有MVC 5 里面那么复杂,因为在MVC 5里面引入了OW ...

  2. 如何一步一步用DDD设计一个电商网站(二)—— 项目架构

    阅读目录 前言 六边形架构 终于开始建项目了 DDD中的3个臭皮匠 CQRS(Command Query Responsibility Segregation) 结语 一.前言 上一篇我们讲了DDD的 ...

  3. Android raw to bmp

    Android raw 格式转 bmp 图像 raw 保存的为裸数据,转换时都需要把它转成RGBA 的方式来显示.其中: 8位RAW: 四位RGBA 来表示一位灰度; 24位RAW: 三位RGB相同, ...

  4. ASP.NET从零开始学习EF的增删改查

           ASP.NET从零开始学习EF的增删改查           最近辞职了,但是离真正的离职还有一段时间,趁着这段空档期,总想着写些东西,想来想去,也不是很明确到底想写个啥,但是闲着也是够 ...

  5. 利用注册表在右键添加VS15的快捷方式打开文件夹

    1.简介 最近安装VS15 Preview 5,本版本可以打开"文件夹" 是否可以向Visual Studio Code一样在文件夹或文件右键菜单添加"Open with ...

  6. .NET Core 2016 回顾

    都在回顾自己的2016,今天我们来看看.NET Core的2016. 每一年的脚步的确是快,转眼间马上就2017.新的一年,带着理想和抱负继续出发. 1 月 ASP.NET 5 改名 ASP.NET ...

  7. 使用Expression实现数据的任意字段过滤(1)

    在项目常常要和数据表格打交道. 现在BS的通常做法都是前端用一个js的Grid控件, 然后通过ajax的方式从后台加载数据, 然后将数据和Grid绑定. 数据往往不是一页可以显示完的, 所以要加分页: ...

  8. MongoDB备份(mongodump)和恢复(mongorestore)

    MongoDB提供了备份和恢复的功能,分别是MongoDB下载目录下的mongodump.exe和mongorestore.exe文件 1.备份数据使用下面的命令: >mongodump -h ...

  9. FineReport:任意时刻只允许在一个客户端登陆账号的插件

    在使用FineReport报表系统中,处于账户安全考虑,有些企业希望同一账号在任意时刻智能在统一客户端登录.那么当A用户在C1客户端登陆后,该账号又在另外一个C2客户端登陆,服务器如何取判断呢? 开发 ...

  10. org.apache.ibatis.binding.BindingException: Invalid bound statement (not found):

    org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): da.huying.usermanag ...