1開啟公眾號開發(fā)者模式
公眾平臺技術(shù)文檔的目的是為了簡明扼要的說明接口的使用,語句難免苦澀難懂,甚至對于不同的讀者,有語意歧義。萬事皆是入門難,對于剛?cè)腴T的開發(fā)者講,更是難上加難。
為了降低門檻,彌補不足,我們編寫了《開發(fā)者指引》來講解微信開放平臺的基礎(chǔ)常見功能,旨在幫助大家入門微信開放平臺的開發(fā)者模式。
已熟知接口使用或有一定公眾平臺開發(fā)經(jīng)驗的開發(fā)者,請直接跳過本文。這篇文章不會給你帶來厲害的編碼技巧亦或接口的深層次講解。對于現(xiàn)有接口存在的疑問,可訪問 #公眾號社區(qū) 發(fā)帖交流、聯(lián)系騰訊客服或使用微信反饋。
1.1 申請服務(wù)器
以騰訊云服務(wù)器為示例:騰訊云服務(wù)器購買入口
如你已有小程序,并且已開通小程序云開發(fā),則可以使用 公眾號環(huán)境共享 能力,在公眾號中使用云開發(fā)。
1.2 搭建服務(wù)
以web.py網(wǎng)絡(luò)框,python,騰訊云服務(wù)器為例介紹。
1)安裝/更新需要用到的軟件
安裝python2.7版本以上
安裝web.py
安裝libxml2, libxslt, lxml python
2)編輯代碼,如果不懂python 語法,請到python官方文檔查詢說明。
vim main.py
# -*- coding: utf-8 -*-
# filename: main.py
import web
urls = (
'/wx', 'Handle',
)
class Handle(object):
def GET(self):
return "hello, this is handle view"
if __name__ == '__main__':
app = web.application(urls, globals())
app.run()
3)如果出現(xiàn)“socket.error: No socket could be created“錯誤信息,可能為80端口號被占用,可能是沒有權(quán)限,請自行查詢解決辦法。如果遇見其他錯誤信息,請到web.py官方文檔,學(xué)習(xí)webpy 框架3執(zhí)行命令:sudo python main.py 80 。
4)url填寫:http://外網(wǎng)IP/wx (外網(wǎng)IP請到騰訊云購買成功處查詢)。如下圖,一個簡單的web應(yīng)用已搭建。
1.3 申請公眾號
郵箱激活后,選擇公眾號類型。不同的公眾號擁有不同的能力,詳情請見wiki:公眾號接口權(quán)限說明,當然,服務(wù)號、企業(yè)號需要一定的證件和相關(guān)資料填寫,如果證件一時不能準備好,沒關(guān)系,公眾號其實已注冊,下次可以根據(jù)此郵箱&密碼登錄再選擇。
1.4 開發(fā)者基本配置
1) 公眾平臺官網(wǎng)登錄之后,找到“基本配置”菜單欄
2) 填寫配置 url填寫:http://外網(wǎng)IP/wx 。外網(wǎng)IP請到騰訊云購買成功處查詢。 http的端口號固定使用80,不可填寫其他。 Token:自主設(shè)置,這個token與公眾平臺wiki中常提的access_token不是一回事。這個token只用于驗證開發(fā)者服務(wù)器。
3) 現(xiàn)在選擇提交肯定是驗證token失敗,因為還需要完成代碼邏輯。改動原先main.py文件,新增handle.py
a)vim main.py
# -*- coding: utf-8 -*-
# filename: main.py
import web
from handle import Handle
urls = (
'/wx', 'Handle',
)
if __name__ == '__main__':
app = web.application(urls, globals())
app.run()
b)vim handle.py
先附加邏輯流程圖
# -*- coding: utf-8 -*-
# filename: handle.py
import hashlib
import web
class Handle(object):
def GET(self):
try:
data = web.input()
if len(data) == 0:
return "hello, this is handle view"
signature = data.signature
timestamp = data.timestamp
nonce = data.nonce
echostr = data.echostr
token = "xxxx" #請按照公眾平臺官網(wǎng)\基本配置中信息填寫
list = [token, timestamp, nonce]
list.sort()
sha1 = hashlib.sha1()
map(sha1.update, list)
hashcode = sha1.hexdigest()
print "handle/GET func: hashcode, signature: ", hashcode, signature
if hashcode == signature:
return echostr
else:
return ""
except Exception, Argument:
return Argument
4) 重新啟動成功后(python main.py 80),點擊提交按鈕。若提示”token驗證失敗”, 請認真檢查代碼或網(wǎng)絡(luò)鏈接等。若token驗證成功,會自動返回基本配置的主頁面,點擊啟動按鈕
1.5 重要事情提前交代
接下來,文章準備從兩個簡單的示例入手。
示例一:實現(xiàn)“你說我學(xué)”
示例二:實現(xiàn)“圖尚往來”
兩個簡單的示例后,是一些基礎(chǔ)功能的介紹:素材管理、自定義菜單、群發(fā)。所有的示例代碼是為了簡明的說明問題,避免代碼復(fù)雜化。
在實際中搭建一個安全穩(wěn)定高效的公眾號,建議參考框架如下圖:
主要有三個部分:負責(zé)業(yè)務(wù)邏輯部分的服務(wù)器,負責(zé)對接微信API的API-Proxy服務(wù)器,以及唯一的AccessToken中控服務(wù)器
1)AccessToken中控服務(wù)器:
負責(zé): 提供主動刷新和被動刷新機制來刷新accessToken并存儲(為了防止并發(fā)刷新,注意加并發(fā)鎖),提供給業(yè)務(wù)邏輯有效的accessToken。
優(yōu)點: 避免業(yè)務(wù)邏輯方并發(fā)獲取access_token,避免AccessToken互相覆蓋,提高業(yè)務(wù)功能的穩(wěn)定性。
2)API-Proxy服務(wù)器:
負責(zé):專一與微信API對接,不同的服務(wù)器可以負責(zé)對接不同的業(yè)務(wù)邏輯,更可進行調(diào)用頻率、權(quán)限限制。
優(yōu)點:某臺API-proxy異常,還有其余服務(wù)器支持繼續(xù)提供服務(wù),提高穩(wěn)定性,避免直接暴漏內(nèi)部接口,有效防止惡意攻擊,提高安全性。
2 實現(xiàn)“你問我答”
目的:
1)理解被動消息的含義
2)理解收\發(fā)消息機制
預(yù)實現(xiàn)功能:
粉絲給公眾號一條文本消息,公眾號立馬回復(fù)一條文本消息給粉絲,不需要通過公眾平臺網(wǎng)頁操作。
2.1 接受文本消息
即粉絲給公眾號發(fā)送的文本消息。官方wiki鏈接:接收普通消息
粉絲給公眾號發(fā)送文本消息:“歡迎開啟公眾號開發(fā)者模式”,在開發(fā)者后臺,收到公眾平臺發(fā)送的xml 如下:(下文均隱藏了ToUserName 及 FromUserName 信息)
1460537339
6272960105994287618
解釋:
createTime 是微信公眾平臺記錄粉絲發(fā)送該消息的具體時間
text: 用于標記該xml 是文本消息,一般用于區(qū)別判斷
歡迎開啟公眾號開發(fā)者模式: 說明該粉絲發(fā)給公眾號的具體內(nèi)容是歡迎開啟公眾號開發(fā)者模式
MsgId: 是公眾平臺為記錄識別該消息的一個標記數(shù)值, 微信后臺系統(tǒng)自動產(chǎn)生
2.2 被動回復(fù)文本消息
即公眾號給粉絲發(fā)送的文本消息,官方wiki鏈接: 被動回復(fù)用戶消息
特別強調(diào):
1) 被動回復(fù)消息,即發(fā)送被動響應(yīng)消息,不同于客服消息接口
2) 它其實并不是一種接口,而是對微信服務(wù)器發(fā)過來消息的一次回復(fù)
3) 收到粉絲消息后不想或者不能5秒內(nèi)回復(fù)時,需回復(fù)“success”字符串(下文詳細介紹)
4) 客服接口在滿足一定條件下隨時調(diào)用
公眾號想回復(fù)給粉絲一條文本消息,內(nèi)容為“test”, 那么開發(fā)者發(fā)送給公眾平臺后臺的xml 內(nèi)容如下:
1460541339
特別備注:
1)ToUserName(接受者)、FromUserName(發(fā)送者) 字段請實際填寫。
2)createtime 只用于標記開發(fā)者回復(fù)消息的時間,微信后臺發(fā)送此消息都是不受這個字段約束。
3)text : 用于標記 此次行為是發(fā)送文本消息 (當然可以是image/voice等類型)。
4)文本換行 ‘\n’。
2.3 回復(fù)success問題
查詢官方wiki 開頭強調(diào): 假如服務(wù)器無法保證在五秒內(nèi)處理回復(fù),則必須回復(fù)“success”或者“”(空串),否則微信后臺會發(fā)起三次重試。
解釋一下為何有這么奇怪的規(guī)定。發(fā)起重試是微信后臺為了盡可以保證粉絲發(fā)送的內(nèi)容開發(fā)者均可以收到。如果開發(fā)者不進行回復(fù),微信后臺沒辦法確認開發(fā)者已收到消息,只好重試。
真的是這樣子嗎?嘗試一下收到消息后,不做任何回復(fù)。在日志中查看到微信后臺發(fā)起了三次重試操作,日志截圖如下:
三次重試后,依舊沒有及時回復(fù)任何內(nèi)容,系統(tǒng)自動在粉絲會話界面出現(xiàn)錯誤提示“該公眾號暫時無法提供服務(wù),請稍后再試”。
如果回復(fù)success,微信后臺可以確定開發(fā)者收到了粉絲消息,沒有任何異常提示。因此請大家注意回復(fù)success的問題。
2.4 流程圖
2.5 碼代碼
main.py文件不改變,handle.py 需要增加一下代碼,增加新的文件receive.py, reply.py
1)vim handle.py
# -*- coding: utf-8 -*-#
# filename: handle.py
import hashlib
import reply
import receive
import web
class Handle(object):
def POST(self):
try:
webData = web.data()
print "Handle Post webdata is ", webData
#后臺打日志
recMsg = receive.parse_xml(webData)
if isinstance(recMsg, receive.Msg) and recMsg.MsgType == 'text':
toUser = recMsg.FromUserName
fromUser = recMsg.ToUserName
content = "test"
replyMsg = reply.TextMsg(toUser, fromUser, content)
return replyMsg.send()
else:
print "暫且不處理"
return "success"
except Exception, Argment:
return Argment
2)vim receive.py
# -*- coding: utf-8 -*-#
# filename: receive.py
import xml.etree.ElementTree as ET
def parse_xml(web_data):
if len(web_data) == 0:
return None
xmlData = ET.fromstring(web_data)
msg_type = xmlData.find('MsgType').text
if msg_type == 'text':
return TextMsg(xmlData)
elif msg_type == 'image':
return ImageMsg(xmlData)
class Msg(object):
def __init__(self, xmlData):
self.ToUserName = xmlData.find('ToUserName').text
self.FromUserName = xmlData.find('FromUserName').text
self.CreateTime = xmlData.find('CreateTime').text
self.MsgType = xmlData.find('MsgType').text
self.MsgId = xmlData.find('MsgId').text
class TextMsg(Msg):
def __init__(self, xmlData):
Msg.__init__(self, xmlData)
self.Content = xmlData.find('Content').text.encode("utf-8")
class ImageMsg(Msg):
def __init__(self, xmlData):
Msg.__init__(self, xmlData)
self.PicUrl = xmlData.find('PicUrl').text
self.MediaId = xmlData.find('MediaId').text
3)vim reply.py
# -*- coding: utf-8 -*-#
# filename: reply.py
import time
class Msg(object):
def __init__(self):
pass
def send(self):
return "success"
class TextMsg(Msg):
def __init__(self, toUserName, fromUserName, content):
self.__dict = dict()
self.__dict['ToUserName'] = toUserName
self.__dict['FromUserName'] = fromUserName
self.__dict['CreateTime'] = int(time.time())
self.__dict['Content'] = content
def send(self):
XmlForm = """
{CreateTime}
"""
return XmlForm.format(**self.__dict)
class ImageMsg(Msg):
def __init__(self, toUserName, fromUserName, mediaId):
self.__dict = dict()
self.__dict['ToUserName'] = toUserName
self.__dict['FromUserName'] = fromUserName
self.__dict['CreateTime'] = int(time.time())
self.__dict['MediaId'] = mediaId
def send(self):
XmlForm = """
{CreateTime}
"""
return XmlForm.format(**self.__dict)
碼好代碼之后,重新啟動程序,sudo python main.py 80。
2.6 在線測試
微信公眾平臺有提供一個在線測試的平臺方便開發(fā)者模擬場景測試代碼邏輯。正如 2.2被動回復(fù)文本消息 交代此被動回復(fù)接口不同于客服接口,測試時也要注意區(qū)別。
在線測試目的在于測試開發(fā)者代碼邏輯是否有誤、是否符合預(yù)期。即便測試成功也不會發(fā)送內(nèi)容給粉絲。所以可以隨意測試。
測試結(jié)果:
1)”請求失敗”,說明代碼有問題,請檢查代碼邏輯。
2)“請求成功”,然后根據(jù)返回結(jié)果查看是否符合預(yù)期。
2.7 真實體驗
拿出手機,微信掃描公眾號二維碼,成為自己公眾號的第一個粉絲。公眾號二維碼位置如下圖:
測試如下圖:
3 實現(xiàn)“圖”尚往來
目的:
1)引入素材管理
2)以文本消息,圖片消息為基礎(chǔ),可自行理解剩余的語音消息、視頻消息、地理消息等
預(yù)實現(xiàn)功能:
接受粉絲發(fā)送的圖片消息,并立馬回復(fù)相同的圖片給粉絲。
3.1 接收圖片消息
即粉絲給公眾號發(fā)送的圖片消息。官方wiki鏈接:消息管理/接收消息-接受普通消息/ 圖片消息從實例講解,粉絲給公眾號發(fā)送一張圖片消息,在公眾號開發(fā)者后臺接收到的xml如下:
1460536575
6272956824639273066
特別說明:
PicUrl: 這個參數(shù)是微信系統(tǒng)把“粉絲“發(fā)送的圖片消息自動轉(zhuǎn)化成url。 這個url可用瀏覽器打開查看到圖片。
MediaId: 是微信系統(tǒng)產(chǎn)生的id 用于標記該圖片,詳情可參考wiki素材管理/獲取臨時素材,
3.2 被動回復(fù)圖片消息
即公眾號給粉絲發(fā)送的圖片消息。官方wiki鏈接:消息管理/發(fā)送消息-被動回復(fù)用戶消息/ 圖片消息
特別說明:
1) 被動回復(fù)消息,即發(fā)送被動響應(yīng)消息,不同于客服消息接口
2) 它其實并不是一種接口,而是對微信服務(wù)器發(fā)過來消息的一次回復(fù)
3) 收到粉絲消息后不想或者不能5秒內(nèi)回復(fù)時,需回復(fù)“success”字符串(下文詳細介紹)
4) 客服接口在滿足一定條件下隨時調(diào)用
開發(fā)者發(fā)送給微信后臺的xml 如下:
1460536576
這里填寫的MediaId的內(nèi)容,其實就是粉絲的發(fā)送圖片的原MediaId,所以粉絲收到了一張一模一樣的原圖。 如果想回復(fù)粉絲其它圖片怎么呢?
1) 新增素材,請參考 新增臨時素材 或者 新增永久素材
2) 獲取其MediaId,請參考 獲取臨時素材MediaID 或者 獲取永久素材MediaID
3.3 流程圖
3.4 碼代碼
只顯示更改的代碼部分,其余部分參考上小節(jié),在線測試,真實體驗,回復(fù)空串,請參考 實現(xiàn)"你問我答"。 vim handle.py
# -*- coding: utf-8 -*-
# filename: handle.py
import hashlib
import reply
import receive
import web
class Handle(object):
def POST(self):
try:
webData = web.data()
print "Handle Post webdata is ", webData #后臺打日志
recMsg = receive.parse_xml(webData)
if isinstance(recMsg, receive.Msg):
toUser = recMsg.FromUserName
fromUser = recMsg.ToUserName
if recMsg.MsgType == 'text':
content = "test"
replyMsg = reply.TextMsg(toUser, fromUser, content)
return replyMsg.send()
if recMsg.MsgType == 'image':
mediaId = recMsg.MediaId
replyMsg = reply.ImageMsg(toUser, fromUser, mediaId)
return replyMsg.send()
else:
return reply.Msg().send()
else:
print "暫且不處理"
return reply.Msg().send()
except Exception, Argment:
return Argment
4 AccessToken
AccessToken 的意義請參考公眾平臺wiki介紹。
4.1 查看appid及appsecret
公眾平臺官網(wǎng)查看, 其中AppSecret 不點擊重置時候,則一直保持不變。
4.2 獲取accessToken
4.2.1臨時方法獲取
為了方便先體驗其他接口,可以臨時通過在線測試 或者 瀏覽器獲取accessToken。
4.2.2 接口獲取
詳情請見公眾平臺wiki
特別強調(diào):
1) 第三方需要一個access_token獲取和刷新的中控服務(wù)器。
2) 并發(fā)獲取access_token會導(dǎo)致AccessToken互相覆蓋,影響具體的業(yè)務(wù)功能
4.3 碼代碼
再次重復(fù)說明,下面代碼只是為了簡單說明接口獲取方式。實際中并不推薦,尤其是業(yè)務(wù)繁重的公眾號,更需要中控服務(wù)器,統(tǒng)一的獲取accessToken。
vim basic.py
# -*- coding: utf-8 -*-
# filename: basic.py
import urllib
import time
import json
class Basic:
def __init__(self):
self.__accessToken = ''
self.__leftTime = 0
def __real_get_access_token(self):
appId = "xxxxx"
appSecret = "xxxxx"
postUrl = ("https://api.weixin.qq.com/cgi-bin/token?grant_type="
"client_credential&appid=%s&secret=%s" % (appId, appSecret))
urlResp = urllib.urlopen(postUrl)
urlResp = json.loads(urlResp.read())
self.__accessToken = urlResp['access_token']
self.__leftTime = urlResp['expires_in']
def get_access_token(self):
if self.__leftTime < 10:
self.__real_get_access_token()
return self.__accessToken
def run(self):
while(True):
if self.__leftTime > 10:
time.sleep(2)
self.__leftTime -= 2
else:
self.__real_get_access_token()
5 臨時素材
公眾號經(jīng)常有需要用到一些臨時性的多媒體素材的場景,例如在使用接口特別是發(fā)送消息時,對多媒體文件、多媒體消息的獲取和調(diào)用等操作,是通過MediaID來進行的。譬如實現(xiàn)“圖”尚往來中,粉絲給公眾號發(fā)送圖片消息,便產(chǎn)生一臨時素材。
因為永久素材有數(shù)量的限制,但是公眾號又需要臨時性使用一些素材,因而產(chǎn)生了臨時素材。這類素材不在微信公眾平臺后臺長期存儲,所以在公眾平臺官網(wǎng)的素材管理中查詢不到,但是可以通過接口對其操作。
其他詳情請以公眾平臺官網(wǎng)wiki介紹為依據(jù)。
5.1 新建臨時素材
接口詳情請依據(jù)wiki介紹。提供參考代碼如何上傳素材作為臨時素材,供其它接口使用。
vim media.py 編寫完成之后,直接運行media.py 即可上傳臨時素材。
# -*- coding: utf-8 -*-
# filename: media.py
from basic import Basic
import urllib2
import poster.encode
from poster.streaminghttp import register_openers
class Media(object):
def __init__(self):
register_openers()
# 上傳圖片
def upload(self, accessToken, filePath, mediaType):
openFile = open(filePath, "rb")
param = {'media': openFile}
postData, postHeaders = poster.encode.multipart_encode(param)
postUrl = "https://api.weixin.qq.com/cgi-bin/media/upload?access_token=%s&type=%s" % (
accessToken, mediaType)
request = urllib2.Request(postUrl, postData, postHeaders)
urlResp = urllib2.urlopen(request)
print urlResp.read()
if __name__ == '__main__':
myMedia = Media()
accessToken = Basic().get_access_token()
filePath = "D:/code/mpGuide/media/test.jpg" # 請按實際填寫
mediaType = "image"
myMedia.upload(accessToken, filePath, mediaType)
5.2 獲取臨時素材MediaID
臨時素材的MediaID 沒有提供特定的接口進行統(tǒng)一查詢,因此有倆種方式
1) 通過接口上次的臨時素材,在調(diào)用成功的情況下,從返回JSON數(shù)據(jù)中提取MediaID,可臨時使用
2) 粉絲互動中的臨時素材,可從xml 數(shù)據(jù)提取MediaID,可臨時使用
5.3 下載臨時素材
5.3.1 手工體驗
開發(fā)者如何保存粉絲發(fā)送的圖片呢?接口文檔:獲取臨時素材接口,為方便理解,從最簡單瀏覽器獲取素材的方法入手,根據(jù)實際情況,瀏覽器輸入網(wǎng)址: https://api.weixin.qq.com/cgi-bin/media/get?access_token=ACCESS_TOKEN&media_id=MEDIA_ID (自行替換數(shù)據(jù)) ACCESS_TOKEN 如 "AccessToken"章節(jié)講解 MEDIA_ID 如 圖尚往來/接受圖片消息xml中的MediaId 講解 只要數(shù)據(jù)正確,則會下載圖片到本地,如下圖:
5.3.2接口獲取
現(xiàn)在已經(jīng)理解這個接口的功能了,只剩碼代碼了。
vim media.py
# -*- coding: utf-8 -*-
# filename: media.py
import urllib2
import json
from basic import Basic
class Media(object):
def get(self, accessToken, mediaId):
postUrl = "https://api.weixin.qq.com/cgi-bin/media/get?access_token=%s&media_id=%s" % (
accessToken, mediaId)
urlResp = urllib2.urlopen(postUrl)
headers = urlResp.info().__dict__['headers']
if ('Content-Type: application/json\r\n' in headers) or ('Content-Type: text/plain\r\n' in headers):
jsonDict = json.loads(urlResp.read())
print jsonDict
else:
buffer = urlResp.read() # 素材的二進制
mediaFile = file("test_media.jpg", "wb")
mediaFile.write(buffer)
print "get successful"
if __name__ == '__main__':
myMedia = Media()
accessToken = Basic().get_access_token()
mediaId = "2ZsPnDj9XIQlGfws31MUfR5Iuz-rcn7F6LkX3NRCsw7nDpg2268e-dbGB67WWM-N"
myMedia.get(accessToken, mediaId)
直接運行 media.py 即可把想要的素材下載下來,其中圖文消息類型的,會直接在屏幕輸出json數(shù)據(jù)段。
6 永久素材
6.1 新建永久素材的方式
6.1.1 手工體驗
公眾號官網(wǎng)的素材管理新增素材。補充一點,公眾平臺只以MediaID區(qū)分素材,MediaID不等于素材的文件名。MediaID只能通過接口查詢,公眾平臺官網(wǎng)看到的是素材的文件名,如下圖:
6.1.2 新增永久素材
新增永久素材接口(詳情見wiki),跟新增臨時素材的操作差不多,使用url不一樣而已,這里避免重復(fù),以新增永久圖文素材接口為例,新增其他類型的素材請參考新增臨時素材代碼。
vim material.py
# -*- coding: utf-8 -*-
# filename: material.py
import urllib2
import json
from basic import Basic
class Material(object):
# 上傳圖文
def add_news(self, accessToken, news):
postUrl = "https://api.weixin.qq.com/cgi-bin/material/add_news?access_token=%s" % accessToken
urlResp = urllib2.urlopen(postUrl, news)
print urlResp.read()
if __name__ == '__main__':
myMaterial = Material()
accessToken = Basic().get_access_token()
news = (
{
"articles":
[
{
"title": "test",
"thumb_media_id": "X2UMe5WdDJSS2AS6BQkhTw9raS0pBdpv8wMZ9NnEzns",
"author": "vickey",
"digest": "",
"show_cover_pic": 1,
"content": "

",
"content_source_url": "",
}
]
})
# news 是個dict類型,可通過下面方式修改內(nèi)容
#news['articles'][0]['title'] = u"測試".encode('utf-8')
# print news['articles'][0]['title']
news = json.dumps(news, ensure_ascii=False)
myMaterial.add_news(accessToken, news)
6.2 獲取永久素材MediaID
1) 通過新增永久素材接口(詳情見wiki)新增素材時,保存MediaID
2) 通過獲取永久素材列表(下文介紹) 的方式獲取素材信息,從而得到MediaID
6.3 獲取素材列表
官方wiki鏈接:獲取素材列表特別說明:此接口只是批量拉取素材信息,不是一次性拉去所有素材的信息,所以可以理解offset字段的含義了吧。
vim material.py
# -*- coding: utf-8 -*-
# filename: material.py
import urllib2
import json
import poster.encode
from poster.streaminghttp import register_openers
from basic import Basic
class Material(object):
def __init__(self):
register_openers()
#上傳
def upload(self, accessToken, filePath, mediaType):
openFile = open(filePath, "rb")
fileName = "hello"
param = {'media': openFile, 'filename': fileName}
#param = {'media': openFile}
postData, postHeaders = poster.encode.multipart_encode(param)
postUrl = "https://api.weixin.qq.com/cgi-bin/material/add_material?access_token=%s&type=%s" % (accessToken, mediaType)
request = urllib2.Request(postUrl, postData, postHeaders)
urlResp = urllib2.urlopen(request)
print urlResp.read()
#下載
def get(self, accessToken, mediaId):
postUrl = "https://api.weixin.qq.com/cgi-bin/material/get_material?access_token=%s" % accessToken
postData = "{ \"media_id\": \"%s\" }" % mediaId
urlResp = urllib2.urlopen(postUrl, postData)
headers = urlResp.info().__dict__['headers']
if ('Content-Type: application/json\r\n' in headers) or ('Content-Type: text/plain\r\n' in headers):
jsonDict = json.loads(urlResp.read())
print jsonDict
else:
buffer = urlResp.read() # 素材的二進制
mediaFile = file("test_media.jpg", "wb")
mediaFile.write(buffer)
print "get successful"
#刪除
def delete(self, accessToken, mediaId):
postUrl = "https://api.weixin.qq.com/cgi-bin/material/del_material?access_token=%s" % accessToken
postData = "{ \"media_id\": \"%s\" }" % mediaId
urlResp = urllib2.urlopen(postUrl, postData)
print urlResp.read()
#獲取素材列表
def batch_get(self, accessToken, mediaType, offset=0, count=20):
postUrl = ("https://api.weixin.qq.com/cgi-bin/material"
"/batchget_material?access_token=%s" % accessToken)
postData = ("{ \"type\": \"%s\", \"offset\": %d, \"count\": %d }"
% (mediaType, offset, count))
urlResp = urllib2.urlopen(postUrl, postData)
print urlResp.read()
if __name__ == '__main__':
myMaterial = Material()
accessToken = Basic().get_access_token()
mediaType = "news"
myMaterial.batch_get(accessToken, mediaType)
6.4 刪除永久素材
如果我想刪除掉 20160102.jpg 這張圖片,除了官網(wǎng)直接操作,也可以使用接口: 刪除永久素材 接口文檔。
首先需要知道該圖片的mediaID,方法上小節(jié)已講述。代碼可參考上小節(jié):Material().delete() 接口 調(diào)用接口成功后,在公眾平臺官網(wǎng)素材管理的圖片中,查詢不到已刪除的圖片。
7 自定義菜單
自定義菜單意義作用請參考創(chuàng)建接口 介紹。
目標:三個菜單欄,體驗click、view、media_id 三種類型的菜單按鈕,其他類型在本小節(jié)學(xué)習(xí)之后,自行請查詢公眾平臺wiki說明領(lǐng)悟。
7.1 創(chuàng)建菜單界面
1)根據(jù)公眾平臺wiki 給的json 數(shù)據(jù)編寫代碼,其中涉及media_id部分請閱讀"永久素材"章節(jié)。
vim menu.py
# -*- coding: utf-8 -*-
# filename: menu.py
import urllib
from basic import Basic
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": "開發(fā)指引",
"key": "mpGuide"
},
{
"name": "公眾平臺",
"sub_button":
[
{
"type": "view",
"name": "更新公告",
"url": "http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1418702138&token=&lang=zh_CN"
},
{
"type": "view",
"name": "接口權(quán)限說明",
"url": "http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1418702138&token=&lang=zh_CN"
},
{
"type": "view",
"name": "返回碼說明",
"url": "http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1433747234&token=&lang=zh_CN"
}
]
},
{
"type": "media_id",
"name": "旅行",
"media_id": "z2zOokJvlzCXXNhSjF46gdx6rSghwX2xOD5GUV9nbX4"
}
]
}
"""
accessToken = Basic().get_access_token()
#myMenu.delete(accessToken)
myMenu.create(postJson, accessToken)
2)在騰訊云服務(wù)器上執(zhí)行命令:python menu.py。
3)查看: 重新關(guān)注公眾號后即可看到新創(chuàng)建菜單界面,題外話,如果不重新關(guān)注,公眾號界面也會自動更改,但有時間延遲。
如下圖所示,點擊子菜單“更新公告“(view類型),彈出網(wǎng)頁(pc版本)
點擊旅行(media_id類型),公眾號顯示了一篇圖文消息,如下圖所示:
點擊開發(fā)指引(click類型),發(fā)現(xiàn)公眾號系統(tǒng)提示:“該公眾號暫時無法提供服務(wù)“。
7.2 完善菜單功能
查看公眾平臺自定義菜單與自定義菜單事件推送 后,可知:點擊click類型button,微信后臺會推送一個event類型的xml 給開發(fā)者。
顯然,click類型的還需要開發(fā)者進一步完善后臺代碼邏輯,增加對自定義菜單事件推送的相應(yīng)。
7.2.1 流程圖
7.2.2碼代碼
- vim handle.py (修改)
# -*- coding: utf-8 -*-
# filename: handle.py
import reply
import receive
import web
class Handle(object):
def POST(self):
try:
webData = web.data()
print "Handle Post webdata is ", webData # 后臺打日志
recMsg = receive.parse_xml(webData)
if isinstance(recMsg, receive.Msg):
toUser = recMsg.FromUserName
fromUser = recMsg.ToUserName
if recMsg.MsgType == 'text':
content = "test"
replyMsg = reply.TextMsg(toUser, fromUser, content)
return replyMsg.send()
if recMsg.MsgType == 'image':
mediaId = recMsg.MediaId
replyMsg = reply.ImageMsg(toUser, fromUser, mediaId)
return replyMsg.send()
if isinstance(recMsg, receive.EventMsg):
toUser = recMsg.FromUserName
fromUser = recMsg.ToUserName
if recMsg.Event == 'CLICK':
if recMsg.Eventkey == 'mpGuide':
content = u"編寫中,尚未完成".encode('utf-8')
replyMsg = reply.TextMsg(toUser, fromUser, content)
return replyMsg.send()
print "暫且不處理"
return reply.Msg().send()
except Exception, Argment:
return Argment
2)vim receive.py (修改)
# -*- coding: utf-8 -*-
# filename: receive.py
import xml.etree.ElementTree as ET
def parse_xml(web_data):
if len(web_data) == 0:
return None
xmlData = ET.fromstring(web_data)
msg_type = xmlData.find('MsgType').text
if msg_type == 'event':
event_type = xmlData.find('Event').text
if event_type == 'CLICK':
return Click(xmlData)
#elif event_type in ('subscribe', 'unsubscribe'):
#return Subscribe(xmlData)
#elif event_type == 'VIEW':
#return View(xmlData)
#elif event_type == 'LOCATION':
#return LocationEvent(xmlData)
#elif event_type == 'SCAN':
#return Scan(xmlData)
elif msg_type == 'text':
return TextMsg(xmlData)
elif msg_type == 'image':
return ImageMsg(xmlData)
class EventMsg(object):
def __init__(self, xmlData):
self.ToUserName = xmlData.find('ToUserName').text
self.FromUserName = xmlData.find('FromUserName').text
self.CreateTime = xmlData.find('CreateTime').text
self.MsgType = xmlData.find('MsgType').text
self.Event = xmlData.find('Event').text
class Click(EventMsg):
def __init__(self, xmlData):
EventMsg.__init__(self, xmlData)
self.Eventkey = xmlData.find('EventKey').text
7.3 體驗
編譯好代碼后,重新啟動服務(wù),(sudo python main.py 80),view類型、media_id類型的本身就很容易實現(xiàn),現(xiàn)在重點看一下click類型的菜單按鈕。
微信掃碼成為公眾號的粉絲,點擊菜單按鈕“開發(fā)指引”。
查看后臺日志,發(fā)現(xiàn)接收到一條xml,如截圖:
公眾號的后臺代碼設(shè)置對該事件的處理是回復(fù)一條內(nèi)容為“編寫之中”的文本消息,因此公眾號發(fā)送了一條文本消息給我,如圖:
好啦,到此,目標已實現(xiàn)。對于自定義菜單其他類型,均同理可操作。
8 關(guān)于反饋問題
是程序肯定有bug,所以在使用開放平臺過程中,肯定會遇見各種各樣的問題,可能是自己的坑,也可能是微信團隊的鍋。當自己查自己代碼千千遍,依舊沒有發(fā)現(xiàn)問題時候,可以通過 #公眾號社區(qū) 發(fā)帖交流、騰訊客服等等渠道,請求微信團隊的幫助。如何高效快速的得到幫助呢?下面強調(diào)三個要點:
1)簡明扼要的描述清楚場景以及遇見問題,描述過程中盡可能使用wiki上的名稱,譬如自定義菜單,素材管理等專有名詞,不然開發(fā)根本不知道你在說什么。
2)提供賬號信息:AppID(登錄公眾平臺官網(wǎng)->基本配置),若牽扯粉絲提供粉絲的OpenID。
3)提供bug的發(fā)生時間,至少要以小時為單位(年-月-日-小時),當然越具體越容易查明問題。