java对接微信公众号

前言

微信官方提供了非常完善的接入文档,如果想了解文档的具体内容,可直接浏览微信开发文档。但是为了方便开发,一般不会直接去根据微信开发文档进行开发,github上有许多开源项目对微信开发文档进行了封装,可自行下载。

一、填写服务器配置

在这里插入图片描述

参数解析

  1. URL:是开发者用来接收微信消息和事件推送的接口URL(POST),另外,同一个路径下的GET请求为token验证接口。
  2. Token:可由开发者可以任意填写,用作生成签名(该Token会和接口URL中包含的Token进行比对,从而验证安全性)
  3. EncodingAESKey:由开发者手动填写或随机生成,将用作消息体加解密密钥,推荐使用兼容模式

二、开发验证服务器有效性接口

GET请求进行验证token有效性

    @ApiOperation("微信配置推送地址验证token")
    @RequestMapping(value = "/wx", method = RequestMethod.GET)
    public String checkSignature(@RequestParam(name = "signature", required = false) String signature,
                                 @RequestParam(name = "nonce", required = false) String nonce,
                                 @RequestParam(name = "timestamp", required = false) String timestamp,
                                 @RequestParam(name = "echostr", required = false) String echostr) {
        System.out.println("get有消息来了!");

        // 通过检验signature对请求进行校验,若校验成功则原样返回echostr,表示接入成功,否则接入失败
        log.info("接收参数:" + "===========signature:" + signature + "===========timestamp:" + timestamp + "===========nonce:" + nonce);
        Boolean flag = false;
        try {
            flag = SignUtil.checkSignature(signature, timestamp, nonce);
        } catch (Exception e) {
            flag = false;
        }
        if (flag) {
            log.info("接入成功");
            return echostr;
        }
        log.error("接入失败");
        return "";
    }

Utils

package com.riskeys.sd.custom.utils;

import java.security.*;
import java.util.Arrays;
import java.util.Map;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * 提供签名服务。 目前提供的签名服务包括:SHA-1,SHA256,SHA256withRSA.
 */
@Slf4j
@Component
public class SignUtil {

    private static String CHARSET_UTF8 = "UTF-8";
    private static String SHA256withRSA = "SHA256withRSA";

    private static String checkToken;

    @Value("${demo.weChat.checkToken}")
    private void setCheckToken(String token) {
        checkToken = token;
    }

    public SignUtil() {
    }


    /**
     * SHA256withRSA算法签名
     *
     * @param text          需要签名的字符串
     * @param privateKeyStr 私钥
     * @return
     */
    public static String makeSignSHA256withRSA(String text, String privateKeyStr) {
        if (text == null || privateKeyStr == null) {
            return "";
        }
        try {
            // 获取私钥
            PrivateKey privateKey = OcpRSAUtil.getPrivateKey(privateKeyStr);
            // 进行签名服务
            Signature signature = Signature.getInstance(SHA256withRSA);
            signature.initSign(privateKey);
            signature.update(text.getBytes(CHARSET_UTF8));
            byte[] signedData = signature.sign();
            return Base64.encodeBase64String(signedData);
        } catch (Exception e) {
            log.error("签名失败", e);
        }
        return "";
    }

    /**
     * SHA256withRSA算法验签
     * @param text         待验签的字符串
     * @param signedData   待验签的签名
     * @param publicKeyStr 公钥
     * @return
     */
    public static boolean verifySHA256withRSA(String text, String signedData, String publicKeyStr) {
        if (text == null || signedData == null || publicKeyStr == null) {
            return false;
        }
        try {
            PublicKey publicKey = OcpRSAUtil.getPublicKey(publicKeyStr);
            Signature signature = Signature.getInstance(SHA256withRSA);
            signature.initVerify(publicKey);
            signature.update(text.getBytes(CHARSET_UTF8));
            return signature.verify(Base64.decodeBase64(signedData));
        } catch (Exception e) {
            log.error("验签失败", e);
        }
        return false;
    }

    /**
     * 验证签名
     *
     * @param signature
     * @param timestamp
     * @param nonce
     * @return
     */
    public static boolean checkSignature(String signature, String timestamp, String nonce) {
        String[] arr = new String[]{checkToken, timestamp, nonce};
        // 将token、timestamp、nonce三个参数进行字典序排序
        Arrays.sort(arr);
        StringBuilder content = new StringBuilder();
        for (int i = 0; i < arr.length; i++) {
            content.append(arr[i]);
        }
        MessageDigest md = null;
        String tmpStr = null;

        try {
            md = MessageDigest.getInstance("SHA-1");
            // 将三个参数字符串拼接成一个字符串进行sha1加密
            byte[] digest = md.digest(content.toString().getBytes());
            tmpStr = byteToStr(digest);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }

        content = null;
        // 将sha1加密后的字符串可与signature对比,标识该请求来源于微信
        return tmpStr != null ? tmpStr.equals(signature.toUpperCase()) : false;
    }

