Tiny微信框架是怎樣設計的?

     微信對國人而言,想必大名鼎鼎,活躍用戶數已經突破6.5億,足以說明這款應用的生命力。可是使用人數衆多,不表明微信的API設計優異,有過微信公衆號開發經驗的人,想必複雜的報文,衆多的服務API以及各類公衆號資源與權限設置搞得頭痛。其實Tiny框架設計理念之一就是簡化開發人員的工做,設計Tiny微信框架能夠必定程度上減小通常開發人員的難度。 java

     前段時間本人寫過一篇博文《微信框架的幾個層次》,提到了十個層級,介紹以前先說一下微信的消息通信機制,主要分爲被動推送和主動請求兩種模式 json

      1、被動推送模式。此時微信服務器是通信發起方,用戶服務器是通信接收方。 緩存

     

     這種模式下推送報文分兩類:消息和事件。如用戶在微信客戶端發送的文本消息、圖片消息在通信層面上就是消息報文;而事件報文通常用於處理異步響應,好比用戶點擊微信菜單觸發菜單事件等。 安全

     2、主動請求模式。此時用戶服務器是通信發起方,而微信服務器則是通信接收方。 服務器

    

      主動請求場景不少,微信開發平臺提供的大部分API都是這種模式,如自定義菜單、素材管理、支付等。而微信服務器與微信客戶端之間的數據更新有如下兩種方式: 微信


  1. 服務器主動推送信息。如微信的羣發消息接口,在用戶服務器觸發羣發消息後,由微信服務器往目標客戶端主動推送消息。
  2. 客戶端拉取信息。如自定義菜單的管理接口,在用戶服務器修改了自定義菜單的內容後,微信服務器並不會主動推送內容,而是由微信客戶端發現緩存過時後,自行向微信服務器請求更新。

     Tiny微信框架的核心接口如圖所示: session

 以上接口涵蓋了微信通信、報文轉換、消息接收和發送、上下文會話、業務處理等諸多方面,接口說明以下: 微信開發


接口
接口說明
WeiXinConnector 微信鏈接管理,管理接收消息和請求消息,同時保持微信的通信信息(驗證令牌和JS訪問票據等)
WeiXinContext 微信上下文環境,支持保存微信的用戶會話,也能夠記錄各個業務處理器的操做結果。
WeiXinConvert 微信消息/結果轉換統一接口,支持優先級排序
WeiXinHandler 微信業務處理器,支持按優先級排序。按類型能夠分爲發送和接收處理器。開發人員須要擴展該接口實現業務邏輯。
WeiXinManager 微信配置管理器,負責加載微信API接口相關參數,和渲染微信URL。
WeiXinReceiver 微信接收消息器,負責接收微信服務器推送過來的消息和事件,WeiXinConnector委託其接收消息。
WeiXinSender 微信發送消息器,負責發送消息和上傳文件到微信服務器,並處理響應,WeiXinConnector委託其發送消息。
WeiXinSession 微信用戶會話,目前以微信的openId作主鍵。
WeiXinSessionManager 微信會話管理器,負責新增、修改和清理微信用戶會話。


第一層次:通訊處理


微信的服務主要是基於HTTP協議,安全經過訪問令牌(access_token)保證;少數業務場景使用HTTPS加密協議,甚至涉及安全證書,例如微信商戶的支付接口。 架構

Tiny微信框架的通信處理由WeiXinConnector總調度,接口定義以下: app


public interface WeiXinConnector {

	/**
	 * 默認的bean配置名稱
	 */
	public static final String DEFAULT_BEAN_NAME="weiXinConnector";
	
	public static final String ACCESS_TOKEN="ACCESS_TOKEN";

	/**
	 * 獲取當前的管理號客戶端信息
	 * @return
	 */
	Client  getClient();
	
    /**
     * 得到微信消息發送者,負責往微信服務器發送消息
     * @return
     */
    WeiXinSender getWeiXinSender();
	
    /**
     * 得到微信消息接收者,負責解析微信服務器推送過來的消息
     * @return
     */
	WeiXinReceiver getWeiXinReceiver();
	
	/**
	 * 獲取微信的會話管理者
	 * @return
	 */
	WeiXinSessionManager  getWeiXinSessionManager();
	
	/**
	 * 獲取微信驗證令牌
	 * @return
	 */
	AccessToken getAccessToken();
	
