重要声明:本文章仅仅代表了作者个人对此观点的理解和表述。读者请查阅时持自己的意见进行讨论。
目录
本系列博文还包含了下面的博客:
- 【微信公众号开发】一、运作及配置流程简介
- 【微信公众号开发】二、解析微信请求及响应消息(本文)
- 【微信公众号开发】三、解析微信事件XML数据消息及响应
- 【微信公众号开发】四、公众号按钮设置及自己的微信按钮编辑器
- 【微信公众号开发】五、微信网页授权获取用户openId
- 【微信公众号开发】六、微信JS的使用
- 【微信公众号开发】七、微信JS需要注意的坑
- 【微信公众号开发】八、微信JS发起支付
微信开发第一步即为配置我方服务器的接口地址,为了成功配置,需要在配置前先将此接口根据微信的调用流程来开发完成,才可能成功的配置到微信后台。如果还不清楚微信调用此接口地址的流程,请先阅读上一篇文章《【微信公众号开发】一、运作及流程简介》。
一、创建 Controller
在开始写接口之前,我先创建一个专门用于处理微信请求的 Controller
,集中将微信的请求在这个 Controller
里面完成。我将创建一个适用于 SpringBoot
或 SpringMVC
项目的 Controller
,取名为 WeChatController
。下面贴出了创建好的程序代码:
// WeChatController.java
@Controller
@RequestMapping("/wechat")
public class WeChatController {
// 配置到微信后台的地址。
@RequestMapping("/config")
@ResponseBody
public String config(HttpServletRequest request) throws Exception {
// TODO 待完成。
return null;
}
}
现在,只需要完成这个 config
方法,即可完成对微信请求的处理。我将使用几步分开来完成对微信请求的处理,这几步可以大致总结为如下:
- 获取请求参数
- 对参数进行排序拼接
- 加密参数对比是否正确
- 响应结果
二、获取微信配置参数
阅读了上一篇文章《【微信公众号开发】一、运作及流程简介》便可知道,在微信后台配置时,微信发送过来的请求是GET方式的,要在GET请求中传递参数,只能通过url
链接上拼接传递参数过来,这样的参数(在url链接上的)通常我们叫做 Query
参数,整个参数体的格式是 x-www-form-urlencoded
格式的,对于这样的格式,可以轻松的通过 HttpServletRequest
对象获取到。下面则开始获取微信请求来的参数。
// 配置到微信后台的地址。
@RequestMapping("/config")
@ResponseBody
public String config(HttpServletRequest request) throws Exception {
// 获取微信请求参数
String signature = request.getParameter("signature");
String timestamp = request.getParameter("timestamp");
String nonce = request.getParameter("nonce");
String echostr = request.getParameter("echostr");
// 判断echostr不为空,表示这是配置时的请求。
if(!Util.isEmpty(echostr)) {
}
return null;
}
要特别注意,这个接口其实并不止是在后台配置时调用,以后每一次用户在公众号里的操作所触发的事件,都会通过这个接口通知到我方服务器。所以,十分有必要区分一下是在后台配置时的调用还是用户事件的调用,有一个比较简单的方法,如果发现微信请求传递了 echostr
字段,就表明这只是在配置时的调用,用户事件的推送是不会有这个字段传递过来的。
三、对参数排序拼接
微信官方接入文档中说明了,要先将 token
、timestamp
、nonce
三个参数进行字典序排序, 然后进行sha1加密。所谓字典排序,说简单一点可以是按照字母a->z排序,好在java帮我们写好了许多的快捷排序方法。现在进行排序并拼接字符串。
// 配置到微信后台的地址。
@RequestMapping("/config")
@ResponseBody
public String config(HttpServletRequest request) throws Exception {
// 获取微信请求参数
String signature = request.getParameter("signature");
String timestamp = request.getParameter("timestamp");
String nonce = request.getParameter("nonce");
String echostr = request.getParameter("echostr");
// 参数排序。token 就要换成自己实际写的token
String[] params = new String[] {timestamp, nonce, "token"};
Arrays.sort(params);
// 拼接
String paramStr = params[0] + params[1] + params[2];
// 判断echostr不为空,表示这是配置时的请求。
if(!Util.isEmpty(echostr)) {
}
return null;
}
此处 token
字段的值需要换成自己后台将要使用的token值,我使用字符串token仅仅意思一下。排序方面使用了Java现成的Arrays.sort
方法实现。现在可以对拼接好的字符串结果paramStr
进行sha1
加密操作了。
四、加密参数对比
对于加密这一块,复杂度往往都是比较高的,java设计加密解密时,不仅仅只针对某一种方式,而是可以实现多种不同的加解密使用较为统一的加解密流程,我将介绍在java里使用sha1
的加密方式,这种代码流程可能适用于其他的加密方式,这样降低了不同加密算法的程序上的学习成本。
要使用Java进行不同算法加密,先要在java里获取对应算法的封装类,这里我们使用 sha1
算法,使用 MessageDigest.getInstance()
方法就可以方便的获取到不同的算法封装对象,只需要我们传入对应的算法就可以了。示列代码如下:
// 加密
// 获取sha1算法封装类
MessageDigest sha1Digest = MessageDigest.getInstance("SHA-1");
// 进行加密
byte[] digestResult = sha1Digest.digest(paramStr.getBytes("UTF-8"));
由于 digest 需要byte数组二进制数据,所以,将拼接好的字符串通过UTF-8编码获取到二进制数据,然后进行加密,加密完成后返回了加密结果的二进制数据。这个结果二进制数据是不能直接转换成可视字符串的,如果直接使用 String 进行字符串构建,出来的结果是一堆乱码数据。所以为了将结果以较友好的可视字符串表示出来,常常使用 16进制字符串来表示sha1算法的结果,为此,需要为这个需求专门写一个方法:将二进制byte数组转换为16进制字符串。
class Util {
// 将二进制数据转换为16进制字符串。
public static String byte2HexString(byte[] src) {
StringBuilder stringBuilder = new StringBuilder();
if (src == null || src.length <= 0) {
return null;
}
for (byte b : src) {
String hv = Integer.toHexString(b & 0xFF);
if (hv.length() < 2) {
stringBuilder.append(0);
}
stringBuilder.append(hv);
}
return stringBuilder.toString();
}
// ...
}
有了这个方法,现在可以把加密的结果转换为字符串了:
// 加密
// 获取sha1算法封装类
MessageDigest sha1Digest = MessageDigest.getInstance("SHA-1");
// 进行加密
byte[] digestResult = sha1Digest.digest(paramStr.getBytes("UTF-8"));
// 拿到加密结果
String mySignature = Util.byte2HexString(digestResult);
现在,只需要对比一下请求传入的签名和我们自己加密的结果是否相同,就能确定这次请求是不是真的来自微信后台了:
// 是否正确
boolean signSuccess = mySignature.equals(signature);
将自己得到的加密结果和传入的进行对比。
五、响应结果
一切困难都已被攻克,只要告诉我们的校验结果,就完成了此接口的最后一步开发:
if (!signSuccess) {
return "signature check fail"; // 不正确就直接返回失败提示。
}
// 判断echostr不为空,表示这是配置时的请求。
if(!Util.isEmpty(echostr)) {
return echostr; // 正确,返回传入的随机字符串。
}
如果校验不通过,直接返回失败文字。校验通过就会继续后面的代码,检测到这是配置请求,就返回原样传入的随机字符串。最终,完整的接口方法代码是:
// 配置到微信后台的地址。
@RequestMapping("/config")
@ResponseBody
public String config(HttpServletRequest request) throws Exception {
// 获取微信请求参数
String signature = request.getParameter("signature");
String timestamp = request.getParameter("timestamp");
String nonce = request.getParameter("nonce");
String echostr = request.getParameter("echostr");
// 参数排序。 token 就要换成自己实际写的token
String[] params = new String[] {timestamp, nonce, "token"};
Arrays.sort(params);
// 拼接
String paramStr = params[0] + params[1] + params[2];
// 加密
// 获取sha1算法封装类
MessageDigest sha1Digest = MessageDigest.getInstance("SHA-1");
// 进行加密
byte[] digestResult = sha1Digest.digest(paramStr.getBytes("UTF-8"));
// 拿到加密结果
String mySignature = Util.byte2HexString(digestResult);
// 是否正确
boolean signSuccess = mySignature.equals(signature);
if (!signSuccess) {
return "signature check fail"; // 不正确就直接返回失败提示。
}
// 判断echostr不为空,表示这是配置时的请求。
if(!Util.isEmpty(echostr)) {
return echostr; // 正确,返回传入的随机字符串。
}
return null;
}
六、继续开发
完成了接口配置,才真正是步入微信开发的大门,从config方法可以看到,当请求中没有 随机字符串时,我们还要处理好真正的来自用户的各种事件请求。下一篇将介绍如何响应用户的发送的消息。