微信公衆平臺後臺接入簡明指南

 

廖雪峯 / 編程 / 2014-1-18 20:20 / 閱讀: 31500java

如今微信愈來愈火了,公衆平臺也愈來愈火,做爲一個公司或者網站,沒有一個公衆號,你都很差意思跟人打招呼,更別提遞名片了。python

可是,開通了公衆平臺,靠人工去響應用戶消息,不但技術含量不夠,並且,人少也忙不過來啊。編程

幸虧微信公衆平臺有開發模式,只要接入了微信後臺,用戶消息會被髮送到咱們指定的服務器上,而後,由服務器向用戶回覆消息。這種方式,比提示用戶「回覆1看xxx,回覆2看xxx」顯得高端大氣上檔次。json

開發模式須要準備網站而且接入微信後臺,在微信目前文檔不完善,接口不友好的狀況下,本文將詳細講解如何快速接入微信公衆平臺。ruby

準備工做

首先,你須要有一個微信公衆號,好比「中華詩詞」。在往下繼續閱讀前,請自覺掏出手機,打開微信掃一掃:服務器

qrcode_shici

其次,你須要有一個獨立域名的網站,用來和微信服務器交互。微信

接入公衆平臺

登陸微信公衆平臺後臺後,點「功能」-「高級功能」-「開發模式」,進入開發模式,若是公衆平臺顯示「還沒有成爲開發者」,就點擊「成爲開發者」:微信公衆平臺

not-weixin-dev

贊成協議後,填寫URL和Token:框架

weixin-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對比:

Python:

def check_signature(signature, timestamp, nonce): L = [timestamp, nonce, token] L.sort() s = L[0] + L[1] + L[2] return hashlib.sha1(s).hexdigest() == signature 

Java:

public static boolean check_signature(signature, timestamp, nonce) { String[] arr = new String[] { timestamp, nonce, token }; Arrays.sort(arr); String s = arr[0] + arr[1] + arr[2]; md = MessageDigest.getInstance("SHA-1"); byte[] digest = md.digest(s.getBytes("utf-8")); return signature == bytes2HexString(digest); 

注意token不是微信服務器發過來的,而是咱們本身寫死的一個常量,就是在微信後臺填寫的Token。

若是計算的sha1和微信傳過來的signature相等,說明這個請求確實是微信後臺發過來的,若是是別人僞造的請求,因爲他不知道token,因此,沒法計算出正確的signature。

要防止第三方經過監聽發動replay攻擊,還須要驗證timestamp和nonce,這個之後再討論。

若是signature計算無誤,就把微信後臺傳過來的echostr原封不動地傳回去,這樣,就能夠經過驗證,成爲開發者。

在確保開發模式打開的狀況下,微信後臺會把用戶消息發到咱們的服務器上,也就是URL:http://www.example.com/weixin

dev-mode-on

微信後臺發送消息是一個POST請求,但和普通的POST請求不一樣的是,首先,URL會帶上signature、timestamp、nonce這3個參數:

POST http://www.example.com/weixin?signature=xxx&timestamp=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參數就須要用到更底層的代碼。好比,在Python中,必須經過WSGI的environ字典獲取,而且本身來解析:

# python: environ = ... qs = environ.get('query_string', '') q = urlparse.parse_qs(qs) signature = q['signature'][0] timestamp = q['timestamp'][0] nonce = q['nonce'][0] # TODO: check signature... 

在Java中,用HttpServletRequest在POST模式下別想用getParameter()拿到URL參數,必須用getQueryString()而後本身想辦法解析字符串:

// java:
String qs = request.getQueryString();
Map<String, String> map = parse(qs); // TODO: check signature... 

而後,咱們再討論如何讀取微信後臺發過來的XML。在Python中,須要從environ讀取原始的wsgi.input流:

fp = environ['wsgi.input'] 

在Java中,須要從HttpServletRequest中獲取Reader流:

Reader reader = request.getReader();

若是有亂碼,寫一個EncodingFilter把Request強制設置爲UTF-8編碼:

public class EncodingFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { req.setCharacterEncoding("UTF-8"); resp.setCharacterEncoding("UTF-8"); chain.doFilter(req, resp); } public void init(FilterConfig config) throws ServletException {} public void destroy() {} } 

不須要讀取爲字符串,只要有了流就能夠解析XML了,建議用SAX解析,最終咱們應該獲得微信的XML中傳過來的幾個值:

ToUserName: 'abc'
FromUserName: 'xyz'
CreateTime: '12345678'
MsgType: 'text'
Content: '用戶發的消息'

根據MsgType咱們能夠判斷消息是文本、語音、圖片、位置仍是視頻,而後,構造一個XML回覆給微信後臺,若是一切順利,微信後臺就把咱們的消息發給用戶。

目前咱們只討論如何回覆文本消息,只需構造以下的XML:

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

在回覆的XML中,把接收的ToUserName和FromUserName交換,這兩個字符串都是用戶ID(公衆號自己也是一個用戶ID),CreateTime是以秒爲單位的UNIX時間戳,計算以下:

Python:

CreateTime = int(time.time()) 

Java:

long CreateTime = System.currentTimeMillis() / 1000; 

MsgType還是text,Content就是咱們自動回覆的消息,注意不要超過600個字符。

回覆的時候,須要注意,一是最好明確地設置Content-Type: text/xml,二是XML的編碼必須是UTF-8,不然,回覆的消息就會出現亂碼。

如何建立回覆XML?因爲該XML結構至關簡單,因此無需動用任何XML接口,直接拼接字符串最簡單快速。

最後,把代碼部署到服務器,記住把接收的參數和XML,以及本身生成的XML在log中打印出來,一邊看log,一邊用手機端的微信來調試。只要調通了一種接口,其餘接口參考微信文檔就很容易開發了:

http://mp.weixin.qq.com/wiki/

限制

目前,微信公衆平臺的API還有不少限制,好比沒有天天自動羣發消息的API,要回復圖文等多媒體消息須要V認證等等。

思考

微信和微信公衆平臺雖然產品很先進,但後臺API設計得確實不咋地。因爲API是給開發人員使用的,因此,設計一個好的API要從開發人員的角度出發。與其使用笨重的XML,不如使用更符合Web潮流的JSON。並且,沒有必要把驗證單獨用GET區分,徹底能夠所有使用POST方式,在JSON中把全部信息所有包括,以action和data來區分消息類型和數據,例如,驗證服務器:

{
    "signature": "xxx", "timestamp": 123456, "nonce": "xxx", "action": "verify", "data": { "echostr": "echo" } } 

發送消息:

{
    "signature": "xxx", "timestamp": 123456, "nonce": "xxx", "action": "msg", "data": { "id": "123456", "type": "text", "from": "user-abc", "to": "user-xyz", "create_time": 1234567, "content": "blablabla..." } } 

回覆消息:

{
    "action": "msg", "data": { "type": "text", "from": "user-xyz", "to": "user-abc", "create_time": 1234567, "content": "reply to..." } } 

這樣設計的API,各類編程語言都能處理,並且處理邏輯更簡單,速度更快。

相關文章
相關標籤/搜索