微信是一个时代的标志,虽然它现在不温不火,但我们大部分人离不开它。最近我帮朋友的公司接入了微信公众号第三方,使其成为第三方开发者。
网上公众号的开发教程,描述很多,但第三方的就几乎没有,可能是商业部分,大家都保密吧。我是无所谓,因为这东西不难,用心就可以搞定,我把它开源,放到github上。
github地址:cppfun@wechat-open-third-party-dev
第一部分我们先要介绍几个参数,我直接截图,然后进行说明:
https://open.weixin.qq.com
这里面的参数:
# 登录授权的发起页域名:wx.domain.com
# 授权事件接收URL: http://wx.domain.com/auth
# 公众号消息与事件接收URL: http://wx.domain.com/receive/$APPID$
这三个参数你需要在新建应用时就进行确认,但不用截图记下,提交后会有记录,实时可以查看修改。
接下来我们讲讲授权流程的技术实现。为了避免您的困惑,我准备采用小而精的方式,一步步来用代码实现。
在开始之前,我先说说微信第三方开发平台的思路。我们先回到公众号开发,公众号开发是利用公众号的api来控制粉丝即用户;
微信第三方开发平台是利用更高层的公众号api来控制各个授权的公众号(我个人觉得这种类比思维很重要,有了这种思维来开发微信第三方开发平台很简单)。
https://open.weixin.qq.com上面的文档说实话,很一般。
我们进入后端第一环节,接收腾讯微信服务器推送component_verify_ticket。这里和python公众号开发一样,我们将其保存到json文件里,其实到数据库里面也可以,但我觉得没必要,因为它是唯一的。
首先我们需要个回调验证类:
class WxOpenCallback:
def
__init__
(
self
):
self.
token
=
token
def check_signature
(
self
, pams
):
if
not
self.
token:
return HttpResponse
(
'TOKEN is not defined!'
)
msg_signature
= pams.
get
(
'msg_signature'
,
''
)
timestamp
= pams.
get
(
'timestamp'
,
''
)
nonce
= pams.
get
(
'nonce'
,
''
)
tmparr
=
[
self.
token
, timestamp
, nonce
]
tmparr.
sort
(
)
string
=
''.
join
(tmparr
)
string
= hashlib.
sha1
(
string
).
hexdigest
(
)
# print signature
# print string
return msg_signature
==
string
这里面的token就是上面截图当中的token,其余的都是微信服务器post过来的参数。
接着我们获取component_verify_ticket,这里微信为了安全,传递过来的是加密的xml,我们需要解密,解密的python类微信已经提供给我们。我这里不再累述。
wxOpenCallBack
= WxOpenCallback
(
)
is_valid
= wxOpenCallBack.
check_signature
(request.
GET
)
if is_valid:
# get the url params
msg_signature
= request.
GET.
get
(
'msg_signature'
,
''
)
timestamp
= request.
GET.
get
(
'timestamp'
,
''
)
nonce
= request.
GET.
get
(
'nonce'
,
''
)
# get the xml
encrypt_xml
= smart_str
(request.
body
)
decryp_xml
= WxUtils.
get_decrypt_xml
(encrypt_xml
=encrypt_xml
,
msg_signature
=msg_signature
,
timestamp
=timestamp
,
nonce
=nonce
)
if
len
(decryp_xml
)
!=
0:
ticket_xml
= etree.
fromstring
(decryp_xml
)
infoType
= ticket_xml.
find
(
'InfoType'
).
text
if infoType
==
'component_verify_ticket':
data
=
{
'ComponentVerifyTicket': ticket_xml.
find
(
'ComponentVerifyTicket'
).
text
}
json_file
=
open
(
'com_ticket.json'
,
'w'
)
json_file.
write
(json.
dumps
(data
)
)
json_file.
close
(
)
这里我们将component_verify_ticket存储到com_ticket.json文件中。另外这里我封装了微信的加解密类,代码如下:
class WxUtils:
def get_encrypt_xml
(
self
, reply_xml
, nonce
):
encrypt
= WXBizMsgCrypt
(
token
, encodingAESKey
, component_appid
)
ret_encrypt
, encrypt_xml
= encrypt.
EncryptMsg
(reply_xml
, nonce
)
if ret_encrypt
==
0:
return encrypt_xml
else:
return
''
def get_decrypt_xml
(
self
, encrypt_xml
, msg_signature
, timestamp
, nonce
):
decrypt
= WXBizMsgCrypt
(
token
, encodingAESKey
, component_appid
)
ret_decrypt
, decrypt_xml
= decrypt.
DecryptMsg
(encrypt_xml
,
msg_signature
,
timestamp
,
nonce
)
if ret_decrypt
==
0:
return decrypt_xml
else:
return
''
填坑1:
你用以上代码来解密时,其实会出现一个问题,微信传递过来的xml文件中没有ToUserName字段,所以以上的解密方法会报错,我们看一下微信传递过来的xml:
>
>something
>
>1413192605
>
>component_verify_ticket
>
>something
>
>
这时你怎么做? 我是直接判断xml中有没有ToUserName字段,如果没有,我就把AppId替换为ToUserName。我们可以顺便看下微信的解密方法中的其中一段代码:
def extract
(
self
, xmltext
):
"""提取出xml数据包中的加密消息
@param xmltext: 待提取的xml字符串
@return: 提取出的加密消息字符串
"""
try:
xml_tree
= ET.
fromstring
(xmltext
)
encrypt
= xml_tree.
find
(
"Encrypt"
)
touser_name
= xml_tree.
find
(
"ToUserName"
)
return ierror.
WXBizMsgCrypt_OK
, encrypt.
text
, touser_name.
text
except
Exception
,e:
#print e
return ierror.
WXBizMsgCrypt_ParseXml_Error
,
None
,
None
这是提取加密字符串的方法,没有ToUserName的xml会报错:
WXBizMsgCrypt_ParseXml_Error
= -
40002
所以从这一点可以看出微信内部的开发人员水平我个人觉得也一般,至少没有考虑后来的扩展,它直接写死了。
还有一个办法就是在你的xml中提取出ComponentVerifyTicket字段,然后重新拼接个xml来解密,从思想的角度来讲,明显替换的方法效率高。
填坑2:
这个坑在后面会用到,就是当你接收的xml中有中文字符是时,我们需要在进行加解密前先进行utf8编码:
reply_xml
=reply_xml.
encode
(
'utf-8'
)
这样在加解密的时候,就不会再报错。
好了,第一节就到这里,我们马上开始第二节。