在《Web API應用架構設計分析(1)》和《Web API應用架構設計分析(2)》中對WebAPI的架構進行了一定的剖析,在當今移動優先的口號下,傳統平台都紛紛開發了屬於自己的Web API平台,方便各種終端系統的接入,很多企業的需求都是以Web API優先的理念來設計整個企業應用體系的。Web API作為整個紐帶的核心,在整個核心層需要考慮到統一性、穩定性、以及安全性等方面因素。本文主要介紹,Web API應用架構,在Winform整合中的角色,以及如何實現在Winform混合架構里面的整合案例。
1、Web API介紹回顧
Web API 是一種應用接口框架,它能夠構建HTTP服務以支撐更廣泛的客戶端(包括瀏覽器,手機和平板電腦等移動設備)的框架, ASP.NET Web API 是一種用於在 .NET Framework 上構建 RESTful 應用程序的理想平台。在目前發達的應用場景下,我們往往需要接入Winform客戶端、APP程序、網站程序、以及目前熱火朝天的微信應用等,這些數據應該可以由同一個服務提供,這個就是我們所需要構建的Web API平台。由於Web API層作為一個公共的接口層,我們就很好保證了各個界面應用層的數據一致性。
通過上面的了解,我們可以知道,所有外部的應用,其實都可以基於一個相同的Web API核心開展的,如下圖所示。

在當前大平台,大應用的背景下,可以基於一個整體的平台,構建很多應用生態鏈,這樣就可以把Web API作為核心層,可以在上面開發我們各種企業業務應用了。

2、Web API在Winform框架中的整合
在Winform界面里面,我們除了可以利用直接訪問數據庫方式,以及采用訪問分布式WCF服務的方式接入,還可以使得它能夠訪問Web API的數據服務,從而構建成一個適應性更加廣泛、功能更加強大的混合式開發框架模式;對於Web API,由於它提供的是一種無狀態的接口訪問,而且往往Web API一般為了多種客戶端接入的需要,可能需要發布在公網上進行訪問,因此我們需要更加注重Web API接口層的安全性。
除了直連數據庫訪問的傳統模式,WCF分布式訪問的WCF服務訪問模式,還可以接入API分布式訪問的Web API接口模式,他們的關系構成了一個完整的Winform應用體系,如下圖所示。

混合式框架的實現細節,就是通過一個類似開關模式的配置模塊,確定是采用直接訪問數據庫方式,還是訪問WCF服務的方式,它們兩者是統一到一個Facade接口門面層上,如果考慮到Web API層,基於混合式的架構,也就是在這個Facade接口門面層上增加多一個Web API的接口的封裝成即可。具體整個框架的架構圖如下所示。

3、Web API訪問的安全性考慮
由於Web API是基於互聯網的應用,因此安全性要遠比在本地訪問數據庫的要嚴格的多,基於通用的做法,一般采用幾道門檻來處理這些問題,一個是基於CA證書的HTTPS進行數據傳輸,防止數據被竊聽,具體可以參考《Web API應用支持HTTPS的經驗總結》;二是采用參數加密簽名方式傳遞,對傳遞的參數,增加一個加密簽名,在服務器端驗證簽名內容,防止被篡改;三是對一般的接口訪問,都需要使用用戶身份的token進行校驗,只要檢查通過才允許訪問數據。
Web API接口的訪問方式,大概可以分為幾類:
1)一個是使用用戶令牌,通過Web API接口進行數據訪問。這種方式,可以有效識別用戶的身份,為用戶接口返回用戶相關的數據,如包括用戶信息維護、密碼修改、或者用戶聯系人等與用戶身份相關的數據。
2)一種是使用安全簽名進行數據提交。這種方式提交的數據,URL連接的簽名參數是經過安全一定規則的加密的,服務器收到數據后也經過同樣規則的安全加密,確認數據沒有被中途篡改后,再進行數據修改處理。因此我們可以為不同接入方式,如Web/APP/Winfrom等不同接入方式指定不同的加密秘鑰,但是秘鑰是雙方約定的,並不在網絡連接上傳輸,連接傳輸的一般是這個接入的AppID,服務器通過這個AppID來進行簽名參數的加密對比,這種方式,類似微信后台的回調處理機制,它們就是經過這樣的處理。
3)一種方式是提供公開的接口調用,不需要傳入用戶令牌、或者對參數進行加密簽名的,這種接口一般較少,只是提供一些很常規的數據顯示而已。

