Senparc.Weixin.MP SDK 微信公衆平臺開發教程(七):解決用戶上下文(Session)問題

    從這篇文章中咱們已經瞭解了微信公衆平臺消息傳遞的方式,這種方式有一個先天的缺陷:不一樣用戶的請求都來自同一個微信服務器,這使得常規的Session沒法使用(始終面對同一個請求對象,何況還有對方服務器Cookie是否能保存的問題)。html

    這就要求咱們本身創建一套獨立的對話上下文請求機制。git

    上一篇《Senparc.Weixin.MP SDK 微信公衆平臺開發教程(六):瞭解MessageHandler》中咱們瞭解到,Senparc.Weixin.MP SDK提供了一套很是便捷的消息處理機制,這套機制上面,咱們有增長了一個叫MessageContext的上下文處理機制,下面咱們就來講一說這個功能的使用(參考開源項目Wiki中的《用戶上下文WeixinContext和MessageContext》一文)。github

     更多實際應用的代碼能夠參考開源項目中的Demo:https://github.com/JeffreySu/WeiXinMPSDK,此外您也能夠關注下方的微信二維碼,不斷髮送文字及各類信息進行測試(發送文字的時候才能看到完整結果,期間能夠發送其餘任意類型)。web

Senparc.Weixin.MP SDK官方測試微信

 


 

概述

因爲微信公衆平臺的特殊機制,全部的信息都由微信服務器轉發而來,所以服務器是沒法使用Session對用戶會話的上下文進行管理的。算法

爲此Senparc.WeiXin.MP SDK增長了上下文的模塊,並集成到了MessageHandler中。小程序

WeixinContext

WeixinContext是全部單個用戶上下文(MessageContext)實體的容器(暫且稱爲全局上下文)。WeixinContext自己不是靜態類,意味着您能夠在同一個應用中建立多個上下文實體。緩存

同時,一個靜態的WeixinContext實例被放入到MessageHandler<TM>中,所以全部項目中由MessageHandler<TM>派生的子類中的WeixinContext是惟一的、全局的(注:TM爲實現IMessageContext的類,包括SDK中已經提供的MessageContext)。服務器

所以咱們在任何一個實現了MessageHandler<TM>的實例中(好比叫MyMessageHandler),均可以訪問到一個類型和名稱都叫WeixinContext的對象。微信

WeixinContext用於保存所用戶的上下文(MessageContext),而且提供了一系列的方法,主要方法包括:websocket

/// <summary>
      /// 重置全部上下文參數,全部記錄將被清空
      /// </summary>
      public void Restore()
      {
          ...
      }

      /// <summary>
      /// 獲取MessageContext,若是不存在,返回null
      /// 這個方法的更重要意義在於操做TM隊列,及時移除過時信息,並將最新活動的對象移到尾部
      /// </summary>
      /// <param name="userName">用戶名(OpenId)</param>
      /// <returns></returns>
      private TM GetMessageContext(string userName)
      {
          ...
      }

      /// <summary>
      /// 獲取MessageContext
      /// </summary>
      /// <param name="userName">用戶名(OpenId)</param>
      /// <param name="createIfNotExists">True:若是用戶不存在,則建立一個實例,並返回這個最新的實例
      /// False:用戶儲存在,則返回null</param>
      /// <returns></returns>
      private TM GetMessageContext(string userName, bool createIfNotExists)
      {
          ...
      }


      /// <summary>
      /// 獲取MessageContext,若是不存在,使用requestMessage信息初始化一個,並返回原始實例
      /// </summary>
      /// <returns></returns>
      public TM GetMessageContext(IRequestMessageBase requestMessage)
      {
          ...
      }

      /// <summary>
      /// 獲取MessageContext,若是不存在,使用requestMessage信息初始化一個,並返回原始實例
      /// </summary>
      /// <returns></returns>
      public TM GetMessageContext(IResponseMessageBase responseMessage)
      {
          ...
      }

      /// <summary>
      /// 記錄請求信息
      /// </summary>
      /// <param name="requestMessage">請求信息</param>
      public void InsertMessage(IRequestMessageBase requestMessage)
      {
          ...
      }

      /// <summary>
      /// 記錄響應信息
      /// </summary>
      /// <param name="responseMessage">響應信息</param>
      public void InsertMessage(IResponseMessageBase responseMessage)
      {
          ...
      }

      /// <summary>
      /// 獲取最新一條請求數據,若是不存在,則返回Null
      /// </summary>
      /// <param name="userName">用戶名(OpenId)</param>
      /// <returns></returns>
      public IRequestMessageBase GetLastRequestMessage(string userName)
      {
          ...
      }

      /// <summary>
      /// 獲取最新一條響應數據,若是不存在,則返回Null
      /// </summary>
      /// <param name="userName">用戶名(OpenId)</param>
      /// <returns></returns>
      public IResponseMessageBase GetLastResponseMessage(string userName)
      {
          ...
      }

