【微信公众号开发】二、解析微信请求及响应消息

by img Microanswer Create at:Jul 1, 2019 3:35:13 PM 

Tags: 微信公众号 Java 前端 h5 微信



重要声明:本文章仅仅代表了作者个人对此观点的理解和表述。读者请查阅时持自己的意见进行讨论。

目录

本系列博文还包含了下面的博客:

  1. 【微信公众号开发】一、运作及配置流程简介
  2. 【微信公众号开发】二、解析微信请求及响应消息(本文)
  3. 【微信公众号开发】三、解析微信事件XML数据消息及响应
  4. 【微信公众号开发】四、公众号按钮设置及自己的微信按钮编辑器
  5. 【微信公众号开发】五、微信网页授权获取用户openId
  6. 【微信公众号开发】六、微信JS的使用
  7. 【微信公众号开发】七、微信JS需要注意的坑
  8. 【微信公众号开发】八、微信JS发起支付

微信开发第一步即为配置我方服务器的接口地址,为了成功配置,需要在配置前先将此接口根据微信的调用流程来开发完成,才可能成功的配置到微信后台。如果还不清楚微信调用此接口地址的流程,请先阅读上一篇文章《【微信公众号开发】一、运作及流程简介》

一、创建 Controller

在开始写接口之前,我先创建一个专门用于处理微信请求的 Controller ,集中将微信的请求在这个 Controller 里面完成。我将创建一个适用于 SpringBootSpringMVC 项目的 Controller,取名为 WeChatController。下面贴出了创建好的程序代码:

// WeChatController.java

@Controller
@RequestMapping("/wechat")
public class WeChatController {

    // 配置到微信后台的地址。
    @RequestMapping("/config")
    @ResponseBody
    public String config(HttpServletRequest request) throws Exception {
        // TODO 待完成。
        return null;
    }
}

现在,只需要完成这个 config 方法,即可完成对微信请求的处理。我将使用几步分开来完成对微信请求的处理,这几步可以大致总结为如下:

  1. 获取请求参数
  2. 对参数进行排序拼接
  3. 加密参数对比是否正确
  4. 响应结果

二、获取微信配置参数

阅读了上一篇文章《【微信公众号开发】一、运作及流程简介》便可知道,在微信后台配置时,微信发送过来的请求是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 字段,就表明这只是在配置时的调用,用户事件的推送是不会有这个字段传递过来的。

三、对参数排序拼接

微信官方接入文档中说明了,要先将 tokentimestampnonce 三个参数进行字典序排序, 然后进行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方法可以看到,当请求中没有 随机字符串时,我们还要处理好真正的来自用户的各种事件请求。下一篇将介绍如何响应用户的发送的消息。

下一篇: 【微信公众号开发】三、解析微信事件XML数据消息及响应

Full text complete, Reproduction please indicate the source. Help you? Not as good as one:
Comment(Comments need to be logged in. You are not logged in.)
You need to log in before you can comment.

Comments (0 Comments)