基於上面的考慮,我們一般需要設計Web API對象的接口的時候,需要考慮安全性的原因,也就是需要增加更多的一些字段信息了。
如可以在增刪改這些接口,除了傳入token信息外(標識具體用戶),也還是需要傳入簽名信息,如下接口所示。
/// <summary> /// 插入指定對象到數據庫中 /// </summary> /// <param name="info">指定的對象</param> /// <returns>執行操作是否成功。</returns> public virtual CommonResult Insert(T info, string token, string signature, string timestamp, string nonce, string appid)
上面接口,除了info對象為對象創建的參數外,其他幾個參數,都是為了安全性的考慮而加入的。
在接口里面,我們就需要對用戶的權限和簽名信息進行校驗,然后在進行下一步的數據處理,如果校驗權限和參數完整性不通過,則會被攔截,不執行數據庫的處理了。
//如果用戶token檢查不通過,則拋出MyApiException異常。 //檢查用戶是否有權限,否則拋出MyDenyAccessException異常 base.CheckAuthorized(AuthorizeKey.InsertKey, token, signature, timestamp, nonce, appid);
除了這些對數據修改的特殊性接口,有時候我們還需要查找等類似的,不對數據產生變化的接口,只需要傳入令牌即可,如下接口所示。
/// <summary> /// 查詢數據庫,檢查是否存在指定ID的對象 /// </summary> /// <param name="id">對象的ID值</param> /// <returns>存在則返回指定的對象,否則返回Null</returns> [HttpGet] public virtual T FindByID(string id, string token) { //如果用戶token檢查不通過,則拋出MyApiException異常。 //檢查用戶是否有權限,否則拋出MyDenyAccessException異常 base.CheckAuthorized(AuthorizeKey.ViewKey, token); T info = baseBLL.FindByID(id); return info; }
我們可以看到,上面還是會對token進行校驗,不過少了很多簽名所需的日期標識、隨機數,完整性校驗簽名,應用ID等參數。
我們會根據用戶的token進行解析,如果是正常的token並可以通過解析,那么獲取對應用戶的權限,判斷是否可以進行下一步處理即可。
如果順利通過,那么訪問數據庫,把所需的數據返回給調用者即可。
上面提到了用戶令牌,用戶令牌是一個類似實際生活的通行證,是通過用戶名、密碼等信息獲取到的一個安全令牌,可以在多個接口進行傳遞的字符串,較少密碼參數的傳輸,提高安全性。
這個用戶令牌,一般由單獨的接口產生,我們一般放到AuthController里面,這個控制器負責用戶令牌相關的處理調用。
/// <summary> /// 注冊用戶獲取訪問令牌接口 /// </summary> /// <param name="username">用戶登錄名稱</param> /// <param name="password">用戶密碼</param> /// <param name="signature">加密簽名字符串</param> /// <param name="timestamp">時間戳</param> /// <param name="nonce">隨機數</param> /// <param name="appid">應用接入ID</param> /// <returns></returns> TokenResult GetAccessToken(string username, string password, string signature, string timestamp, string nonce, string appid);
如下代碼是具體業務模塊里面,說明如何獲取用於操作各種接口的token令牌的,當然,實際環境下,一般都會使用HTTPS協議獲取數據了,演示代碼如下所示。
string appid = "myapi_123456"; string appsecret = "mySecret_2856FB9DBE31"; //使用API方式,需要在緩存里面設置特殊的信息 var url = "http://localhost:9001/api/Auth/GetAccessToken" + GetSignatureUrl(appid, appsecret) + "&username=admin&password="; TokenResult result = JsonHelper<TokenResult>.ConvertJson(url); if(result == null) { MessageDxUtil.ShowError("獲取授權信息出錯,請檢查地址是否正確!"); }
由於Web API的調用,都是一種無狀態方式的調用方式,我們通過token來傳遞我們的用戶信息,這樣我們只需要驗證Token就可以了。JWT的令牌生成邏輯如下所示

