使用delphi+intraweb進行微信開發5—準備實現微信API,先從獲取AccessToken開始
在前4講中我們已經使iw開發的應用成功和微信進行了對接,再接下來的章節中我們開始逐一嘗試和實現微信的各個API,開始前先來點準備工作 |
- 首先需要明確的是,微信的API都是通過https調用實現的,分為post方法調用和get方法調用。不需要上傳數據的采用get方法(例如獲取AccessToken),而需要向微信服務器提交數據的采用post方法(例如創建菜單)。
- 微信方法調用均需傳遞AccessToken(URL參數方式),這個AccessToken不是我們微信接入時使用的Token,這個AccessToken專門用於微信API調用,AccessToken有過期時間,而且每天有請求次數限制,據說是為了防止不良的程序調用導致微信服務器出現異常。因此在這種情況下則必須在獲取AccessToken後進行保存,在即將過期前再重新獲取。
好吧,讓我們開始:首先定義post和get方法。這裏我們采用Indy實現,不需要再安裝什麽第三方組件了,也沒有大量並發的要求(咱們這是客戶端程序),簡單易用最重要。 |
/// <summary>
/// 向指定URL發起Get請求
/// </summary>
/// <param name="http">TIdHTTP</param>
/// <param name="URL">指定URL</param>
/// <param name="Max">Get請求失敗最大重試次數</param>
/// <returns>返回騰訊服務器響應(string類型的json格式數據)</returns>
function GetMethod(http: TIdHTTP; URL: String; Max: Integer): String;
var
RespData: TStringStream;
begin
RespData := TStringStream.Create(‘‘, TEncoding.UTF8);
try
try
http.Get(URL, RespData);
http.Request.Referer := URL;
Result := RespData.DataString;
except
Dec(Max);
if Max = 0 then
begin
Result := ‘‘;
exit;
end;
Result := GetMethod(http, URL, Max);
end;
finally
FreeAndNil(RespData);
end;
end;
/// <summary>
/// 向指定URL提交數據(Post)
/// </summary>
/// <param name="http">TIdHTTP</param>
/// <param name="URL">指定URL</param>
/// <param name="Data">要提交的數據(UTF8String)</param>
/// <param name="Max">Post請求失敗最大重試次數</param>
/// <returns>返回騰訊服務器響應(string類型的json格式數據)</returns>
function PostMethod(http: TIdHTTP; URL: String; Data: UTF8String;
Max: Integer): String;
var
PostData, RespData: TStringStream;
begin
RespData := TStringStream.Create(‘‘);
PostData := TStringStream.Create(Data);
try
try
if http = nil then
exit;
http.Post(URL, PostData, RespData);
Result := RespData.DataString;
http.Request.Referer := URL;
except
Dec(Max);
if Max = 0 then
begin
Result := ‘‘;
exit;
end;
Result := PostMethod(http, URL, Data, Max);
end;
finally
http.Disconnect;
FreeAndNil(RespData);
FreeAndNil(PostData);
end;
end;
有了上面兩個方法我們就可以開始測試微信API了。
? 不過你有沒有註意到,微信請求是https,不是http啊,所以似乎還需要讓Indy支持ssl傳輸才行啊,這當然沒有問題,上Indy官網下載SSL支持DLL即可,分為64位和32位版本不要搞錯,下載後和編譯好的IW程序放置在同一目錄下即可(說實在的下載網站我給忘了,大家可以百度一下,如果找不到給我留言,我把我下載的發出來)。
接下來研究下如何獲取這個AccessToken |
由於AccessToken在API調用中都需要使用,因此先來獲取AccessToken吧,關於AccessToken的解釋請看微信文檔:http://mp.weixin.qq.com/wiki/14/9f9c82c1af308e3b14ba9b973f99a8ba.html
使用的微信命令URL是:https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
采用Get方法。
如你所見:
1、URL調用需傳遞【APPID】和【APPSECRET】兩個參數,返回結果為Json格式字符串。
2、調用成功的情況下,微信會返回下述JSON數據包給公眾號:
{"access_token":"ACCESS_TOKEN","expires_in":7200}
參數 | 說明 |
---|---|
access_token | 獲取到的憑證 |
expires_in | 憑證有效時間,單位:秒 |
3、錯誤時微信會返回錯誤碼等信息,JSON數據包示例如下(該示例為AppID無效錯誤):
{"errcode":40013,"errmsg":"invalid appid"}
? 好吧,好像還得找個Json解析的組件,嗯嗯,夠麻煩的,推薦使用第三方組件“SuperObject”進行Json格式解析,不過你要非得自行進行Json字符串編解碼也行,嗯,可能會累點,相信我,最好找個封裝完善的Json組件,工欲善其事必先利其器,否則創建菜單什麽的時候有你受的。
嗯,Json結果解析判斷什麽的我就不說了,再說說AccessToken的緩存問題。 |
簡單說:“為了保密appsecrect,第三方需要一個access_token獲取和刷新的中控服務器。而其他業務邏輯服務器所使用的access_token均來自於該中控服務器,不應該各自去刷新,否則會造成access_token覆蓋而影響業務;”這個是微信文檔原話,所以讓我們看看怎麽緩存這個東東。
先聲明一個TAccessToken記錄,然後利用一個全局公開的變量,再加上一個泛型容器,如此的簡單:
/// <summary>
/// AccessToken記錄,包含AccessToken值和過期時間
/// </summary>
TAccessToken = record
AccessToken: string;
AccessTokenExpiresDt: TDateTime;
end;
var
gAccessTokenLst: TDictionary<string, TAccessToken>;
gCS: TCriticalSection;
? 聲明成記錄類型的好處是,可以當做簡單變量來使用,要是聲明成指針或者類,哦哦,創建再釋放,太麻煩了。微信返回的json結果中是這個AccessToken還有多少秒過期,我們換算成時間類型會比較好用一些。
? 泛型我喜歡,讓Hash表成為強類型的。
? 如上gAccessTokenLst用來存儲獲取的AccessToken,key就是微信號的APPID,估計這個不會重復吧!上面還聲明了一個臨界區對象gCS,大約我不說你也能猜到,既然是全局的那就得上鎖,防止寫入混亂。
? 上面那兩個變量gAccessTokenLst和gCS我是在單元的initialization部分實例化的,並在finalization進行了釋放,在這兩個地方處理全局變量的好處是:運行時只執行一次。是初始化全局變量的絕佳地點。
萬事具備了,最終獲取AccessToken的函數代碼如下: |
procedure TWxSdkImp.GetAccessToken(const appid, appsecret: string;
var AccessToken: string; const GetNew: Boolean = false);
var
URL: string;
JSONObject: ISuperObject;
temp, sKey: string;
recAccessToken: TAccessToken;
begin
if (appid = ‘‘) or (appsecret = ‘‘) then
raise Exception.Create(‘TWxSdkImp.GetAccessToken執行出錯,參數應用ID或者應用秘鑰不能為空!‘);
recAccessToken.AccessToken := ‘‘;
sKey := appid;
if gAccessTokenLst.ContainsKey(sKey) then
recAccessToken := gAccessTokenLst.Items[sKey];
// 如果要求重新獲取AccessToken 或者 尚未獲取AccessToken 或者 已經獲取了但是離過期不足30秒
gCS.Enter;
try
if GetNew or (recAccessToken.AccessToken = ‘‘) or (SecondSpan(recAccessToken.AccessTokenExpiresDt, Now) < 30) then
begin
URL := Format(WxCmdUrl_GetAccessToken, [appid, appsecret]);
temp := GetMethod(http, URL, 3);
JSONObject := ParseJson(temp, [‘"access_token"‘, ‘"errcode"‘]);
if Pos(‘"access_token"‘, temp) > 0 then
begin
recAccessToken.AccessToken := JSONObject[‘access_token‘].AsString;
recAccessToken.AccessTokenExpiresDt := IncSecond(Now, JSONObject[‘expires_in‘].AsInteger);
if gAccessTokenLst.ContainsKey(sKey) then
gAccessTokenLst.Remove(sKey);
gAccessTokenLst.Add(sKey, recAccessToken);
end else
raise Exception.Create(‘TWxSdkImp.GetAccessToken執行出錯,服務器返回錯誤代碼:‘ + JSONObject[‘errcode‘].AsString + ‘,錯誤信息:‘ + JSONObject[‘errmsg‘].AsString + ‘!‘