首页  >  文章  >  微信小程序  >  微信公众平台开发:解决用户上下文(Session)问题

微信公众平台开发:解决用户上下文(Session)问题

高洛峰
高洛峰原创
2017-02-27 13:08:183056浏览

概述

由于微信公众平台的特殊机制,所有的信息都由微信服务器转发而来,因此服务器是无法使用Session对用户会话的上下文进行管理的。

为此Senparc.WeiXin.MP SDK增加了上下文的模块,并集成到了MessageHandler中。

WeixinContext

WeixinContext是所有单个用户上下文(MessageContext)实体的容器(暂且称为全局上下文)。WeixinContext本身不是静态类,意味着您可以在同一个应用中创建多个上下文实体。

同时,一个静态的WeixinContext实例被放入到MessageHandler68b68cd4f5c098c0c63d969fd4792098中,因此所有项目中由MessageHandler68b68cd4f5c098c0c63d969fd4792098派生的子类中的WeixinContext是唯一的、全局的(注:TM为实现IMessageContext的类,包括SDK中已经提供的MessageContext)。

因此我们在任何一个实现了MessageHandler68b68cd4f5c098c0c63d969fd4792098的实例中(比如叫MyMessageHandler),都可以访问到一个类型和名称都叫WeixinContext的对象。

WeixinContext用于保存所用户的上下文(MessageContext),并且提供了一系列的方法,主要方法包括:

     /// 631fb227578dfffda61e1fa4d04b7d25
     /// 重置所有上下文参数,所有记录将被清空
     /// 039f3e95db2a684c7b74365531eb6044
     public void Restore()
     {
         ...
     }

     /// 631fb227578dfffda61e1fa4d04b7d25
     /// 获取MessageContext,如果不存在,返回null
     /// 这个方法的更重要意义在于操作TM队列,及时移除过期信息,并将最新活动的对象移到尾部
     /// 039f3e95db2a684c7b74365531eb6044
     /// 1794c6f51726e50d37c9c385cf874668用户名(OpenId)8bb7487ae6a16a43571bc14c7fcf93c2
     /// 2363942ed0d6cd3e85bae1dffa568116f7735d9f6a7af371769ab5c16d23b2f3
     private TM GetMessageContext(string userName)
     {
         ...
     }

     /// 631fb227578dfffda61e1fa4d04b7d25
     /// 获取MessageContext
     /// 039f3e95db2a684c7b74365531eb6044
     /// 1794c6f51726e50d37c9c385cf874668用户名(OpenId)8bb7487ae6a16a43571bc14c7fcf93c2
     /// af650b2eb7093485a0b0dd4fa5a5276fTrue:如果用户不存在,则创建一个实例,并返回这个最新的实例
     /// False:用户储存在,则返回null8bb7487ae6a16a43571bc14c7fcf93c2
     /// 2363942ed0d6cd3e85bae1dffa568116f7735d9f6a7af371769ab5c16d23b2f3
     private TM GetMessageContext(string userName, bool createIfNotExists)
     {
         ...
     }


     /// 631fb227578dfffda61e1fa4d04b7d25
     /// 获取MessageContext,如果不存在,使用requestMessage信息初始化一个,并返回原始实例
     /// 039f3e95db2a684c7b74365531eb6044
     /// 2363942ed0d6cd3e85bae1dffa568116f7735d9f6a7af371769ab5c16d23b2f3
     public TM GetMessageContext(IRequestMessageBase requestMessage)
     {
         ...
     }

     /// 631fb227578dfffda61e1fa4d04b7d25
     /// 获取MessageContext,如果不存在,使用requestMessage信息初始化一个,并返回原始实例
     /// 039f3e95db2a684c7b74365531eb6044
     /// 2363942ed0d6cd3e85bae1dffa568116f7735d9f6a7af371769ab5c16d23b2f3
     public TM GetMessageContext(IResponseMessageBase responseMessage)
     {
         ...
     }

     /// 631fb227578dfffda61e1fa4d04b7d25
     /// 记录请求信息
     /// 039f3e95db2a684c7b74365531eb6044
     /// 07efe70b0a4fe39827b6de4429d4a825请求信息8bb7487ae6a16a43571bc14c7fcf93c2
     public void InsertMessage(IRequestMessageBase requestMessage)
     {
         ...
     }

     /// 631fb227578dfffda61e1fa4d04b7d25
     /// 记录响应信息
     /// 039f3e95db2a684c7b74365531eb6044
     /// 3cb21f208a80a13d13e9ead31b8df93d响应信息8bb7487ae6a16a43571bc14c7fcf93c2
     public void InsertMessage(IResponseMessageBase responseMessage)
     {
         ...
     }

     /// 631fb227578dfffda61e1fa4d04b7d25
     /// 获取最新一条请求数据,如果不存在,则返回Null
     /// 039f3e95db2a684c7b74365531eb6044
     /// 1794c6f51726e50d37c9c385cf874668用户名(OpenId)8bb7487ae6a16a43571bc14c7fcf93c2
     /// 2363942ed0d6cd3e85bae1dffa568116f7735d9f6a7af371769ab5c16d23b2f3
     public IRequestMessageBase GetLastRequestMessage(string userName)
     {
         ...
     }

     /// 631fb227578dfffda61e1fa4d04b7d25
     /// 获取最新一条响应数据,如果不存在,则返回Null
     /// 039f3e95db2a684c7b74365531eb6044
     /// 1794c6f51726e50d37c9c385cf874668用户名(OpenId)8bb7487ae6a16a43571bc14c7fcf93c2
     /// 2363942ed0d6cd3e85bae1dffa568116f7735d9f6a7af371769ab5c16d23b2f3
     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的作用。

    上述MessageContext41040d08b7519ac9aae356ee76ec31a0类已经提供了比较完备的常用的消息处理的方法,可以直接使用。如果有更加特殊的需求,需要自定义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()等方法,或者添加其他的属性及方法。

更多微信公众平台开发:解决用户上下文(Session)问题相关文章请关注PHP中文网!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn