在《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調用都縮短到一行。
這么做的同時,除了讓代碼更加簡便,我們還有兩個願望:
- 讓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中發布。
系列教程索引
地址:http://www.cnblogs.com/szw/archive/2013/05/14/weixin-course-index.html
- Senparc.Weixin.MP SDK 微信公眾平台開發教程(一):微信公眾平台注冊
- Senparc.Weixin.MP SDK 微信公眾平台開發教程(二):成為開發者
- Senparc.Weixin.MP SDK 微信公眾平台開發教程(三):微信公眾平台開發驗證
- Senparc.Weixin.MP SDK 微信公眾平台開發教程(四):Hello World
- Senparc.Weixin.MP SDK 微信公眾平台開發教程(五):使用Senparc.Weixin.MP SDK
- Senparc.Weixin.MP SDK 微信公眾平台開發教程(六):了解MessageHandler
- Senparc.Weixin.MP SDK 微信公眾平台開發教程(七):解決用戶上下文(Session)問題
- Senparc.Weixin.MP SDK 微信公眾平台開發教程(八):通用接口說明
- Senparc.Weixin.MP SDK 微信公眾平台開發教程(九):自定義菜單接口說明
- Senparc.Weixin.MP SDK 微信公眾平台開發教程(十):多客服接口說明
- Senparc.Weixin.MP SDK 微信公眾平台開發教程(十一):高級接口說明
- Senparc.Weixin.MP SDK 微信公眾平台開發教程(十二):OAuth2.0說明
- Senparc.Weixin.MP SDK 微信公眾平台開發教程(十三):地圖相關接口說明
- Senparc.Weixin.MP SDK 微信公眾平台開發教程(十四):請求消息去重
- Senparc.Weixin.MP SDK 微信公眾平台開發教程(十五):消息加密
- Senparc.Weixin.MP SDK 微信公眾平台開發教程(十六):AccessToken自動管理機制
- Senparc.Weixin.MP SDK 微信公眾平台開發教程(十七):個性化菜單接口說明
- Senparc.Weixin.MP SDK 微信公眾平台開發教程(十八):Web代理功能
- Senparc.Weixin.MP SDK 微信公眾平台開發教程(十九):MessageHandler 的未知類型消息處理
- Senparc.Weixin.MP SDK 微信公眾平台開發教程(二十):使用菜單消息功能
- Senparc.Weixin.MP SDK 微信公眾平台開發教程(二十一):在小程序中使用 WebSocket (.NET Core)
- Senparc.Weixin.MP SDK 微信公眾平台開發教程(二十二):如何安裝 Nuget(dll) 后使用項目源代碼調試
