背景
现在用java开发微信公众号等功能,虽然说已经有了一些成熟的框架,但都是Servlet相关的,缺少采用Vert.x框架的。Vert.x的特点是,在消息机制(event bus)的基础上,支持采用多种语言进行开发,包括但不限于java, node.js和scala等。我们希望,开发一个基于Vert.x的框架,可以方便微信平台的开发,同时利用消息机制,让多种语言可以复用这种基础能力,快速接入微信平台。阅读这篇文章,需要你对Vert.x的event bus机制有所了解。
现有的微信通信机制
我们首先对微信现有的通信机制做一个简单的了解。在下面的描述里,WechatServer(W.S.)指的是微信服务器, Server(S.)指的是开发者接入微信的服务器。
W.S.发消息给S.以及S.的回复
W.S.通过接口URL,即开发者填写的URL,通过Post请求,将消息推送到S。这些消息的类型,有用户点击了菜单,用户发送了消息等,消息是用XML定义的。同时S.可以通过在回复中携带消息,作为对此的回复。
W.S. ==(Post 接口URL with XML Body)==> S.
<========(XML Body)=====
一个文本消息的定义如下:
<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>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[FromUser]]></FromUserName>
<CreateTime>123456789</CreateTime>
<MsgType><![CDATA[event]]></MsgType>
<Event><![CDATA[CLICK]]></Event>
<EventKey><![CDATA[EVENTKEY]]></EventKey>
</xml>
回复的消息定义和上面的类似。
S.发送请求给W.S.
S.在对菜单进行操作的时候,以及群发消息的时候,是通过Post/Get请求到W.S.对应的各自业务接口,会携带JSON定义的内容,来完成请求。W.S.以JSON返回结果。
S. ==(Post/Get 业务URL with JSON Body)==> W.S.
<========(JSON Body)============
自定义菜单创建接口的示例:
http请求方式:POST(请使用https协议) https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN
{
"button":[
{
"type":"click",
"name":"今日歌曲",
"key":"V1001_TODAY_MUSIC"
},
{
"name":"菜单",
"sub_button":[
{
"type":"view",
"name":"搜索",
"url":"http://www.soso.com/"
},
{
"type":"view",
"name":"视频",
"url":"http://v.qq.com/"
},
{
"type":"click",
"name":"赞一下我们",
"key":"V1001_GOOD"
}]
}]
}
回复:
{"errcode":0,"errmsg":"ok"}
自定义菜单查询接口的示例:
http请求方式:GET
https://api.weixin.qq.com/cgi-bin/menu/get?access_token=ACCESS_TOKEN
回复:
{
"menu": {
"button": [
{
"type": "click",
"name": "今日歌曲",
"key": "V1001_TODAY_MUSIC",
"sub_button": [ ]
}
],
"menuid": 208396938
},
"conditionalmenu": [
{
"button": [
{
"type": "click",
"name": "今日歌曲",
"key": "V1001_TODAY_MUSIC",
"sub_button": [ ]
},
{
"name": "菜单",
"sub_button": [
{
"type": "view",
"name": "搜索",
"url": "http://www.soso.com/",
"sub_button": [ ]
},
{
"type": "view",
"name": "视频",
"url": "http://v.qq.com/",
"sub_button": [ ]
},
{
"type": "click",
"name": "赞一下我们",
"key": "V1001_GOOD",
"sub_button": [ ]
}
]
}
],
"matchrule": {
"group_id": 2,
"sex": 1,
"country": "中国",
"province": "广东",
"city": "广州",
"client_platform_type": 2
},
"menuid": 208396993
}
]
API的聚合方式: 业务实体 VS 消息通讯
通常来说,针对某个领域的框架,会提供一些业务实体来完成操作。例如自定义菜单,可以提供这样的业务实体:
Menu.Get(handler)
.Create(menu, handler)
.Update(menu, handler)
.delete(menu, handler)
并且,将菜单相关的消息推送以事件的方式封装:
Menu.On("click", handler)
这种业务实体的映射,也可以说是RPC,的风格是相当可以的。但是我们会以消息通讯这种松散的方式来实现框架,例如说:
eventBus.send("fzs.wechat.menu.get", message, handler)
eventBus.send("fzs.wechat.menu.create", handler)
//接收消息推送的地址,address和事件名一一对应。
eventBus.consumer("fzs.wechat.event.click", message, handler)
eventBus.consumer("fzs.wechat.event.view", message, handler)
我们倾向于使用第二种方式来实现,因为这样对于多语言支持是友好的。
工作计划
-
将微信的接口分类,聚合到不同的address。
-
针对W.S. => S.的请求,路由到一个拦截器,将请求转发到不同的address,同时要将XML Body转为JSON表述的消息。
-
针对S. => W.S.的请求,实现一系列Consumer,根据address, 将消息的内容转为相应的请求,调用微信相对应的业务URL。











网友评论