用flask微信公众号开发简单流程:服务器配置、对接微信服务器、接收发送微信消息、获取微信授权拉到客户微信资料

67 篇文章 2 订阅

一、基础配置:服务器配置

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&timestamp=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源码详解)

  1. 验证URL有效性成功后即接入生效,成为开发者。如果公众号类型为服务号(订阅号只能使用普通消息接口),可以在公众平台网站中申请认证,认证成功的服务号将获得众多接口权限,以满足开发者需求。

  2. 此后用户每次向公众号发送消息、或者产生自定义菜单点击事件时,开发者填写的服务器配置URL将得到微信服务器推送过来的消息和事件,然后开发者可以依据自身业务逻辑进行响应,例如回复消息等。

  3. 用户向公众号发送消息时,公众号方收到的消息发送者是一个OpenID,是使用用户微信号加密后的结果,每个用户对每个公众号有一个唯一的OpenID。

第1步:对接微信服务器(验证、接收微信服务器传过来的参数)

1-1验证服务器地址的有效性

开发者提交信息后,微信服务器将发送GET请求到填写的服务器地址URL上,GET请求携带四个参数:
在这里插入图片描述
开发者通过检验signature对请求进行校验。若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,否则接入失败。

校验流程:
  1. 将token、timestamp、nonce三个参数进行字典序排序
  2. 将三个参数字符串拼接成一个字符串进行sha1加密
  3. 开发者获得加密后的字符串可与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解析器解析,类似转义。

普通消息类别

  1. 文本消息
  2. 图片消息
  3. 语音消息
  4. 视频消息
  5. 小视频消息
  6. 地理位置消息
  7. 链接消息

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数据等

【回复的消息类型】

  1. 文本消息
  2. 图片消息
  3. 语音消息
  4. 视频消息
  5. 音乐消息
  6. 图文消息
  7. 回复文本消息

【回复格式】

<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)

效果:在对应公众号上回复

  1. 任何文字消息都会回复一样的消息,非文字,则回复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消息创建时间 (整型)
MsgTypeimage
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消息创建时间 (整型)
MsgTypeimage
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消息创建时间 (整型)
MsgTypevideo
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)

六、关注/取消关注事件

  1. 用户在关注与取消关注公众号时,微信会把这个事件推送到开发者填写的URL。

  2. 微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次。

  3. 假如服务器无法保证在五秒内处理并回复,可以直接回复空串,微信服务器不会对此作任何处理,并且不会发起重试。

<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

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值