重要声明:本文章仅仅代表了作者个人对此观点的理解和表述。读者请查阅时持自己的意见进行讨论。
目录
本系列博文还包含了下面的博客:
- 【微信公众号开发】一、运作及配置流程简介
 - 【微信公众号开发】二、解析微信请求及响应消息(本文)
 - 【微信公众号开发】三、解析微信事件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方法可以看到,当请求中没有 随机字符串时,我们还要处理好真正的来自用户的各种事件请求。下一篇将介绍如何响应用户的发送的消息。