一、基础配置:服务器配置
1)登录公众号平台获取开发者ID(AppID)和开发者密码(AppSecret)
拉到底【开发】——【基本设置】——按提示开启功能——把appID、AppSecret保存下来
或使用测试号进行开发:https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login
2)部署服务器用flask
先用自带的wsgi即可,uwsgi+nginx配置可以后再做
IP 101.200.170.171
www.xxx.cn
账号: s8001 ~ s8020
密码: 123456a
flask程序的url /wechat80xx 运行的端口80xx
微信配置url http://101.200.170.171/wechat80xx
1.切换到
ssh s80xx@101.200.170.171
切换到flask_py2 虚拟环境 workon flask_py2
如果提示workon命令未找到 source ~/.bashrc
2.复制本地文件到远程
scp是复制本地文件到远程,类似cp复制命令
用法:scp 源文件 目标文件
scp ./wechat.py s80xx@101.200.170.171:~/xxxx/
3.运行
#查看正在运行的80端口
nestat -apn|grep 80
# ==> 输出 tcp 0 0 127.0.0.1:80 0.0.0.0:* LISTEN 12743...
#关闭80端口
kill -9 12743
#运行flask
python index.py
3)新建文件夹和文件【www/wechat/index.py】
# coding:utf-8
from flask import Flask, request, abort, render_template
import hashlib
import xmltodict
import time
import urllib.request
import json
# 常量
WECHAT_TOKEN = "bluesky" # 【1】微信的token令牌,可随便写,但和配置时的token要统一
WECHAT_APPID = "wx0cffd65998cbf988" #【2】此处填一步得到的appID
WECHAT_APPSECRET = "96dxxxxxxxd3a" #【3】此处填一步得到的AppSecret
app = Flask(__name__)
@app.route("/wechat", methods=["GET", "POST"])
def wechat():
"""对接微信公众号服务器"""
# 接收微信服务器发送的参数
signature = request.args.get("signature")
timestamp = request.args.get("timestamp")
nonce = request.args.get("nonce")
# 校验参数
if not all([signature, timestamp, nonce]):
abort(400)
# 按照微信的流程进行计算签名
li = [WECHAT_TOKEN, timestamp, nonce]
# 排序
li.sort()
# 拼接字符串
tmp_str = "".join(li)
# 进行sha1加密, 得到正确的签名值
sign = hashlib.sha1(tmp_str.encode("utf-8")).hexdigest()
# 将自己计算的签名值与请求的签名参数进行对比,如果相同,则证明请求来自微信服务器
if signature != sign:
# 表示请求不是微信发的
abort(403)
else:
# 表示是微信发送的请求
if request.method == "GET":
# 表示是第一次接入微信服务器的验证
echostr = request.args.get("echostr")
if not echostr:
abort(400)
return echostr
elif request.method == "POST":
# 表示微信服务器转发消息过来
xml_str = request.data
if not xml_str:
abort(400)
# 对xml字符串进行解析
xml_dict = xmltodict.parse(xml_str)
xml_dict = xml_dict.get("xml")
# 提取消息类型
msg_type = xml_dict.get("MsgType")
if msg_type == "text":
# 表示发送的是文本消息
# 构造返回值,经由微信服务器回复给用户的消息内容
resp_dict = {
"xml": {
"ToUserName": xml_dict.get("FromUserName"),
"FromUserName": xml_dict.get("ToUserName"),
"CreateTime": int(time.time()),
"MsgType": "text",
"Content": xml_dict.get("Content")
}
}
else:
resp_dict = {
"xml": {
"ToUserName": xml_dict.get("FromUserName"),
"FromUserName": xml_dict.get("ToUserName"),
"CreateTime": int(time.time()),
"MsgType": "text",
"Content": "i love u"
}
}
# 将字典转换为xml字符串
resp_xml_str = xmltodict.unparse(resp_dict)
# 返回消息数据给微信服务器
return resp_xml_str
# www.itcastcpp.cn/wechat/index
@app.route("/wechat/index")
def index():
"""让用户通过微信访问的网页页面视图"""
# 从微信服务器中拿去用户的资料数据
# 1. 拿去code参数
code = request.args.get("code")
if not code:
return u"确实code参数"
# 2. 向微信服务器发送http请求,获取access_token
url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code" \
% (WECHAT_APPID, WECHAT_APPSECRET, code)
# 使用urllib2的urlopen方法发送请求
# 如果只传网址url参数,则默认使用http的get请求方式, 返回响应对象
response = urllib.request.urlopen(url) #urllib2.urlopen(url)
# 获取响应体数据,微信返回的json数据
json_str = response.read()
resp_dict = json.loads(json_str)
# 提取access_token
if "errcode" in resp_dict:
return u"获取access_token失败"
access_token = resp_dict.get("access_token")
open_id = resp_dict.get("openid") # 用户的编号
# 3. 向微信服务器发送http请求,获取用户的资料数据
url = "https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s&lang=zh_CN" \
% (access_token, open_id)
response = urllib.request.urlopen(url) #urllib2.urlopen(url)
# 读取微信传回的json的响应体数据
user_json_str = response.read()
user_dict_data = json.loads(user_json_str)
if "errcode" in user_dict_data:
return u"获取用户信息失败"
else:
# 将用户的资料数据填充到页面中
return render_template("index.html", user=user_dict_data)
if __name__ == '__main__':
app.run(host="0.0.0.0",port=80, debug=True)
4)运行3步代码,在命令窗输入
python index.py
(.env) [root@VM_0_11_centos web1]# python index.py
* Serving Flask app "index" (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: on
* Running on http://0.0.0.0:80/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 188-188-588
5)配置微信的【服务器配置】
- 最后点提交:如提示【token验证失败】则说明配置不对仔细检查1-4步后再试
- 1处填的是服务器ip或域名都可
- 1处/wechat和源码里
@app.route("/wechat", methods=["GET", "POST"])
要统一
此时再看服务器命令窗输出如下:
188.58.88.28 - - [28/Apr/2020 16:00:27] "GET /wechat?signature=362ceb6bf4dxxxxxx670792b97ef355&echostr=940xxx8x0xx9×tamp=158xxx827&nonce=2xxx888 HTTP/1.0" 200 -
188.58.88.28 - - [28/Apr/2020 16:14:36] "GET / HTTP/1.1" 404 -
188.58.88.28 - - [28/Apr/2020 16:14:37] "GET /robots.txt HTTP/1.1" 404 -
188.58.88.28 - - [28/Apr/2020 16:14:37] "GET /sitemap.xml HTTP/1.1" 404 -
188.58.88.28 - - [28/Apr/2020 16:14:38] "GET /.well-known/security.txt HTTP/1.1" 404 -
6)最后点【启用】提示’开启成功’即可
二、公众号验证(一.3源码详解)
-
验证URL有效性成功后即接入生效,成为开发者。如果公众号类型为服务号(订阅号只能使用普通消息接口),可以在公众平台网站中申请认证,认证成功的服务号将获得众多接口权限,以满足开发者需求。
-
此后用户每次向公众号发送消息、或者产生自定义菜单点击事件时,开发者填写的服务器配置URL将得到微信服务器推送过来的消息和事件,然后开发者可以依据自身业务逻辑进行响应,例如回复消息等。
-
用户向公众号发送消息时,公众号方收到的消息发送者是一个OpenID,是使用用户微信号加密后的结果,每个用户对每个公众号有一个唯一的OpenID。
第1步:对接微信服务器(验证、接收微信服务器传过来的参数)
1-1验证服务器地址的有效性
开发者提交信息后,微信服务器将发送GET请求到填写的服务器地址URL上,GET请求携带四个参数:
开发者通过检验signature对请求进行校验。若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,否则接入失败。
校验流程:
- 将token、timestamp、nonce三个参数进行字典序排序
- 将三个参数字符串拼接成一个字符串进行sha1加密
- 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
源码如下:
from flask import Flask,request,make_response
import hashlib
app = Flask(__name__)
@app.route('/wechat')
def wechat():
#设置token
token = 'python'
#获取参数
data = request.args
signature = data.get('signature')
timestamp = data.get('timestamp')
nonce = data.get('nonce')
echostr = data.get('echostr')
#对参数进行字典排序,拼接字符串
temp = [timestamp,nonce,token]
temp.sort()
temp = ''.join(temp)
#加密,需进行encode()设置,原:hashlib.sha1(temp).hexdigest()
if (hashlib.sha1(tmp_str.encode("utf-8")).hexdigest()==signature):
return make_response(echostr)
else:
return 'error',403
if __name__ == '__main__':
app.run(host="0.0.0.0",port=80, debug=True)
效果:此处部署完成后即可在一.5步完成提交认证
三、接收微信服务器发来的普通消息并回复
1)当普通微信用户向公众账号发消息时,微信服务器将POST消息的XML数据包到开发者填写的URL上。
微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次。假如服务器无法保证在五秒内处理并回复,可以直接回复空串,微信服务器不会对此作任何处理,并且不会发起重试。
各消息类型的推送使用XML数据包结构,如:
<xml>
<ToUserName><![CDATA[gh_866835093fea]]></ToUserName> #发给哪个公众号(开发者公众号)
<FromUserName><![CDATA[ogdotwSc_MmEEsJs9-ABZ1QL_4r4]]></FromUserName> #来自哪个用户(1个openID)
<CreateTime>1478317060</CreateTime> #消息创建时间(整型)
<MsgType><![CDATA[text]]></MsgType> #消息类型有文本、视频、语音等等
<Content><![CDATA[你好]]></Content> #消息内容
<MsgId>6349323426230210995</MsgId> #消息id微信管理的,每个消息id都是唯一的(64位整型)
</xml>
【注意】:<![CDATA 与 ]]> 括起来的数据不会被xml解析器解析,类似转义。
普通消息类别
- 文本消息
- 图片消息
- 语音消息
- 视频消息
- 小视频消息
- 地理位置消息
- 链接消息
2)解析xml工具:xmltodict 模块基本用法
xmltodict 是一个用来处理xml数据的很方便的模块。包含两个常用方法parse和unparse
1 xml2dict【parse】
xmltodict.parse()方法可以将xml数据转为python中的dict字典数据:
>>> import xmltodict
>>> xml_str = """
... <xml>
... <ToUserName><![CDATA[gh_866835093fea]]></ToUserName>
... <FromUserName><![CDATA[ogdotwSc_MmEEsJs9-ABZ1QL_4r4]]></FromUserName>
... <CreateTime>1478317060</CreateTime>
... <MsgType><![CDATA[text]]></MsgType>
... <Content><![CDATA[你好]]></Content>
... <MsgId>6349323426230210995</MsgId>
... </xml>
... """
>>>
>>> xml_dict = xmltodict.parse(xml_str)
>>> type(xml_dict)
<class 'collections.OrderedDict'> # 类字典型,可以按照字典方法操作
>>>
>>> xml_dict
OrderedDict([(u'xml', OrderedDict([(u'ToUserName', u'gh_866835093fea'), (u'FromUserName', u'ogdotwSc_MmEEsJs9-ABZ1QL_4r4'), (u'CreateTime', u'1478317060'), (u'MsgType', u'text'), (u'Content', u'\u4f60\u597d'), (u'MsgId', u'6349323426230210995')]))])
>>>
>>> xml_dict['xml']
OrderedDict([(u'ToUserName', u'gh_866835093fea'), (u'FromUserName', u'ogdotwSc_MmEEsJs9-ABZ1QL_4r4'), (u'CreateTime', u'1478317060'), (u'MsgType', u'text'), (u'Content', u'\u4f60\u597d'), (u'MsgId', u'6349323426230210995')])
>>>
>>> for key, val in xml_dict['xml'].items():
... print key, "=", val
...
ToUserName = gh_866835093fea
FromUserName = ogdotwSc_MmEEsJs9-ABZ1QL_4r4
CreateTime = 1478317060
MsgType = text
Content = 你好
MsgId = 6349323426230210995
2 反向 dict2xml 【unparse】
xmltodict.unparse()方法可以将字典转换为xml字符串:
xml_dict = {
"xml": {
"ToUserName" : "gh_866835093fea",
"FromUserName" : "ogdotwSc_MmEEsJs9-ABZ1QL_4r4",
"CreateTime" : "1478317060",
"MsgType" : "text",
"Content" : u"你好",
"MsgId" : "6349323426230210995",
}
}
>>> xml_str = xmltodict.unparse(xml_dict)
>>> print xml_str
<?xml version="1.0" encoding="utf-8"?>
<xml><FromUserName>ogdotwSc_MmEEsJs9-ABZ1QL_4r4</FromUserName><MsgId>6349323426230210995</MsgId><ToUserName>gh_866835093fea</ToUserName><Content>你好</Content><MsgType>text</MsgType><CreateTime>1478317060</CreateTime></xml>
>>>
>>> xml_str = xmltodict.unparse(xml_dict, pretty=True) # pretty表示友好输出
>>> print xml_str
<?xml version="1.0" encoding="utf-8"?>
<xml>
<FromUserName>ogdotwSc_MmEEsJs9-ABZ1QL_4r4</FromUserName>
<MsgId>6349323426230210995</MsgId>
<ToUserName>gh_866835093fea</ToUserName>
<Content>你好</Content>
<MsgType>text</MsgType>
<CreateTime>1478317060</CreateTime>
</xml>
3)被动回复消息
当用户发送消息给公众号时(或某些特定的用户操作引发的事件推送时),会产生一个POST请求,开发者可以在响应包中返回特定XML结构,来对该消息进行响应(现支持回复文本、图片、图文、语音、视频、音乐)。严格来说,发送被动响应消息其实并不是一种接口,而是对微信服务器发过来消息的一次回复。
假如服务器无法保证在五秒内处理并回复,必须做出下述回复,这样微信服务器才不会对此作任何处理,并且不会发起重试(这种情况下,可以使用客服消息接口进行异步回复),否则,将出现严重的错误提示。详见下面说明:
- (推荐方式)直接回复success
- 直接回复空串(指字节长度为0的空字符串,而不是XML结构体中content字段的内容为空)
- 一旦遇到以下情况,微信都会在公众号会话中,向用户下发系统提示“该公众号暂时无法提供服务,请稍后再试”:
开发者回复了异常数据,比如JSON数据等
【回复的消息类型】
- 文本消息
- 图片消息
- 语音消息
- 视频消息
- 音乐消息
- 图文消息
- 回复文本消息
【回复格式】
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName> #必须,接收账号
<FromUserName><![CDATA[fromUser]]></FromUserName> #必须,开发者账号
<CreateTime>12345678</CreateTime> #必须,创建时间
<MsgType><![CDATA[text]]></MsgType> #必须,消息类型
<Content><![CDATA[你好]]></Content> #必须,回复的内容,支持换行,
</xml>
消息回复源码
from flask import Flask,request,make_response
import hashlib
import xmltodict
import time
app = Flask(__name__)
TOKEN = 'bluesky'
@app.route('/wechat',methods=['GET','POST'])
def wechat():
if request.method=='GET':
data = request.args
signature = data.get('signature')
timestamp = data.get('timestamp')
nonce = data.get('nonce')
echostr = data.get('echostr')
temp = [timestamp,nonce,TOKEN]
temp.sort()
temp = ''.join(temp)
if (hashlib.sha1(temp).hexdigest()==signature):
return make_response(echostr)
if request.method=='POST':
xml = request.data
req = xmltodict.parse(xml)['xml']
if 'text' == req.get('MsgType'):
resp = {
'ToUserName':req.get('FromUserName'),
'FromUserName':req.get('ToUserName'),
'CreateTime':int(time.time()),
'MsgType':'text',
'Content':req.get('Content')
}
xml = xmltodict.unparse({'xml': resp})
print req.get('Content')
return xml
else:
resp = {
'ToUserName': req.get('FromUserName', ''),
'FromUserName': req.get('ToUserName', ''),
'CreateTime': int(time.time()),
'MsgType': 'text',
'Content': 'I LOVE ITCAST'
}
xml = xmltodict.unparse({'xml':resp})
return xml
if __name__ == '__main__':
app.run(host="0.0.0.0",port=80, debug=True)
效果:在对应公众号上回复
- 任何文字消息都会回复一样的消息,非文字,则回复i love itcast(输入法的emoji表情算文字)
四、接收其他普通消息
1)接收图片消息
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>1348831860</CreateTime>
<MsgType><![CDATA[image]]></MsgType>
<PicUrl><![CDATA[this is a url]]></PicUrl>
<MediaId><![CDATA[media_id]]></MediaId>
<MsgId>1234567890123456</MsgId>
</xml>
参数 | 描述 |
---|---|
ToUserName | 开发者微信号 |
FromUserName | 发送方帐号(一个OpenID) |
CreateTime | 消息创建时间 (整型) |
MsgType | image |
PicUrl | 图片链接 |
MediaId | 图片消息媒体id,可以调用多媒体文件下载接口拉取数据。 |
MsgId | 消息id,64位整型 |
2)接收视频消息
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>1357290913</CreateTime>
<MsgType><![CDATA[video]]></MsgType>
<MediaId><![CDATA[media_id]]></MediaId>
<ThumbMediaId><![CDATA[thumb_media_id]]></ThumbMediaId>
<MsgId>1234567890123456</MsgId>
</xml>
参数 | 描述 |
---|---|
ToUserName | 开发者微信号 |
FromUserName | 发送方帐号(一个OpenID) |
CreateTime | 消息创建时间 (整型) |
MsgType | 视频为video |
MediaId | 视频消息媒体id,可以调用多媒体文件下载接口拉取数据。 |
ThumbMediaId | 视频消息缩略图的媒体id,可以调用多媒体文件下载接口拉取数据。 |
MsgId | 消息id,64位整型 |
3)接收小视频消息
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>1357290913</CreateTime>
<MsgType><![CDATA[shortvideo]]></MsgType>
<MediaId><![CDATA[media_id]]></MediaId>
<ThumbMediaId><![CDATA[thumb_media_id]]></ThumbMediaId>
<MsgId>1234567890123456</MsgId>
</xml>
参数 | 描述 |
---|---|
ToUserName | 开发者微信号 |
FromUserName | 发送方帐号(一个OpenID) |
CreateTime | 消息创建时间 (整型) |
MsgType | 小视频为shortvideo |
MediaId | 视频消息媒体id,可以调用多媒体文件下载接口拉取数据。 |
ThumbMediaId | 视频消息缩略图的媒体id,可以调用多媒体文件下载接口拉取数据。 |
MsgId | 消息id,64位整型 |
4)接收语音消息
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>1357290913</CreateTime>
<MsgType><![CDATA[voice]]></MsgType>
<MediaId><![CDATA[media_id]]></MediaId>
<Format><![CDATA[Format]]></Format>
<MsgId>1234567890123456</MsgId>
</xml>
参数 | 描述 |
---|---|
ToUserName | 开发者微信号 |
FromUserName | 发送方帐号(一个OpenID) |
CreateTime | 消息创建时间 (整型) |
MsgType | 语音为voice |
MediaId | 语音消息媒体id,可以调用多媒体文件下载接口拉取数据。 |
Format | 语音格式,如amr,speex等 |
MsgID | 消息id,64位整型 |
请注意,开通语音识别后,用户每次发送语音给公众号时,微信会在推送的语音消息XML数据包中,增加一个Recongnition字段(注:由于客户端缓存,开发者开启或者关闭语音识别功能,对新关注者立刻生效,对已关注用户需要24小时生效。开发者可以重新关注此帐号进行测试)。
4-1开启语音识别后的语音XML数据包如下:
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>1357290913</CreateTime>
<MsgType><![CDATA[voice]]></MsgType>
<MediaId><![CDATA[media_id]]></MediaId>
<Format><![CDATA[Format]]></Format>
<Recognition><![CDATA[腾讯微信团队]]></Recognition>
<MsgId>1234567890123456</MsgId>
</xml>
多出的字段中,Format为语音格式,一般为amr,Recognition为语音识别结果,使用UTF8编码。
五、回复其他普通消息
1.回复图片消息
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>12345678</CreateTime>
<MsgType><![CDATA[image]]></MsgType>
<Image>
<MediaId><![CDATA[media_id]]></MediaId>
</Image>
</xml>
参数 | 是否必须 | 说明 |
---|---|---|
ToUserName | 是 | 接收方帐号(收到的OpenID) |
FromUserName | 是 | 开发者微信号 |
CreateTime | 是 | 消息创建时间 (整型) |
MsgType | 是 | image |
MediaId | 是 | 通过素材管理接口上传多媒体文件,得到的id。 |
2.回复语音消息
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>12345678</CreateTime>
<MsgType><![CDATA[voice]]></MsgType>
<Voice>
<MediaId><![CDATA[media_id]]></MediaId>
</Voice>
</xml>
参数 | 是否必须 | 说明 |
---|---|---|
ToUserName | 是 | 接收方帐号(收到的OpenID) |
FromUserName | 是 | 开发者微信号 |
CreateTime | 是 | 消息创建时间戳 (整型) |
MsgType | 是 | 语音,voice |
MediaId | 是 | 通过素材管理接口上传多媒体文件,得到的id |
3.回复视频消息
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>12345678</CreateTime>
<MsgType><![CDATA[video]]></MsgType>
<Video>
<MediaId><![CDATA[media_id]]></MediaId>
<Title><![CDATA[title]]></Title>
<Description><![CDATA[description]]></Description>
</Video>
</xml>
参数 | 是否必须 | 说明 |
---|---|---|
ToUserName | 是 | 接收方帐号(收到的OpenID) |
FromUserName | 是 | 开发者微信号 |
CreateTime | 是 | 消息创建时间 (整型) |
MsgType | 是 | video |
MediaId | 是 | 通过素材管理接口上传多媒体文件,得到的id |
Title | 否 | 视频消息的标题 |
Description | 否 | 视频消息的描述 |
4.回复用户语音消息识别
from flask import Flask,request
import xmltodict
import time
app = Flask(__name__)
@app.route('/wechat8000',methods=['GET','POST'])
def wechat():
if request.method == 'POST':
xml = request.data
req = xmltodict.parse(xml)['xml']
msg_type = req.get('MsgType')
if 'text' == msg_type:
resp = {
'ToUserName':req.get('FromUserName',''),
'FromUserName':req.get('ToUserName',''),
'CreateTime':int(time.time()),
'MsgType':'text',
'Content':req.get('Content')
}
elif 'voice' == msg_type:
resp = {
'ToUserName':req.get('FromUserName',''),
'FromUserName':req.get('ToUserName',''),
'CreateTime':int(time.time()),
'MsgType':'text',
'Content':req.get('Recognition',u'无法识别')
}
else:
resp = {
'ToUserName':req.get('FromUserName',''),
'FromUserName':req.get('ToUserName',''),
'CreateTime':int(time.time()),
'MsgType':'text',
'Content':'I LOVE ITCAST'
}
xml = xmltodict.unparse({'xml':resp})
return xml
if __name__ == '__main__':
app.run(port=8000,debug=True)
六、关注/取消关注事件
-
用户在关注与取消关注公众号时,微信会把这个事件推送到开发者填写的URL。
-
微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次。
-
假如服务器无法保证在五秒内处理并回复,可以直接回复空串,微信服务器不会对此作任何处理,并且不会发起重试。
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[FromUser]]></FromUserName>
<CreateTime>123456789</CreateTime>
<MsgType><![CDATA[event]]></MsgType>
<Event><![CDATA[subscribe]]></Event>
</xml>
参数 | 描述 |
---|---|
ToUserName | 开发者微信号 |
FromUserName | 发送方帐号(一个OpenID) |
CreateTime | 消息创建时间 (整型) |
MsgType | 消息类型,event |
Event | 事件类型,subscribe(订阅)、unsubscribe(取消订阅) |
@app.route('/wechat8000',methods=['GET','POST'])
def wechat():
if request.method == 'POST':
req_xml = request.data
req = xmltodict.parse(req_xml)['xml']
msg_type = req.get("MsgType")
if "text" == msg_type:
resp = {
"ToUserName":req.get("FromUserName", ""),
"FromUserName":req.get("ToUserName", ""),
"CreateTime":int(time.time()),
"MsgType":"text",
"Content":req.get("Content", "")
}
elif "voice" == msg_type:
resp = {
"ToUserName":req.get("FromUserName", ""),
"FromUserName":req.get("ToUserName", ""),
"CreateTime":int(time.time()),
"MsgType":"text",
"Content":req.get("Recognition", u"未识别")
}
elif "event" == msg_type:
if "subscribe" == req.get("Event"):
resp = {
"ToUserName":req.get("FromUserName", ""),
"FromUserName":req.get("ToUserName", ""),
"CreateTime":int(time.time()),
"MsgType":"text",
"Content":u"感谢您的关注!"
}
else:
resp = None
else:
resp = {
"ToUserName":req.get("FromUserName", ""),
"FromUserName":req.get("ToUserName", ""),
"CreateTime":int(time.time()),
"MsgType":"text",
"Content":"I love you, itcast!"
}
if resp:
resp_xml = xmltodict.unparse({"xml":resp})
else:
resp_xml = ""
return resp_xml
七、微信网页授权
【需求】
现在,我们要实现一个微信内网页,通过微信访问网页时,网页会展示微信用户的个人信息。因为涉及到用户的个人信息,所以需要有用户授权才可以。当用户授权后,我们的网页服务器(开发者服务器)会拿到用户的“授权书”(code),我们用这个code向微信服务器领取访问令牌(accecc_token)和用户的身份号码(openid),然后凭借access_token和openid向微信服务器提取用户的个人信息。
【步骤】
- 第一步:用户同意授权,获取code
- 第二步:通过code换取网页授权access_token
- 第三步:拉取用户信息(需scope为 snsapi_userinfo)
那么,如何拿到用户的授权code呢?
授权是由微信发起让用户进行确认,在这个过程中是微信在与用户进行交互,所以用户应该先访问微信的内容,用户确认后再由微信将用户导向到我们的网页链接地址,并携带上code参数。我们把这个过程叫做网页回调,类似于我们在程序编写时用到的回调函数,都是回调的思想。
获取流程
1. 设置网页授权回调域名
在微信公众号请求用户网页授权之前,开发者需要先到公众平台官网中的开发者中心页配置授权回调域名。请注意,这里填写的是域名(是一个字符串),而不是URL,因此请勿加 http:// 等协议头;
授权回调域名配置规范为全域名,比如需要网页授权的域名为:www.qq.com,配置以后此域名下面的页面http://www.qq.com/music.html 、 http://www.qq.com/login.html 都可以进行OAuth2.0鉴权。但http://pay.qq.com 、 http://music.qq.com 、 http://qq.com无法进行OAuth2.0鉴权。
2. 用户同意授权,获取code
让用户访问一下链接地址:
https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
下图为scope等于snsapi_userinfo时的授权页面:
用户同意授权后
如果用户同意授权,页面将跳转至 redirect_uri/?code=CODE&state=STATE。若用户禁止授权,则重定向后不会带上code参数,仅会带上state参数redirect_uri?state=STATE
3. 通过code换取网页授权access_token
请求方法
https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
参数说明
返回值
正确时返回的JSON数据包如下:
{
"access_token":"ACCESS_TOKEN",
"expires_in":7200,
"refresh_token":"REFRESH_TOKEN",
"openid":"OPENID",
"scope":"SCOPE"
}
错误时微信会返回JSON数据包如下(示例为Code无效错误):
{
"errcode":40029,
"errmsg":"invalid code"
}
4. 拉取用户信息(需scope为 snsapi_userinfo)
请求方法
https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN
参数说明
返回值
正确时返回的JSON数据包如下:
{
"openid":" OPENID",
" nickname": NICKNAME,
"sex":"1",
"province":"PROVINCE"
"city":"CITY",
"country":"COUNTRY",
"headimgurl": "http://wx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/46",
"privilege":[
"PRIVILEGE1"
"PRIVILEGE2"
],
"unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
}
错误时微信会返回JSON数据包如下:
{
"errcode":40003,
"errmsg":" invalid openid "
}
http://www.itcastcpp.cn/wechat8000/index
urllib.quote()
实际授权地址
https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx36766f74dbfeef15&redirect_uri=http%3A//www.itcastcpp.cn/wechat8000/index
&response_type=code&scope=snsapi_userinfo&state=1#wechat_redirect