	/**
	 * 得到微信的JS訪問票據
	 * @return
	 */
	JsApiTicket getJsApiTicket();
	
	/**
	 * 發送微信消息
	 * @param message
	 */
    void send(ToServerMessage message);
    
    /**
     * 上傳微信文件
     * @param upload
     */
    void upload(WeiXinHttpUpload upload);
	
	/**
	 * 接收微信消息
	 * @param request
	 * @param response
	 */
	void receive(HttpServletRequest request,HttpServletResponse response);
	
}


具體到底層的HTTP和HTTPS協議通信,org.tinygroup.weixinhttp工程提供了業務接口WeiXinHttpConnector實現具體的協議通信。


public interface WeiXinHttpConnector {

	/**
	 * 默認的bean配置名稱
	 */
	public static final String DEFAULT_BEAN_NAME="weiXinHttpConnector";
	
	 /**
     * 用get方式訪問微信URL
     *
     * @param url       要訪問的微信URL
     * @return 請求結果
     */
    String getUrl(String url);
    
    /**
     * 用post方式訪問微信URL
     *
     * @param url       要訪問的微信URL
     * @param content
     * @param cert
     * @return 請求結果
     */
    String postUrl(String url, String content,WeiXinCert cert);
    
    /**
     * 上傳文件
     * @param url
     * @param upload
     * @return
     */
    String upload(String url,WeiXinHttpUpload upload);
}
目前微信框架實現了HttpClient3.1和HttpClient4.5.1兩個版本的底層通信方案,對開發人員而言只須要配置不一樣的依賴,而無需關心具體通信代碼。



第二層次:報文解析


本人一直對微信的報文設計很有微詞,從總體上看微信報文缺少統一規範,XML、JSON格式混用,字段命名也不規範。Tiny微信提供WeiXinConvert接口負責報文與對象之間的轉換,目前XML報文經過Xsteam轉換,JSON報文經過fastjson轉換。接口定義以下:


public interface WeiXinConvert extends Comparable<WeiXinConvert> {
    
	/**
	 * 得到優先級
	 * @return
	 */
	int getPriority();

	/**
	 * 設置優先級
	 * @param priority
	 */
    void setPriority(int priority);
    
    /**
     * 得到報文的狀態
     * @return
     */
    WeiXinConvertMode getWeiXinConvertMode();
    
    /**
     * 得到結果類型
     * @return
     */
    Class<?> getCalssType();
    
	/**
	 * 判斷轉換接口可否處理輸入信息(微信報文會出現不一樣類型報文字段一致的狀況,須要根據上下文判斷)
	 * @param <INPUT>
	 * @param input
	 * @param context
	 * @return
	 */
    <INPUT> boolean isMatch(INPUT input,WeiXinContext context);
	
	/**
	 * 轉換消息(微信報文會出現不一樣類型報文字段一致的狀況,須要根據上下文判斷)
	 * @param input
	 * @return
	 */
    <OUTPUT,INPUT> OUTPUT convert(INPUT input,WeiXinContext context);
	
}


對開發人員而言,無需思考如何構建複雜的報文,只需依賴對應微信業務模塊的子工程,構建不一樣的消息對象,而底層的報文解析與轉換甚至通信都由tiny框架處理。


 

第三層次:報文模擬發送


微信發送報文調試最麻煩的地方就是訪問令牌(access_token),這個是根據用戶應用動態生成的,並且只保持兩個小時有效。Tiny微信框架提供了模擬測試頁面,只須要bean配置頁面設置相關appId和APP祕鑰等參數,開發人員在頁面就無需手動輸入訪問令牌。測試頁面以下:



第四層次:報文模擬接收


接收報文一般是用來模擬手機端的發送消息,特別是一些複雜交互場景:如命令行菜單,若是每次都經過手機端調試。效率很是低。而經過本測試頁面,直接輸入模擬的手機報文直接就能夠獲得報文結果,準確而且快速。模擬頁面如圖:

第五層次:報文處理框架


前面在介紹微信核心接口時提到過WeiXinReceiver和WeiXinSender,分別處理微信推送消息與主動發送消息。可是用戶的業務是複雜多變的,Tiny是如何保證微信框架的可擴展性呢?其實WeiXinReceiver和WeiXinSender是由一組有序WeiXinHandler組成,而每個WeiXinHandler均可以處理一類消息,接口定義以下:

public interface WeiXinHandler extends Comparable<WeiXinHandler> {
    
