微信公眾平台開發:AccessToken自動管理機制

高洛峰
發布: 2017-02-27 13:40:54
原創
3044 人瀏覽過

在《微信大眾平台開發:通用介面說明》中,我介紹了取得AccessToken(通用介面)的方法。

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

var accessToken = AccessTokenContainer.TryGetAccessToken(appId, appSecret);
登入後複製

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

var accessToken = AccessTokenContainer.GetAccessToken(_appId);
登入後複製

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

var result = CommonApi.GetMenu(accessToken);
登入後複製

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

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

讓API可以自動處理已經變更的AccessToken(在負載平衡等多個伺服器同時操作同在一個微信公眾號的情況下,可能出現AccessToken在外部被刷新,導致本機AccessToken失效的情況),並且重新取得、傳回最終正確的API結果。

不改變目前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中發布。


更多微信公眾平台開發:AccessToken自動管理機制相關文章請關注PHP中文網!


相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板