# Java 企业微信回调配置

企业微信 API 文档地址:https://open.work.weixin.qq.com/api/doc/90001/90143/91116

# 获取 Token 和 EncodingAESKey

登录企业微信后台。

1:点击右下角图标打开网页进入

2:打开工作台,打开应用,点击右上角图标进入 (图标和下图右下角图标一样)

image-20211119235542725

打开需要配置的应用,点击 启用API接收

image-20211120000109418

随机生成 Token 和 EncodingAESKey

image-20211120000249712

# 准备 URL 接口

两个接口,相同路径,一个 GET 一个 POST 请求。上面调用的是 GET 请求判断 URL 服务是否具备解析能力,通过后保存成功,之后调用 POST 请求

接口内容如下

import cn.hutool.core.util.XmlUtil;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.InputStream;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.Map;
@RestController
@RequestMapping("/api/wx")
public class WxSampleController {
    private static final Logger logger = LoggerFactory.getLogger(WxSampleController.class);
    /** 企业微信回调验证 */
    @GetMapping("/callback")
    public void connect(HttpServletRequest request, HttpServletResponse response){
        //msg_signature = hash 算法 (token+timestamp+nonce+echostr); 
        // 形象说法就是标记,防止参数传递过程被篡改
        String msg_signature = request.getParameter("msg_signature");// 微信加密签名
        String timestamp = request.getParameter("timestamp");// 时间戳
        String nonce = request.getParameter("nonce");// 随机数
        //echostr = AES 算法使用 EncodingAESKey 密钥加密明文后的值
        String echoStr = request.getParameter("echostr");// 随机字符串
        String token = ""; // 前面获取的 Token
        String encodingaeskey = ""; // 前面获取的 EncodingAESKey
        String secretId = ""; // 企业 ID
        try (PrintWriter out = response.getWriter()) {
            // 微信准备的加解密类
            WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(token,encodingaeskey,secretId);
            String sEchoStr = wxcpt.VerifyURL(msg_signature,timestamp,nonce,echoStr);
            if(StringUtils.isBlank(sEchoStr)){
                logger.error("URL验证失败");
            }
            out.write(sEchoStr);
            out.flush();
        }catch (Exception e){
            logger.error("企业微信回调url验证错误",e);
        }
    }
    /** 企业微信消息交互 */
    @PostMapping( "/callback")
    public void acceptMessage(HttpServletRequest request, RedirectAttributes attributes, HttpSession session){
		//msg_signature = hash 算法 (token+timestamp+nonce+echostr); 
        // 形象说法就是标记,防止参数传递过程被篡改
        String sMsgSignature = request.getParameter("msg_signature");// 微信加密签名
        String sTimestamp = request.getParameter("timestamp");// 时间戳
        String sNonce = request.getParameter("nonce");// 随机数
        String token = ""; // 前面获取的 Token
        String encodingaeskey = ""; // 前面获取的 EncodingAESKey
        String corpId = ""; // 企业 ID
        try{
            InputStream inputStream = request.getInputStream();
            String sPostData = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
            WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(token,encodingaeskey,corpId);
            String sMsg = wxcpt.DecryptMsg(sMsgSignature,sTimestamp,sNonce,sPostData);
            //sMsg 是 xml 格式的字符串,我使用的是 hutool 工具下的插件转 map 的,你也可以用其它的方式
           	Map<String, String> map = new HashMap<String, String>(10);
            Document document = XmlUtil.parseXml(sMsg);
            List<Element> xml = XmlUtil.getElements(document.getDocumentElement(), null);
            for(Element x:xml){
                map.put(x.getTagName(),x.getTextContent());
            }
            // 业务代码 ...
        }catch (Exception e){
            logger.error("企业微信消息交互错误",e);
        }
    }
}

# WXBizMsgCrypt 类下载

加解密库官方:下载地址 本文使用 JAVA 的下载地址

将文件全部拷贝到模块目录下用于引用

image-20211120140010877

# 结束

image-20211120142416576

文档很好很全面,这个配置一波过,就是只提供一个环境的消息服务器配置有点少。

# 2022/01/05 日 问题补充

# Last encoded character (before the paddings if any)

首先是我升级其它 jar 将 commons-codec 这个 jar 包版本升级到 1.14,导致企业微信提供的 WXBizMsgCrypt 类下 WXBizMsgCrypt 方法中的代码执行错误

// 报错代码
aesKey = Base64.decodeBase64(encodingAesKey + "=");

原因是在执行解密时调用 validateCharacter 方法,来验证在上下文中是否可以解码最后的尾随字符,而微信生成的 EncodingAESKey 无法通过校验。回退版本发现原来版本是 1.11。1.11 版本没有这个方法

# 解决办法

直接用当前 commons-codec 版本的加密方法加密一个 32 位的字符串。生成后得到 44 位的字符串去掉尾巴 = 字符串,得 43 位的字符串。它就是 EncodingAESKey 。再将这个 EncodingAESKey 替换微信随机生成的 EncodingAESKey 就可以了。

// 32 位字符串可以使用 UUID 去掉中间的 - 就剩下 32 位了
Base64.encodeBase64String(UUID.randomUUID().toString().replaceAll("-","").getBytes());