Flask 微信公眾號開發


公眾號接口

1. 公眾號消息會話

目前公眾號內主要有這樣幾類消息服務的類型,分別用於不同的場景。

群發消息

公眾號可以以一定頻次(訂閱號為每天1次,服務號為每月4次),向用戶群發消息,包括文字消息、圖文消息、圖片、視頻、語音等。

被動回復消息

在用戶給公眾號發消息后,微信服務器會將消息發到開發者預先在開發者中心設置的服務器地址(開發者需要進行消息真實性驗證),公眾號可以在5秒內做出回復,可以回復一個消息,也可以回復命令告訴微信服務器這條消息暫不回復。被動回復消息可以設置加密(在公眾平台官網的開發者中心處設置,設置后,按照消息加解密文檔來進行處理。其他3種消息的調用因為是API調用而不是對請求的返回,所以不需要加解密)。

客服消息

在用戶給公眾號發消息后的48小時內,公眾號可以給用戶發送不限數量的消息,主要用於客服場景。用戶的行為會觸發事件推送,某些事件推送是支持公眾號據此發送客服消息的,詳見微信推送消息與事件說明文檔。

模板消息

在需要對用戶發送服務通知(如刷卡提醒、服務預約成功通知等)時,公眾號可以用特定內容模板,主動向用戶發送消息。

2. 公眾號內網頁

對於公眾號內網頁,提供以下場景接口:

網頁授權獲取用戶基本信息

通過該接口,可以獲取用戶的基本信息

微信JS-SDK

是開發者在網頁上通過JavaScript代碼使用微信原生功能的工具包,開發者可以使用它在網頁上錄制和播放微信語音、監聽微信分享、上傳手機本地圖片、拍照等許多能力。

3. 微信開發者文檔

微信開發者文檔網址 https://mp.weixin.qq.com/wiki/home/index.html

接入微信公眾平台

接入微信公眾平台開發,開發者需要按照如下步驟完成:

  1. 填寫服務器配置
  2. 驗證服務器地址的有效性
  3. 依據接口文檔實現業務邏輯

 

填寫服務器配置

登錄微信公眾平台官網后,在公眾平台后台管理頁面 - 開發者中心頁,點擊“修改配置”按鈕,填寫服務器地址(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參數內容,則接入生效,成為開發者成功,否則接入失敗。

校驗流程:

  1. 將token、timestamp、nonce三個參數進行字典序排序
  2. 將三個參數字符串拼接成一個字符串進行sha1加密
  3. 開發者獲得加密后的字符串可與signature對比,標識該請求來源於微信

Python代碼實現(以Flask框架為例):

# -*- coding:utf-8 -*-

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

    # 1. 將token、timestamp、nonce三個參數進行字典序排序
    temp = [WECHAT_TOKEN, timestamp, nonce]
    temp.sort()
    # 2. 將三個參數字符串拼接成一個字符串進行sha1加密
    temp = "".join(temp)
    # sig是我們計算出來的簽名結果
    sig = hashlib.sha1(temp).hexdigest()

    # 3. 開發者獲得加密后的字符串可與signature對比,標識該請求來源於微信
    if sig == signature:
        # 根據請求方式.返回不同的內容 ,如果是get方式,代表是驗證服務器有效性
        # 如果POST方式,代表是微服務器轉發給我們的消息
        if request.method == "GET":
            return echostr

    else:
        return 'errno', 403


if __name__ == '__main__':
    app.run(host='0.0.0.0',port=8000)

運行上述的代碼后,再點擊提交,測試就會通過

 

公眾號接收與發送消息

驗證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>
>>>

  

普通消息類別

  1. 文本消息
  2. 圖片消息
  3. 語音消息
  4. 視頻消息
  5. 小視頻消息
  6. 地理位置消息
  7. 鏈接消息

  

文本消息

 

 <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結構,來對該消息進行響應(現支持回復文本、圖片、圖文、語音、視頻、音樂)。嚴格來說,發送被動響應消息其實並不是一種接口,而是對微信服務器發過來消息的一次回復。

假如服務器無法保證在五秒內處理並回復,必須做出下述回復,這樣微信服務器才不會對此作任何處理,並且不會發起重試(這種情況下,可以使用客服消息接口進行異步回復),否則,將出現嚴重的錯誤提示。詳見下面說明:

  1. (推薦方式)直接回復success
  2. 直接回復空串(指字節長度為0的空字符串,而不是XML結構體中content字段的內容為空)

一旦遇到以下情況,微信都會在公眾號會話中,向用戶下發系統提示“該公眾號暫時無法提供服務,請稍后再試”:

  1. 開發者在5秒內未回復任何內容
  2. 開發者回復了異常數據,比如JSON數據等

回復的消息類型

  1. 文本消息
  2. 圖片消息
  3. 語音消息
  4. 視頻消息
  5. 音樂消息
  6. 圖文消息

回復文本消息

<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>12345678</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[你好]]></Content>
</xml>

 