    int getPriority();

    void setPriority(int priority);
    
    WeiXinHandlerMode getWeiXinHandlerMode();
    
    /**
     * 是否匹配對象和上下文
     * @param <T>
     * @param message
     * @return
     */
    <T> boolean isMatch(T message,WeiXinContext context);

    
    /**
     * 處理對象
     * @param <T>
     * @param message
     * @param context
     */
    <T> void process(T message,WeiXinContext context);
}

簡單舉個例子,好比開發一個圖片消息處理器ImageMessageHandler,用來處理微信客戶端的圖片類消息,代碼以下:

public class ImageMessageHandler extends AbstractWeiXinHandler{
    public WeiXinHandlerMode getWeiXinHandlerMode() {
        return WeiXinHandlerMode.RECEIVE;
    }
    public <T> boolean isMatch(T message, WeiXinContext context) {
        return message instanceof ImageMessage;
    }
    //具體業務處理
    public <T> void process(T message, WeiXinContext context)  {
        ImageMessage mess = (ImageMessage) message;
         
        //邏輯處理
        TextReplyMessage replyMessage= new TextReplyMessage();
        replyMessage.setContent("回覆圖片消息["+mess.getPicUrl()+"]");
        replyMessage.setToUserName(mess.getFromUserName());
        replyMessage.setFromUserName(mess.getToUserName());
        replyMessage.setCreateTime((int)(System.currentTimeMillis()/1000));
         
        context.setOutput(replyMessage);
    }
}



用戶主要是編寫isMatch和process這兩個函數,前者決定這個業務類能處理哪些微信消息和事件,後者是真正的業務處理類。微信消息的包裝和轉換由微信框架提供,用戶應該關心業務處理邏輯,原則上一個Handler只建議處理一類消息。編寫完畢後,須要將Handler配置成bean文件,微信框架就能調用了。

ImageMessageHandler的做用是接收微信客戶端發送的圖片類消息,並返回圖片地址給用戶,效果以下:


第六層次:上下文保持

微信是有上下文概念的,好比微信應用的小遊戲:猜數字。用戶輸入一個數字,而服務器告訴用戶比目標值偏大仍是偏小,直到用戶猜中爲止。遊戲很簡單,可是這就涉及到上下文會話,Tiny微信框架提供WeiXinSession接口做爲上下文統一接口,而WeiXinSessionManager做爲上下文的管理接口存在。


WeiXinSession接口定義以下:

public interface WeiXinSession extends Serializable{

	/**
	 * 會話Id
	 * @return
	 */
	String getSessionId();
	
	/**
	 * 是否包含某元素
	 * @param name
	 * @return
	 */
	boolean contains(String name);
	
	/**
	 * 返回指定name的序列化對象
	 * @param <T>
	 * @param name
	 * @return
	 */
	<T extends Serializable> T getParameter(String name);
	
	/**
	 * 設置序列化的參數對象
	 * @param <T>
	 * @param name
	 * @param value
	 */
	<T extends Serializable> void setParameter(String name,T value);
	
	 /**
     * 取得session的建立時間。
     *
     * @return 建立時間戮
     */
    long getCreationTime();

    /**
     * 取得最近訪問時間。
     *
     * @return 最近訪問時間戮
     */
    long getLastAccessedTime();

    /**
     * 取得session的最大不活動期限,超過此時間,session就會失效。
     *
     * @return 不活動期限的秒數,0表示永不過時
     */
    int getMaxInactiveInterval();
    
    /**
     * 設置session的最大不活動期限,單位秒
     * @param maxInactiveInterval
     */
    void setMaxInactiveInterval(int maxInactiveInterval);
	
	/**
     * 判斷session有沒有過時。
     *
     * @return 若是過時了,則返回<code>true</code>
     */
    boolean isExpired();
    
    /**
     * 更新session
     */
    void update();
}



WeiXinSessionManager管理接口主要提供建立、刪除、查詢上下文會話的操做API,接口定義以下:


public interface WeiXinSessionManager {
	
	/**
	 * 默認的bean配置名稱
	 */
	public static final String DEFAULT_BEAN_NAME="weiXinSessionManager";
	
	/**
	 * 建立會話
	 * @param sessionId
	 * @return
	 */
	WeiXinSession createWeiXinSession(String sessionId);
	/**
	 * 查詢會話
	 * @param sessionId
	 * @return
	 */
	WeiXinSession getWeiXinSession(String sessionId);
	
