微信公衆開發——使用消息接口

html

南苑隨筆app

以前寫了一篇搭建sae的python平臺,經過微信公衆平臺驗證的博文。如今就是要承上啓下,開始開發的第一步。也許你會說開發的第一步就是寫代碼。那我會對你表示鄙視,儘管南苑隨筆是個很簡單的應用,可是多少也還須要設計一下。肯定一下需求,爲了未來可以吸引到粉絲來關注個人公衆號,我必需要定位好個人app,而後提供優秀的內容,並肯定它的運營模式,才能進一步地提高咱們的應用水準。python

 

需求分析

首先,這個app是要用python寫的,那麼就必定要優雅一點,pythonic一點。對於我本身來講,我要使得這個app至少可以知足我閱讀若干與南苑相關的文章的要求才行。故而須要對南苑隨筆進行需求分析:linux

 

定位: 爲南苑或者外校同窗提供咱們在南苑生活和學習的文章。文體不限,內容形式包括{圖片,語音,文字},推送給用戶,這正是微信可以提供給個人。web

 

約定: 容許投稿,可是投稿必須經過管理員的審覈。其實很大程度上,咱們是爲了向粉絲們推介 @bibo-果凍 和 @daoluan-鄭思願 合做的那本《南苑隨筆——IT小小鳥外傳》。數據庫

 

內容: 校園生活,學習,感情,分享,趣味,攝影,文學做品,簡單社交 ,歌曲等等。django

 

服務: 提供每1天/2天按期推送文章或語音。同時相應用戶發來的請求,對用戶請求進行分析後返回所要求的內容。編程

 

性能: 要求服務器在5秒內返回用戶的請求內容,在網絡差的時候,這個就難說了。可以同時處理至少20位粉絲的請求(其實到底有沒有這麼多粉絲髮來請求,我內心仍是沒有數啊)。ubuntu

系統設計

此次,我吸收了上次的經驗教訓,要先完成邏輯和數據的設計,最後纔去設計咱們的交互界面(包括交互方式和菜單)。數據先行,那麼一切就從構建起數據庫開始。參照wordpress的博客數據庫,就能夠設計出基本的數據庫框架。這部分就交給 @daoluan-思願 來搞定,我相信他會完成的至關出色。瀏覽器

 

本系統須要一個博客系統,和一個微信服務app,博客系統做爲web端,用瀏覽器可訪問的。而微信app則負責推送到用戶的微信賬號聊天界面。故而咱們的數據庫設計就很清晰了,須要:服務器

user表——用戶

post表——文章

blog配置——博客的設置

category表——分類

———————————次要———————————

url表——友情連接

guest_book表——留言簿

 

而我須要用到的僅僅是博客系統數據庫的一個子集——post表和guest_book表(也許會用到category表)。故而開發微信app則只須要自行建立兩個表進行實驗便可。

 

發送消息試驗

暫且不把設計邏輯排到日程上來,咱們來嘗試一下在第三方服務器和微信服務器,以及客戶端進行通訊。假如用戶發送來一個請求,因爲已經綁定了URL,且開啓了開發模式,那麼就微信服務器就會轉發這個請求到咱們的服務器,咱們的服務器要解析這個請求,而後返回所請求的內容。

 

咱們須要一個類來處理get請求,以及定時向微信服務器傳送羣發的文章。

該類 weixin_scnuwriter設計以下:

handle_request(request)         # 處理請求

parse_request_xml(xml)        # 解析請求

response_msg(request)            # 響應請求(關鍵邏輯部分)

pack_text_xml(post_msg, response_msg)  # 打包響應的文本內容,須要參考微信API,供response_msg調用

 

# 另外在數據庫方面要可以讀取,須要django於數據庫打交道,不過咱們暫時不實現,只是作一個簡單的測試。

 

下面是handle_request代碼:

1
2
3
4
5
6
7
8
9
def  handle_request(request): 
     if  request.method  = =  'GET'
         response  =  HttpResponse(check_signature(request),content_type = "text/plain" )     # 若是是GET請求,那麼覈對簽名
         return  response
     elif  request.method  = =  'POST' :  
         response  =  HttpResponse(response_msg(request),content_type = "application/xml" )   # 若是是POST請求,那麼響應消息
         return  response 
     else
         return  None

