基于微信公用平台的webapp开发

2013-01-28 10:31

微信为什么会成为腾讯未来五年内的工作重点, 我们不在这儿讨论. 我们本文仅讨论如何利用微信这个平台, 来做一些好玩的东西.

管理界面简介

微信公众平台管理界面登录入口如下:

http://mp.weixin.qq.com/cgi-bin/loginpage?t=wxm-login

为了要做一个基于微信公众平台的webapp, 你首先需要在这儿注册一个帐号. 现在开始玩这个东东的人还不算太多, 你可以考虑抢注一个好名字(如果你真的很看好微信的未来, 你甚至可以在这儿抢注一些名字, 以后再卖出). 首先, 你需要新注册一个QQ号. 然后登录, 开始设置你的app的名字, logo等信息, 这些文职性的工作完成之后, 我们开始做一些好玩的事情.

首先, 作为最基本的功能, 你能够在管理界面上设置新加你这个帐号为好友后, 你所有的这个帐号给对方发送的消息, 你可以把帐号的基本介绍写在这儿. 例如你在这儿写"你好, 欢迎你加我为好友"后, 某个人如果找到你这个帐号, 并加你这个帐号为好友后, 他就会自动收到你刚刚设置的这条消息.

然后, 你能够设置消息自动回复内容. 此时, 无论对方发过来什么消息, 都会收到这个统一的自动回复. 这样显然不够好玩, 不过对开发者的胃口. 不过在特殊的时候, 这个功能还是有用处的. 例如后面会说到你会用一个网站来给用户提供动态内容. 不过万一这个网站挂掉了或者被墙了, 那么这儿设置一个自动回复就是很有必要的了.

另外, 你还可以在这儿设置关键字自动回复. 例如用户发过来的消息里面有"讲个笑话"这四个字, 你就可以把一个预设的笑话作为消息内容发给他. 相比于之前固定一条消息的自动回复, 这种基于关键字的回复已经比较好玩了. 不过我们可以通过"公众平台消息接口"这个功能来做些更好玩更动态一点儿的东西.

公众平台消息接口

选择使用公众平台消息接口后, 你需要填写一些个人联系的方式. 这儿的手机号需要是真实的, 因为会有短信验证码发送过来, 确认手机号码. 接下来你需要填写你提供给微信的web接口等信息. 使用了公众平台消息接口后, 当用户给你的帐号发送了一条消息后, 腾讯会将这个消息内容和属性发送给你的这个web接口. 你在这个接口中写逻辑, 并按照固定协议返回后, 腾讯再将这个消息发送给对应的人.

微信会向你所提供的接口发送两种请求. 一个是GET, 仅用来检验你的接口的在线情况. 另一个是POST, 用来传递用户发送给这个微信帐号的信息. 前一个请求处理起来很容易, 从GET请求中拿到echostr这个键的值后返回即可, 后面也有代码实例, 不再详述. 后一个请求中, 腾讯会直接把一个XML的内容作为键发过来. 你需要在POST字典中拿到这个键的内容. 具体的情况读者可以参考官方文档

下面来看看具体的代码吧. 为了避免自己写cgi, 我用来bottle.py这个单文件的web框架. 首先来看看GET的处理和POST请求中信息的提取和处理.

import os
import time
from xml.etree import ElementTree

from webster import get_webster

from bottle import debug, default_app, run, get, request, post


TEMPLATE = """<xml>
<ToUserName><![CDATA[%(toUser)s]]></ToUserName>
<FromUserName><![CDATA[%(fromUser)s]]></FromUserName>
<CreateTime>%(create_time)s</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[%(content)s]]></Content>
<FuncFlag>0</FuncFlag>
</xml>"""


@get('/')
def index():
    return request.GET.get('echostr')

@post('/')
def index_post():
    xmldata = request.POST.keys()[0]
    tree = ElementTree.fromstring(xmldata)
    return get_message(tree)

def get_message(tree):
    if tree.find('MsgType').text != 'text':
        content = u"乖表调皮~"
    else:
        query = tree.find('Content').text.lower().strip()
        try:
            query = query.encode("ascii")
            content = get_webster(query)
        except UnicodeEncodeError:
            content = u"乖表调皮~"
    data_dict = {
        'toUser': tree.find('FromUserName').text,
        'fromUser': tree.find('ToUserName').text,
        'create_time': int(time.time()),
        'content': content
    }
    return TEMPLATE % data_dict

os.chdir(os.path.dirname(__file__))
application = default_app()

我写这段代码是为了完成一个在线字典应用. 用户将待查询的词语作为一个文本消息发送过来, 我拿到待查的词语后通过调用韦氏字典的API来拿到词语的释义, 再返回给用户. 为了代码能够把逻辑分离开, 具体从Webster那儿拿词语释义的逻辑我放在了一个独立的模块里面.

import, TEMPLATE和HELP我们先不看, 我们先看index这个函数. 这一段的逻辑如前所述, 从GET字典中拿到echostr这个属性后返回. 接下来是index_post这个函数. 注意到, 我们这儿仍是用"/"来处理HTTP请求. 只不过这儿所要求的HTTP方法是POST. 这个函数里逻辑也不难, 就是从POST里拿到我们要找的键(实际上是一个XML), 用ElementTree来解析这个XML字符串. 然后将解析得到的结构作为参数丢给我们真正用来解析用户请求的逻辑get_message.

在get_message的一开头, 我们先过滤掉我们不希望得到的消息类型, 只保留文本. 我们接下来从tree中拿到用户请求的unicode编码的数据. 如果这个数据不能被encode成ascii, 则这可能是一个非法请求. 如果请求合法, 我们则调用get_webster函数, 返回一个unicode字符串来完成请求. 最后, 我们把待返回的消息的关键数据组成一个字典, 通过TEMPLATE渲染成一个字符串, 返回给腾讯.

最后这两行是bottle.py所要求, 或者应该说是WSGI协议所要求的. 这个脚本中需要有application这个app来处理wsgi收进来的请求. 在apache配置中, 我们添加的vhost文件如下:

ServerName webster.xiaket.org

ServerAdmin xiaket@gmail.com
WSGIDaemonProcess webster user=www-data group=www-data processes=1 threads=5
WSGIScriptAlias / /srv/www/webster.xiaket.org/site/main.py


    WSGIProcessGroup webster
    WSGIApplicationGroup %{GLOBAL}
    Order deny,allow
    Allow from all


ErrorLog /srv/www/webster.xiaket.org/logs/webster_error.log
LogLevel debug
CustomLog "|/usr/sbin/rotatelogs -l /srv/www/webster.xiaket.org/logs/webster_access.log.%Y.%m.%d 86400" combined
ServerSignature Off

碎碎念们