我的微信'智障聊天助手'的设计思路


声明:本文转载自https://my.oschina.net/u/3491123/blog/1807004,转载目的在于传递更多信息,仅供学习交流之用。如有侵权行为,请联系我,我会及时删除。

前言

每次写前言最费神,就是感兴趣想研究研究,有了一点点成果希望分享交流,如果能帮助别人就很好,如果有人指导一下就更好了。这次是关于'微信机器人'的个人设计。

功能简介

现在的功能比较简陋,仅实现了聊天机器人(基于图灵机器人API) 和 定时提醒功能(可以实现按时点/周期定制)。但是整体构架是设计出来了,进行扩展 应该 还是挺方便的。放几张图作示例。

登陆部署程序的端口,用个人微信扫码登陆(注意:请先确认微信账号是否能正常登陆wx.qq.com) 登陆图片 扫码图片 扫码后,通过另一个账号发送 召唤智障机器人 激活。大致聊天情况如下 示例 示例 示例 因为功能还不稳定,所以就不挂我自己的程序了。如果稳定了再挂

依赖介绍

  • JDK8
  • Springboot2,最近使用Springboot习惯,写起来顺手,同时希望能部署在服务器上长期运行,所以设计成web服务管理。
  • itchat4j 开源的基于微信web协议开发的个人微信号扩展接口的java实现。免除了自己去研究微信web,感谢。为了符合项目需求,做了一点微小的改动,jar包在源码的libs文件夹下
  • mysql,实现一些数据持久化功能。主要原因在于微信web是有登陆时限的,同时升级程序重启时希望能恢复定时任务、保留用户关联信息等。
  • 图灵机器人API 需要自己申请一个apikey。免费版聊天真的很智障( ╯╰ )

整体设计思路

