基于企业微信的消息接收


本篇是基于企业微信API的消息推送的下集,之前也提到过,接收信息比推送稍微复杂一丢丢,需要实现一个回调服务。

根据官方文档,配置回调服务时,需要能同时支持HttpGet以及HttpPost两种能力

  • 企业微信会先判断URL服务是否具备解析企业微信推送消息的能力。 具体方式是,企业微信往URL服务上发一条Get请求带签名及密文参数到URL服务上,如果URL服务检查签名通过,并能正确返回密文参数对应的明文字符串,则验证通过。此时在企业微信的配置就开始生效。

  • 后续的业务请求(比如应用菜单的点击事件,用户消息等),都会类似的方式(签名+密文)向服务URL推送消息。URL服务验证签名通过后,需要将POST数据解密,就可以得到对应的业务消息明文。

1. 首先设置接收消息的参数

在企业的管理端后台,进入需要设置接收消息的目标应用,点击“接收消息”的“设置API接收”按钮,进入配置页面。

用的URL、Token、EncodingAESKey三个参数

  • URL是企业后台接收企业微信推送请求的访问协议和地址,支持http或https协议(为了提高安全性,建议使用https)。
  • Token可由企业任意填写,用于生成签名。
  • EncodingAESKey用于消息体的加密。

2. 回调服务

回调服务用什么实现都可以,我这里django比较熟就使用django了。

主要在于消息的加解密,鉴于加解密算法相对复杂,企业微信提供了算法库。目前已有c++/python/php/java/golang/c#等语言版本,均提供了解密、加密、验证URL三个接口,自己去官方文档下就是了。

views.py :

from django.http import HttpResponse
from django.shortcuts import render
from django.views import View
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator
import urllib.parse

from .WXBizMsgCrypt3 import WXBizMsgCrypt
import xml.etree.cElementTree as ET
import sys

# Create your views here.
@method_decorator(csrf_exempt, name='dispatch')
class Receive(View):
    def __init__(self, **kwargs) -> None:
        super().__init__(**kwargs)
        self.wxcpt = WXBizMsgCrypt(
            sToken='你的Token', sEncodingAESKey='你的EncodingAESKey', sReceiveId='你的corp_id')  # corp_id

    def get(self, request):
        msg_signature = urllib.parse.unquote(
            request.GET.get('msg_signature', ''))  # 加密签名
        timestamp = urllib.parse.unquote(
            request.GET.get('timestamp', ''))  # 时间戳
        nonce = urllib.parse.unquote(request.GET.get('nonce', ''))  # 随机数
        echostr = urllib.parse.unquote(
            request.GET.get('echostr', ''))  # 加密的字符串

        ret, sEchoStr = self.wxcpt.VerifyURL(
            msg_signature, timestamp, nonce, echostr)
        # print(sEchoStr)
        if(ret != 0):
            print("ERR: VerifyURL ret: " + str(ret))
            sys.exit(1)
        # 验证URL成功,将sEchoStr返回给企业号
        return HttpResponse(sEchoStr)

    def post(self, request):
        msg_signature = urllib.parse.unquote(
            request.GET.get('msg_signature', ''))  # 加密签名
        timestamp = urllib.parse.unquote(
            request.GET.get('timestamp', ''))  # 时间戳
        nonce = urllib.parse.unquote(request.GET.get('nonce', ''))  # 随机数
        sReqData = request.body.decode('utf-8')  # 加密的消息

        ret, sMsg = self.wxcpt.DecryptMsg(
            sReqData, msg_signature, timestamp, nonce)
        #print(ret, sMsg)
        if(ret != 0):
            print("ERR: DecryptMsg ret: " + str(ret))
            sys.exit(1)
        # 解密成功,sMsg即为xml格式的明文
        # TODO: 对明文的处理
        xml_tree = ET.fromstring(sMsg)
        content = xml_tree.find("Content").text
        ToUserName = xml_tree.find("ToUserName").text
        FromUserName = xml_tree.find("FromUserName").text
        CreateTime = xml_tree.find("CreateTime").text
        MsgId = xml_tree.find("MsgId").text
        AgentID = xml_tree.find("AgentID").text
        print(f'content:{content}', f'ToUserName:{ToUserName}',
              f'FromUserName:{FromUserName}',)

        # 构造回复消息结构体
        sRespData = f'''
        <xml>
        <ToUserName>{ToUserName}</ToUserName>
        <FromUserName>{FromUserName}</FromUserName>
        <CreateTime>{CreateTime}</CreateTime>
        <MsgType>text</MsgType>
        <Content>我也{content}!</Content>
        <MsgId>{MsgId}</MsgId>
        <AgentID>{AgentID}</AgentID>
        </xml>
        '''
        ret, sEncryptMsg = self.wxcpt.EncryptMsg(sRespData, nonce, timestamp)
        if(ret != 0):
            print("ERR: EncryptMsg ret: " + str(ret))
            sys.exit(1)
        # ret == 0 加密成功,企业需要将sEncryptMsg返回给企业号
        # 假如企业无法保证在五秒内处理并回复,或者不想回复任何内容,可以直接返回200(即以空串为返回包)。
        # 企业后续可以使用主动发消息接口进行异步回复。
        return HttpResponse(sEncryptMsg)

python版本有个坑的地方在于使用了pycrypto第三方库

"""

关于Crypto.Cipher模块,ImportError: No module named 'Crypto'解决方案

请到官方网站 https://www.dlitz.net/software/pycrypto/ 下载pycrypto。

下载后,按照README中的“Installation”小节的提示进行pycrypto安装。

pip install pycryptodome
"""

3. 效果

微信图片_20220610144038

不小心暴露了价值百万的人工智能IP,可以看到emoji也能显示,消息类型支持:文本、图片、语音、视频、位置以及链接信息,具体可以参考官方文档。

后台接收到信息后就可以自己去实现相应的业务逻辑,比如用户向应用发消息,识别消息关键词,回复不同的消息内容;用户点击应用菜单时,转化为指令,执行自动化任务等等等。