微信第三方开放平台开发攻略

发布 : 2017-06-19 分类 : Python 浏览 :
1
2
3
4
5
微信开放平台现在已经开放了公众号第三方平台的功能

这个由官方推出的正式第三方平台功能解决了广大开发者的最后一线顾虑

可以让开发者的第三方平台托管更多的公众账号,对这些公众账号进行更好的服务。
1
2
3
4
5
6
7
8
9
10
除此之外,白名单IP地址列表也很重要

涉及到后续调试时获取ticket、pre_auth_code等时的可以发起请求的IP地址
这些参数在本机调试是不行的

因为不在白名单IP列表中,因此调试过程必须在白名单IP列表的服务器内进行。

提交完资料后,等待审核通过即可获得属于你的第三方平台的AppID和AppSecret

然后就可以进入调试步骤了。

创建公众号第三方平台

Markdown

获取ticket协议

1
ticket值叫做component_verify_ticket,是构成微信开放平台API请求中的参数之一

Markdown

1
2
3
所有第三方平台和公众号之间的消息往来都需要进行加密

因此收到任何事件推送时,都需要对接收到的消息进行解密,才能获取到真正的信息

获取token

1
2
3
4
5
6
7
token完整称为component_access_token,它是用来获取预授权码的关键参数之一。

有了ticket后,获取component_access_token比较直观,api的地址为:https://api.weixin.qq.com/cgi-bin/component/api_component_token,参数需要用POST方式发送

这里有最需要注意的一点:POST数据必须以json格式发送。

第三方平台access_token有效期,为两个小时

获取预授权码

1
2
3
4
5
api地址为:https://api.weixin.qq.com/cgi-bin/component/api_create_preauthcode?component_access_token

以此url为基础上,使用POST方式将json格式数据发送到api来获取预授权码pre_auth_code

预授权码有效期,为20分钟

公众号授权第三方平台

Markdown

第三方平台方获取预授权码(pre_auth_code)

1
预授权码是第三方平台方实现授权托管的必备信息

引入用户进入授权页

1
2
3
4
5
6
第三方平台方可以在自己的网站:中放置“微信公众号授权”或者“小程序授权”的入口,引导公众号和小程序管理员进入授权页。

授权页网址为
https://mp.weixin.qq.com/cgi-bin/componentloginpage?component_appid=xxxx&pre_auth_code=xxxxx&redirect_uri=xxxx

该网址中第三方平台方需要提供第三方平台方appid、预授权码和回调URI

Markdown

用户确认并同意登录授权给第三方平台方

1
用户进入第三方平台授权页后,需要确认并同意将自己的公众号或小程序授权给第三方平台方,完成授权流程。

Markdown

授权后回调URI,得到授权码(authorization_code)和过期时间

1
2
授权流程完成后,授权页会自动跳转进入回调URI
并在URL参数中返回授权码和过期时间(redirect_url?auth_code=xxx&expires_in=600)

利用授权码调用公众号或小程序的相关API

1
2
3
4
5
6
在得到授权码后
第三方平台方可以使用授权码换取授权公众号或小程序的接口调用凭据(authorizer_access_token,也简称为令牌)
再通过该接口调用凭据,按照公众号开发者文档或小程序开发文档的说明

去调用公众号或小程序相关API(能调用哪些API,取决于用户将哪些权限集授权给了第三方平台方,也取决于公众号或小程序自身拥有哪些接口权限)
使用JS SDK等能力

具体实现代码

componentloginpage.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
* {
margin: 0;
padding: 0;
}

body {
background: #333;
font-size: 12px;
font-family: "微软雅黑";
}

.test {
width: 100px;
margin: auto;
}

a {
display: inline-block;
padding: 10px 20px;
background: #369;
color: #FFF;
margin: 100px auto;
}
</style>
</head>
<body>
<div class="test">
<a href="{{ url }}">点我跳转</a>
</div>
</body>
</html>

main.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
from flask import request, render_template
from application.utils.wxopenserver import WxOpenCallback
from .main import main

@main.route('/componentloginpage.html', methods=['POST', 'GET'])
def componentloginpage():
wxOpenCallback = WxOpenCallback()
url = wxOpenCallback.To_componentloginpage()
return render_template('wechat_open/componentloginpage.html', url=url)

@main.route('/cgi-bin/oauth/callback', methods=['POST', 'GET'])
def authorize_callback():
args = dict(request.args.items())
data = request.get_data()
print('/cgi-bin/oauth/callback args=', args, '===data====', data)

