本篇是基于企业微信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. 效果
不小心暴露了价值百万的人工智能IP,可以看到emoji也能显示,消息类型支持:文本、图片、语音、视频、位置以及链接信息,具体可以参考官方文档。
后台接收到信息后就可以自己去实现相应的业务逻辑,比如用户向应用发消息,识别消息关键词,回复不同的消息内容;用户点击应用菜单时,转化为指令,执行自动化任务等等等。