	/**
	 * 添加會話
	 * @param session
	 */
	void addWeiXinSession(WeiXinSession session);
	
	/**
	 * 手動刪除會話
	 * @param sessionId
	 * @return
	 */
	void removeWeiXinSession(String sessionId);
	
	/**
	 * 遍歷會話
	 * @return
	 */
	WeiXinSession[] getWeiXinSessions();
	
	/**
	 * 清理會話過時的Session
	 */
	void expireWeiXinSessions();
	
	/**
	 * 清理所有Session
	 */
	void clear();
	
	/**
	 * Session最大過時時間設置,單位s,默認0
	 * @return
	 */
	int getMaxInactiveInterval();
	
	/**
	 * Session清理線程首次延遲時間,單位s,默認值60
	 * @return
	 */
	int getExpireTimerDelay();
	
	/**
	 * Session清理線程運行週期,單位s,默認值300
	 * @return
	 */
	int getExpireTimePeriod();
}



固然Tiny微信框架提供了上下文相關接口,不表明每一類消息強制進行會話管理,好比簡單文本消息,微信客戶端的位置消息,徹底能夠請求-響應這種模式進行處理。


第七層次:處理的水平擴展能力

前文講過Tiny微信框架的具體業務是由WeiXinHandler接口完成的,而WeiXinHandler接口是不依賴WeiXinConnector等委託對象,所以經過擴展WeiXinHandler接口徹底能夠實現處理能力的水平擴展,好比Tiny框架自己有服務中心,支持分佈式服務,那麼咱們能夠在WeiXinHandler接口包裝服務中心,從而實現分佈式服務。

第八層次:命令處理框架

實際上一個微信公衆號,許多的時候都是經過使用者用文字(語音識別也歸到用文字)的方式與平臺進行交互,這個時候,其實就是一個命令行的處理。Tiny微信框架經過org.tinygroup.menucommand實現相關需求,開發人員只須要配置XML就可完成複雜的命令行處理。目前支持兩種模式:

  1. 簡單模式。用戶不須要動態數據,僅須要配置便可。相似電話黃頁,支持逐級遞歸,針對這種模式,Tiny支持配置方式,用戶無需編碼。
  2. 動態模式。用戶除了靜態數據,還涉及動態交互,沒法經過配置解決。Tiny框架提供了MenuCommandHandler接口,而後在配置中指定具體類或bean,框架就能解決。


配置文件是以menuconfig.xml爲結尾,以演示工程的command.menuconfig.xml爲例:


