個人微信'智障聊天助手'的設計思路

前言

每次寫前言最費神,就是感興趣想研究研究,有了一點點成果但願分享交流,若是能幫助別人就很好,若是有人指導一下就更好了。此次是關於'微信機器人'的我的設計。java

功能簡介

如今的功能比較簡陋,僅實現了聊天機器人(基於圖靈機器人API) 和 定時提醒功能(能夠實現按時點/週期定製)。可是總體構架是設計出來了,進行擴展 應該 仍是挺方便的。放幾張圖做示例。mysql

登錄部署程序的端口,用我的微信掃碼登錄(注意:請先確認微信帳號是否能正常登錄wx.qq.com) 登錄圖片 掃碼圖片 掃碼後,經過另外一個帳號發送 召喚智障機器人 激活。大體聊天狀況以下 示例 示例 示例 由於功能還不穩定,因此就不掛我本身的程序了。若是穩定了再掛git

依賴介紹

  • JDK8
  • Springboot2,最近使用Springboot習慣,寫起來順手,同時但願能部署在服務器上長期運行,因此設計成web服務管理。
  • itchat4j 開源的基於微信web協議開發的我的微信號擴展接口的java實現。免除了本身去研究微信web,感謝。爲了符合項目需求,作了一點微小的改動,jar包在源碼的libs文件夾下
  • mysql,實現一些數據持久化功能。主要緣由在於微信web是有登錄時限的,同時升級程序重啓時但願能恢復定時任務、保留用戶關聯信息等。
  • 圖靈機器人API 須要本身申請一個apikey。免費版聊天真的很智障( ╯╰ )

總體設計思路

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

  • 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是執行方法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接口就能夠了。

嗯,核心想分享的思路就到這裏了,其餘的具體實現能夠看看源碼。我以爲個人程序設計比較繁瑣,可是保留了比較好的擴展性,由於也是本身第一次作比較完整的程序設計,因此想分享一下。若是由大佬恰巧看到,願意指導一下也是極好的。

整個程序目前只能算半成品,修修補補的地方不少。我本想作的差很少再分享一下,不過感受任重道遠,因此提早獻醜。

源碼傳送門

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!

相關文章
相關標籤/搜索