Senparc.Weixin.MP SDK 微信公眾平臺開發教程(十六):AccessToken自動管理機制

SZW發表於2015-07-06

    在《Senparc.Weixin.MP SDK 微信公眾平臺開發教程(八):通用介面說明》中,我介紹了獲取AccessToken(通用介面)的方法。

    在實際的開發過程中,所有的高階介面都需要提供AccessToken,因此我們每次在呼叫高階介面之前,都需要執行一次獲取AccessToken的方法,例如:

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

或者當你對appId和appSecret進行過全域性註冊之後,也可以這樣做:

var accessToken = AccessTokenContainer.GetAccessToken(_appId);

    然後使用這個accessToken輸入到高階介面的方法中,例如我們可以這樣獲取選單:

var result = CommonApi.GetMenu(accessToken);

  通常情況下,這已經是一個很簡潔的API呼叫過程。但是我們不願意就這樣停止,我們準備把幾乎所有的API呼叫都縮短到一行。

     這麼做的同時,除了讓程式碼更加簡便,我們還有兩個願望:

  1. 讓API可以自動處理已經變更的AccessToken(在負載均衡等多個伺服器同時操作同一個微信公眾號的情況下,可能出現AccessToken在外部被重新整理,導致本機AccessToken失效的情況),並且重新獲取、返回最終正確的API結果。
  2. 不改變目前API呼叫的方式,完全向下相容。

 

呼叫程式碼

    修改之後,我們可以直接這樣一行呼叫API,每次只需要提供一個appId:

var result = CommonApi.GetMenu(appId);

  當前在執行之前,我們需要像以前一樣全域性註冊一下appId和appSecret:

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()方法,程式碼如下:

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 = null, bool retryIfFaild = true) where 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的原始碼原來是這樣的:

        /// <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()方法之後:

        /// <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中釋出。

 

系列教程索引

地址: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 微信公眾平臺開發教程(二十):使用選單訊息功能

相關文章