Senparc.Weixin.MP SDK 微信公众平台开发教程(七):解决用户上下文(Session)问题

简介: 从这篇文章中我们已经了解了微信公众平台消息传递的方式,这种方式有一个先天的缺陷:不同用户的请求都来自同一个微信服务器,这使得常规的Session无法使用(始终面对同一个请求对象,况且还有对方服务器Cookie是否能保存的问题)。

    从这篇文章中我们已经了解了微信公众平台消息传递的方式,这种方式有一个先天的缺陷:不同用户的请求都来自同一个微信服务器,这使得常规的Session无法使用(始终面对同一个请求对象,况且还有对方服务器Cookie是否能保存的问题)。

    这就要求我们自己建立一套独立的对话上下文请求机制。

    上一篇《Senparc.Weixin.MP SDK 微信公众平台开发教程(六):了解MessageHandler》中我们了解到,Senparc.Weixin.MP SDK提供了一套非常便捷的消息处理机制,这套机制上面,我们有增加了一个叫MessageContext的上下文处理机制,下面我们就来说一说这个功能的使用(参考开源项目Wiki中的《用户上下文WeixinContext和MessageContext》一文)。

     更多实际应用的代码可以参考开源项目中的Demo:https://github.com/JeffreySu/WeiXinMPSDK,此外您也可以关注下方的微信二维码,不断发送文字及各种信息进行测试(发送文字的时候才能看到完整结果,期间可以发送其他任意类型)。

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),并且提供了一系列的方法,主要方法包括:

      /// <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代理功能

 

QQ:498977166

http://szw.cnblogs.com/
研究、探讨.NET开发
转载请注明出处和作者,谢谢!

 


微信开发深度解析:微信公众号、小程序高效开发秘籍
Senparc官方教程《微信开发深度解析:微信公众号、小程序高效开发秘籍》,耗时2年精心打造的微信开发权威教程,点击这里,购买正版

 

目录
相关文章
|
1月前
|
JSON 前端开发 API
使用微信JS-SDK调用发票接口的完整开发指南
本文介绍了如何使用微信JS-SDK的`chooseInvoiceTitle`接口来调用微信的发票功能。通过微信发票接口,用户可以选择开具个人或单位发票,并获取相关发票信息,如抬头、税号、公司地址等。在文中,详细描述了JS-SDK的初始化、发票接口的调用方式,并提供了完整的代码示例。文章还介绍了如何处理返回的发票信息,帮助开发者快速集成微信发票功能。
82 2
|
1月前
|
移动开发 安全 API
微信H5支付--微信JS-SDK支付--点金计划
本文详细介绍了微信H5支付和JS-SDK支付的原理、配置和开发流程,涵盖了H5支付在移动端浏览器外唤起微信支付的细节,以及JS-SDK支付在微信内置浏览器中完成支付的相关注意事项。文章还针对微信支付常见问题,提供了解决方案和代码示例。最后,文章深入解析了微信支付点金计划,包括商家小票的自定义开发、API接口以及支付成功后的页面展示逻辑,为开发者提供了完整的开发参考。
56 0
微信H5支付--微信JS-SDK支付--点金计划
|
3月前
|
编解码 缓存 开发工具
Pico Neo 3教程☀️ 三、SDK 的进阶功能
Pico Neo 3教程☀️ 三、SDK 的进阶功能
|
3月前
|
开发工具 图形学 Android开发
Pico Neo 3教程☀️ 二、从 PicoVR Unity SDK 迁移至 Unity XR SDK
Pico Neo 3教程☀️ 二、从 PicoVR Unity SDK 迁移至 Unity XR SDK
|
3月前
|
开发工具 vr&ar 图形学
Pico Neo 3教程☀️ 一、SDK的下载与快速入门
Pico Neo 3教程☀️ 一、SDK的下载与快速入门
|
3月前
|
缓存 JavaScript 前端开发
微信 JS-SDK Demo “分享信息设置” API 及数字签名生成方法(NodeJS版本)
微信 JS-SDK Demo “分享信息设置” API 及数字签名生成方法(NodeJS版本)更新时间(2020-10-29)
|
5月前
|
开发工具
微信客服系统开发SDK使用教程- 拉取当前微信个人号列表请求(立即)
微信客服系统开发SDK使用教程- 拉取当前微信个人号列表请求(立即)
|
4月前
|
PHP 开发工具
tp5+微信公众号服务器配置时使用官方sdk还是token验证失败
tp5+微信公众号服务器配置时使用官方sdk还是token验证失败
40 0
|
5月前
|
API 开发工具
企业微信SDK接口API调用-触发推送企业微信微信好友
企业微信SDK接口API调用-触发推送企业微信微信好友
|
5月前
|
Java API 开发工具
企业微信api,企业微信sdk接口java调用源码
企业微信api,企业微信sdk接口java调用源码

热门文章

最新文章

下一篇
无影云桌面