奇迹蛋的软件架构如图所示,主要有6个模块。结合上一篇环境搭建中所描述的,系统主要与微信服务器、数据库以及API服务器之间有交互,在这里分别通过微信适配层、数据库管理模块、外部API适配层来做接口。

 

微信机器人怎么弄的 java 微信机器人编写_自然语言处理

这里所画的Servlet实际就是配置在微信公共账号中的URL,这样就把用户的请求和咱的机器人关联了起来。

首先在微信中配置url,如下图所示:

微信机器人怎么弄的 java 微信机器人编写_聊天机器人_02

在程序的web.xml中有如下配置:

<servlet>
    <servlet-name>MainServlet</servlet-name>
    <servlet-class>org.miracle.robot.servlet.MainServlet</servlet-class>
  </servlet>
  
  <servlet-mapping>
    <servlet-name>MainServlet</servlet-name>
    <url-pattern>/MainServlet</url-pattern>
  </servlet-mapping>

微信机器人怎么弄的 java 微信机器人编写_微信机器人怎么弄的 java_03

这样微信服务器就通过url: http://miracledan7robot.duapp.com/MainServlet 把用户请求交给咱们的org.miracle.robot.servlet.MainServlet来处理,很容易理解吧~

咱在MainServlet中调用了微信适配层的方法来处理消息。

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("UTF-8");  
        response.setCharacterEncoding("UTF-8");  
  
        // 将消息交给微信适配层处理  
        String resp_msg = WeiXinAdaptor.processRequest(request);  
          
        // 回复响应消息  
        PrintWriter out = response.getWriter();  
        out.print(resp_msg);  
        out.close();  
}


知道消息是怎么进入咱的系统后,就来看一看系统内部是如何运作的。

1、微信适配层:用于解析和封装微信消息。

通过微信的开发者文档了解到,当普通微信用户向公众账号发消息时,微信服务器将POST消息的XML数据包到开发者填写的URL上,也就是说微信服务器提供的消息接口是xml格式的,为了处理这些数据,必须将xml数据解析成系统容易处理的数据结构。如:


<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数据,以及将回复给微信服务器的消息封装成xml格式的数据。

微信提供了文本、图片等几类消息,根据解析出来的消息类型,调用service模块提供的方法进行处理即可。

这个模块没有啥业务逻辑,比较简单,也没啥可说的。代码从柳峰博客中所给出的示例代码移植过来,只是根据自己的需要做了一点点调整。

代码结构如下:


微信机器人怎么弄的 java 微信机器人编写_聊天机器人_04



2、主业务处理模块:处理解析后的消息。里面有几个子模块:

Ø  Context:对话上下文模块,类似于聊天记录。人在交流的时候会基于之前的对话而建立相应的语境,而不是孤立的一问一答。因此,我针对每个用户建立一个唯一的会话表,作为与该用户对话的上下文,这样机器人的回复便不再受限于用户本次所发送的消息,而是与用户对话的所有消息!

Ø  Alogrithm:分词算法(Word Segment Algorithm)是引用的新浪的分词算法库,通过外部API调用;自然语言处理算法(NLP Algorithm)里面实现了主要的机器人聊天逻辑,当前设计的还比较简陋。

Ø  Extensible Module:预留的扩展模块,以后可以加入地理位置、图片、音频等消息的处理。

这块是主要的业务逻辑,在后面博文中单独写。


3、DAO模块:数据访问对象(DataAccess Objects),隔离业务逻辑和数据库操作。


主要设计了两个数据结构:Context和Dialogue。

Context:与每个用户建立语境,记录聊天记录和聊天状态。

Dialogue:微信中都是一问一答的形式,因此设计了Dialogue即“对话”这个数据结构,用于保存每次对话的问题和答案。

当前系统数据库用到了4张表:

t_context: 保存用户对话上下文,记录用户最后一次的问题以及状态等。最初设计这些信息是用HashMap直接保存在内存中的,结果因为程序部署在分布式服务器上,不能这样用(这个问题后面会详细说),只好写到数据库中。百度还给了另一个解决方案,使用百度的cache服务,没仔细去研究。

t_tree:知识库通过树形结构来记录Dialogue中的问题和答案,该结构类似于B树,一个节点会有N个子节点,树的高度依赖于句子中词的个数。树的每个节点中可保存一个答案,其实可以做成每个节点保存一组答案,答案再设置优先级等等。当前这个设计比较简陋,不过实现起来最简单。

t_word:将问题分解成若干个词保存在该表中,与树中的节点一一对应。

t_answer:将每个问题的答案保存在该表中,与树中的节点是1对N的关系。

微信机器人怎么弄的 java 微信机器人编写_微信机器人怎么弄的 java_05

DAO模块的设计就是操作这些表,完成程序中的数据结构和数据库中的表记录的转换。分离业务逻辑和数据库操作。


4、外部API模块:通过url访问来获取使用第三方提供的API,比如天气查询、搜歌、分词等。第三方返回的结果基本都是xml或json格式的,所以会用到公共模块的xml和json解析方法。

Ø  HTTP Service用于封装url访问接口,与第三方交互。

Ø Word Segment Service:分词算法,使用的sina的api,http://5.tbip.sinaapp.com/api.php?str=[sentence]&type=[json]

Ø Music Service:使用的百度音乐盒,http://box.zhangmen.baidu.com/x?op=12&count=1&title=[song]$$[singer]$$$$

Ø Weather Service:使用的车联网的天气服务,http://api.map.baidu.com/telematics/v3/weather?location=[city]&output=[json]&ak=[api]

上面url中用“[]”框起来的都是需要填入的参数。


5、Database Manager:数据库管理模块,主要是数据库连接和操作接口。


6、公共接口:日志、公共库、xml和json解析。基本都是使用的公共api,如何使用会穿插在后面的代码实现中来讲。

这里主要想聊一聊日志功能,百度云平台为开发者提供了日志服务,能够分级别将日志输出到后台,java可以使用JDK Log或Log4J。我使用的是JDK Log,需要在WEB-INF下添加一个duapp-web.xml文件,配置如下:


<?xml version="1.0" encoding="UTF-8"?>
<du-web-app xmlns="http://bae.baidu.com/java/1.0">  
  <!-- 注意级别顺序应该从高往低 -->  
  <system-properties>  
    <property name="java.util.logging.level" value="SEVERE"/>  
    <property name="java.util.logging.level" value="WARNING"/> 
  </system-properties>  
</du-web-app>

我这里只输出了错误和警告级别的日志,配置DEBUG可以输出INFO级别的日志。代码中使用Logger来输出日志即可,百度开发文档中已经给出了例子:

importjava.util.logging.Logger;
importjava.util.logging.Level;
 
Logger logger = Logger. getLogger("name");
logger.log(Level.INFO, " this is for notice log print ");


日志这个功能应该要好好利用,方便版本的调试和维护。

以前在学校写代码,想想都觉得NB,代码写的非常奔放,没有日志、没有统计,出了问题直接debug或者gdb。

工作后才知道,原来还有日志和统计这种东西,版本上线后根本不可能断点调试,没有日志和统计只能等死,所以在正确的地方增加正确的统计和日志非常重要。

欢迎拍砖~