Flask 微信公衆號開發

公衆號接口

1. 公衆號消息會話

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

羣發消息

公衆號能夠以必定頻次(訂閱號爲天天1次,服務號爲每個月4次),向用戶羣發消息,包括文字消息、圖文消息、圖片、視頻、語音等。python

被動回覆消息

在用戶給公衆號發消息後,微信服務器會將消息發到開發者預先在開發者中心設置的服務器地址(開發者須要進行消息真實性驗證),公衆號能夠在5秒內作出回覆,能夠回覆一個消息,也能夠回覆命令告訴微信服務器這條消息暫不回覆。被動回覆消息能夠設置加密(在公衆平臺官網的開發者中心處設置,設置後,按照消息加解密文檔來進行處理。其餘3種消息的調用由於是API調用而不是對請求的返回,因此不須要加解密)。nginx

客服消息

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

模板消息

在須要對用戶發送服務通知(如刷卡提醒、服務預定成功通知等)時,公衆號能夠用特定內容模板,主動向用戶發送消息。json

2. 公衆號內網頁

對於公衆號內網頁,提供如下場景接口:flask

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

經過該接口,能夠獲取用戶的基本信息api

微信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 

相關文章
相關標籤/搜索