每次寫前言最費神,就是感興趣想研究研究,有了一點點成果但願分享交流,若是能幫助別人就很好,若是有人指導一下就更好了。此次是關於'微信機器人'的我的設計。java
如今的功能比較簡陋,僅實現了聊天機器人(基於圖靈機器人API) 和 定時提醒功能(能夠實現按時點/週期定製)。可是總體構架是設計出來了,進行擴展 應該 仍是挺方便的。放幾張圖做示例。mysql
登錄部署程序的端口,用我的微信掃碼登錄(注意:請先確認微信帳號是否能正常登錄wx.qq.com) 掃碼後,經過另外一個帳號發送 召喚智障機器人 激活。大體聊天狀況以下 由於功能還不穩定,因此就不掛我本身的程序了。若是穩定了再掛git
itchat4j 提供了 Wechat 主類做爲入口程序,須要注入一個實現IMsgHandlerFace接口的實現類做爲接收消息的回調。這方面我本身完成一個CentreMessageHandler,目前只處理text消息。github
@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須要實現我定義的一個接口處理程序web
/** * 處理器接口, 定義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的實現是這樣的spring
/** * 決定使用哪一個處理組件進行處理 * @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,執行順序sql
若是對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是執行方法express
/** * 場景信息接口 * @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中置入場景api
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
若是有更好的設計思路,但願能分享一下,Thanks!