前言
每次写前言最费神,就是感兴趣想研究研究,有了一点点成果希望分享交流,如果能帮助别人就很好,如果有人指导一下就更好了。这次是关于'微信机器人'的个人设计。
功能简介
现在的功能比较简陋,仅实现了聊天机器人(基于图灵机器人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接口就可以了。
嗯,核心想分享的思路就到这里了,其他的具体实现可以看看源码。我觉得我的程序设计比较繁琐,但是保留了比较好的扩展性,因为也是自己第一次做比较完整的程序设计,所以想分享一下。如果由大佬恰巧看到,愿意指导一下也是极好的。
整个程序目前只能算半成品,修修补补的地方很多。我本想做的差不多再分享一下,不过感觉任重道远,所以提前献丑。
源码传送门
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!
共同学习,写下你的评论
评论加载中...
作者其他优质文章