├── .gitattributes
├── config.py
├── ierror.py
├── .github
└── FUNDING.yml
├── LICENSE
├── README.md
├── func.py
├── main.py
└── WXBizMsgCrypt.py
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/config.py:
--------------------------------------------------------------------------------
1 | # sToken和sEncodingAESKey来自于企业微信创建的应用
2 | # sCorpID为企业微信号
3 |
4 | sToken = ""
5 | sEncodingAESKey = ""
6 | sCorpID = ""
--------------------------------------------------------------------------------
/ierror.py:
--------------------------------------------------------------------------------
1 | WXBizMsgCrypt_OK = 0
2 | WXBizMsgCrypt_ValidateSignature_Error = -40001
3 | WXBizMsgCrypt_ParseXml_Error = -40002
4 | WXBizMsgCrypt_ComputeSignature_Error = -40003
5 | WXBizMsgCrypt_IllegalAesKey = -40004
6 | WXBizMsgCrypt_ValidateCorpid_Error = -40005
7 | WXBizMsgCrypt_EncryptAES_Error = -40006
8 | WXBizMsgCrypt_DecryptAES_Error = -40007
9 | WXBizMsgCrypt_IllegalBuffer = -40008
10 | WXBizMsgCrypt_EncodeBase64_Error = -40009
11 | WXBizMsgCrypt_DecodeBase64_Error = -40010
12 | WXBizMsgCrypt_GenReturnXml_Error = -40011
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 lyleshaw
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # FastAPI-WeWork-Robot
2 |
3 | > 基于FastAPI创建的、使用企业微信交互的机器人系统
4 |
5 | ## 起因
6 |
7 | 我从去年想要拥有一个TODOlist开始,一步步的拓展这个想法:从单纯的列出任务,到任务到期提醒...一点点的有其他新的想法。
8 |
9 | 关于推送方式,我最早是希望通过发送邮件,然后通过微信企业邮的邮件提醒达到推送效果,而后发现了server酱,又开始使用这种方式实现微信推送,期间又接触了某个可以白嫖的短信api,但是免费一个帐号大概400条(配置还很麻烦),最终因为学校的企业微信最近推送了好几条消息...意识到企业微信是目前最适合的推送方式。
10 |
11 | 当然,单纯了做了任务提醒的功能后,发现企业微信的api还挺好玩的,于是一点点看文档写了其他一些小功能(参考func.py文件)
12 |
13 | ## 配置及使用
14 |
15 | > requirements:fastapi,uvicorn
16 |
17 | ### 企业微信配置
18 |
19 | 首先,请配置config.py文件按要求填写:
20 |
21 | ```
22 | # sToken和sEncodingAESKey来自于企业微信创建的应用
23 | # sCorpID为企业微信号
24 |
25 | sToken = ""
26 | sEncodingAESKey = ""
27 | sCorpID = ""
28 | ```
29 |
30 | ### 中间件配置
31 |
32 | 如果想手动设置中间件(默认为不限制访问),请参考FastAPI文档设置main.py文件中的如下代码
33 |
34 | ```
35 | app.add_middleware(
36 | CORSMiddleware,
37 | allow_origins=['*'],
38 | allow_credentials=True,
39 | allow_methods=["*"],
40 | allow_headers=["*"],
41 | )
42 | ```
43 |
44 | ### 使用
45 |
46 | 请先启动main.py程序
47 |
48 | 然后按照[企业微信的要求](https://work.weixin.qq.com/api/doc/90001/90143/91116)完成URL验证。
49 |
50 | 而后即可正常使用。
51 |
52 | ## 自定义功能
53 |
54 | 如果您需要自定义企业微信被动回复功能,可以对func.py文件中的handle_msg函数进行修改。
55 |
56 | handle_msg有两个参数
57 |
58 | + to_user_id: str # 用户名
59 | + recived_msg: str # 用户发送的信息
60 |
61 | 返回值即为被动回复的消息内容
62 |
63 | ## 反馈&联系我
64 |
65 | 本项目基于[企业微信官方python库](https://github.com/sbzhu/weworkapi_python)进行修改。
66 |
67 | 如果有其他问题,可以通过如下方式联系我
68 |
69 | + [lyle@hdu.edu.cn](mailto:lyle@hdu.edu.cn)
70 | + [lyleshaw.com](https://lyleshaw.com/)
--------------------------------------------------------------------------------
/func.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import pandas as pd
3 | import os
4 |
5 | # 将信息写入csv表格
6 | def write_msg(to_user_id: str, key: str, value: str):
7 | file_name = to_user_id + '.csv'
8 | if os.path.exists(file_name)!=1:
9 | f = open(file_name,mode='a+')
10 | f.write("Owner,Key,Value")
11 | f.close()
12 | data = pd.read_csv(file_name,encoding='gbk')
13 | data = data.values.tolist()
14 | for i in data:
15 | if i[1] == key:
16 | return [0,"error"]
17 | data.append([to_user_id,key,value])
18 | data = pd.core.frame.DataFrame(data)
19 | data.rename(columns={0:'Owner',1:'Key',2:'Value'},inplace=True)
20 | data.to_csv(file_name,index=None,encoding='gbk')
21 | return [1,"success"]
22 |
23 | # 从csv表格中读取
24 | def read_msg(to_user_id: str, key: str):
25 | file_name = to_user_id + '.csv'
26 | if os.path.exists(file_name)!=1:
27 | f = open(file_name,mode='a+')
28 | f.write("Owner,Key,Value")
29 | f.close()
30 | data = pd.read_csv(file_name,encoding='gbk')
31 | data = data.values.tolist()
32 | for i in data:
33 | if str(i[1]) == key:
34 | return [1,i[2]]
35 | return [0,"not found"]
36 |
37 | # 处理消息函数
38 | # to_user_id为用户名
39 | # recived_msg为接收的消息
40 | def handle_msg(to_user_id: str, recived_msg: str):
41 | name = to_user_id
42 | if '存' in recived_msg:
43 | _, key, value = recived_msg.split(" ")
44 | ret = write_msg(to_user_id,key,value)
45 | if ret[0]==0:
46 | return name+"您好,您的关键词已重复,请重新输入"
47 | else:
48 | return name+"您好,您的关键词"+key+"已录入"
49 | elif '取' in recived_msg:
50 | _, key = recived_msg.split(" ")
51 | ret, msg = read_msg(to_user_id,key)
52 | if ret==0:
53 | return name+"您好,未找到您的关键词,请重新输入"
54 | else:
55 | return name+"您好,您的关键词"+key+"的内容是:\n" + str(msg)
56 | else:
57 | return recived_msg
--------------------------------------------------------------------------------
/main.py:
--------------------------------------------------------------------------------
1 | from typing import List
2 | from fastapi import Depends, FastAPI, HTTPException
3 | import time
4 | import sys
5 | from fastapi.middleware.cors import CORSMiddleware
6 | from pydantic import BaseModel
7 | from starlette.requests import Request
8 | from typing import TypeVar, Generic, Type, Any
9 | from xml.etree.ElementTree import fromstring
10 | import xml.etree.cElementTree as ET
11 |
12 | from WXBizMsgCrypt import WXBizMsgCrypt
13 | from config import sCorpID,sEncodingAESKey,sToken
14 | import func
15 |
16 | # 启动App
17 | app = FastAPI()
18 |
19 | # 设置中间件
20 | app.add_middleware(
21 | CORSMiddleware,
22 | allow_origins=['*'],
23 | allow_credentials=True,
24 | allow_methods=["*"],
25 | allow_headers=["*"],
26 | )
27 |
28 | # 创建登录会话
29 | wxcpt=WXBizMsgCrypt(sToken,sEncodingAESKey,sCorpID)
30 |
31 |
32 | # 以下为接受XML格式数据部分
33 | T = TypeVar("T", bound=BaseModel)
34 |
35 | class Item(BaseModel):
36 | ToUserName: str
37 | AgentID: str
38 | Encrypt: str
39 |
40 | class XmlBody(Generic[T]):
41 | def __init__(self, model_class: Type[T]):
42 | self.model_class = model_class
43 |
44 | async def __call__(self, request: Request) -> T:
45 | # the following check is unnecessary if always using xml,
46 | # but enables the use of json too
47 | # print(request.headers.get("Content-Type"))
48 | if '/xml' in request.headers.get("Content-Type", ""):
49 | body = await request.body()
50 | doc = fromstring(body)
51 | dict_data = {}
52 | for node in doc.getchildren():
53 | dict_data[node.tag] = node.text
54 | else:
55 | dict_data = await request.json()
56 | return self.model_class.parse_obj(dict_data)
57 |
58 |
59 | # 接受消息模版
60 | Recived_Temp = """
61 |
62 |
63 |
64 | """
65 |
66 | #发送消息模版
67 | Send_Temp = """
68 | %(ToUserName)s
69 | %(FromUserName)s
70 | %(timestamp)s
71 | text
72 | %(content)s
73 | """
74 |
75 |
76 |
77 | # 回调验证部分
78 | @app.get("/")
79 | async def Verify(msg_signature: str, timestamp: str, nonce: str, echostr: str):
80 | sVerifyMsgSig = msg_signature
81 | sVerifyTimeStamp = timestamp
82 | sVerifyNonce = nonce
83 | sVerifyEchoStr = echostr
84 | ret, sReplyEchoStr = wxcpt.VerifyURL(sVerifyMsgSig, sVerifyTimeStamp,sVerifyNonce,sVerifyEchoStr)
85 | if( ret!=0 ):
86 | print("ERR: DecryptMsg ret: " + str(ret))
87 | sys.exit(1)
88 | return int(sReplyEchoStr)
89 |
90 | # 消息接收部分
91 | @app.post("/")
92 | async def main(msg_signature: str, timestamp: str, nonce: str, q: str = None, item: Item = Depends(XmlBody(Item))):
93 | Recived_dict = {
94 | 'ToUserName': item.ToUserName,
95 | 'AgentID': item.AgentID,
96 | 'Encrypt': item.Encrypt,
97 | }
98 | ReqData = Recived_Temp % Recived_dict
99 | ret,sMsg=wxcpt.DecryptMsg(sPostData=ReqData, sMsgSignature=msg_signature, sTimeStamp=timestamp, sNonce=nonce)
100 | if( ret!=0 ):
101 | print("ERR: DecryptMsg ret: " + str(ret))
102 | sys.exit(1)
103 | xml_tree = ET.fromstring(sMsg)
104 | content_recived = xml_tree.find("Content").text
105 | FromUserName = xml_tree.find("FromUserName").text
106 | ToUserName = xml_tree.find("ToUserName").text
107 |
108 | # 消息处理部分
109 | content_send = func.handle_msg(to_user_id = FromUserName, recived_msg = content_recived)
110 |
111 | Send_dict = {
112 | "ToUserName": ToUserName,
113 | "FromUserName": FromUserName,
114 | "timestamp": timestamp,
115 | "content": content_send,
116 | }
117 | sRespData = Send_Temp % Send_dict
118 | ret,sEncryptMsg=wxcpt.EncryptMsg(sReplyMsg = sRespData, sNonce = nonce, timestamp = timestamp)
119 | return sEncryptMsg
120 |
121 | # 启动服务
122 | if __name__ == '__main__':
123 | import uvicorn
124 | uvicorn.run(app, host="0.0.0.0", port=8181)
--------------------------------------------------------------------------------
/WXBizMsgCrypt.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- encoding:utf-8 -*-
3 |
4 | """ 对企业微信发送给企业后台的消息加解密示例代码.
5 | @copyright: Copyright (c) 1998-2014 Tencent Inc.
6 |
7 | """
8 | # ------------------------------------------------------------------------
9 | import logging
10 | import base64
11 | import random
12 | import hashlib
13 | import time
14 | import struct
15 | from Crypto.Cipher import AES
16 | import xml.etree.cElementTree as ET
17 | import socket
18 |
19 | import ierror
20 |
21 |
22 | """
23 | 关于Crypto.Cipher模块,ImportError: No module named 'Crypto'解决方案
24 | 请到官方网站 https://www.dlitz.net/software/pycrypto/ 下载pycrypto。
25 | 下载后,按照README中的“Installation”小节的提示进行pycrypto安装。
26 | """
27 |
28 |
29 | class FormatException(Exception):
30 | pass
31 |
32 |
33 | def throw_exception(message, exception_class=FormatException):
34 | """my define raise exception function"""
35 | raise exception_class(message)
36 |
37 |
38 | class SHA1:
39 | """计算企业微信的消息签名接口"""
40 |
41 | def getSHA1(self, token, timestamp, nonce, encrypt):
42 | """用SHA1算法生成安全签名
43 | @param token: 票据
44 | @param timestamp: 时间戳
45 | @param encrypt: 密文
46 | @param nonce: 随机字符串
47 | @return: 安全签名
48 | """
49 | try:
50 | sortlist = [token, timestamp, nonce, encrypt]
51 | sortlist.sort()
52 | sha = hashlib.sha1()
53 | sha.update("".join(sortlist).encode())
54 | return ierror.WXBizMsgCrypt_OK, sha.hexdigest()
55 | except Exception as e:
56 | logger = logging.getLogger()
57 | logger.error(e)
58 | return ierror.WXBizMsgCrypt_ComputeSignature_Error, None
59 |
60 |
61 | class XMLParse:
62 | """提供提取消息格式中的密文及生成回复消息格式的接口"""
63 |
64 | # xml消息模板
65 | AES_TEXT_RESPONSE_TEMPLATE = """
66 |
67 |
68 | %(timestamp)s
69 |
70 | """
71 |
72 | def extract(self, xmltext):
73 | """提取出xml数据包中的加密消息
74 | @param xmltext: 待提取的xml字符串
75 | @return: 提取出的加密消息字符串
76 | """
77 | try:
78 | xml_tree = ET.fromstring(xmltext)
79 | encrypt = xml_tree.find("Encrypt")
80 | return ierror.WXBizMsgCrypt_OK, encrypt.text
81 | except Exception as e:
82 | logger = logging.getLogger()
83 | logger.error(e)
84 | return ierror.WXBizMsgCrypt_ParseXml_Error, None
85 |
86 | def generate(self, encrypt, signature, timestamp, nonce):
87 | """生成xml消息
88 | @param encrypt: 加密后的消息密文
89 | @param signature: 安全签名
90 | @param timestamp: 时间戳
91 | @param nonce: 随机字符串
92 | @return: 生成的xml字符串
93 | """
94 | resp_dict = {
95 | 'msg_encrypt': encrypt,
96 | 'msg_signaturet': signature,
97 | 'timestamp': timestamp,
98 | 'nonce': nonce,
99 | }
100 | resp_xml = self.AES_TEXT_RESPONSE_TEMPLATE % resp_dict
101 | return resp_xml
102 |
103 |
104 | class PKCS7Encoder():
105 | """提供基于PKCS7算法的加解密接口"""
106 |
107 | block_size = 32
108 |
109 | def encode(self, text):
110 | """ 对需要加密的明文进行填充补位
111 | @param text: 需要进行填充补位操作的明文
112 | @return: 补齐明文字符串
113 | """
114 | text_length = len(text)
115 | # 计算需要填充的位数
116 | amount_to_pad = self.block_size - (text_length % self.block_size)
117 | if amount_to_pad == 0:
118 | amount_to_pad = self.block_size
119 | # 获得补位所用的字符
120 | pad = chr(amount_to_pad)
121 | return text + (pad * amount_to_pad).encode()
122 |
123 | def decode(self, decrypted):
124 | """删除解密后明文的补位字符
125 | @param decrypted: 解密后的明文
126 | @return: 删除补位字符后的明文
127 | """
128 | pad = ord(decrypted[-1])
129 | if pad < 1 or pad > 32:
130 | pad = 0
131 | return decrypted[:-pad]
132 |
133 |
134 | class Prpcrypt(object):
135 | """提供接收和推送给企业微信消息的加解密接口"""
136 |
137 | def __init__(self, key):
138 |
139 | # self.key = base64.b64decode(key+"=")
140 | self.key = key
141 | # 设置加解密模式为AES的CBC模式
142 | self.mode = AES.MODE_CBC
143 |
144 | def encrypt(self, text, receiveid):
145 | """对明文进行加密
146 | @param text: 需要加密的明文
147 | @return: 加密得到的字符串
148 | """
149 | # 16位随机字符串添加到明文开头
150 | text = text.encode()
151 | text = self.get_random_str() + struct.pack("I", socket.htonl(len(text))) + text + receiveid.encode()
152 |
153 | # 使用自定义的填充方式对明文进行补位填充
154 | pkcs7 = PKCS7Encoder()
155 | text = pkcs7.encode(text)
156 | # 加密
157 | cryptor = AES.new(self.key, self.mode, self.key[:16])
158 | try:
159 | ciphertext = cryptor.encrypt(text)
160 | # 使用BASE64对加密后的字符串进行编码
161 | return ierror.WXBizMsgCrypt_OK, base64.b64encode(ciphertext)
162 | except Exception as e:
163 | logger = logging.getLogger()
164 | logger.error(e)
165 | return ierror.WXBizMsgCrypt_EncryptAES_Error, None
166 |
167 | def decrypt(self, text, receiveid):
168 | """对解密后的明文进行补位删除
169 | @param text: 密文
170 | @return: 删除填充补位后的明文
171 | """
172 | try:
173 | cryptor = AES.new(self.key, self.mode, self.key[:16])
174 | # 使用BASE64对密文进行解码,然后AES-CBC解密
175 | plain_text = cryptor.decrypt(base64.b64decode(text))
176 | except Exception as e:
177 | logger = logging.getLogger()
178 | logger.error(e)
179 | return ierror.WXBizMsgCrypt_DecryptAES_Error, None
180 | try:
181 | pad = plain_text[-1]
182 | # 去掉补位字符串
183 | # pkcs7 = PKCS7Encoder()
184 | # plain_text = pkcs7.encode(plain_text)
185 | # 去除16位随机字符串
186 | content = plain_text[16:-pad]
187 | xml_len = socket.ntohl(struct.unpack("I", content[: 4])[0])
188 | xml_content = content[4: xml_len + 4]
189 | from_receiveid = content[xml_len + 4:]
190 | except Exception as e:
191 | logger = logging.getLogger()
192 | logger.error(e)
193 | return ierror.WXBizMsgCrypt_IllegalBuffer, None
194 |
195 | if from_receiveid.decode('utf8') != receiveid:
196 | return ierror.WXBizMsgCrypt_ValidateCorpid_Error, None
197 | return 0, xml_content
198 |
199 | def get_random_str(self):
200 | """ 随机生成16位字符串
201 | @return: 16位字符串
202 | """
203 | return str(random.randint(1000000000000000, 9999999999999999)).encode()
204 |
205 |
206 | class WXBizMsgCrypt(object):
207 | # 构造函数
208 | def __init__(self, sToken, sEncodingAESKey, sReceiveId):
209 | try:
210 | self.key = base64.b64decode(sEncodingAESKey + "=")
211 | assert len(self.key) == 32
212 | except:
213 | throw_exception("[error]: EncodingAESKey unvalid !", FormatException)
214 | # return ierror.WXBizMsgCrypt_IllegalAesKey,None
215 | self.m_sToken = sToken
216 | self.m_sReceiveId = sReceiveId
217 |
218 | # 验证URL
219 | # @param sMsgSignature: 签名串,对应URL参数的msg_signature
220 | # @param sTimeStamp: 时间戳,对应URL参数的timestamp
221 | # @param sNonce: 随机串,对应URL参数的nonce
222 | # @param sEchoStr: 随机串,对应URL参数的echostr
223 | # @param sReplyEchoStr: 解密之后的echostr,当return返回0时有效
224 | # @return:成功0,失败返回对应的错误码
225 |
226 | def VerifyURL(self, sMsgSignature, sTimeStamp, sNonce, sEchoStr):
227 | sha1 = SHA1()
228 | ret, signature = sha1.getSHA1(self.m_sToken, sTimeStamp, sNonce, sEchoStr)
229 | if ret != 0:
230 | return ret, None
231 | if not signature == sMsgSignature:
232 | return ierror.WXBizMsgCrypt_ValidateSignature_Error, None
233 | pc = Prpcrypt(self.key)
234 | ret, sReplyEchoStr = pc.decrypt(sEchoStr, self.m_sReceiveId)
235 | return ret, sReplyEchoStr
236 |
237 | def EncryptMsg(self, sReplyMsg, sNonce, timestamp=None):
238 | # 将企业回复用户的消息加密打包
239 | # @param sReplyMsg: 企业号待回复用户的消息,xml格式的字符串
240 | # @param sTimeStamp: 时间戳,可以自己生成,也可以用URL参数的timestamp,如为None则自动用当前时间
241 | # @param sNonce: 随机串,可以自己生成,也可以用URL参数的nonce
242 | # sEncryptMsg: 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的xml格式的字符串,
243 | # return:成功0,sEncryptMsg,失败返回对应的错误码None
244 | pc = Prpcrypt(self.key)
245 | ret, encrypt = pc.encrypt(sReplyMsg, self.m_sReceiveId)
246 | encrypt = encrypt.decode('utf8')
247 | if ret != 0:
248 | return ret, None
249 | if timestamp is None:
250 | timestamp = str(int(time.time()))
251 | # 生成安全签名
252 | sha1 = SHA1()
253 | ret, signature = sha1.getSHA1(self.m_sToken, timestamp, sNonce, encrypt)
254 | if ret != 0:
255 | return ret, None
256 | xmlParse = XMLParse()
257 | return ret, xmlParse.generate(encrypt, signature, timestamp, sNonce)
258 |
259 | def DecryptMsg(self, sPostData, sMsgSignature, sTimeStamp, sNonce):
260 | # 检验消息的真实性,并且获取解密后的明文
261 | # @param sMsgSignature: 签名串,对应URL参数的msg_signature
262 | # @param sTimeStamp: 时间戳,对应URL参数的timestamp
263 | # @param sNonce: 随机串,对应URL参数的nonce
264 | # @param sPostData: 密文,对应POST请求的数据
265 | # xml_content: 解密后的原文,当return返回0时有效
266 | # @return: 成功0,失败返回对应的错误码
267 | # 验证安全签名
268 | xmlParse = XMLParse()
269 | ret, encrypt = xmlParse.extract(sPostData)
270 | if ret != 0:
271 | return ret, None
272 | sha1 = SHA1()
273 | ret, signature = sha1.getSHA1(self.m_sToken, sTimeStamp, sNonce, encrypt)
274 | if ret != 0:
275 | return ret, None
276 | if not signature == sMsgSignature:
277 | return ierror.WXBizMsgCrypt_ValidateSignature_Error, None
278 | pc = Prpcrypt(self.key)
279 | ret, xml_content = pc.decrypt(encrypt, self.m_sReceiveId)
280 | return ret, xml_content
--------------------------------------------------------------------------------