1墓捻、開啟公眾號(hào)開發(fā)者模式
公眾平臺(tái)的技術(shù)文檔目的為了簡明扼要的交代接口的使用厉碟,語句難免苦澀難懂废菱,甚至對(duì)于不同的讀者搀继,有語義歧義吟策。萬事皆是入門難,對(duì)于剛?cè)腴T的開發(fā)者講苟翻,更是難上加難搭伤,往往看了半天wiki,就是不懂說的什么鬼袜瞬。
為了降低門檻,彌補(bǔ)不足身堡,特地編寫了《開發(fā)者指引》邓尤,講解了一下微信開放平臺(tái)的基礎(chǔ)常見功能,目的重在幫助大家入門微信開放平臺(tái)的開發(fā)者模式。
對(duì)于已熟知或者在一定公眾平臺(tái)開發(fā)經(jīng)驗(yàn)的開發(fā)小哥汞扎,請(qǐng)直接跳過本文季稳。這篇文章不會(huì)給你帶來厲害的編碼技巧亦或接口的深層次講解。對(duì)于現(xiàn)有接口存在的疑問澈魄,請(qǐng)直接呼叫克服或者微信投訴景鼠,不要要在本文浪費(fèi)時(shí)間。
為了防止小編語意上歧義痹扇,在給大家?guī)砝_铛漓,直接用最暴力的方法帶你入門公眾號(hào)開發(fā)——附錄簡明代碼
1.1申請(qǐng)服務(wù)器
以騰訊云服務(wù)器為示例:騰訊云服務(wù)器購買入口,購買指導(dǎo)請(qǐng)參考快速人們Linux云服務(wù)器鲫构。
學(xué)生黨注意:騰訊公司為在讀高校生提供了云+校園計(jì)劃浓恶,1元/月即可使用騰訊云。
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 a test"
if _name_=='_main_':
app = web.application(urls,globals())
3)如果出現(xiàn)“socket.error:No socket could be created”錯(cuò)誤信息赫模,可能為80端口號(hào)被占用树肃,可能是沒有權(quán)限,請(qǐng)自行查詢解決辦法嘴瓤。如果遇見其他錯(cuò)誤信息扫外,請(qǐng)到web.py官方文檔學(xué)習(xí)webpy框架3)執(zhí)行命令:sudo python main.py 80.
4)瀏覽器輸入http://外網(wǎng)IP:80/wx(外網(wǎng)IP請(qǐng)到騰訊云購買成功處查詢)。如下圖廓脆,一個(gè)簡單的web應(yīng)用已搭建筛谚。
1.3 申請(qǐng)公眾號(hào)
郵箱激活后,選擇公眾號(hào)類型停忿。不同的公眾號(hào)擁有不同的能力驾讲,詳情請(qǐng)見wiki:公眾號(hào)接口權(quán)限說明。當(dāng)然席赂,服務(wù)號(hào)吮铭、企業(yè)號(hào)需要一定的證件和相關(guān)資料填寫,如果證件一時(shí)不能準(zhǔn)備好颅停,沒關(guān)系谓晌,公眾號(hào)其實(shí)已注冊(cè),下次可以根據(jù)此郵箱&密碼登錄再選中癞揉。
1.4 開發(fā)者基本配置
1)公眾平臺(tái)官網(wǎng)登錄之后纸肉,找到“基本配置”菜單欄
2)填寫配置
URL填寫:http://外網(wǎng)IP:端口號(hào)/wx溺欧。外網(wǎng)IP請(qǐng)到騰訊云購買成功處查詢,http的端口號(hào)固定使用80柏肪,不可填寫其他姐刁。
Token:自主設(shè)置,這個(gè)token與公眾平臺(tái)wiki中常提的access_token不是一回事烦味。這個(gè)token只用于驗(yàn)證開發(fā)者服務(wù)器聂使。
3)現(xiàn)在選擇提交肯定是驗(yàn)證token失敗,因?yàn)檫€需要完成代碼邏輯谬俄。改動(dòng)原先main.py文件柏靶,新增handle.py
a)vim main.py
# ?-*- coding:utf-8 -*-
# filename: main.py
import web
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
improt 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" #請(qǐng)按公眾平臺(tái)官網(wǎng)\基本配置中信息填寫
list = [token,timestamp, nonce]
list..sort()
shal = hashlib.shal()
map(shal.update,list)
hashcode = shal.hexdigest()
print "handle/GET func: hashcode,signature:",hashcode,signature
if hashcode == signature:
return echostr
else:
return ""
except Exception, Argument:
return Argument
4)重新啟動(dòng)成功后(Python main.py 80),點(diǎn)擊提交按鈕凤瘦。若提示“token驗(yàn)證失敗”宿礁,請(qǐng)認(rèn)真檢查代碼或網(wǎng)絡(luò)鏈接等。若token驗(yàn)證成功蔬芥,會(huì)自動(dòng)返回基本配置的主頁面梆靖,點(diǎn)擊啟動(dòng)按鈕。
1.5 重要事情提前交代
接下來笔诵,文章準(zhǔn)備從兩個(gè)簡單的示例入手返吻。
示例一:實(shí)現(xiàn)“你說我學(xué)”
示例一:實(shí)現(xiàn)“圖尚往來”
兩個(gè)簡單的示例后,是一些基礎(chǔ)功能的介紹:素材管理乎婿、自定義菜單测僵、群發(fā)。所有示例代碼是為了簡明的說明問題谢翎,避免代碼復(fù)雜化捍靠。在實(shí)際中搭建一個(gè)安全穩(wěn)定高效的公眾號(hào),建議參考框架如下圖:
主要有三個(gè)部分:負(fù)責(zé)業(yè)務(wù)邏輯部分的服務(wù)器森逮,負(fù)責(zé)對(duì)接微信API的API-Proxy服務(wù)器榨婆,以及唯一的Access Token中控服務(wù)器
1)AccessToken中控服務(wù)器:
負(fù)責(zé):提供主動(dòng)刷新和被動(dòng)刷新機(jī)制來刷新accessToken并存儲(chǔ)(為了防止并發(fā)刷新,注意加并發(fā)鎖)褒侧,提供給業(yè)務(wù)邏輯有效的accessToken良风。
優(yōu)點(diǎn):避免業(yè)務(wù)邏輯方并發(fā)獲取access_token,避免AccessToken互相覆蓋,提高業(yè)務(wù)功能的穩(wěn)定性闷供。
2)API-Proxy服務(wù)器:
負(fù)責(zé):專一與微信API對(duì)接烟央,不同的服務(wù)器可以負(fù)責(zé)對(duì)接不同的業(yè)務(wù)邏輯,更可進(jìn)行調(diào)用頻率歪脏、權(quán)限限制疑俭。
優(yōu)點(diǎn):某臺(tái)API-Proxy異常,還有其余服務(wù)器支持繼續(xù)提供服務(wù)婿失,提高穩(wěn)定性钞艇,避免直接暴漏內(nèi)部接口鬼贱,有效防止惡意攻擊,提高安全性香璃。
2 實(shí)現(xiàn)“你問我答”
目的:
1)理解被動(dòng)消息的含義
2)理解收\發(fā)下次機(jī)制
預(yù)實(shí)現(xiàn)功能:粉絲給公眾號(hào)一條文本消息,公眾號(hào)立馬回復(fù)一條文本消息給粉絲舟误,不需要經(jīng)過公眾平臺(tái)網(wǎng)頁操作葡秒。
2.1接受文本消息
即粉絲給公眾號(hào)發(fā)送的文本消息。官方wiki鏈接:消息管理/接收消息-接受普通消息
粉絲給公眾號(hào)發(fā)送文本消息:“歡迎開啟公眾號(hào)開發(fā)者模式”嵌溢,在開發(fā)者后臺(tái)眯牧,收到公眾平臺(tái)發(fā)送的xml如下:(下文隱藏了ToUserName及FromUserName消息)
<xml>
<ToUserName><![CDATA[公眾號(hào)]]></ToUserName>
<FromUserName><![CDATA[粉絲號(hào)]]></FromUserName>
<CreateTime>1460537339</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[歡迎開啟公眾號(hào)開發(fā)者模式]]></Content>
<MsgId>6272960105994287618</MsgId>
</xml>
解釋:
createTime 是微信公眾平臺(tái)記錄粉絲發(fā)送該消息的具體時(shí)間
text:用于標(biāo)記該xml是文本消息,一般用于去唄判斷
歡迎開啟公眾號(hào)開發(fā)者模式:說明該粉絲發(fā)給公眾號(hào)的具體內(nèi)容是歡迎開啟公眾號(hào)開發(fā)者模式
MsgId:是公眾平臺(tái)為記錄識(shí)別該消息的一個(gè)標(biāo)記數(shù)值赖草,微信后臺(tái)系統(tǒng)自動(dòng)產(chǎn)生
2.2 被動(dòng)回復(fù)文本消息
即公眾號(hào)給粉絲發(fā)送的文本消息学少,官方wiki鏈接:消息管理/接收消息-被動(dòng)回復(fù)消息
特別強(qiáng)調(diào):
1)被動(dòng)回復(fù)消息,即發(fā)送被動(dòng)響應(yīng)消息秧骑,不同于客服消息接口
2)它其實(shí)并不是一種接口版确,而是對(duì)微信服務(wù)器發(fā)過來消息的一次回復(fù)
3)收到粉絲消息后不想或者不能5秒內(nèi)回復(fù)時(shí),需回復(fù)“success”字符串(下文詳細(xì)介紹)
4)客服接口在滿足一定條件下隨時(shí)調(diào)用
公眾號(hào)想回復(fù)給粉絲一條文本消息乎折,內(nèi)容為“test”绒疗,那么開發(fā)者發(fā)送給公眾平臺(tái)后臺(tái)的xml內(nèi)容如下:
<xml>
<ToUserName><![CDATA[粉絲號(hào)]]</ToUserName>
<FromUserName><![CDATA[公眾號(hào)]]></FromUserName>
<CreateTime>1460541339</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[test]]></Content>
</xml>
特別備注:
1)ToUserName(接受者)、FromUserName(發(fā)送者)字段請(qǐng)實(shí)際填寫骂澄。
2)createtime只用于標(biāo)記開發(fā)者回復(fù)消息的時(shí)間吓蘑、微信后臺(tái)發(fā)送此消息都是不受這個(gè)字段約束。
3)text:用于標(biāo)記此次行為是發(fā)送文本消息(當(dāng)然可以是image/voice等類型)坟冲。
4)文本換行 ‘\n’磨镶。
2.3 回復(fù)success問題
查詢官方wiki開頭強(qiáng)調(diào):加入服務(wù)器無法保證在五秒內(nèi)處理回復(fù),則必須回復(fù)“success”或者“”(空串)健提,否則微信后臺(tái)會(huì)發(fā)起三次重試琳猫。
解釋一下為何有這么奇怪的規(guī)定。發(fā)起重試是微信后臺(tái)為了盡可以保證粉絲發(fā)送的內(nèi)容開發(fā)者可以收到矩桂。如果開發(fā)者不進(jìn)行回復(fù)沸移,微信后臺(tái)沒辦法確認(rèn)開發(fā)者已收到消息,只好重試侄榴。
真的是這樣子嗎雹锣?嘗試一下收到消息后,不做任何回復(fù)癞蚕。在日志中查看到微信后臺(tái)發(fā)起了三次重試操作蕊爵,日志截圖如下:
三次重試后,依舊沒有及時(shí)回復(fù)任何內(nèi)容桦山,系統(tǒng)自動(dòng)在粉絲會(huì)話界面出現(xiàn)錯(cuò)誤提示“該公眾號(hào)暫時(shí)無法提供服務(wù)攒射,請(qǐng)稍后再試”醋旦。
如果回復(fù)success,微信后臺(tái)可以確定開發(fā)者收到了粉絲消息,沒有任何異常提示会放。因此請(qǐng)大家注意回復(fù)success的問題饲齐。
2.5 碼代碼
main.py文件不改變,handle.py需要增加一下代碼咧最,增加新的文件receive.py捂人,reple.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 #后臺(tái)打日志
recMsg = receive.parse_xml(webData)
if isinstance(recMsg,receive.Msg) and recMsg.MsgType == 'text':
toUser = recMsg.FromUserName
fromUser = recMsg.ToUserName
content = "test"
replyMsg = reple.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)
mas_type = xmlData.find('MsgType').text
if mas_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 _int_(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 = """
<xml>
<ToUserName><![CDATA[{ToUserName}]]></ToUserName>
<FromUserName><![CDATA[{FromUserName}]]></FromUserName>
<CreateTime>{CreateTime}</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[{Content}]]></Content>
</xml>
"""
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 = """
<xml>
<ToUserName><![CDATA[{ToUserName}]]></ToUserName>
<FromUserName><![CDATA[{FromUserName}]></FromUserName>
<CreateTime>{CreateTime}</CreateTime>
<MsgType><![CDATA[image]]></MsgType>
<Image>
<MediaId><![CDATA[{MediaId}]]></MediaId>
</Image>
</xml>
"""
return XmlForm.format(**self._dict)
碼好代碼之后,重新啟動(dòng)程序矢沿,sudo python main.py 80滥搭。
2.6 在線測(cè)試
微信公眾平臺(tái)有提供一個(gè)在線測(cè)試平臺(tái),方便開發(fā)者模擬場(chǎng)景測(cè)試代碼邏輯捣鲸。正如2.2被動(dòng)回復(fù)文本消息 交代此被動(dòng)回復(fù)接口不同于客服接口瑟匆,測(cè)試時(shí)也要注意區(qū)別。
在線測(cè)試目的在于測(cè)試開發(fā)者代碼邏輯是否有誤栽惶、是否符合預(yù)期愁溜。即便測(cè)試成功也不會(huì)發(fā)送內(nèi)容給粉絲。所以可以隨意測(cè)試媒役。
測(cè)試結(jié)果:
1)“請(qǐng)求失敗”祝谚,說明代碼有問題,請(qǐng)檢查代碼邏輯酣衷。
2)“請(qǐng)求成功”交惯,然后根據(jù)返回結(jié)果查看是否符合預(yù)期。
2.7 真實(shí)體驗(yàn)
拿出手機(jī)穿仪,微信掃描公眾號(hào)二維碼席爽,成為自己公眾號(hào)的第一個(gè)粉絲。公眾號(hào)二維碼位置如下圖:
3 實(shí)現(xiàn)“圖”尚往來
目的:
1)引入素材管理
2)以文本消息啊片,圖片消息為基礎(chǔ)只锻,可自行理解剩余的語音消息、視頻消息紫谷、地理消息等
預(yù)實(shí)現(xiàn)功能:
接受粉絲發(fā)送的圖片消息齐饮,并立馬回復(fù)相同的圖片給粉絲。
3.1 接收?qǐng)D片消息
即粉絲給公眾號(hào)發(fā)送的圖片消息笤昨。官方wiki鏈接:消息管理/接收消息-接受普通消息/圖片消息http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140453&token=&lang=zh_CN祖驱。從實(shí)例講解,粉絲給公眾號(hào)發(fā)送一張圖片消息瞒窒,在公眾號(hào)開發(fā)者后臺(tái)接收到的xml如下:
<xml>
<ToUserName><![CDATA[公眾號(hào)]]></ToUserName>
<FromUserName><![CDATA[粉絲號(hào)]]></FromUserName>
<CreateTime>1460536575</CreateTime>
<MsgType><![CDATA[image]]</MsgType>
<PicUrl><![CDATA[http://mmbiz.qpic.cn/xxxxx/0]]></PicUrl>
<MsgId>6272956824639273066</MsgId>
<MediaId><![CDATA[gyci5a-xxxxx-OL]]></MediaId>
</xml>
特別說明:
PicUrl:這個(gè)參數(shù)是微信系統(tǒng)把“粉絲”發(fā)送的圖片消息自動(dòng)轉(zhuǎn)化成url.這個(gè)url可用瀏覽器打開查看圖片捺僻。
MediaId:是微信系統(tǒng)產(chǎn)生的id用于標(biāo)記該圖片,詳情可參看wiki素材管理/獲取臨時(shí)素材。http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1444738727&token=&lang=zh_CN
3.2 被動(dòng)回復(fù)圖片消息
即公眾號(hào)給粉絲發(fā)送的圖片消息匕坯。官方wiki鏈接:消息管理/發(fā)送消息-被動(dòng)回復(fù)用戶消息/圖片消息束昵。http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140543&token=&lang=zh_CN
特別說明:
1)被動(dòng)回復(fù)消息,即發(fā)送被動(dòng)響應(yīng)消息葛峻,不同于客服消息接口
2)它其實(shí)并不是一種接口锹雏,而是對(duì)微信服務(wù)器發(fā)過來消息的一次回復(fù)
3)收到粉絲消息后不想或者不能5秒內(nèi)回復(fù)時(shí),需回復(fù)“success”字符串(下文詳細(xì)介紹)
4)客服接口在滿足一定條件下隨時(shí)調(diào)用
開發(fā)者發(fā)送給微信后臺(tái)的xml如下:
<xml>
<ToUserName><![CDATA[粉絲號(hào)]]></FromUserName>
<CreateTime>1460536575</CreateTime>
<MsgType><![CDATA[image]]></MsgType>
<Image>
<MediaId><!CDATA[gyci5oxxxxxxv3cOL]]</MediaId>
</Image>
</xml>
這里填寫的MediaId的內(nèi)容术奖,其實(shí)就是給粉絲的發(fā)送圖片的原MediaId逼侦,所以粉絲收到了一張一模一樣的原圖。
如果想回復(fù)粉絲其他圖片怎么呢腰耙?
1)新增素材,請(qǐng)參考新增臨時(shí)素材或者新增永久素材
2)獲取其MediaId铲球,請(qǐng)參考獲取臨時(shí)素材MediaID或者獲取永久素材MediaID
3.3 流程圖
3.4 碼代碼
只顯示更改的代碼部分挺庞,其余部分參考上小節(jié),在線測(cè)試稼病,真實(shí)體驗(yàn)选侨,回復(fù)空串,請(qǐng)參考實(shí)現(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 #后臺(tái)打日志
recMsg = receive.parse_xml(webData)
if isinstance(recMsg,receive.Msg):
toUSER = recMsg.FromUserName
fromUser = recMsg.ToUserName
if recMsg.Msg.MsgType == 'text':
content = "test"
replyMsg = reply.TextMsg(toUser,fromUser,content)
return replyMsg.send()
if recMsg.MsgType == 'image':
mediaId = recMsg.MediaId
replyMsg = reple.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的意義請(qǐng)參考公眾平臺(tái)wikihttp://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140183&token=&lang=zh_CN介紹援制。
4.1 查看appid及appsecret
公眾平臺(tái)官網(wǎng)查看,其中AppSecret不點(diǎn)擊重置時(shí)候芍瑞,則一直保持不變晨仑。
4.2 獲取accessToken
4.2.1 臨時(shí)方法獲取
為了方便先體驗(yàn)其他接口,可以臨時(shí)通過在線測(cè)試http://mp.weixin.qq.com/debug/或者瀏覽器獲取accessToken拆檬。
4.2.2 接口獲取
詳情請(qǐng)見:公眾平臺(tái)wikihttp://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140183&token=&lang=zh_CN
特別強(qiáng)調(diào):
1)第三方需要一個(gè)access_token獲取和刷新的中控服務(wù)器洪己。
2)并發(fā)獲取access_token會(huì)導(dǎo)致AccessToken互相覆蓋,影響具體的業(yè)務(wù)功能
4.3 碼代碼
再次重復(fù)說明竟贯,下面代碼只是為了簡單說明接口獲取方式答捕。實(shí)際中并不推薦,尤其是業(yè)務(wù)繁重的公眾號(hào)屑那,更需要中控服務(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 臨時(shí)素材
公眾號(hào)經(jīng)常有需要用到一些臨時(shí)性的多媒體素材的場(chǎng)景持际,例如在使用接口特別是發(fā)送消息時(shí)沃琅,對(duì)多媒體文件、多媒體消息的獲取和調(diào)用等操作选酗,是通過MediaID來進(jìn)行的阵难。譬如實(shí)現(xiàn)“圖”尚往來中,粉絲給公眾號(hào)發(fā)送圖片消息芒填,便產(chǎn)生一臨時(shí)素材呜叫。
因?yàn)橛谰盟夭挠袛?shù)量限制空繁,但是公眾號(hào)又需要臨時(shí)性使用一些素材,因而產(chǎn)生了臨時(shí)素材朱庆。這類素材不在微信公眾平臺(tái)后臺(tái)長期存儲(chǔ)盛泡,所以在公眾平臺(tái)官網(wǎng)的素材管理中查詢不到,但是可以通過接口對(duì)其操作娱颊。
其他詳情請(qǐng)以公眾平臺(tái)官網(wǎng)wiki介紹為依據(jù)傲诵。
5.1 新建臨時(shí)素材
接口詳情請(qǐng)依據(jù)wiki介紹。提供參考代碼如何上傳素材為臨時(shí)素材箱硕,供其他接口使用拴竹。
vim media.py 編寫完成之后,直接運(yùn)行media.py即可上傳臨時(shí)素材剧罩。
# -*- 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" #請(qǐng)按實(shí)際填寫
mediaType = "image"
myMedia.upload(accessToken, filePath, mediaType)
5.2 獲取臨時(shí)素材MdidaID
臨時(shí)素材的MediaID沒有提供特定的接口進(jìn)行統(tǒng)一查詢栓拜,因此有兩種方式
1)通過接口上次的臨時(shí)素材,在調(diào)用成功的情況下惠昔,從返回JSON數(shù)據(jù)中提取MediaID,可臨時(shí)使用
2)粉絲互動(dòng)中的臨時(shí)素材幕与,可從xml數(shù)據(jù)中提取MedaID,可臨時(shí)使用
5.3 下載臨時(shí)素材
5.3.1 手工體驗(yàn)
開發(fā)者如何保存粉絲發(fā)送的圖片呢?接口文檔:獲取臨時(shí)素材接口http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1444738727&token=&lang=zh_CN镇防,為方便理解啦鸣,從最簡單瀏覽器獲取素材的方法入手,根據(jù)實(shí)際情況来氧,瀏覽器輸入網(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ù)正確诫给,則會(huì)下載圖片到本地,如下圖:
5.3.2 接口獲取
現(xiàn)在已經(jīng)理解這個(gè)接口的功能了啦扬,只剩碼代碼了蝙搔。vim media.py
# -*- coding:utf-9 -*-
# 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)
rulResp = 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() #素材的二進(jìn)制
mediaFile = file("test_meda.jpg", "wb")
mediaFile.write(buffer)
print "get successful"
if _name_ == '_main_':
myMedia = Media()
accessToken = Basic().get_access_token()
meidaId = "2ZsPnDj9XIQlGfws31MUfR5Iuz-rcn7F6LkX3NRCsw7nDpg2268e-dbGB67WWM-N"
myMedia.get(accessToken,mediaId)
直接運(yùn)行media.py即可把想要的素材下載下來,其中圖文消息類型的考传,會(huì)直接在屏幕輸出json數(shù)據(jù)段吃型。
6 永久素材
6.1 新建永久素材的方式
6.1.1 手工體驗(yàn)
公眾號(hào)官網(wǎng)的素材管理新增素材。補(bǔ)充一點(diǎn)僚楞,公眾平臺(tái)只以MediaID區(qū)分素材勤晚,MediaID不等于素材的文件名。MediaID只能通過接口查詢泉褐,公眾平臺(tái)官網(wǎng)看到的是素材的文件名赐写,如下圖:
6.1.2 接口刪除
新增永久素材接口(詳情見wiki)http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1444738729&token=&lang=zh_CN跟新增臨時(shí)素材的操作差不多,使用URL不一樣而已膜赃,這里避免重復(fù)挺邀,以新增永久圖文素材接口為例,新增其他類型的素材請(qǐng)參考新增臨時(shí)素材代碼。vim material.py
# -*- coding: urt-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": "<p><img data-s=\"300,640\" data-type=\"jpeg\" data-src=\"http://mmbiz.qpic.cn/mmbiz/iaK7BytM0QFPLhxfSMhOHlZd2Q5cw3YibKVf4dgNpLHXdUkvl65NBSMU71rFfOEKF3ucmXuwAQbNdiaaS3441d5rg/0?wx_fmt=jpeg\" data-ratio=\"0.748653500897666\" data-w=\"\"? />",<br /></p>",
"content_source_url":"",
}
]
})
#news 是個(gè)dict類型端铛,可通過下面方式修改內(nèi)容
#news['articles'][0]['title'] = u"測(cè)試".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)http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1444738729&token=&lang=zh_CN新增素材時(shí)泣矛,保存MediaID
2)通過獲取永久素材列表(下文介紹)的方式獲取素材信息,從而得到MediaID
6.3 獲取素材列表
官方wiki鏈接:獲取素材列表http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1444738734&token=&lang=zh_CN禾蚕。特別說明:此接口只是批量拉取素材信息您朽,不是一次性拉去所有素材的信息,所以可以理解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 = 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() #素材的二進(jìn)制
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 = urllib.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)直接操作,也可以使用接口:刪除永久素材http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1444738731&token=&lang=zh_CN接口文檔倍试。
首先需要知道該圖片的mediaID,方法上小節(jié)已講述讯屈。代碼可參考上小節(jié):Material().delete()接口
調(diào)用接口成功后,在公眾平臺(tái)官網(wǎng)素材管理的圖片中县习,查詢不到已刪除的圖片耻煤。
7 自定義菜單
自定義菜單意義作用請(qǐng)參考官方wikihttp://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141013&token=&lang=zh_CN介紹。
目標(biāo):三個(gè)菜單欄准颓,體驗(yàn)click、view棺妓、media_id三種類型的菜單按鈕攘已,其他類型在本小節(jié)學(xué)習(xí)之后,請(qǐng)自行查詢公眾平臺(tái)wiki說明領(lǐng)悟怜跑。
7.1 創(chuàng)建菜單界面
1)根據(jù)公眾平臺(tái)wiki給的json數(shù)據(jù)編寫代碼样勃,其中涉及media_id部分請(qǐng)閱讀“永久素材”章節(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":"公眾平臺(tái)",
"sub_button":
[
{
"type":"vies",
"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)注公眾號(hào)后可查看新創(chuàng)建菜單界面,題外話植锉,如果不重新關(guān)注辫樱,公眾號(hào)界面也會(huì)自動(dòng)更改,但有時(shí)間延遲俊庇。
如下圖所示狮暑,點(diǎn)擊子菜單“更新公告“(view類型),彈出網(wǎng)頁(PC版本)
點(diǎn)擊旅行(media_id類型)辉饱,公眾號(hào)顯示了一篇圖文消息搬男,如下圖所示:
7.2 完善菜單功能
查看公眾平臺(tái)自定義菜單wiki與公眾平臺(tái)wiki/自定義菜單事件推送后,可知:點(diǎn)擊click類型button彭沼,微信后臺(tái)會(huì)推送一個(gè)event類型的xml給開發(fā)者缔逛。
顯然,click類型的還需要開發(fā)者進(jìn)一步完善后臺(tái)代碼邏輯,增加對(duì)自定義菜單事件推送的響應(yīng)褐奴。
7.2.1 流程圖
7.2.2 碼代碼
1)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 #后臺(tái)打日志
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):
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 == '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 == '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體驗(yàn)
編譯好代碼后,重新啟動(dòng)服務(wù)歉糜,(sudo python main.py 80),view類型乘寒、meidia_id類型的本身就很容易實(shí)現(xiàn),現(xiàn)在重點(diǎn)看一下click類型的菜單按鈕匪补。
微信掃碼成為公眾號(hào)的粉絲伞辛,點(diǎn)擊菜單按鈕“開發(fā)指引”。
查看后臺(tái)日志夯缺,發(fā)現(xiàn)接收到一條xml蚤氏,如截圖:
公眾號(hào)的后臺(tái)代碼設(shè)置對(duì)該事件的處理是回復(fù)一條內(nèi)容為“編寫之中”的文本消息,因此公眾號(hào)發(fā)送了一條文本消息給我踊兜,如圖:
好啦竿滨,到此,目標(biāo)已實(shí)現(xiàn)捏境。對(duì)于自定義菜單其他類型于游,均同理可操作。
8 關(guān)于反饋問題
是程序肯定有bug垫言,所以在使用開放平臺(tái)過程中贰剥,肯定會(huì)遇見各種各樣的問題,可能是自己的坑筷频,也可能是微信團(tuán)隊(duì)的鍋蚌成。當(dāng)自己查自己代碼千千遍,依舊沒有發(fā)現(xiàn)問題時(shí)候凛捏,可以通過騰訊客服等等渠道担忧,請(qǐng)求微信團(tuán)隊(duì)的幫助。如何高效快速的得到幫助呢坯癣?下面強(qiáng)調(diào)三個(gè)要點(diǎn):
1)精明扼要的描述清楚場(chǎng)景以及遇見問題瓶盛,描述過程中盡可能使用wiki上的名稱,譬如自定義菜單示罗,素材管理等專有名詞蓬网,不然開發(fā)根本不知道你在說什么。
2)提供賬號(hào)信息:AppID(登錄公眾平臺(tái)官網(wǎng)->基本配置)鹉勒,若牽扯粉絲提供粉絲的OpenID帆锋。
3)提供bug的發(fā)生時(shí)間,至少要以小時(shí)為單位(年-月-日-小時(shí))禽额,當(dāng)然越具體越容易查明問題锯厢。