    /**
     * 将字节数组转换为十六进制字符串
     *
     * @param byteArray
     * @return
     */
    private static String byteToStr(byte[] byteArray) {
        String strDigest = "";
        for (int i = 0; i < byteArray.length; i++) {
            strDigest += byteToHexStr(byteArray[i]);
        }
        return strDigest;
    }

    /**
     * 将字节转换为十六进制字符串
     *
     * @param mByte
     * @return
     */
    private static String byteToHexStr(byte mByte) {
        char[] Digit = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
        char[] tempArr = new char[2];
        tempArr[0] = Digit[(mByte >>> 4) & 0X0F];
        tempArr[1] = Digit[mByte & 0X0F];

        String s = new String(tempArr);
        return s;
    }

}

三、开发处理微信事件推送接口

POST请求进行事件处理(业务处理)

    @ApiOperation("接收微信推送通知(扫码关注,取消关注,扫码已关注,普通互动消息)")
    @PostMapping(value = "/wx", produces = "application/xml")
    public String pushEventsPost(HttpServletRequest request) throws Exception {
        String method = request.getMethod();
        if ("POST".equals(method)) {
            weChatEventsService.pushEvents(request);
        }
        return "success";
    }

service

    @Override
    public void pushEvents(HttpServletRequest request) {
        String xmlStr = "";
        try {
            xmlStr = XmlUtil.inputStream2StringNew(request.getInputStream());
            Map<String, String> requestMap = XmlUtil.parseXml(xmlStr);
            String json = JSONObject.toJSONString(requestMap);
            log.info("WeChatEventsServiceImpl pushEvents request json: " + json);
            if (!StringUtils.isEmpty(requestMap.get("event"))) {
                WeChatAttentionDto weChatAttentionDto = JSON.parseObject(JSON.toJSONString(requestMap), WeChatAttentionDto.class);
                //处理推送消息
                this.weChatEvents(weChatAttentionDto);
            } else if (!StringUtils.isEmpty(requestMap.get("msgType"))) {
                //暂不进行处理
            }
        } catch (Exception e) {
            log.error("WeChatEventsServiceImpl pushEvents xml解析失败:" + xmlStr);
            e.printStackTrace();
        }
    }

XmlUtil

package com.riskeys.tobtoc.util;

import org.apache.commons.lang3.StringUtils;
import org.dom4j.io.SAXReader;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.servlet.http.HttpServletRequest;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.security.MessageDigest;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class XmlUtil {

    // 将输入流使用指定编码转化为字符串
    public static String inputStream2StringNew(InputStream inputStream) throws Exception {
        // 建立输入流读取类
        InputStreamReader reader = new InputStreamReader(inputStream);
        // 设定每次读取字符个数
        char[] data = new char[512];
        int dataSize = 0;
        // 循环读取
        StringBuilder stringBuilder = new StringBuilder();
        while ((dataSize = reader.read(data)) != -1) {
            stringBuilder.append(data, 0, dataSize);
        }
        return stringBuilder.toString();
    }

    // 将 xml 文件解析为指定类型的实体对象。此方法只能解析简单的只有一层的xml
    private static DocumentBuilderFactory documentBuilderFactory = null;

    //屏蔽某些编译时的警告信息(在强制类型转换的时候编译器会给出警告)
    public static Map<String, String> parseXml(String Str) throws Exception {
        // 将解析结果存储在HashMap中
        Map<String, String> map = new HashMap<String, String>();

        // 从request中取得输入流
        // 读取输入流
        SAXReader reader = new SAXReader();
        org.dom4j.Document document = reader.read(new ByteArrayInputStream(Str.getBytes()));
        // 得到xml根元素
        org.dom4j.Element root = document.getRootElement();
        // 得到根元素的所有子节点
        List<org.dom4j.Element> elementList = root.elements();

        // 遍历所有子节点
        for (org.dom4j.Element e : elementList)
            map.put(getName(e.getName()), e.getText());
        return map;
    }

    private static String getName(String name) {
        if (StringUtils.isBlank(name)) {
            return null;
        }
        String frist = String.valueOf(name.charAt(0)).toLowerCase();
        return frist + name.substring(1, name.length());
    }
}

处理消息推送

//处理推送消息
                this.weChatEvents(weChatAttentionDto);

可以分类型进行推送逻辑处理:
未关注扫码进行关注、已关注情况下扫码、取消关注等

总结

  1. 在公众号开发平台配置url、token、等信息时,需要 启用配置。
  2. 需区分GET和POST请求各自的作用
  3. 测试环境进行调试的时候,http请求验证通过,但是在消息推送时有可能转成https请求,需要进行相应处理,才能正确接收事件推送。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值