令牌生成后,我們需要在Web API調用處理前,對令牌進行校驗,確保令牌是正確有效的。
除了令牌的規則,還有一個是加密簽名的處理,加密簽名需要客戶端和服務器端約定相同的秘鑰,一般由Web API統一分配,然后傳輸的時候,客戶端使用應用ID即可。
加密簽名在服務端(Web API端)的驗證流程參考微信的接口的處理方式,處理邏輯如下所示。
1)檢查timestamp 與系統時間是否相差在合理時間內,如10分鍾。
2)將appSecret、timestamp、nonce三個參數進行字典序排序
3)將三個參數字符串拼接成一個字符串進行SHA1加密
4)加密后的字符串可與signature對比,若匹配則標識該次請求來源於某應用端,請求是合法的。
4、Web API基類設計分析
上面介紹了一些Web API控制器的職能,一般情況下,我們設計一個架構,還需要考慮到基類對象之間的重用關系,盡可能把接口抽象到基類層面上去,減少子類的開發代碼量,降低維護成本。
基於上面的目的,參考了我的Web開發框架對於MVC控制器的設計思路

重新整理了Web API的控制器設計對象繼承關系,如下所示:

我們關鍵的核心就是設計好BusinessController<B, T>這個基類,里面設計了大量的通用接口,包括常規的增刪改查、分頁等處理接口,那么子類繼承過來就可以直接擁有這些接口了,多方便啊。


5)Web API客戶端(混合式Winform框架模塊)的調用
上面介紹了Web API服務端平台的架構設計思路,通過上面的整合,我們減輕了開發重復功能的增刪改查等基礎功能的控制器代碼,把這些接口抽象到接口里面即可實現。
但是我們具體應該如何遵循統一接口層Facade層的約定,然后統一調用WebAPI層的接口,做到悄無聲息的從不同的數據源里面獲取數據,展示在客戶端里面呢。
上面我們分析到,整個混合式Winform框架模塊里面,設計方面考慮了數據的獲取方面:包含了直接從數據庫獲取,從WCF服務獲取,以及Web API層的數據獲取三部分內容,當然還可以有更多的數據接入模式(如WebService等),設計效果如下所示。

所有的數據接入,我們在Facade層都統一到接口里面,客戶端的調用也統一到了CallerFactory<T>這個泛型工廠里面,我們根據配置的不同,從不同的模塊里面加載,從而實現不同數據源的動態獲取了。
如下邏輯就是CallerFactory<T>泛型工廠類的加載邏輯,如下所示:

為了實現簡化客戶端調用的封裝,我們一般也把常規的通用操作封裝一下,如下是我原先混合框架里面的設計思路,里面的封裝都是通過***Caller的類來進行數據的訪問的,這些類統一實現一定關系的集成封裝。

為了簡化說明調用接口的處理,這里把上面的關系進行了簡化,並加入了Web API的調用封裝類的處理,幾種訪問模式下的調用端封裝繼承關系,如下設計圖所示。

最底層的幾個DictDataCaller分別是不同訪問方式下的接口調用封裝類,對於Web API來說,它的訪問代碼就是如下所示。
public override bool Delete(string key) { var action = "Delete"; string url = GetPostUrlWithToken(action) +string.Format("&id={0}", key); CommonResult result = JsonHelper<CommonResult>.ConvertJson(url); return result.Success; } public List<DictDataInfo> FindByTypeID(string dictTypeId) { var action = "FindByTypeID"; string url = GetTokenUrl(action) + string.Format("&dictTypeId={0}", dictTypeId); List<DictDataInfo> result = JsonHelper<List<DictDataInfo>>.ConvertJson(url); return result; }
第一個Delete函數是基類提供的,這里進行了重寫,一般情況下,不需要處理就具備增刪改分頁等基礎接口的調用封裝了。
由於所有的實現類都實現繼承了統一的Facade層的接口,那么統一調用也就是自然而然的事情了。所以在Winform界面里面,所有的調用都是使用CallerFactory<T>進行了統一的處理,數據訪問的不同不影響接口的處理, 三種方式的數據調用,統一都是下面的代碼進行處理。
DictDataInfo info = CallerFactory<IDictDataService>.Instance.FindByID(ID); if (info != null) { SetInfo(info); try { bool succeed = CallerFactory<IDictDataService>.Instance.Update(info, info.ID.ToString()); return succeed; } catch (Exception ex) { LogTextHelper.Error(ex); MessageDxUtil.ShowError(ex.Message); } }
系列文章如下所示:
Web API應用架構在Winform混合框架中的應用(1)
Web API應用架構在Winform混合框架中的應用(2)--自定義異常結果的處理
Web API應用架構在Winform混合框架中的應用(3)--Winfrom界面調用WebAPI的過程分解