if args.get('auth_code') == None:
wxOpenCallback = WxOpenCallback()
is_valid = wxOpenCallback.check_signature(args)
# 如果消息体合法
if is_valid:
print("消息体合法!")
# 获取微信服务器发送过来的component_verify_ticket协议
wxOpenCallback.get_component_verify_ticket(request)
# 获取第三方平台接口调用凭据
component_access_token = wxOpenCallback.get_component_access_token()
if component_access_token != None:
wxOpenCallback.get_pre_auth_code()
else:
print("auth_code:", args.get('auth_code'))
wxOpenCallback = WxOpenCallback()
auth_code_value = args.get('auth_code')
# 使用授权码换取公众号或小程序的接口调用凭据和授权信息
json_data = wxOpenCallback.get_authorization_info(auth_code_value)
print("使用授权码换取公众号或小程序的接口调用凭据和授权信息:", json_data.get('authorization_info'))
# 授权方appid
authorizer_appid = json_data.get('authorization_info').get('authorizer_appid')
print("授权方appid:", authorizer_appid)
# 授权方接口调用凭据
authorizer_access_token = json_data.get('authorization_info').get('authorizer_access_token')
print("授权方接口调用凭据:", authorizer_access_token)
# 授权方的刷新令牌
authorizer_refresh_token = json_data.get('authorization_info').get('authorizer_refresh_token')
print("授权方的刷新令牌:", authorizer_refresh_token)
# 获取(刷新)授权公众号或小程序的接口调用凭据
refresh_authorizer_access_token = wxOpenCallback.get_refresh_authorizer_access_token(authorizer_appid,
authorizer_refresh_token)
print("获取(刷新)授权公众号或小程序的接口调用凭据:", refresh_authorizer_access_token)
# 获取授权方的帐号基本信息
authorizer_info = wxOpenCallback.get_authorizer_info(authorizer_appid)
print("获取授权方的帐号基本信息:", authorizer_info)
# 获取授权方的选项设置信息
option_info = wxOpenCallback.get_option_info(authorizer_appid)
print("获取授权方的选项设置信息:", option_info)
# 设置授权方的选项信息
wxOpenCallback.set_option_info(authorizer_appid, "voice_recognize", "1")
print("设置授权方的选项信息:", wxOpenCallback.set_option_info(authorizer_appid, "voice_recognize", "1"))
# 拉取当前所有已授权的帐号基本信息
print("拉取当前所有已授权的帐号基本信息:", wxOpenCallback.get_authorizer_list())
return 'suceess'

wxopenserver.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#########################################################################
# Author: Haodi Wang
# Created Time: Jun 23 Sep 2017 15:55:41 PM CST
# File Name: wxopenserver.py
# Description: 微信第三方平台工具类
#########################################################################

from config import wechat_config
from application.utils.WXBizMsgCrypt import WXBizMsgCrypt
import xml.etree.cElementTree as ET
from application.utils.redis_func import get_redis_conn
import hashlib
import time
import requests
import json


class WxOpenCallback:
def __init__(self):
self.redis_conn = get_redis_conn('resources')
self.token = wechat_config.WECHAT_COMPONENT_TOKEN
self.component_appid = wechat_config.WECHAT_COMPONENT_APPID
self.component_appsecret = wechat_config.WECHAT_COMPONENT_APPSECRET
self.encodingAESKey = wechat_config.WECHAT_COMPONENT_ENCODINGAESKEY

# 验证消息体的合法性
def check_signature(self, param):
if not self.token:
return 'Token is not defined!'
timestamp = param.get('timestamp')
nonce = param.get('nonce')
signature = param.get('signature')
tmparr = [self.token, timestamp, nonce]
tmparr.sort()
str = ''.join(tmparr)
# 使用sha1安全哈希算法,作为十六进制数据字符串值
str = hashlib.sha1(str.encode('utf-8')).hexdigest()
return signature == str

# 1.获取微信服务器发送过来的component_verify_ticket协议
def get_component_verify_ticket(self, param):
args = dict(param.args.items())
# 使用Tencent提供工具类解密接口
from_xml = param.get_data()
from_str = str(from_xml, encoding='utf-8').split(" ")

from_xml = ""
for i in from_str:
from_xml = (from_xml + i.replace("\\n", "").replace("AppId", "ToUserName")).strip()