咱們以前寫認證的時候,都是直接將url導向到check_signature直接進行響應處理,可是這裏咱們對請求的類型進行區分,由於驗證的時候發送的是GET請求,而對咱們本身的服務器推送消息的時候,它是使用POST請求的,由於POST方法能夠向服務器發送大量的數據(表單提交,文件上傳等),在這裏是xml結構。記得要在urls裏面配置一個到handle_request的映射,個人是:「 url(r'^$', handle_request),  」。逗號是不能丟的。

 

handle_request須要調用check_signature,以前咱們用了return HttpResponse(echoStr)來驗證,可是咱們把HttpResponse的封裝放到了handle_request中,那麼咱們就只需在check_signature中返回echoStr就好了,固然,驗證一次就好了,如今已經進入開發階段,這函數將用不到。其實應該在一開始,咱們就應該像如今同樣寫。check_signature以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 檢測簽名,只對get有效      
def  check_signature(request): 
     global  TOKEN 
     signature  =  request.GET.get( "signature" None )
     timestamp  =  request.GET.get( "timestamp" None )
     nonce  =  request.GET.get( "nonce" None )
     echoStr  =  request.GET.get( "echostr" , None )
     token  =  TOKEN 
     tmpList  =  [token,timestamp,nonce] 
     tmpList.sort() 
     tmpstr  =  "%s%s%s"  %  tuple (tmpList) 
     tmpstr  =  hashlib.sha1(tmpstr).hexdigest()
                                                 
     if  tmpstr  = =  signature:
         return  echoStr
     else
         return  None

很明顯15行變了。瀏覽器和服務器之間,並非簡單的傳遞數據,還有解析包的過程,服務器返回一個http類型的文檔,瀏覽器要進行協議解析,讀取頭部等東西,進而解析出整個文檔。這就是爲何須要在返回的時候包裝成HttpResponse,不然瀏覽器讀不懂,django就報錯說:返回的不是一個httpresponse類型的對象。我曾經參考《linux C 編程指南》寫過一個簡單的服務器程序,服務器程序,發送了固定的頭部,而後對各類不一樣類型的返回打印不一樣的文檔,例如404,503等等,而對於正常狀況,則讀取一個html文件予以返回。協議與就是相互商量好的約定,故而協議通常都約定了要以什麼開頭,或者按照協議裏寫的東西來解析。

 

下面是parse_request_xml函數,用來解析請求,該請求來自微信服務器,包含了一個xml結構,使用的是POST方法,待會咱們就用個工具來測試一下怎麼模擬POST請求,而不使用微信發送(微信發送須要通過微信服務器,並且咱們不知道錯在哪裏)。

1
2
3
4
5
6
7
# 解析請求,拆解到一個字典裏       
def  parse_request_xml(rootElem):
     msg  =  {}
     if  rootElem.tag  = =  'xml' :
     for  child  in  rootElem:
         msg[child.tag]  =  smart_str(child.text)   # 得到內容 
     return  msg

很明顯,對於一個xml結構(實際上是一個字符串),進行解析,就須要用到它的tag,來區分究竟是什麼內容,以及獲取對應tag的內容,放到python咱們程序中的一個字典結構裏。

 

下面是response_msg函數:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def  response_msg(request):
     # 從request中獲取輸入文本
     rawStr  =  smart_str(request.raw_post_data)
     # 將文本進行解析,獲得請求的數據
     msg  =  parse_request_xml(ET.fromstring(rawStr))
     # 根據請求消息來處理內容返回
     query_str  =  msg.get( "Content" )
     # query_str = "hello"
     response_msg  =  ""
     # 使用簡單的處理邏輯,有待擴展
     if  query_str  = =  "hello" :
     response_msg  =  "hello to you too"
     else :
     response_msg  =  "how are you, unkown"
     # 返回消息
     # 包括post_msg,和對應的 response_msg
     return  pack_text_xml(msg, response_msg);

