> 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [developer.work.weixin.qq.com](https://developer.work.weixin.qq.com/tutorial/detail/38) > 在上一节教程中,我们创建了一个第三方应用,得到了对应的 suite_id/ suite_secret 信息,但是并没有对数据回调和接口回调进行响应,网页提示验证不通过。 在上一节教程中,我们创建了一个第三方应用,得到了对应的 suite_id/ suite_secret 信息,但是并没有对数据回调和接口回调进行响应,网页提示验证不通过。 在本节课程中,我们将完善服务端的逻辑,接收企业微信的回调,使回调 URL 的配置能够通过验证。 通过本节教程,开发者将了解: 1. 如何接收企业微信的指令和数据回调 2. 如何对企业微信的回调进行验证 3. 如何实现第三方应用的回调服务 [](#%E5%87%86%E5%A4%87%E5%B7%A5%E4%BD%9C)准备工作 --------------------------------------------- 在开始本教程内容前,开发者需要做如下准备: * 已创建第三方应用,并在第三方【应用详情】-【回调配置】找到对应的 Token 和 EncodingAESKey,可参考 [一:如何创建第三方应用](https://developer.work.weixin.qq.com/tutorial/detail/37)。 * 在回调的实现逻辑中需要进行加解密计算,企业微信已经提供了 C++ Python PHP Java Go C# Node.js 等语言版本的加解密库,并且均提供了解密、加密、验证 URL 三个接口,开发者可以根据开发需要 [下载加解密库](https://developer.work.weixin.qq.com/devtool/introduce?id=10128)。 * 根据实际开发需要,搭建内网穿透服务。 本节教程将以 Node.js 作为代码语言示例,讲解对应的操作流程。 [](#%E5%9B%9E%E8%B0%83%E7%9A%84%E5%AE%9E%E7%8E%B0%E7%B1%BB%E5%9E%8B)回调的实现类型 --------------------------------------------------------------------------- 在配置应用的开发信息时,我们已经知道了第三方应用的回调配置有数据回调和指令回调两种。 **数据回调**,用于接收托管企业微信应用的用户消息、进入应用事件、通讯录变更事件。 **指令回调**,用于接收应用授权变更事件(应用添加、删除、修改)以及 ticket 参数,ticket 说明详 API 接口说明。 对于数据回调和指令回调的两个 URL ,在服务端的实现时,都必须同时支持 HttpGet 以及 HttpPost 两种能力。 #### [](#1%E3%80%81get-%E7%B1%BB%E5%9E%8B)1、Get 类型 仅用于在应用创建配置应用信息时的验证,企业微信服务端会向回调 URL 发起一个 Get 请求,当该回调 URL 按照约定进行了响应后,表明第三方服务具备解析企业微信推送消息的能力。 #### [](#2%E3%80%81post-%E7%B1%BB%E5%9E%8B)2、Post 类型 用于实际的业务请求,比如应用菜单的点击事件,用户消息等。当有回调的行为发生时,企业微信服务端会向该回调 URL 发起一个 Post 请求,同时数据会已加密的形式推送到该回调 URL,第三方服务商接受信息、解密信息,处理业务逻辑,并且按照约定进行响应即可。 [](#%E5%9B%9E%E8%B0%83%E5%86%85%E7%BD%91%E7%A9%BF%E9%80%8F)回调内网穿透 ----------------------------------------------------------------- 回调服务的 URL 是一个公网的 URL,如果在实际进行应用开发时,代码和服务是部署在本地机器的话,那么需要搭建一个内网穿透服务,要让企业微信服务器发来的数据回调和指令回调,能够到达本地服务。 三方应用开发的内网穿透,可以使用官方提供的 [内网穿透工具](https://developer.work.weixin.qq.com/devtool/introduce?id=40600) 进行快速调试和验证。 目前社区有许多较成熟的内网穿透解决方案,开发者可根据实际需要进行选择。 [](#get-%E5%9B%9E%E8%B0%83%E5%AE%9E%E7%8E%B0)Get 回调实现 ----------------------------------------------------- 根据创建第三方应用时填写的回调 URL,假设为 `http://myapp.com/callback/command` ,我们将来实现这个 URL 的 get 请求路由,并且按照要求响应。 ### [](#1%E3%80%81%E5%AE%9E%E7%8E%B0-get-%E8%B7%AF%E7%94%B1)1、实现 get 路由 数据回调 URL 和 指令回调 URL 均需要支持 HttpGet 形式的请求回调。 ``` const express = require('express'); const router = express.Router(); // get方式 用于验证 router.get('/callback/data', validateCallback); router.get('/callback/command', validateCallback); ``` ### [](#2%E3%80%81%E8%A7%A3%E6%9E%90%E5%9B%9E%E8%B0%83%E5%8F%82%E6%95%B0)2、解析回调参数 企业微信回调服务会通过 HttpGet 的形式请求回调 URL,并将 msg_signature、timestamp、nonce、echostr 以 query 参数的形式传递过来。我们在回调 URL 的逻辑中,解析出这些参数。 ``` // 从 query 中获取相关参数 const { msg_signature, timestamp, nonce, echostr } = req.query; ``` ### [](#3%E3%80%81%E8%AE%A1%E7%AE%97%E7%AD%BE%E5%90%8D%E5%B9%B6%E6%A0%A1%E9%AA%8C)3、计算签名并校验 根据在上一步获取的参数 timestamp、nonce、echostr,连同 Token 一起,使用加解密库的加密函数重新加密计算签名。 ``` // 引入官方的加解密库 const crypto = require('@wecom/crypto'); // 在应用详情页找到对应的token const token = ''; // 重新计算签名 const signature = crypto.getSignature(token, timestamp, nonce, echostr); console.log('signature', signature); ``` ### [](#4%E3%80%81%E8%A7%A3%E5%AF%86%E5%9B%9E%E8%B0%83%E7%9A%84%E5%8A%A0%E5%AF%86%E4%B8%B2%EF%BC%8C%E5%B9%B6%E5%B0%86%E5%86%85%E5%AE%B9%E8%BF%94%E5%9B%9E)4、解密回调的加密串,并将内容返回 通过 EncodingAESKey 和解析出来的 echostr 作为参数,使用加解密库的解密函数,解密出对应的 message,并立即以明文的形式直接返回。 ``` // 校验签名是否正确 if (signature === msg_signature) { console.info('签名验证成功') // 在应用详情页找到对应的EncodingAESKey const aeskey = ''; // 如果签名校验正确,解密 message const { message } = crypto.decrypt(aeskey, echostr); console.log('message', message); // 返回 message 信息 res.send(message); } ``` 到这里,我们已经完成了回调 URL 的 get 实现,在【应用详情】-【回调配置】重新点击编辑保存,便可以重新触发指令回调 URL 和数据回调 URL 的验证,确保网页不再提示 “服务商未响应请求,将无法获取用户事件回调”。 ![](https://wdoc-76491.picgzc.qpic.cn/MTY4ODg1MTQxODI4MzY5Mw_570775_SGrHlYPO531_prpl_1653450614?w=1280&h=435.23956723338483) 想要了解更多 Get 回调的实现,可以参考 [支持 http-get 请求验证 url 有效性](https://developer.work.weixin.qq.com/document/path/91116#31-%E6%94%AF%E6%8C%81http-get%E8%AF%B7%E6%B1%82%E9%AA%8C%E8%AF%81url%E6%9C%89%E6%95%88%E6%80%A7)。 [](#post-%E5%9B%9E%E8%B0%83%E5%AE%9E%E7%8E%B0)Post 回调实现 ------------------------------------------------------- ### [](#1%E3%80%81%E5%AE%9E%E7%8E%B0-post-%E8%B7%AF%E7%94%B1)1、实现 post 路由 数据回调 URL 和 指令回调 URL 均需要支持 HttpPost 形式的请求回调。 ``` const express = require('express'); const router = express.Router(); // post方式 用于授权、业务数据回调 router.post('/callback/command', function (req, res, next) {}); ``` ### [](#2%E3%80%81%E8%A7%A3%E6%9E%90%E5%8F%82%E6%95%B0%EF%BC%8C%E5%B9%B6%E6%A0%A1%E9%AA%8C%E7%AD%BE%E5%90%8D)2、解析参数,并校验签名 企业微信回调服务会通过 HttpPost 的形式请求该回调 URL,并将 msg_signature、timestamp、nonce 以 query 参数的形式传递过来,像 HttpGet 形式一样,需要先对参数进行解析,并且校验签名有效。 获取的参数 timestamp、nonce、echostr,连同 Token 一起,使用加解密库的加密函数重新加密计算签名。 ``` // 引入官方的加解密库 const crypto = require('@wecom/crypto'); // 从 query 中获取相关参数 const { msg_signature, timestamp, nonce } = req.query; // 在应用详情页找到对应的token const token = ''; // 重新计算签名 const signature = crypto.getSignature(token, timestamp, nonce, echostr); console.log('signature', signature); ``` ### [](#3%E3%80%81%E8%A7%A3%E5%AF%86%E5%AF%86%E6%96%87%E6%B6%88%E6%81%AF)3、解密密文消息 在 HttpPost 回调中,密文内容是以 XML 字符串的形式放在 RawBody 中,通过对该 XML 字符的解析读取出对应的加密消息 Encrypt。 通过 EncodingAESKey 和加密消息 Encrypt 作为参数,使用加解密库的解密函数,解密出对应的 message。 而 message 仍然是一个 XML 字符串格式,再次对该 XML 解析,便可以得到本次 HttpPost 回调实际的数据信息。 ``` // 解析 xml 的字符串内容 let wholeXML = await getRawBody(req, { length: req.headers['content-length'], limit: '1mb', encoding: 'utf-8' }); // 将 xml 解析成 json let formatJson = await parseXML(wholeXML); // 得到加密消息体 let encrypt = formatJson.Encrypt; // 在应用详情页找到对应的EncodingAESKey const aeskey = ''; // 将加密消息体进行解密,解密后仍旧是 xml 字符串 let messageXML = crypto.decrypt(aeskey, encrypt); // 把解密后 xml 消息体字符串,解析成 json let callbackDataBody = await parseXML(messageXML.message); console.log('CallbackData', callbackDataBody); ``` ### [](#4%E3%80%81%E5%93%8D%E5%BA%94%E6%9C%AC%E6%AC%A1%E8%AF%B7%E6%B1%82)4、响应本次请求 不同的业务回调要求返回不同内容,在本节中我们直接返回 `success` 字符串即可。 想要了解企业微信回调设置的更详细信息,可以参考文档 [回调配置](https://developer.work.weixin.qq.com/document/path/91116)。 到这里,我们已经完成了第三方应用的 HttpPost 回调配置逻辑框架,在下一节教程,我们就可以利用这个指令回调实现 suite_ticket 的接收,从而获得 suite_access_token。