decrypt_test = WXBizMsgCrypt(self.token, self.encodingAESKey, self.component_appid)
ret, decryp_xml = decrypt_test.DecryptMsg(from_xml, args.get("msg_signature"),
args.get("timestamp"),
args.get("nonce"))
if len(decryp_xml) != 0:
xml_tree = ET.fromstring(decryp_xml)
if xml_tree.find("InfoType").text == 'component_verify_ticket':
ComponentVerifyTicket = xml_tree.find("ComponentVerifyTicket")
data = {
"ticket": ComponentVerifyTicket.text
}
ticket = data.get('ticket')
# save file
self.redis_conn.set('ticket', ticket)
else:
return None

# 2.获取第三方平台接口调用凭据
# 每个令牌是存在有效期(2小时)的,且令牌的调用不是无限制的
def get_component_access_token(self):
now = time.time()
print("now:", now)
print("redis_conn.get('expires_in'):", self.redis_conn.get('expires_in'))
print(float(self.redis_conn.get('expires_in')) < now)
if self.redis_conn.get('component_access_token') != None:
expires_in = float(self.redis_conn.get('expires_in'))
if expires_in < now:
print("Now:", now)
url = "https://api.weixin.qq.com/cgi-bin/component/api_component_token"
print("redis_conn.get('ticket'):", self.redis_conn.get('ticket'))
payload = {
"component_appid": self.component_appid,
"component_appsecret": self.component_appsecret,
"component_verify_ticket": self.redis_conn.get('ticket')
}
headers = {'content-type': 'application/json'}
response = requests.post(url, data=json.dumps(payload), headers=headers)
print(response.text)
component_access_token = json.loads(response.text).get('component_access_token')
expires_in = int(now) + 6200
# save key
self.redis_conn.set('component_access_token', component_access_token)
self.redis_conn.set('expires_in', expires_in)
return component_access_token
else:
return self.redis_conn.get('component_access_token')
else:
url = "https://api.weixin.qq.com/cgi-bin/component/api_component_token"
payload = {
"component_appid": self.component_appid,
"component_appsecret": self.component_appsecret,
"component_verify_ticket": self.redis_conn.get('ticket')
}
# print("payload:", payload)
headers = {'content-type': 'application/json'}
response = requests.post(url, data=json.dumps(payload), headers=headers)
# print(response.text)
component_access_token = json.loads(response.text).get('component_access_token')
# print("component_access_token:", component_access_token)
expires_in = int(now) + 6200
if component_access_token != None:
# save key
self.redis_conn.set('component_access_token', component_access_token)
self.redis_conn.set('expires_in', expires_in)

return component_access_token

# 3.获取预授权码
# 预授权码有效期,为20分钟
# component_access_token:令牌每1小时50分就会去重新获取
def get_pre_auth_code(self):
if self.redis_conn.get('pre_auth_code') != None:
now = time.time()
print('now:', now)
print('pre_auth_code_expires_in:', self.redis_conn.get('pre_auth_code_expires_in'))
print(float(self.redis_conn.get('pre_auth_code_expires_in')) < now)
if float(self.redis_conn.get('pre_auth_code_expires_in')) < now:
url = 'https://api.weixin.qq.com/cgi-bin/component/api_create_preauthcode?component_access_token=%s' % self.redis_conn.get(
'component_access_token')
headers = {'content-type': 'application/json'}
payload = {'component_appid': self.component_appid}
response = requests.post(url, data=json.dumps(payload), headers=headers)
pre_auth_code = json.loads(response.text).get('pre_auth_code')
expires_in = int(now) + 1100
print("pre_auth_code:", pre_auth_code)
print("expires_in:", expires_in)
if pre_auth_code != None:
# save key
self.redis_conn.set('pre_auth_code', pre_auth_code)
self.redis_conn.set('pre_auth_code_expires_in', expires_in)
return pre_auth_code
else:
return self.redis_conn.get('pre_auth_code')
else:
now = time.time()
url = 'https://api.weixin.qq.com/cgi-bin/component/api_create_preauthcode?component_access_token=%s' % self.redis_conn.get(
'component_access_token')
headers = {'content-type': 'application/json'}
payload = {'component_appid': self.component_appid}
response = requests.post(url, data=json.dumps(payload), headers=headers)
pre_auth_code = json.loads(response.text).get('pre_auth_code')
expires_in = int(now) + 1100
print("pre_auth_code:", pre_auth_code)
print("expires_in:", expires_in)
if pre_auth_code != None:
# save key
self.redis_conn.set('pre_auth_code', pre_auth_code)
self.redis_conn.set('pre_auth_code_expires_in', expires_in)
return pre_auth_code

