Flask 微信公众号开发
公众号接口
1. 公众号消息会话
目前公众号内主要有这样几类消息服务的类型,分别用于不同的场景。
群发消息
公众号可以以一定频次(订阅号为每天1次,服务号为每月4次),向用户群发消息,包括文字消息、图文消息、图片、视频、语音等。
被动回复消息
在用户给公众号发消息后,微信服务器会将消息发到开发者预先在开发者中心设置的服务器地址(开发者需要进行消息真实性验证),公众号可以在5秒内做出回复,可以回复一个消息,也可以回复命令告诉微信服务器这条消息暂不回复。被动回复消息可以设置加密(在公众平台官网的开发者中心处设置,设置后,按照消息加解密文档来进行处理。其他3种消息的调用因为是API调用而不是对请求的返回,所以不需要加解密)。
客服消息
在用户给公众号发消息后的48小时内,公众号可以给用户发送不限数量的消息,主要用于客服场景。用户的行为会触发事件推送,某些事件推送是支持公众号据此发送客服消息的,详见微信推送消息与事件说明文档。
模板消息
在需要对用户发送服务通知(如刷卡提醒、服务预约成功通知等)时,公众号可以用特定内容模板,主动向用户发送消息。
2. 公众号内网页
对于公众号内网页,提供以下场景接口:
网页授权获取用户基本信息
通过该接口,可以获取用户的基本信息
微信JS-SDK
是开发者在网页上通过JavaScript代码使用微信原生功能的工具包,开发者可以使用它在网页上录制和播放微信语音、监听微信分享、上传手机本地图片、拍照等许多能力。
3. 微信开发者文档
微信开发者文档网址 https://mp.weixin.qq.com/wiki/home/index.html
接入微信公众平台
接入微信公众平台开发,开发者需要按照如下步骤完成:
- 填写服务器配置
- 验证服务器地址的有效性
- 依据接口文档实现业务逻辑
填写服务器配置
登录微信公众平台官网后,在公众平台后台管理页面 - 开发者中心页,点击“修改配置”按钮,填写服务器地址(URL)、Token和EncodingAESKey,其中URL是开发者用来接收微信消息和事件的接口URL。Token可由开发者可以任意填写,用作生成签名(该Token会和接口URL中包含的Token进行比对,从而验证安全性)。EncodingAESKey由开发者手动填写或随机生成,将用作消息体加解密密钥。
同时,开发者可选择消息加解密方式:明文模式、兼容模式和安全模式。模式的选择与服务器配置在提交后都会立即生效,请开发者谨慎填写及选择。加解密方式的默认状态为明文模式,选择兼容模式和安全模式需要提前配置好相关加解密代码,详情请参考消息体签名及加解密部分的文档。
微信公众号接口只支持80接口。
公众平台页面
利用测试平台
测试平台登陆地址 http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login
配置阿里服务器nginx
在nginx添加以下配置
- vi /etc/nginx/sites-available/default
添加以下配置实现80端口的转发
- location /weixin {
- proxy_pass http://127.0.0.1:8000;
- }
- http://47.95.8.70/weixin
验证服务器地址的有效性
开发者提交信息后,微信服务器将发送GET请求到填写的服务器地址URL上,GET请求携带四个参数:
开发者通过检验signature对请求进行校验。若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,否则接入失败。
校验流程:
- 将token、timestamp、nonce三个参数进行字典序排序
- 将三个参数字符串拼接成一个字符串进行sha1加密
- 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
Python代码实现(以Flask框架为例):
- # -*- coding:utf- -*-
- from flask import Flask, request, make_response
- import hashlib
- import xmltodict
- import time
- app = Flask(__name__)
- WECHAT_TOKEN = "zhangbiao"
- @app.route('/weixin', methods=['GET', 'POST'])
- def wechat():
- args = request.args
- print args
- signature = args.get('signature')
- timestamp = args.get('timestamp')
- nonce = args.get('nonce')
- echostr = args.get('echostr')
- # . 将token、timestamp、nonce三个参数进行字典序排序
- temp = [WECHAT_TOKEN, timestamp, nonce]
- temp.sort()
- # . 将三个参数字符串拼接成一个字符串进行sha1加密
- temp = "".join(temp)
- # sig是我们计算出来的签名结果
- sig = hashlib.sha1(temp).hexdigest()
- # . 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
- if sig == signature:
- # 根据请求方式.返回不同的内容 ,如果是get方式,代表是验证服务器有效性
- # 如果POST方式,代表是微服务器转发给我们的消息
- if request.method == "GET":
- return echostr
- else:
- return 'errno',
- if __name__ == '__main__':
- app.run(host='0.0.0.0',port=)
运行上述的代码后,再点击提交,测试就会通过
公众号接收与发送消息
验证URL有效性成功后即接入生效,成为开发者。如果公众号类型为服务号(订阅号只能使用普通消息接口),可以在公众平台网站中申请认证,认证成功的服务号将获得众多接口权限,以满足开发者需求。
此后用户每次向公众号发送消息、或者产生自定义菜单点击事件时,开发者填写的服务器配置URL将得到微信服务器推送过来的消息和事件,然后开发者可以依据自身业务逻辑进行响应,例如回复消息等。
用户向公众号发送消息时,公众号方收到的消息发送者是一个OpenID,是使用用户微信号加密后的结果,每个用户对每个公众号有一个唯一的OpenID。
接收普通消息
当普通微信用户向公众账号发消息时,微信服务器将POST消息的XML数据包到开发者填写的URL上。
微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次。假如服务器无法保证在五秒内处理并回复,可以直接回复空串,微信服务器不会对此作任何处理,并且不会发起重试。
各消息类型的推送使用XML数据包结构,如:
- <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>
注意:<![CDATA
与 ]]>
括起来的数据不会被xml解析器解析。
xmltodict 模块基本用法
xmltodict 是一个用来处理xml数据的很方便的模块。包含两个常用方法parse和unparse
1. 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. 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>
- >>>
普通消息类别
- 文本消息
- 图片消息
- 语音消息
- 视频消息
- 小视频消息
- 地理位置消息
- 链接消息
文本消息
- <xml>
- <ToUserName><![CDATA[toUser]]></ToUserName>
- <FromUserName><![CDATA[fromUser]]></FromUserName>
- <CreateTime>1348831860</CreateTime>
- <MsgType><![CDATA[text]]></MsgType>
- <Content><![CDATA[this is a test]]></Content>
- <MsgId>1234567890123456</MsgId>
- </xml>
被动回复消息
当用户发送消息给公众号时(或某些特定的用户操作引发的事件推送时),会产生一个POST请求,开发者可以在响应包中返回特定XML结构,来对该消息进行响应(现支持回复文本、图片、图文、语音、视频、音乐)。严格来说,发送被动响应消息其实并不是一种接口,而是对微信服务器发过来消息的一次回复。
假如服务器无法保证在五秒内处理并回复,必须做出下述回复,这样微信服务器才不会对此作任何处理,并且不会发起重试(这种情况下,可以使用客服消息接口进行异步回复),否则,将出现严重的错误提示。详见下面说明:
- (推荐方式)直接回复success
- 直接回复空串(指字节长度为0的空字符串,而不是XML结构体中content字段的内容为空)
一旦遇到以下情况,微信都会在公众号会话中,向用户下发系统提示“该公众号暂时无法提供服务,请稍后再试”:
- 开发者在5秒内未回复任何内容
- 开发者回复了异常数据,比如JSON数据等
回复的消息类型
- 文本消息
- 图片消息
- 语音消息
- 视频消息
- 音乐消息
- 图文消息
回复文本消息
- <xml>
- <ToUserName><![CDATA[toUser]]></ToUserName>
- <FromUserName><![CDATA[fromUser]]></FromUserName>
- <CreateTime>12345678</CreateTime>
- <MsgType><![CDATA[text]]></MsgType>
- <Content><![CDATA[你好]]></Content>
- </xml>
代码实现
我们现在来实现一个针对文本消息的收发程序。实现的业务逻辑类似与“鹦鹉学舌”,粉丝发什么内容,我们就传回给粉丝什么内容。
- # -*- coding:utf- -*-
- from flask import Flask, request, make_response
- import hashlib
- import xmltodict
- import time
- app = Flask(__name__)
- WECHAT_TOKEN = "zhangbiao"
- @app.route('/weixin', methods=['GET', 'POST'])
- def wechat():
- args = request.args
- print args
- signature = args.get('signature')
- timestamp = args.get('timestamp')
- nonce = args.get('nonce')
- echostr = args.get('echostr')
- # . 将token、timestamp、nonce三个参数进行字典序排序
- temp = [WECHAT_TOKEN, timestamp, nonce]
- temp.sort()
- # . 将三个参数字符串拼接成一个字符串进行sha1加密
- temp = "".join(temp)
- # sig是我们计算出来的签名结果
- sig = hashlib.sha1(temp).hexdigest()
- # . 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
- if sig == signature:
- # 根据请求方式.返回不同的内容 ,如果是get方式,代表是验证服务器有效性
- # 如果POST方式,代表是微服务器转发给我们的消息
- if request.method == "GET":
- return echostr
- else:
- resp_data = request.data
- resp_dict = xmltodict.parse(resp_data).get('xml')
- print resp_dict
- # 如果是文本消息
- if 'text' == resp_dict.get('MsgType'):
- response = {
- "ToUserName": resp_dict.get('FromUserName'),
- "FromUserName": resp_dict.get('ToUserName'),
- "CreateTime": int(time.time()),
- "MsgType": "text",
- "Content": resp_dict.get('Content'),
- }
- print resp_dict.get('Content')
- else:
- response = {
- "ToUserName": resp_dict.get('FromUserName'),
- "FromUserName": resp_dict.get('ToUserName'),
- "CreateTime": int(time.time()),
- "MsgType": "text",
- "Content": u"哈哈哈哈",
- }
- if response:
- response = {"xml": response}
- response = xmltodict.unparse(response)
- else:
- response = ''
- return make_response(response)
- else:
- return 'errno',
- if __name__ == '__main__':
- app.run(host='0.0.0.0',port=)
tasks.py
有趣的表情
QQ表情
实际是字符串转义,如 /::D
、/::P
等,仍属于文本信息。
emoji
绘文字(日语:絵文字/えもじ emoji)是日本在无线通信中所使用的视觉情感符号,绘意指图形,文字则是图形的隐喻,可用来代表多种表情,如笑脸表示笑、蛋糕表示食物等。
在NTTDoCoMo的i-mode系统电话系统中,绘文字的尺寸是12x12 像素,在传送时,一个图形有2个字节。Unicode编码为E63E到E757,而在Shift-JIS编码则是从F89F到F9FC。基本的绘文字共有176个符号,在C-HTML4.0的编程语言中,则另增添了76个情感符号。
最早由栗田穰崇(Shigetaka Kurita)创作,并在日本网络及手机用户中流行。
自苹果公司发布的iOS 5输入法中加入了emoji后,这种表情符号开始席卷全球,目前emoji已被大多数现代计算机系统所兼容的Unicode编码采纳,普遍应用于各种手机短信和社交网络中。
本质是Unicode字符,也属于文本消息。
自定表情
微信的自定义表情不是文本,也不是图片,而是一种不支持的格式,微信未提供处理此消息的接口。
接收其他普通消息
接收图片消息
- <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位整型 |
接收视频消息
- <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>
接收小视频消息
- <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>
接收语音消息
- <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>
请注意,开通语音识别后,用户每次发送语音给公众号时,微信会在推送的语音消息XML数据包中,增加一个Recognition字段(注:由于客户端缓存,开发者开启或者关闭语音识别功能,对新关注者立刻生效,对已关注用户需要24小时生效。开发者可以重新关注此帐号进行测试)。开启语音识别后的语音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编码。
回复其他普通消息
回复图片消息
- <xml>
- <ToUserName><![CDATA[toUser]]></ToUserName>
- <FromUserName><![CDATA[fromUser]]></FromUserName>
- <CreateTime>12345678</CreateTime>
- <MsgType><![CDATA[image]]></MsgType>
- <Image>
- <MediaId><![CDATA[media_id]]></MediaId>
- </Image>
- </xml>
回复视频消息
- <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>
回复用户语音消息识别(代码实现)
- 把语音的消息转换成文字返回
- # -*- coding:utf- -*-
- from flask import Flask, request, make_response
- import hashlib
- import xmltodict
- import time
- app = Flask(__name__)
- WECHAT_TOKEN = "zhangbiao"
- @app.route('/weixin', methods=['GET', 'POST'])
- def wechat():
- args = request.args
- print args
- signature = args.get('signature')
- timestamp = args.get('timestamp')
- nonce = args.get('nonce')
- echostr = args.get('echostr')
- # . 将token、timestamp、nonce三个参数进行字典序排序
- temp = [WECHAT_TOKEN, timestamp, nonce]
- temp.sort()
- # . 将三个参数字符串拼接成一个字符串进行sha1加密
- temp = "".join(temp)
- # sig是我们计算出来的签名结果
- sig = hashlib.sha1(temp).hexdigest()
- # . 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
- if sig == signature:
- # 根据请求方式.返回不同的内容 ,如果是get方式,代表是验证服务器有效性
- # 如果POST方式,代表是微服务器转发给我们的消息
- if request.method == "GET":
- return echostr
- else:
- resp_data = request.data
- resp_dict = xmltodict.parse(resp_data).get('xml')
- if 'voice' == resp_dict.get('MsgType'):
- print resp_data
- res = resp_dict.get('Recognition') or u'未识别'
- response = {
- "ToUserName": resp_dict.get('FromUserName'),
- "FromUserName": resp_dict.get('ToUserName'),
- "CreateTime": int(time.time()),
- "MsgType": "text",
- "Content": res
- }
- else:
- response = {
- "ToUserName": resp_dict.get('FromUserName'),
- "FromUserName": resp_dict.get('ToUserName'),
- "CreateTime": int(time.time()),
- "MsgType": "text",
- "Content": u"哈哈哈哈",
- }
- if response:
- response = {"xml": response}
- response = xmltodict.unparse(response)
- else:
- response = ''
- return make_response(response)
- else:
- return 'errno',
- if __name__ == '__main__':
- app.run(host='0.0.0.0',port=)
- voice.py
voice.py
关注/取消关注事件
用户在关注与取消关注公众号时,微信会把这个事件推送到开发者填写的URL。
微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次。
假如服务器无法保证在五秒内处理并回复,可以直接回复空串,微信服务器不会对此作任何处理,并且不会发起重试。
- <xml>
- <ToUserName><![CDATA[toUser]]></ToUserName>
- <FromUserName><![CDATA[FromUser]]></FromUserName>
- <CreateTime>123456789</CreateTime>
- <MsgType><![CDATA[event]]></MsgType>
- <Event><![CDATA[subscribe]]></Event>
- </xml>
关注成功后,返回感谢关注
- # -*- coding:utf- -*-
- from flask import Flask, request, make_response
- import hashlib
- import xmltodict
- import time
- app = Flask(__name__)
- WECHAT_TOKEN = "zhangbiao"
- @app.route('/weixin', methods=['GET', 'POST'])
- def wechat():
- args = request.args
- print args
- signature = args.get('signature')
- timestamp = args.get('timestamp')
- nonce = args.get('nonce')
- echostr = args.get('echostr')
- # . 将token、timestamp、nonce三个参数进行字典序排序
- temp = [WECHAT_TOKEN, timestamp, nonce]
- temp.sort()
- # . 将三个参数字符串拼接成一个字符串进行sha1加密
- temp = "".join(temp)
- # sig是我们计算出来的签名结果
- sig = hashlib.sha1(temp).hexdigest()
- # . 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
- if sig == signature:
- # 根据请求方式.返回不同的内容 ,如果是get方式,代表是验证服务器有效性
- # 如果POST方式,代表是微服务器转发给我们的消息
- if request.method == "GET":
- return echostr
- else:
- resp_data = request.data
- resp_dict = xmltodict.parse(resp_data).get('xml')
- print resp_dict.get('MsgType')
- if "event" == resp_dict.get('MsgType'):
- if "subscribe" == resp_dict.get("Event"):
- response = {
- "ToUserName": resp_dict.get("FromUserName", ""),
- "FromUserName": resp_dict.get("ToUserName", ""),
- "CreateTime": int(time.time()),
- "MsgType": "text",
- "Content": u"感谢您的关注!"
- }
- else:
- response = None
- else:
- response = {
- "ToUserName": resp_dict.get('FromUserName'),
- "FromUserName": resp_dict.get('ToUserName'),
- "CreateTime": int(time.time()),
- "MsgType": "text",
- "Content": u"哈哈哈哈",
- }
- if response:
- response = {"xml": response}
- response = xmltodict.unparse(response)
- else:
- response = ''
- return make_response(response)
- else:
- return 'errno',
- if __name__ == '__main__':
- app.run(host='0.0.0.0',port=)
focus.py
获取接口调用凭据
access_token是公众号的全局唯一票据,公众号调用各接口时都需使用access_token。开发者需要进行妥善保存。access_token的存储至少要保留512个字符空间。access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access_token失效。
接口说明
请求方法
https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
参数说明
错误时微信会返回JSON数据包如下:
- {
- "errcode":40013,
- "errmsg":"invalid appid"
- }
代码实现
- # -*- coding:utf- -*-
- import time
- import urllib2
- import json
- from flask import Flask, request
- WECHAT_APPID = ""
- WECHAT_APPSECRET = ""
- class AccessToken(object):
- """
- 获取accessToken
- 保存accessToken
- 判断是否过期,如果没有过期,那么直接返回一次请求的access_token
- """
- access_token = {
- "access_token": "",
- "update_time": time.time(),
- "expires_in":
- }
- @classmethod
- def get_access_token(cls):
- # 判断是否有accessToken or access_token 有没有过期
- # if 没有 access_tokon 或者 access_token 过期了:
- if not cls.access_token.get('access_token') or (
- time.time() - cls.access_token.get('update_time')) > cls.access_token.get('expires_in'):
- # 去获取accessToken
- url = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s' % (
- WECHAT_APPID, WECHAT_APPSECRET)
- # 获取响应
- response = urllib2.urlopen(url).read()
- # 转成字典
- resp_json = json.loads(response)
- if 'errcode' in resp_json:
- raise Exception(resp_json.get('errmsg'))
- else:
- # 保存数据
- cls.access_token['access_token'] = resp_json.get('access_token')
- cls.access_token['expires_in'] = resp_json.get('expires_in')
- cls.access_token['update_time'] = time.time()
- return cls.access_token.get('access_token')
- else:
- return cls.access_token.get('access_token')
- if __name__ == '__main__':
- print AccessToken.get_access_token()
gentate_token
带参数的二维码
为了满足用户渠道推广分析和用户帐号绑定等场景的需要,公众平台提供了生成带参数二维码的接口。使用该接口可以获得多个带不同场景值的二维码,用户扫描后,公众号可以接收到事件推送。
目前有2种类型的二维码:
临时二维码,是有过期时间的,最长可以设置为在二维码生成后的30天(即2592000秒)后过期,但能够生成较多数量。临时二维码主要用于帐号绑定等不要求二维码永久保存的业务场景
永久二维码,是无过期时间的,但数量较少(目前为最多10万个)。永久二维码主要用于适用于帐号绑定、用户来源统计等场景。
获取带参数的二维码的过程包括两步,首先创建二维码ticket,然后凭借ticket到指定URL换取二维码。
创建二维码ticket
每次创建二维码ticket需要提供一个开发者自行设定的参数(scene_id),分别介绍临时二维码和永久二维码的创建二维码ticket过程。
临时二维码请求说明
- http请求方式: POST
- URL: https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=TOKEN
- POST数据格式:json
- POST数据例子:{"expire_seconds": 604800, "action_name": "QR_SCENE", "action_info": {"scene": {"scene_id": 123}}}
永久二维码请求说明
- http请求方式: POST
- URL: https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=TOKEN
- POST数据格式:json
- POST数据例子:{"action_name": "QR_LIMIT_SCENE", "action_info": {"scene": {"scene_id": 123}}}
- 或者也可以使用以下POST数据创建字符串形式的二维码参数:
- {"action_name": "QR_LIMIT_STR_SCENE", "action_info": {"scene": {"scene_str": "123"}}}
返回说明
正确的Json返回结果:
- {"ticket":"gQH47joAAAAAAAAAASxodHRwOi8vd2VpeGluLnFxLmNvbS9xL2taZ2Z3TVRtNzJXV1Brb3ZhYmJJAAIEZ23sUwMEmm3sUw==","expire_seconds":60,"url":"http:\/\/weixin.qq.com\/q\/kZgfwMTm72WWPkovabbI"}
错误的Json返回示例:
- {"errcode":40013,"errmsg":"invalid appid"}
通过ticket换取二维码
获取二维码ticket后,开发者可用ticket换取二维码图片。请注意,本接口无须登录态即可调用。
请求说明
- HTTP GET请求(请使用https协议)
- https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=TICKET
代码实例
- # -*- coding:utf- -*-
- import time
- import urllib2
- import json
- from flask import Flask, request
- WECHAT_APPID = ""
- WECHAT_APPSECRET = ""
- class AccessToken(object):
- """
- 获取accessToken
- 保存accessToken
- 判断是否过期,如果没有过期,那么直接返回一次请求的access_token
- """
- access_token = {
- "access_token": "",
- "update_time": time.time(),
- "expires_in":
- }
- @classmethod
- def get_access_token(cls):
- # 判断是否有accessToken or access_token 有没有过期
- # if 没有 access_tokon 或者 access_token 过期了:
- if not cls.access_token.get('access_token') or (time.time() - cls.access_token.get('update_time')) > cls.access_token.get('expires_in'):
- # 去获取accessToken
- url = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s' % (WECHAT_APPID, WECHAT_APPSECRET)
- # 获取响应
- response = urllib2.urlopen(url).read()
- # 转成字典
- resp_json = json.loads(response)
- if 'errcode' in resp_json:
- raise Exception(resp_json.get('errmsg'))
- else:
- # 保存数据
- cls.access_token['access_token'] = resp_json.get('access_token')
- cls.access_token['expires_in'] = resp_json.get('expires_in')
- cls.access_token['update_time'] = time.time()
- return cls.access_token.get('access_token')
- else:
- return cls.access_token.get('access_token')
- app = Flask(__name__)
- # http://127.0.0.1/get_qrcode?id=1
- @app.route('/get_qrcode')
- def get_qrcode():
- scene_id = request.args.get('id')
- access_token = AccessToken.get_access_token()
- url = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=%s" % access_token
- params = {
- "expire_seconds": ,
- "action_name": "QR_SCENE",
- "action_info": {"scene": {"scene_id": scene_id}}}
- response = urllib2.urlopen(url, data=json.dumps(params)).read()
- # 转成字典
- resp_json = json.loads(response)
- ticket = resp_json.get('ticket')
- if ticket:
- return '<img src="https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=%s">' % ticket
- else:
- return resp_json
- if __name__ == '__main__':
- app.run(host='0.0.0.0')
get_qrcode.py
扫描带参数二维码
用户扫描带场景值二维码时,可能推送以下两种事件:
如果用户还未关注公众号,则用户可以关注公众号,关注后微信会将带场景值关注事件推送给开发者。
如果用户已经关注公众号,则微信会将带场景值扫描事件推送给开发者。
1. 用户未关注时,进行关注后的事件推送
推送XML数据包示例:
- <xml><ToUserName><![CDATA[toUser]]></ToUserName>
- <FromUserName><![CDATA[FromUser]]></FromUserName>
- <CreateTime>123456789</CreateTime>
- <MsgType><![CDATA[event]]></MsgType>
- <Event><![CDATA[subscribe]]></Event>
- <EventKey><![CDATA[qrscene_123123]]></EventKey>
- <Ticket><![CDATA[TICKET]]></Ticket>
- </xml>
参数 | 描述 |
---|---|
ToUserName | 开发者微信号 |
FromUserName | 发送方帐号(一个OpenID) |
CreateTime | 消息创建时间 (整型) |
MsgType | 消息类型,event |
Event | 事件类型,subscribe |
EventKey | 事件KEY值,qrscene_为前缀,后面为二维码的参数值 |
Ticket | 二维码的ticket,可用来换取二维码图片 |
2. 用户已关注时的事件推送
推送XML数据包示例:
- <xml>
- <ToUserName><![CDATA[toUser]]></ToUserName>
- <FromUserName><![CDATA[FromUser]]></FromUserName>
- <CreateTime>123456789</CreateTime>
- <MsgType><![CDATA[event]]></MsgType>
- <Event><![CDATA[SCAN]]></Event>
- <EventKey><![CDATA[SCENE_VALUE]]></EventKey>
- <Ticket><![CDATA[TICKET]]></Ticket>
- </xml>
参数 | 描述 |
---|---|
ToUserName | 开发者微信号 |
FromUserName | 发送方帐号(一个OpenID) |
CreateTime | 消息创建时间 (整型) |
MsgType | 消息类型,event |
Event | 事件类型,SCAN |
EventKey | 事件KEY值,是一个32位无符号整数,即创建二维码时的二维码scene_id |
Ticket | 二维码的ticket,可用来换取二维码图片 |
代码
- # -*- coding:utf- -*-
- from flask import Flask, request, make_response
- import hashlib
- import xmltodict
- import time
- import json
- app = Flask(__name__)
- WECHAT_TOKEN = "zhangbiao"
- @app.route('/weixin', methods=['GET', 'POST'])
- def wechat():
- args = request.args
- signature = args.get('signature')
- timestamp = args.get('timestamp')
- nonce = args.get('nonce')
- echostr = args.get('echostr')
- # . 将token、timestamp、nonce三个参数进行字典序排序
- temp = [WECHAT_TOKEN, timestamp, nonce]
- temp.sort()
- # . 将三个参数字符串拼接成一个字符串进行sha1加密
- temp = "".join(temp)
- # sig是我们计算出来的签名结果
- sig = hashlib.sha1(temp).hexdigest()
- # . 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
- if sig == signature:
- # 根据请求方式.返回不同的内容 ,如果是get方式,代表是验证服务器有效性
- # 如果POST方式,代表是微服务器转发给我们的消息
- if request.method == "GET":
- return echostr
- else:
- resp_data = request.data
- resp_dict = xmltodict.parse(resp_data).get('xml')
- # 如果是文本消息
- if 'text' == resp_dict.get('MsgType'):
- response = {
- "ToUserName": resp_dict.get('FromUserName'),
- "FromUserName": resp_dict.get('ToUserName'),
- "CreateTime": int(time.time()),
- "MsgType": "text",
- "Content": resp_dict.get('Content'),
- }
- print resp_dict.get('Content')
- elif "event" == resp_dict.get('MsgType'):
- if "subscribe" == resp_dict.get("Event"):
- response = {
- "ToUserName": resp_dict.get("FromUserName", ""),
- "FromUserName": resp_dict.get("ToUserName", ""),
- "CreateTime": int(time.time()),
- "MsgType": "text",
- "Content": u"感谢您的关注!"
- }
- if resp_dict.get('EventKey'):
- response["Content"] += u"场景值是:"
- response["Content"] += resp_dict.get('EventKey')
- elif 'SCAN' == resp_dict.get('Event'):
- # 当用户关注过扫描的时候,会进入到这儿
- response = {
- "ToUserName": resp_dict.get("FromUserName", ""),
- "FromUserName": resp_dict.get("ToUserName", ""),
- "CreateTime": int(time.time()),
- "MsgType": "text",
- "Content": resp_dict.get('EventKey')
- }
- print resp_dict.get('Ticket')
- else:
- response = None
- else:
- response = {
- "ToUserName": resp_dict.get('FromUserName'),
- "FromUserName": resp_dict.get('ToUserName'),
- "CreateTime": int(time.time()),
- "MsgType": "text",
- "Content": u"哈哈哈哈",
- }
- if response:
- response = {"xml": response}
- response = xmltodict.unparse(response)
- else:
- response = ''
- return make_response(response)
- else:
- return 'errno',
- if __name__ == '__main__':
- app.run(host='0.0.0.0',port=)
生成自定义菜单
- # -*- coding: utf- -*-
- # filename: menu.py
- import urllib
- from gentate_token import AccessToken
- class Menu(object):
- def __init__(self):
- pass
- def create(self, postData, accessToken):
- postUrl = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=%s" % accessToken
- if isinstance(postData, unicode):
- postData = postData.encode('utf-8')
- urlResp = urllib.urlopen(url=postUrl, data=postData)
- print urlResp.read()
- def query(self, accessToken):
- postUrl = "https://api.weixin.qq.com/cgi-bin/menu/get?access_token=%s" % accessToken
- urlResp = urllib.urlopen(url=postUrl)
- print urlResp.read()
- def delete(self, accessToken):
- postUrl = "https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=%s" % accessToken
- urlResp = urllib.urlopen(url=postUrl)
- print urlResp.read()
- # 获取自定义菜单配置接口
- def get_current_selfmenu_info(self, accessToken):
- postUrl = "https://api.weixin.qq.com/cgi-bin/get_current_selfmenu_info?access_token=%s" % accessToken
- urlResp = urllib.urlopen(url=postUrl)
- print urlResp.read()
- if __name__ == '__main__':
- myMenu = Menu()
- postJson = """
- {
- "button":
- [
- {
- "type": "click",
- "name": "个人信息",
- "key": "geren"
- },
- {
- "type": "click",
- "name": "发展历史",
- "key": "fazhan"
- },
- {
- "type": "click",
- "name": "联系我们",
- "key": "contact"
- }
- ]
- }
- """
- Pjson = '''
- {
- "button": [
- {
- "type": "click",
- "name": "今日歌曲",
- "key": "V1001_TODAY_MUSIC"
- },
- {
- "name": "菜单",
- "sub_button": [
- {
- "type": "view",
- "name": "搜索",
- "url": "http://www.soso.com/"
- },
- {
- "type": "view",
- "name": "个人博客",
- "url": "http://www.cnblogs.com/crazymagic/"
- }
- ]
- }
- ]
- }
- '''
- accessToken =AccessToken.get_access_token()
- # myMenu.delete(accessToken)
- myMenu.create(Pjson, accessToken)
Menu.py
Flask 微信公众号开发的更多相关文章
- 细数Python Flask微信公众号开发中遇到的那些坑
最近两三个月的时间,断断续续边学边做完成了一个微信公众号页面的开发工作.这是一个快递系统,主要功能有用户管理.寄收件地址管理.用户下单,订单管理,订单查询及一些宣传页面等.本文主要细数下开发过程中遇到 ...
- Flask+微信公众号开发(接入指南)
目录 一.注册公众号 二.启用开发者 三.配置服务器配置 四.开发自己的需求 五.写在最后 一.注册公众号 具体的注册过程,根据官方文档一步一步来即可.这里需注意的是订阅号还是服务号:有些比较好的开发 ...
- python之微信公众号开发(基本配置和校验)
前言 最近有微信公众号开发的业务,以前没有用python做过微信公众号开发,记录一下自己的学习和开发历程,共勉! 公众号类型 订阅号 普通订阅号 认证订阅号 服务号 普通服务号 认证服务号 服务方式 ...
- 微信公众号开发(一)--验证服务器地址的Java实现
现在主流上都用php写微信公众号后台,其实作为后端语言之一的java也可以实现. 这篇文章将对验证服务器地址这一步做出实现. 参考资料:1.慕课网-<初识java微信公众号开发>,2.微信 ...
- C#微信公众号开发系列教程三(消息体签名及加解密)
http://www.cnblogs.com/zskbll/p/4139039.html C#微信公众号开发系列教程一(调试环境部署) C#微信公众号开发系列教程一(调试环境部署续:vs远程调试) C ...
- C#微信公众号开发系列教程二(新手接入指南)
http://www.cnblogs.com/zskbll/p/4093954.html 此系列前面已经更新了两篇博文了,都是微信开发的前期准备工作,现在切入正题,本篇讲解新手接入的步骤与方法,大神可 ...
- 微信公众号开发系列教程一(调试环境部署续:vs远程调试)
http://www.cnblogs.com/zskbll/p/4080328.html 目录 C#微信公众号开发系列教程一(调试环境部署) C#微信公众号开发系列教程一(调试环境部署续:vs远程调试 ...
- NET微信公众号开发-5.0微信支付(待测试)
开发前准备. 1.0微信支付官方开发者文档 2.0官方demo下载 我们用c#所以选择.net版本 不过这个官方的demo根本跑步起来 3.0官方demo运行起来解决方案 4.0微信支付官方.net版 ...
- .NET开发者如何愉快的进行微信公众号开发
(此文章同时发表在本人微信公众号"dotNET每日精华文章",欢迎右边二维码来关注.) 题记:这篇文章只是一个如何提高开发效率的简单指导和记录,不会涉及具体的微信公众号开发内容. ...
随机推荐
- DSAPI多功能组件编程应用-反射相关
[DSAPI.DLL下载地址] 在.Net中,反射技术是一种入门困难,熟用快速的东西,对于没有接触过反射技术的程序员来说的确是头疼的,看一旦自己写过了,上手就非常简单了.在本节,将部分.N ...
- [Linux] scp本地服务器和远程服务器拷贝文件
上传本地文件到服务器scp 本地路径 用户名@远程服务器ip:远程路径 下载文件 scp 用户名@远程服务器ip:远程路径 本地路径-r 是上传下载本地目录到远程 远程文件
- php ip2long负数的问题
大家可能都知道php提供了ip2long与long2ip方法对ip地址处理.抛砖引玉,说点概念性滴: 1.ip2long — 将一个IPV4的字符串互联网协议转换成数字格式 int ip2long ( ...
- C# 绘制PDF图形——基本图形、自定义图形、色彩透明度
引言 在PDF中我们可以通过C#程序代码来添加非常丰富的元素来呈现我们想要表达的内容,如绘制表格.文字,添加图形.图像等等.在本篇文章中,我将介绍如何在PDF中绘制图形,并设置图形属性的操作. 文章中 ...
- java之equals 与 == 的区别
== : 1.本质:比较的的是地址,栈内存中存放的对象的内存地址. 2.判断引用所指的对象是否是同一个. 3.两边的操作数必须是同一类型的(可父子类)才能编译通过. 4.值类型(int,char,lo ...
- vis.js 4.21.0 Timeline localization
from:http://visjs.org/timeline_examples.html https://github.com/almende/vis https://github.com/momen ...
- 【20190407】JavaScript-indexOf方法解析
在JavaScript中,字符串类型String和数组类型Array都有indexOf()方法,虽然他们的作用都是返回传入元素在指定字符串或数组中的位置,但他们之间还是存在着一点点不同. Str.in ...
- JS table内容转成二维数组,支持colspan和rowspan
思路:1.先初始化colspan的数据到数组2.根据rowspan和colspan计算th和td的矩阵二次填充数组 说明:需要引用到第三方库jQuery,table中的th和td行和列跨度必须正确 & ...
- 算法题丨Remove Duplicates from Sorted Array
描述 Given a sorted array, remove the duplicates in-place such that each element appear only once and ...
- 腾讯云centos7远程连接配置
1.申请腾讯云 注册腾讯云账号,申请一个centos7的服务器,1G内存,1核处理器,1M网速. 对于这种入门级配置,建议还是别用windows server了,不然不装任何东西,光运行系统就需要60 ...