<!-- 菜單命令節點支持多個菜單配置節點和系統命令節點 -->
<menu-configs>
    <!-- 菜單配置節點能夠嵌套,支持定義子菜單和菜單命令節點 -->
    <menu-config id="m001" name="menu" title="功能目錄" >
        <regex><![CDATA[m|menu|菜單]]></regex>
        <description><![CDATA[微信服務列表]]></description>
        <menu-config id="g001" name="guess" title="數字競猜" path="/game/guessNumber.page">
            <regex><![CDATA[guess|猜數字]]></regex>
            <description><![CDATA[猜數字小遊戲,輸入guess或者猜數字]]></description>
            <menu-command name="new" title="新建遊戲" event-type="enter"
                class-name="org.tinygroup.weixinservice.commandhandler.NewGuessGameHandler">
                <regex><![CDATA[new|新遊戲]]></regex>
                <description><![CDATA[輸入「新遊戲」或者「new」,從新開始猜數字]]></description>
            </menu-command>
            <menu-command name="input" title="輸入數值"
                class-name="org.tinygroup.weixinservice.commandhandler.GuessNumberHandler"
                path="/game/guessNumberResult.page">
                <regex><![CDATA[^[1-9]\d*$]]></regex>
                <description><![CDATA[請輸入1-50之間的整數]]></description>
            </menu-command>
            <menu-command name="del" title="清理用戶數據" event-type="exit"
                class-name="org.tinygroup.weixinservice.commandhandler.DelGuessNumberSessionHandler"
                path="/menucommand/showMenuConfig.page">
                <regex><![CDATA[del|delete]]></regex>
                <description><![CDATA[輸入del或者delete]]></description>
            </menu-command>
        </menu-config>
        <menu-config id="g002" name="robot" title="機器人" >
            <regex><![CDATA[robot]]></regex>
            <description><![CDATA[輸入robot]]></description>
            <menu-command name="input" title="問答環節" event-type="enter"
                system-enable="false" bean-name="askRobotHandler" path="/game/answer.page">
                <regex><![CDATA[[\u4e00-\u9fa5_a-zA-Z0-9]+$]]></regex>
                <description><![CDATA[向智能機器人進行提問]]></description>
            </menu-command>
        </menu-config>
        <menu-config id="g003" name="time" title="時間轉換">
            <menu-command name="1" title="顯示中式時間"
                class-name="org.tinygroup.weixinservice.commandhandler.TimeHandler"
                path="/game/chineseTime.page">
                <regex><![CDATA[1]]></regex>
                <description><![CDATA[輸入1展現中式時間]]></description>
            </menu-command>
            <menu-command name="2" title="顯示英式時間"
                class-name="org.tinygroup.weixinservice.commandhandler.TimeHandler"
                path="/game/englishTime.page">
                <regex><![CDATA[2]]></regex>
                <description><![CDATA[輸入2展現英式時間]]></description>
            </menu-command>
            <regex><![CDATA[time]]></regex>
            <description><![CDATA[展現中式和英式的系統時間]]></description>
        </menu-config>
    </menu-config>
    <!-- 系統命令節點 -->
    <system-command name="root" title="返回根菜單" bean-name="homeCommandHandler"
        path="/menucommand/showMenuConfig.page">
        <regex><![CDATA[root]]></regex>
        <description>輸入root返回菜單的最上級</description>
    </system-command>
    <system-command name="up" title="回到上一級" bean-name="backCommandHandler"
        path="/menucommand/showMenuConfig.page">
        <regex><![CDATA[up]]></regex>
        <description>輸入up,回到當前菜單的上一級</description>
    </system-command>
    <system-command name="list" title="列出子列表" bean-name="queryCommandHandler"
        path="/menucommand/query.page">
        <regex><![CDATA[list|list\s+[\u4e00-\u9fa5_a-zA-Z0-9]+$]]></regex>
        <description>列出系統命令和當前菜單的列表,支持「list 關鍵字」的方式</description>
    </system-command>
    <system-command name="help" title="顯示詳情" bean-name="helpCommandHandler"
        path="/menucommand/help.page">
        <regex><![CDATA[help|help\s+[\u4e00-\u9fa5_a-zA-Z0-9]+$]]></regex>
        <description>列出命令詳情</description>
    </system-command>
    <system-command name="exit" title="退出菜單" bean-name="exitCommandHandler"
        path="/menucommand/exit.page">
        <regex><![CDATA[exit]]></regex>
        <description>輸入exit退出菜單</description>
    </system-command>
</menu-configs>

      menu-configs是總結點,它包含兩類子節點:菜單節點menu-config和系統命令節點system-command。菜單節點支持樹結構,也就是能夠自包含,菜單節點能夠包含菜單命令節點menu-command,僅在當前菜單有效。系統命令不支持嵌套,並且只在menu-configs下面,它是全局有效的。最終效果以下:

    


第九層次:模板語言的引入

咱們都知道,在作業務開發時,確定都不但願把文本信息都放在程序代碼中,這個時候就能夠引入模板語言來把數據和展現來進行一個分割,作業務的只管作業務,作顯示的只管作顯示,井水河水兩不犯。好比命令菜單,不管是顯示信息仍是處理結果都是配置在XML或者模板文件裏,而非硬編碼在java類。

第十層次:模塊化

這也是Tiny微信框架的特點之一。其實在本人設計微信架構以前,也閱讀過很多微信框架的源代碼,它們絕大多數都是定義一個大接口,跟微信服務器API基本上是一一對應的關係,這樣開發人員在使用時要麼引入全部資源,要麼不用。而Tiny微信框架是根據微信公衆號API分類,每一類API接口實現一類工程,這樣用戶在開發時用多少資源就引入多少工程,好比個人公衆號應用是視頻類,可能用到素材管理接口和消息類接口,可是和微信商戶無關,那開發人員在建立工程時只須要引入前二者便可。

好了,關於Tiny微信框架大體介紹如此,若是有開發人員對本框架感興趣,想作一些擴展開發,能夠聯繫本人。

若是您對個人博客感興趣,請點擊左上角的關注,以便及時收到個人相關通知。

相關文章
相關標籤/搜索