微信公众开发――使用消息接口

时间:2022-09-18 17:56:31



南苑随笔app

之前写了一篇搭建sae的python平台,通过微信公众平台验证的博文。现在就是要承上启下,开始开发的第一步。也许你会说开发的第一步就是写代码。那我会对你表示鄙视,尽管南苑随笔是个很简单的应用,但是多少也还需要设计一下。确定一下需求,为了将来能够吸引到粉丝来关注我的公众号,我必须要定位好我的app,然后提供优秀的内容,并确定它的运营模式,才能进一步地提升我们的应用水准。

 

需求分析

首先,这个app是要用python写的,那么就一定要优雅一点,pythonic一点。对于我自己来说,我要使得这个app至少能够满足我阅读若干与南苑相关的文章的要求才行。故而需要对南苑随笔进行需求分析:

 

定位: 为南苑或者外校同学提供我们在南苑生活和学习的文章。文体不限,内容形式包括{图片,语音,文字},推送给用户,这正是微信能够提供给我的。

 

约定: 允许投稿,但是投稿必须通过管理员的审核。其实很大程度上,我们是为了向粉丝们推介 @bibo-果冻 和 @daoluan-郑思愿 合作的那本《南苑随笔——IT小小鸟外传》。

 

内容: 校园生活,学习,感情,分享,趣味,摄影,文学作品,简单社交 ,歌曲等等。

 

服务: 提供每1天/2天定期推送文章或语音。同时相应用户发来的请求,对用户请求进行分析后返回所要求的内容。

 

性能: 要求服务器在5秒内返回用户的请求内容,在网络差的时候,这个就难说了。能够同时处理至少20位粉丝的请求(其实到底有没有这么多粉丝发来请求,我心里还是没有数啊)。

系统设计

这次,我吸取了上次的经验教训,要先完成逻辑和数据的设计,最后才去设计我们的交互界面(包括交互方式和菜单)。数据先行,那么一切就从构建起数据库开始。参照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

 

 

 

 

 

 

 

 

 

 

 

 

 

 

本文出自 “ugeek bibodeng” 博客,请务必保留此出处http://bibodeng.blog.51cto.com/2646046/1181175