南苑随笔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