代碼實現

我們現在來實現一個針對文本消息的收發程序。實現的業務邏輯類似與“鸚鵡學舌”,粉絲發什么內容,我們就傳回給粉絲什么內容。

# -*- coding:utf-8 -*-

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

    # 1. 將token、timestamp、nonce三個參數進行字典序排序
    temp = [WECHAT_TOKEN, timestamp, nonce]
    temp.sort()
    # 2. 將三個參數字符串拼接成一個字符串進行sha1加密
    temp = "".join(temp)
    # sig是我們計算出來的簽名結果
    sig = hashlib.sha1(temp).hexdigest()

    # 3. 開發者獲得加密后的字符串可與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', 403

if __name__ == '__main__':
    app.run(host='0.0.0.0',port=8000)
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-8 -*-

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

    # 1. 將token、timestamp、nonce三個參數進行字典序排序
    temp = [WECHAT_TOKEN, timestamp, nonce]
    temp.sort()
    # 2. 將三個參數字符串拼接成一個字符串進行sha1加密
    temp = "".join(temp)
    # sig是我們計算出來的簽名結果
    sig = hashlib.sha1(temp).hexdigest()

    # 3. 開發者獲得加密后的字符串可與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:
                print 123
                response = {"xml": response}
                print 456
                response = xmltodict.unparse(response)
                print 789
            else:
                response = ''
            return make_response(response)
    else:
        return 'errno', 403

if __name__ == '__main__':
    app.run(host='0.0.0.0',port=8000)

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-8 -*-

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

    # 1. 將token、timestamp、nonce三個參數進行字典序排序
    temp = [WECHAT_TOKEN, timestamp, nonce]
    temp.sort()
    # 2. 將三個參數字符串拼接成一個字符串進行sha1加密
    temp = "".join(temp)
    # sig是我們計算出來的簽名結果
    sig = hashlib.sha1(temp).hexdigest()

    # 3. 開發者獲得加密后的字符串可與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', 403

if __name__ == '__main__':
    app.run(host='0.0.0.0',port=8000)
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-8 -*-
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": 7200
    }

    @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種類型的二維碼:

  1. 臨時二維碼,是有過期時間的,最長可以設置為在二維碼生成后的30天(即2592000秒)后過期,但能夠生成較多數量。臨時二維碼主要用於帳號綁定等不要求二維碼永久保存的業務場景

  2. 永久二維碼,是無過期時間的,但數量較少(目前為最多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-8 -*-
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": 7200
    }

    @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": 604800,
        "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-8 -*-

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')
    # 1. 將token、timestamp、nonce三個參數進行字典序排序
    temp = [WECHAT_TOKEN, timestamp, nonce]
    temp.sort()
    # 2. 將三個參數字符串拼接成一個字符串進行sha1加密
    temp = "".join(temp)
    # sig是我們計算出來的簽名結果
    sig = hashlib.sha1(temp).hexdigest()

    # 3. 開發者獲得加密后的字符串可與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', 403


if __name__ == '__main__':
    app.run(host='0.0.0.0',port=8000)
View Code

 

 

生成自定義菜單  

 

# -*- coding: utf-8 -*-
# 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 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM