這兩個月里,公司的項目基本上都基于微信公眾平臺的開發上。為了可以快速開發,我使用了 Ruby on Rails框架,而不是之前的 Python,這不是說 Python 不可以做到快速開發,其實我更多只是想試試新的東西。但效果不錯。
微信公眾平臺的開發,與語言沒什么太大的關系,大家只要選擇自己熟悉的語言即可,以下我只說明大概的原理,并用 Ruby on Rails 作為代碼示例。
準備工作
首先,你需要有一個微信公眾號,比如 ”SheldonChen寫字的地方"。在往下繼續閱讀前,請自覺掏出手機,打開微信掃一掃:
其次,你需要有一個獨立域名的網站,用來和微信服務器交互。
接入公眾平臺
登錄微信公眾平臺后臺后,點“功能”-“高級功能”-“開發模式”,進入開發模式,如果公眾平臺顯示“尚未成為
開發者”,就點擊“成為開發者”:
同意協議后,填寫URL和Token:
URL是指微信服務器向哪個URL發送消息,假設我們自己的服務器域名是www.example.com,準備用/weixin來接收消息,就填寫:
http://www.example.com/weixin
而Token是微信服務器和我們自己的服務器通信時驗證身份用的,可以隨便填寫,但要注意保密。
然后點“提交”,一般來說會報錯“URL超時”或者“沒有正確返回echostr”,因為我們的后臺還沒有準備好,所以,第一步是接收微信后臺發送的驗證消息,微信后臺會發送一個GET請求到上面的URL,并附帶以下參數:
signature,timestamp,nonce,echostr
我們的服務器在接收到上述參數后,需要驗證signature是否正確,驗證方法是先對timestamp、nonce和token先排序,再拼接成一個字符串,計算出sha1,并和signature對比。
注意token不是微信服務器發過來的,而是我們自己寫死的一個常量,就是在微信后臺填寫的Token。
如果計算的sha1和微信傳過來的signature相等,說明這個請求確實是微信后臺發過來的,如果是別人偽造的請求,由于他不知道token,所以,無法計算出正確的signature。
要防止第三方通過監聽發動replay攻擊,還需要驗證timestamp和nonce,這個以后再討論。
如果signature計算無誤,就把微信后臺傳過來的echostr原封不動地傳回去,這樣,就可以通過驗證,成為開發者。
在確保開發模式打開的情況下,微信后臺會把用戶消息發到我們的服務器上,也就是URL:http://www.example.com/weixin:
微信后臺發送消息是一個POST請求,但和普通的POST請求不同的是,首先,URL會帶上signature、timestamp、nonce這3個參數:
POST http://www.example.com/weixin?signature=xxx×tamp=123456&nonce=123
然后,HTTP請求的BODY是一個不規范的XML:
<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>
我們自己的服務器只需要處理該XML,然后,向微信返回一個類似如下的XML:
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>12345678</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[你好]]></Content>
</xml>
就可以完成消息的回復。微信后臺要求必須在5秒內回復,最多重試3次,否則我們自己的回復消息就到達不了用戶的手機了。如果我們自己的服務器無法在5秒內回復,就回復一個空字符串,告訴微信服務器,不用重試了,這個消息處理不了,不給用戶回復了。
上面的交互邏輯看起來很簡單,但實際上坑有很多。
首先,微信服務器發送的POST請求根本就不符合HTTP規范。原則上POST請求不應該在URL上附帶參數,但微信后臺偏偏要這么干,這就讓很多編程語言的標準框架無法獲取到POST參數,因為標準的POST參數是從HTTP BODY中解析的。
所以,從POST獲取URL參數就需要用到更底層的代碼。