• Senparc.Weixin.MP SDK 微信公众平台开发教程(十六):AccessToken自动管理机制


    Senparc.Weixin.MP SDK 微信公众平台开发教程(十六):AccessToken自动管理机制

        在《Senparc.Weixin.MP SDK 微信公众平台开发教程(八):通用接口说明》中,我介绍了获取AccessToken(通用接口)的方法。

        在实际的开发过程中,所有的高级接口都需要提供AccessToken,因此我们每次在调用高级接口之前,都需要执行一次获取AccessToken的方法,例如:

    1
    var accessToken = AccessTokenContainer.TryGetAccessToken(appId, appSecret);

    或者当你对appId和appSecret进行过全局注册之后,也可以这样做:

    1
    var accessToken = AccessTokenContainer.GetAccessToken(_appId);

        然后使用这个accessToken输入到高级接口的方法中,例如我们可以这样获取菜单:

    1
    var result = CommonApi.GetMenu(accessToken);

      通常情况下,这已经是一个很简洁的API调用过程。但是我们不愿意就这样停止,我们准备把几乎所有的API调用都缩短到一行。

         这么做的同时,除了让代码更加简便,我们还有两个愿望:

    1. 让API可以自动处理已经变更的AccessToken(在负载均衡等多个服务器同时操作同一个微信公众号的情况下,可能出现AccessToken在外部被刷新,导致本机AccessToken失效的情况),并且重新获取、返回最终正确的API结果。
    2. 不改变目前API调用的方式,完全向下兼容。

     

    调用代码

        修改之后,我们可以直接这样一行调用API,每次只需要提供一个appId:

    1
    var result = CommonApi.GetMenu(appId);

      当前在执行之前,我们需要像以前一样全局注册一下appId和appSecret:

    1
    AccessTokenContainer.Register(_appId, _appSecret);//全局只需注册一次,例如可以放在Global的Application_Start()方法中。

      可以看到,原先的accessToken换成了appId(新版本仍然支持输入accessToken),省去了获取accessToken的过程。具体的过程见下文说明。

     

    SDK源代码实现过程

         之前为了实现自动处理(预料外的)过期的AccessToken,SDK已经提供了Senparc.Weixin.MP/AccessTokenHandlerWapper.Do()方法。这次升级将AccessTokenHandlerWapper.cs重命名为ApiHandlerWapper.cs,废除Do()方法,添加TryCommonApi()方法,代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    namespace Senparc.Weixin.MP
    {
        /// <summary>
        /// 针对AccessToken无效或过期的自动处理类
        /// </summary>
        public static class ApiHandlerWapper
        {
            /// <summary>
            /// 使用AccessToken进行操作时,如果遇到AccessToken错误的情况,重新获取AccessToken一次,并重试。
            /// 使用此方法之前必须使用AccessTokenContainer.Register(_appId, _appSecret);或JsApiTicketContainer.Register(_appId, _appSecret);方法对账号信息进行过注册,否则会出错。
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="fun"></param>
            /// <param name="accessTokenOrAppId">AccessToken或AppId。如果为null,则自动取已经注册的第一个appId/appSecret来信息获取AccessToken。</param>
            /// <param name="retryIfFaild">请保留默认值true,不用输入。</param>
            /// <returns></returns>
            public static T TryCommonApi<T>(Func<string, T> fun, string accessTokenOrAppId = nullbool retryIfFaild = truewhere T : WxJsonResult
            {
                string appId = null;
                string accessToken = null;
     
                if (accessTokenOrAppId == null)
                {
                    appId = AccessTokenContainer.GetFirstOrDefaultAppId();
                    if (appId == null)
                    {
                        throw new WeixinException("尚无已经注册的AppId,请先使用AccessTokenContainer.Register完成注册(全局执行一次即可)!");
                    }
                }
                else if (ApiUtility.IsAppId(accessTokenOrAppId))
                {
                    if (!AccessTokenContainer.CheckRegistered(accessTokenOrAppId))
                    {
                        throw new WeixinException("此appId尚未注册,请先使用AccessTokenContainer.Register完成注册(全局执行一次即可)!");
                    }
     
                    appId = accessTokenOrAppId;
                }
                else
                {
                    //accessToken
                    accessToken = accessTokenOrAppId;
                }
     
     
                T result = null;
     
                try
                {
                    if (accessToken == null)
                    {
                        var accessTokenResult = AccessTokenContainer.GetAccessTokenResult(appId, false);
                        accessToken = accessTokenResult.access_token;
                    }
                    result = fun(accessToken);
                }
                catch (ErrorJsonResultException ex)
                {
                    if (!retryIfFaild
                        && appId != null
                        && ex.JsonResult.errcode == ReturnCode.获取access_token时AppSecret错误或者access_token无效)
                    {
                        //尝试重新验证
                        var accessTokenResult = AccessTokenContainer.GetAccessTokenResult(appId, true);
                        accessToken = accessTokenResult.access_token;
                        result = TryCommonApi(fun, appId, false);
                    }
                }
                return result;
            }
        }
    }

      对应API的源代码原来是这样的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    /// <summary>
    /// 获取当前菜单,如果菜单不存在,将返回null
    /// </summary>
    /// <param name="accessToken"></param>
    /// <returns></returns>
    public static GetMenuResult GetMenu(string accessToken)
    {
        var url = string.Format("https://api.weixin.qq.com/cgi-bin/menu/get?access_token={0}", accessToken);
     
        var jsonString = HttpUtility.RequestUtility.HttpGet(url, Encoding.UTF8);
        //var finalResult = GetMenuFromJson(jsonString);
     
        GetMenuResult finalResult;
        JavaScriptSerializer js = new JavaScriptSerializer();
        try
        {
            var jsonResult = js.Deserialize<GetMenuResultFull>(jsonString);
            if (jsonResult.menu == null || jsonResult.menu.button.Count == 0)
            {
                throw new WeixinException(jsonResult.errmsg);
            }
     
            finalResult = GetMenuFromJsonResult(jsonResult);
        }
        catch (WeixinException ex)
        {
            finalResult = null;
        }
     
        return finalResult;
    }

      现在使用TryCommonApi()方法之后:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    /// <summary>
    /// 获取当前菜单,如果菜单不存在,将返回null
    /// </summary>
    /// <param name="accessTokenOrAppId">AccessToken或AppId。当为AppId时,如果AccessToken错误将自动获取一次。当为null时,获取当前注册的第一个AppId。</param>
    /// <returns></returns>
    public static GetMenuResult GetMenu(string accessTokenOrAppId)
    {
        return ApiHandlerWapper.TryCommonApi(accessToken =>
          {
              var url = string.Format("https://api.weixin.qq.com/cgi-bin/menu/get?access_token={0}", accessToken);
     
              var jsonString = HttpUtility.RequestUtility.HttpGet(url, Encoding.UTF8);
              //var finalResult = GetMenuFromJson(jsonString);
     
              GetMenuResult finalResult;
              JavaScriptSerializer js = new JavaScriptSerializer();
              try
              {
                  var jsonResult = js.Deserialize<GetMenuResultFull>(jsonString);
                  if (jsonResult.menu == null || jsonResult.menu.button.Count == 0)
                  {
                      throw new WeixinException(jsonResult.errmsg);
                  }
     
                  finalResult = GetMenuFromJsonResult(jsonResult);
              }
              catch (WeixinException ex)
              {
                  finalResult = null;
              }
     
              return finalResult;
          }, accessTokenOrAppId);
    }

      我们可以观察到有这样几处变化:

        1. 原先的accessToken变量名称改为了accessTokenOrAppId(新版本中所有相关接口都将如此变化)。

        修改之后,这个参数可以输入accessToken(向下兼容),也可以输入appId(无需再获取accessToken),SDK会根据字符串长度自动判断属于哪种类型的参数。提供的参数有3种可能:

            a) appId。使用appId需要事先对appId和appSecret进行全局注册(上文已说过),当调用API的过程中发现缓存的AccessToken过期时,SDK会自动刷新AccessToken,并重新尝试一次API请求,确保返回正确的结果。如果appId没有被注册过,会抛出异常。

            b) accessToken。这种情况下将使用原始的请求方式,如果accessToken无效,将直接抛出异常,不会重试。

            c) null。当accessTokenOrAppId参数为null时,SDK会自动获取全局注册的第一个appId。如果某个应用只针对一个确定的微信号开发,可以使用这种方法。当全局没有注册任何appId时,将抛出异常。

        2. 原方法内的访问API的代码没有做任何修改,只是被嵌套到了return ApiHandlerWapper.TryCommonApi(accessToken =>{...},accessTokenOrAppId)的方法中,以委托的形式出现,目的是为了在第一次可能的请求失败之后,SDK可以自动执行一次一模一样的代码。

        此功能已经在Senparc.Weixin.MP v12.1中发布。

  • 相关阅读:
    [学习笔记]设计模式之Bridge
    整数划分问题 动态规划
    powershell 发邮件
    python 对象序列化并压缩
    python的序列化与反序列化(例子:dict保存成文件,文件读取成dict)
    ACM-ICPC 2018 world final A题 Catch the Plane
    AlphaPose论文笔记《RMPE: Regional Multi-person Pose Estimation》
    《DensePose: Dense Human Pose Estimation In The Wild》阅读笔记
    [转]tensorflow 中的卷积conv2d的padding 到底要padding多少
    OpenPose论文笔记《Realtime Multi-Person 2D Human Pose Estimation using Part Affinity Fields》
  • 原文地址:https://www.cnblogs.com/Alex80/p/5120258.html
Copyright © 2020-2023  润新知