WeixinContext中有兩個用於儲存用戶上下文的對象:MessageCollection及MessageQueue。

這兩個對象中的元素集合是重合的,可是MessageQueue對元素進行了排序,以便及時處理掉頂部過時的上下文。

ExpireMinutes用於定義上下文時間有效期,默認爲90分鐘。能夠在程序的任何地方設置設個參數,且當即生效。

PS:MessageQueue中刪除過時數據的邏輯以極高的效率運做,常規開發時無需考慮CPU佔用及對象衝突的問題(額外校驗時間是否超時)。

MessageContext

MessageContext用於保存單個用戶的上下文信息,被儲存在WeixinContext的MessageCollection及MessageQueue對象中。 IMessageContext定義以下:

    /// <summary>
    /// 微信消息上下文(單個用戶)接口
    /// </summary>
    /// <typeparam name="TRequest">請求消息類型</typeparam>
    /// <typeparam name="TResponse">響應消息類型</typeparam>
    public interface IMessageContext<TRequest,TResponse>
        where TRequest : IRequestMessageBase
        where TResponse : IResponseMessageBase
    {
        /// <summary>
        /// 用戶名(OpenID)
        /// </summary>
        string UserName { get; set; }
        /// <summary>
        /// 最後一次活動時間(用戶主動發送Resquest請求的時間)
        /// </summary>
        DateTime LastActiveTime { get; set; }
        /// <summary>
        /// 接收消息記錄
        /// </summary>
        MessageContainer<TRequest> RequestMessages { get; set; }
        /// <summary>
        /// 響應消息記錄
        /// </summary>
        MessageContainer<TResponse> ResponseMessages { get; set; }
        /// <summary>
        /// 最大儲存容量(分別針對RequestMessages和ResponseMessages)
        /// </summary>
        int MaxRecordCount { get; set; }
        /// <summary>
        /// 臨時儲存數據,如用戶狀態等,出於保持.net 3.5版本,這裏暫不使用dynamic
        /// </summary>
        object StorageData { get; set; }

        /// <summary>
        /// 用於覆蓋WeixinContext所設置的默認過時時間
        /// </summary>
        Double? ExpireMinutes { get; set; }

        /// <summary>
        /// AppStore狀態,系統屬性,請勿操做
        /// </summary>
        AppStoreState AppStoreState { get; set; }

        event EventHandler<WeixinContextRemovedEventArgs<TRequest, TResponse>> MessageContextRemoved;

        void OnRemoved();
    }

  您能夠根據本身的須要建立本身的類,實現這個接口,而且被WeixinContext使用。固然若是你的要求不是那麼特殊,並且你比較懶的話,SDK提供了一個默認的MessageContext實現:

    /// <summary>
    /// 微信消息上下文(單個用戶)
    /// </summary>
    public class MessageContext<TRequest,TResponse>: IMessageContext<TRequest, TResponse>
        where TRequest : IRequestMessageBase
        where TResponse : IResponseMessageBase
    {
        private int _maxRecordCount;

        public string UserName { get; set; }
        public DateTime LastActiveTime { get; set; }
        public MessageContainer<TRequest> RequestMessages { get; set; }
        public MessageContainer<TResponse> ResponseMessages { get; set; }
        public int MaxRecordCount
        {
            get
            {
                return _maxRecordCount;
            }
            set
            {
                RequestMessages.MaxRecordCount = value;
                ResponseMessages.MaxRecordCount = value;

                _maxRecordCount = value;
            }
        }
        public object StorageData { get; set; }

        public Double? ExpireMinutes { get; set; }

        /// <summary>
        /// AppStore狀態,系統屬性,請勿操做
        /// </summary>
        public AppStoreState AppStoreState { get; set; }

        public virtual event EventHandler<WeixinContextRemovedEventArgs<TRequest, TResponse>> MessageContextRemoved = null;

        /// <summary>
        /// 執行上下文被移除的事件
        /// 注意:此事件不是實時觸發的,而是等過時後任意一我的發過來的下一條消息執行以前觸發。
        /// </summary>
        /// <param name="e"></param>
        private void OnMessageContextRemoved(WeixinContextRemovedEventArgs<TRequest, TResponse> e)
        {
            EventHandler<WeixinContextRemovedEventArgs<TRequest, TResponse>> temp = MessageContextRemoved;

            if (temp != null)
            {
                temp(this, e);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="maxRecordCount">maxRecordCount若是小於等於0,則不限制</param>
        public MessageContext(/*MessageContainer<IRequestMessageBase> requestMessageContainer,
            MessageContainer<IResponseMessageBase> responseMessageContainer*/)
        {
            /*
             * 注意:即便使用其餘類實現IMessageContext,
             * 也務必在這裏進行下面的初始化,尤爲是設置當前時間,
             * 這個時間關係到及時從緩存中移除過時的消息,節約內存使用
             */

            RequestMessages = new MessageContainer<TRequest>(MaxRecordCount);
            ResponseMessages = new MessageContainer<TResponse>(MaxRecordCount);
            LastActiveTime = DateTime.Now;
        }

        public virtual void OnRemoved()
        {
            var onRemovedArg = new WeixinContextRemovedEventArgs<TRequest, TResponse>(this);
            OnMessageContextRemoved(onRemovedArg);
        }
    }

  上面的代碼根據註釋很好理解,須要說明一下的是StorageData。這是一個用於儲存任何和用戶上下文有關數據的容器,WeixinContext和IMessageContext沒有對它進行任何引用,徹底由開發者決定裏面的內容(好比用戶執行到哪一步、或某個比較重要的位置信息等等),相似於Session的做用。

    上述MessageContext<TRequest,TResponse>類已經提供了比較完備的經常使用的消息處理的方法,能夠直接使用。若是有更加特殊的需求,須要自定義MessageContext,推薦以這個類爲基類進行必要的重寫,例以下面就是一個自定義的上下文類:

    public class CustomMessageContext : MessageContext<IRequestMessageBase,IResponseMessageBase>
    {
        public CustomMessageContext()
        {
            base.MessageContextRemoved += CustomMessageContext_MessageContextRemoved;
        }

        /// <summary>
        /// 當上下文過時,被移除時觸發的時間
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void CustomMessageContext_MessageContextRemoved(object sender, Senparc.Weixin.Context.WeixinContextRemovedEventArgs<IRequestMessageBase,IResponseMessageBase> e)
        {
            /* 注意,這個事件不是實時觸發的(固然你也能夠專門寫一個線程監控)
             * 爲了提升效率,根據WeixinContext中的算法,這裏的過時消息會在過時後下一條請求執行以前被清除
             */

            var messageContext = e.MessageContext as CustomMessageContext;
            if (messageContext == null)
            {
                return;//若是是正常的調用,messageContext不會爲null
            }

            //TODO:這裏根據須要執行消息過時時候的邏輯,下面的代碼僅供參考

            //Log.InfoFormat("{0}的消息上下文已過時",e.OpenId);
        }
    }

  上面的CustomMessageContext_MessageContextRemoved()方法會在某個用戶的上下文被清除的時候觸發,其中能夠加入本身須要的代碼。除此之外,您也能夠重寫積累中的OnRemoved()等方法,或者添加其餘的屬性及方法。

 

 

系列教程索引

地址:http://www.cnblogs.com/szw/archive/2013/05/14/weixin-course-index.html

  1. Senparc.Weixin.MP SDK 微信公衆平臺開發教程(一):微信公衆平臺註冊
  2. Senparc.Weixin.MP SDK 微信公衆平臺開發教程(二):成爲開發者
  3. Senparc.Weixin.MP SDK 微信公衆平臺開發教程(三):微信公衆平臺開發驗證
  4. Senparc.Weixin.MP SDK 微信公衆平臺開發教程(四):Hello World
  5. Senparc.Weixin.MP SDK 微信公衆平臺開發教程(五):使用Senparc.Weixin.MP SDK
  6. Senparc.Weixin.MP SDK 微信公衆平臺開發教程(六):瞭解MessageHandler
  7. Senparc.Weixin.MP SDK 微信公衆平臺開發教程(七):解決用戶上下文(Session)問題
  8. Senparc.Weixin.MP SDK 微信公衆平臺開發教程(八):通用接口說明
  9. Senparc.Weixin.MP SDK 微信公衆平臺開發教程(九):自定義菜單接口說明
  10. Senparc.Weixin.MP SDK 微信公衆平臺開發教程(十):多客服接口說明
  11. Senparc.Weixin.MP SDK 微信公衆平臺開發教程(十一):高級接口說明
  12. Senparc.Weixin.MP SDK 微信公衆平臺開發教程(十二):OAuth2.0說明
  13. Senparc.Weixin.MP SDK 微信公衆平臺開發教程(十三):地圖相關接口說明
  14. Senparc.Weixin.MP SDK 微信公衆平臺開發教程(十四):請求消息去重
  15. Senparc.Weixin.MP SDK 微信公衆平臺開發教程(十五):消息加密
  16. Senparc.Weixin.MP SDK 微信公衆平臺開發教程(十六):AccessToken自動管理機制
  17. Senparc.Weixin.MP SDK 微信公衆平臺開發教程(十七):個性化菜單接口說明
  18. Senparc.Weixin.MP SDK 微信公衆平臺開發教程(十八):Web代理功能
  19. Senparc.Weixin.MP SDK 微信公衆平臺開發教程(十九):MessageHandler 的未知類型消息處理
  20. Senparc.Weixin.MP SDK 微信公衆平臺開發教程(二十):使用菜單消息功能
  21. Senparc.Weixin.MP SDK 微信公衆平臺開發教程(二十一):在小程序中使用 WebSocket (.NET Core)
  22. Senparc.Weixin.MP SDK 微信公衆平臺開發教程(二十二):如何安裝 Nuget(dll) 後使用項目源代碼調試
相關文章
相關標籤/搜索