itchat4j 提供了 Wechat 主类作为入口程序,需要注入一个实现IMsgHandlerFace接口的实现类作为接收消息的回调。这方面我自己完成一个CentreMessageHandler,目前只处理text消息。

    @Override     public String textMsgHandle(BaseMsg baseMsg) {         // processorManager处理器的管理类         BaseProcessor filter = processorManager.decision(baseMsg);         if (filter != null && filter instanceof TextProcessor) {             TextProcessor processor = (TextProcessor)filter;             try {                 return processor.answer(baseMsg);             }catch (AnswerException e) {                 logger.error("An answerException happened", e);                 return  "我的爸爸写了个bug,可能是缺少女朋友导致,要关心一下吗";             }         }         return null;     } 

我希望程序能更智能,针对不同的输入响应不同的内容,而逻辑不可能都写在一个方法里。所以我学习了一下SpringSecury的Filter链设计,设计了一个Processor链。Processor需要实现我定义的一个接口处理程序

/**  * 处理器接口, 定义decide用于指示处理器是否响应消息  *  * 提供默认的process方法, 用于提供在 {@link Decision} 响应PROCESS时处理消息  * @author BekeyChao@github.com  */ public interface BaseProcessor {     Logger logger = LoggerFactory.getLogger(BaseProcessor.class);      /**      * 是否处理消息      * @param message      * @return {@link Decision} 按需返回你的决定      */     Decision decide(BaseMsg message);       /**      * 处理消息,但不会打断消息的传播      * @param message      */     default void process(BaseMsg message) {         if ( logger.isDebugEnabled() ) {             logger.debug("你请求处理了一个消息, 但是却没有实现 class = " + this.getClass().getName());         }         // ignore     }       enum Decision {         /**          * 处理并结束流程          */         ACCEPT,         /**          * 调用process方法处理消息但不结束流程          */         PROCESS,         /**          * 不处理但不结束流程          */         PASS,         /**          * 拒绝并结束流程          */         DENY;     } } 

这个接口核心在于decide方法,decide的返回值决定了消息的处理逻辑,详见enum Decision。BaseProcessor 在ProcessorManager中其实就是一段ArrayList,按预定义好的Processor顺序决定哪个Processor可以获得处理权限,manager的实现是这样的

    /**      * 决定使用哪个处理组件进行处理      * @param message      * @return null 代表不响应      */     public BaseProcessor decision(BaseMsg message) {         for (BaseProcessor filter: processors) {             switch (filter.decide(message)) {                 case ACCEPT:                     return filter;                 case PROCESS:                     filter.process(message);                     continue;                 case DENY:                     return null;                 default:                     // pass             }         }         return null;     } 

我一共设计了5个Processor,执行顺序

  • CommandTextProcessor 强指令响应器, 用于响应固定系统基本指令, 如开启机器人, 关闭机器人等
  • UserFilter 服务响应过滤器, 可以用于指定服务于特定用户
  • ContextService 上下文管理响应器,用于响应连续的对话内容
  • ScheduleProcessor 行程提醒处理器 响应定制提醒及提醒取消
  • TuringTextProcessor 图灵机器人响应器, 调用图灵机器人接口与用户聊天响应 manager中开放了管理Processor的接口,所以可以实现自己的处理逻辑,进行扩展。

如果对Processor具体逻辑感兴趣可以到源码中看,这里我主要介绍一下ContextService 上下文响应处理器。因为我希望程序聊天是可以有情景的,机器人可以响应一段连续的对话,所以额外设计了一个SceneContext的概念。ContextService 的decide方法就是检查用户是否有场景值,场景值是由其他处理器创建的(在本例中,都是由ScheduleProcessor创建的)。SceneContextHolder用于储存场景值。

    @Override     public Decision decide(BaseMsg message) {         // 用户场景值有消息         if ( SceneContextHolder.getArgumentsByUserId(message.getFromUserName()) != null) {             return Decision.ACCEPT;         }         return Decision.PASS;     } 

场景是一段预定义的响应,act是执行方法

/**  * 场景信息接口  * @author BekeyChao@github.com  */ public interface BaseSceneContext {     // 定义场景Id,保证程序级别的唯一     String sceneId();      String act(String userId, BaseMsg message);      /**      * 定义是否在响应后自动移除场景值,默认true      * @return true 自动移除      */     default boolean isRemovedAfterResponse() {         return true;     }      /**      * 定义会话过期时间 毫秒值 未实现      * @return -1 永不过期      */     long express(); } 

那么我如何知道用户的场景呢?这是一段约定,在ScheduleProcessor行程提醒处理器中,用户消息达成了它的要求时,它会根据消息往SceneContextHolder中置入场景

BaseSceneContext context = SceneContextHolder.getSceneBySceneId('定义的场景Id'); // Arguments是场景参数,约定args[0] 存储场景 SceneContextHolder.setArgumentsByUserId(message.getFromUserName(), new Object[]{ context }); 

这样ContextService就可以通过用户Id拿到场景并执行。 在ChatrobotConfig中会扫描bean,将实现BaseSceneContext的实现类都放到SceneContextHolder中,所以在程序中想新增场景就直接实现BaseSceneContext接口就可以了。

嗯,核心想分享的思路就到这里了,其他的具体实现可以看看源码。我觉得我的程序设计比较繁琐,但是保留了比较好的扩展性,因为也是自己第一次做比较完整的程序设计,所以想分享一下。如果由大佬恰巧看到,愿意指导一下也是极好的。

整个程序目前只能算半成品,修修补补的地方很多。我本想做的差不多再分享一下,不过感觉任重道远,所以提前献丑。

源码传送门

chatrobot

git 关联时少同步了application.properties,主要保存了我私有的key,需要补充完整才能启动

spring.datasource.url =  spring.datasource.username =  spring.datasource.password =           chatrobot.turing.key = 在图灵API申请的key 

存在的已知问题

  • 程序重启时应从数据库中加载任务,暂时没有实现
  • 用户实体关联还没有建立
  • 收到消息时,都会收到一个fromUserId的属性,程序主要靠这个实现指定收发消息。但根据测试,这个id对用户而言并不是固定的!就是每次重新登陆后好友的UserId会变,需要设计一个方法实现唯一用户关联(nickname可能重复)
  • 服务器部署在外网可能会有异常退出。我第一次对外测试的时候部署在aws上,但是半个多小时后莫名挂了。

Futrue

  • 实现定时任务在启动时加载。
  • 将用户也放入Mysql中进行管理。
  • 其他想到什么写什么

希望多多交流

如果有更好的设计思路,希望能分享一下,Thanks!

本文发表于2018年05月05日 19:39
(c)注:本文转载自https://my.oschina.net/u/3491123/blog/1807004,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。如有侵权行为,请联系我们,我们会及时删除.

阅读 1782 讨论 0 喜欢 0

抢先体验

扫码体验
趣味小程序
文字表情生成器

闪念胶囊

你要过得好哇,这样我才能恨你啊,你要是过得不好,我都不知道该恨你还是拥抱你啊。

直抵黄龙府,与诸君痛饮尔。

那时陪伴我的人啊,你们如今在何方。

不出意外的话,我们再也不会见了,祝你前程似锦。

这世界真好,吃野东西也要留出这条命来看看

快捷链接
网站地图
提交友链
Copyright © 2016 - 2021 Cion.
All Rights Reserved.
京ICP备2021004668号-1