消息迴應,這裏的邏輯很簡單,對於hello,就返回一個hello to you too, 其餘則返回 how are you , unkown。未來用戶若是發來「第一篇」等請求,咱們將返回完整的圖文消息。這部分是有待開發的。

 

上面的迴應函數用到了打包返回消息成xml這樣的功能,pack_text_xml函數以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 打包消息xml,做爲返回 
def  pack_text_xml(post_msg,response_msg):
     # f = post_msg['FromUserName']
     # t = post_msg['FromUserName']
     text_tpl  =  '''<xml>
                 <ToUserName><![CDATA[%s]]></ToUserName>
                 <FromUserName><![CDATA[%s]]></FromUserName>
                 <CreateTime>%s</CreateTime>
                 <MsgType><![CDATA[%s]]></MsgType>
                 <Content><![CDATA[%s]]></Content>
                 <FuncFlag>0</FuncFlag>
                 </xml>'''
     text_tpl  =  text_tpl  %  (post_msg[ 'FromUserName' ],post_msg[ 'ToUserName' ], str ( int (time.time())), 'text' ,response_msg)
     # 調換髮送者和接收者,而後填入須要返回的信息到xml中
     return  text_tpl

能夠看到text_tpl其實就是微信API內容的一個——返回text,就是聊天記錄同樣的文本。對於其它內容,有其餘內容的模板,能夠到微信公衆賬號API察看。這裏須要說明的是xml結構裏面要填參數的地方,都有一個佔位符號%s,表示的是一個字符串。而後對於模板,填如咱們的參數。如第12行所示。

poster測試

  好了,咱們能夠開始測試了。我google了一下,有人說使用firefox的poster插件,很是好用。因而我就在firefox下裝了一個,來模擬各類各樣不一樣的http請求。首先,要有一個firefox瀏覽器,ubuntu如今是自帶了,無需安裝,若是沒有能夠去官網下載。而後安裝poster,點這裏,找到了poster插件,在firefox安裝就好了。好了,咱們能夠模擬微信服務器發送咱們的消息轉發了,以下:

使用時,填入post請求時發送的xml,還有你的服務器的url,例如個人是: http://scnuwriter.sinaapp.com 而後點post,就能夠看到下面的畫面:

能夠看到咱們的服務器返回了 hello to you too,這是和咱們的程序相符合的。可是可能你獲得的是一個403fobidden。我google了一下,stack overflow裏面說,那多是由於咱們沒有啓用跨站請求,以防止***,具體參考python的csrf文檔。後來我修改了settings.py文件裏面的middleware配置:

1
2
3
4
5
6
7
8
9
10
11
12
MIDDLEWARE_CLASSES  =  (
     'django.middleware.common.CommonMiddleware' ,
     'django.contrib.sessions.middleware.SessionMiddleware' ,
     'django.middleware.csrf.CsrfViewMiddleware' ,
     'django.contrib.auth.middleware.AuthenticationMiddleware' ,
     'django.contrib.messages.middleware.MessageMiddleware' ,
     # for post:
     #'django.middleware.csrf.CsrfResponseMiddleware',    
                              
     # Uncomment the next line for simple clickjacking protection:
     #'django.middleware.clickjacking.XFrameOptionsMiddleware',
)

很奇怪,我去掉第8行CsrfResponseMiddleware以後,服務器就出錯了。故而我註釋了它,而後在views.py的最前面的函數前面加上一個:@csrf_exempt  。結果就好了,獲得上面截圖的內容。要深刻了解這是爲何,還須要瞭解web的更多信息,還有django的官方文檔,bibo也要好好充電了,知其然,不知其因此然,不可謂爲知也。

 

最後

好吧,用svn上傳你的成果到服務器上吧,用手機來測試。通常用post獲得了正確迴應都不會有錯的。bibo在本身的手機微信上面發送了一個"hello",真的收到了一個"hello to you too"。第一次嘗試微信編程,竟然成功了!yeah。並且還學會了一個工具poster。

 

by bibodeng 2013-04-19   士別三日,當另眼相看

 

scnuwriter示例下載連接:http://vdisk.weibo.com/s/y9wJz  主要看views.py

相關文章
相關標籤/搜索