從這篇文章中我們已經瞭解了微信公眾平臺訊息傳遞的方式,這種方式有一個先天的缺陷:不同使用者的請求都來自同一個微信伺服器,這使得常規的Session無法使用(始終面對同一個請求物件,況且還有對方伺服器Cookie是否能儲存的問題)。
這就要求我們自己建立一套獨立的對話上下文請求機制。
上一篇《Senparc.Weixin.MP SDK 微信公眾平臺開發教程(六):瞭解MessageHandler》中我們瞭解到,Senparc.Weixin.MP SDK提供了一套非常便捷的訊息處理機制,這套機制上面,我們有增加了一個叫MessageContext的上下文處理機制,下面我們就來說一說這個功能的使用(參考開源專案Wiki中的《使用者上下文WeixinContext和MessageContext》一文)。
更多實際應用的程式碼可以參考開源專案中的Demo:https://github.com/JeffreySu/WeiXinMPSDK,此外您也可以關注下方的微信二維碼,不斷髮送文字及各種資訊進行測試(傳送文字的時候才能看到完整結果,期間可以傳送其他任意型別)。
概述
由於微信公眾平臺的特殊機制,所有的資訊都由微信伺服器轉發而來,因此伺服器是無法使用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),並且提供了一系列的方法,主要方法包括:
/// <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
- Senparc.Weixin.MP SDK 微信公眾平臺開發教程(一):微信公眾平臺註冊
- Senparc.Weixin.MP SDK 微信公眾平臺開發教程(二):成為開發者
- Senparc.Weixin.MP SDK 微信公眾平臺開發教程(三):微信公眾平臺開發驗證
- Senparc.Weixin.MP SDK 微信公眾平臺開發教程(四):Hello World
- Senparc.Weixin.MP SDK 微信公眾平臺開發教程(五):使用Senparc.Weixin.MP SDK
- Senparc.Weixin.MP SDK 微信公眾平臺開發教程(六):瞭解MessageHandler
- Senparc.Weixin.MP SDK 微信公眾平臺開發教程(七):解決使用者上下文(Session)問題
- Senparc.Weixin.MP SDK 微信公眾平臺開發教程(八):通用介面說明
- Senparc.Weixin.MP SDK 微信公眾平臺開發教程(九):自定義選單介面說明
- Senparc.Weixin.MP SDK 微信公眾平臺開發教程(十):多客服介面說明
- Senparc.Weixin.MP SDK 微信公眾平臺開發教程(十一):高階介面說明
- Senparc.Weixin.MP SDK 微信公眾平臺開發教程(十二):OAuth2.0說明
- Senparc.Weixin.MP SDK 微信公眾平臺開發教程(十三):地圖相關介面說明
- Senparc.Weixin.MP SDK 微信公眾平臺開發教程(十四):請求訊息去重
- Senparc.Weixin.MP SDK 微信公眾平臺開發教程(十五):訊息加密
- Senparc.Weixin.MP SDK 微信公眾平臺開發教程(十六):AccessToken自動管理機制
- Senparc.Weixin.MP SDK 微信公眾平臺開發教程(十七):個性化選單介面說明
- Senparc.Weixin.MP SDK 微信公眾平臺開發教程(十八):Web代理功能
- Senparc.Weixin.MP SDK 微信公眾平臺開發教程(十九):MessageHandler 的未知型別訊息處理
- Senparc.Weixin.MP SDK 微信公眾平臺開發教程(二十):使用選單訊息功能