# 引入用户进入授权页
def To_componentloginpage(self):
pre_auth_code = self.redis_conn.get('pre_auth_code')
redirect_uri = 'http://bot.lmbang.com/cgi-bin/oauth/callback'
url = 'https://mp.weixin.qq.com/cgi-bin/componentloginpage?component_appid=' + self.component_appid + '&pre_auth_code=' + pre_auth_code + '&redirect_uri=' + redirect_uri
return url

# 4.使用授权码换取公众号或小程序的接口调用凭据和授权信息
def get_authorization_info(self, auth_code_value):
url = 'https://api.weixin.qq.com/cgi-bin/component/api_query_auth?component_access_token=%s' % self.redis_conn.get(
'component_access_token')
headers = {'content-type': 'application/json'}
payload = {'component_appid': self.component_appid,
"authorization_code": auth_code_value}
response = requests.post(url, data=json.dumps(payload), headers=headers)
return response.json()

# 5.获取(刷新)授权公众号或小程序的接口调用凭据
def get_refresh_authorizer_access_token(self, authorizer_appid, authorizer_refresh_token):
url = 'https://api.weixin.qq.com/cgi-bin/component/api_authorizer_token?component_access_token=%s' % self.redis_conn.get(
'component_access_token')
headers = {'content-type': 'application/json'}
payload = {
"component_appid": self.component_appid,
"authorizer_appid": authorizer_appid,
"authorizer_refresh_token": authorizer_refresh_token
}
response = requests.post(url, data=json.dumps(payload), headers=headers)
return response.json()

# 6.获取授权方的帐号基本信息
def get_authorizer_info(self, authorizer_appid):
url = 'https://api.weixin.qq.com/cgi-bin/component/api_get_authorizer_info?component_access_token=%s' % self.redis_conn.get(
'component_access_token')
headers = {'content-type': 'application/json'}
payload = {'component_appid': self.component_appid,
"authorizer_appid": authorizer_appid}
response = requests.post(url, data=json.dumps(payload), headers=headers)
return response.json()

# 7.获取授权方的选项设置信息
def get_option_info(self, authorizer_appid):
url = 'https://api.weixin.qq.com/cgi-bin/component/api_get_authorizer_option?component_access_token=%s' % self.redis_conn.get(
'component_access_token')
headers = {'content-type': 'application/json'}
payload = {'component_appid': self.component_appid,
"authorizer_appid": authorizer_appid,
"option_name": "voice_recognize"}
# location_report(地理位置上报选项) 0无上报 1开启
# voice_recognize(语音识别开关选项)0无上报 1开启
# customer_service(多客服开关选项)0无上报 1开启
response = requests.post(url, data=json.dumps(payload), headers=headers)
return response.json()

# 8.设置授权方的选项信息
def set_option_info(self, authorizer_appid, option_name_value, option_value_value):
url = 'https://api.weixin.qq.com/cgi-bin/component/api_set_authorizer_option?component_access_token=%s' % self.redis_conn.get(
'component_access_token')
headers = {'content-type': 'application/json'}
payload = {
"component_appid": self.component_appid,
"authorizer_appid": authorizer_appid,
"option_name": option_name_value,
"option_value": option_value_value
}
response = requests.post(url, data=json.dumps(payload), headers=headers)
return response.json()

# 9.拉取当前所有已授权的帐号基本信息
def get_authorizer_list(self):
url = 'https://api.weixin.qq.com/cgi-bin/component/api_get_authorizer_list?component_access_token=%s' % self.redis_conn.get(
'component_access_token')
headers = {'content-type': 'application/json'}
payload = {
"component_appid": self.component_appid,
"offset": 0, # 偏移位置/起始位置
"count": 10 # 拉取数量,最大为500
}
response = requests.post(url, data=json.dumps(payload), headers=headers)
return response.json()
本文作者 : Matrix
原文链接 : https://matrixsparse.github.io/2017/06/19/微信第三方开放平台开发攻略/
版权声明 : 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!

知识 & 情怀 | 二者兼得

微信扫一扫, 向我投食

微信扫一扫, 向我投食

支付宝扫一扫, 向我投食

支付宝扫一扫, 向我投食

留下足迹