簡介
Api作為商務邏輯提供方,承載了項目的核心邏輯,因而具有相對高的邏輯複雜性。在這樣的前提下如何簡化代碼編寫,如何規範統一書寫風格和邏輯規範,如何提高代碼的維護性和擴充性。項目的搭建的高內聚低耦合變得重要。
樣本的是一個企業階層專案,架構圖如下
api層.jpg
Security:重寫了Http請求(Override DelegatingHandler),在請求的切面進行合法性判斷,順便進行簽名要求的預先處理。
Client:定義了統一的介面調用方式共調用端使用,簡化及統一了介面使用。
Ctrl層:作為服務的直接提供方,在伺服器上直接提供類似於RestFul風格的介面(感覺嚴格的RestFul風格,需要有完備的領域模型驅動,實際上的情況總是不盡如人意,領域抽象能力不夠。),擷取請求資料,按需調用Filter過濾器,進一步判斷,調用
Model層:作為業務模型層,提供商務邏輯的實際操作。使用統一的實體模型,並聯絡到Ibatis上,進行資料操作。
具體的代碼結構如:
Api-UML.jpg
下面是各個模組的詳細介紹和程式碼範例:
Entity library項目程式碼範例
項目結構如:
entity.jpg
Domain模組,作為實體模型,簡易代碼如下
public class User{ public int Id { get; set; } public string NickName { get; set; } public string Avatar { get; set; }}
Request,請求結構模型,利用了泛型介面,將請求類和返回類聯絡,起到了控制倒轉的作用。
public abstract class AbstractRequest{ public bool ValidateParameters() { //公用方法樣本,驗證參數合法性 }} public interface IRequest<T> where T:AbstractResponse { //擷取介面名稱 string GetApiName(); //擷取介面編碼 string GetApiCode(); }//擷取User資訊的請求結構定義 public class GetUserRequest:AbstractRequest,IRequest<GetUserResponse> { public int Id { get; set; } public string GetApiName() { return "User.GetUserDetail"; } public string GetApiCode() { return "User001"; } }
Response模組,作為請求的傳回型別,定義統一的返回結構,便於消費者進行一致性返回碼判斷處理。
public abstract class AbstractResponse { //返回碼 public int Code { get; set; } //報錯資訊 public string Message { get; set; } } public class GetUserResponse:AbstractResponse { public User User { get; set; } }
Service項目程式碼範例
項目結構如:
service.jpg
程式碼範例:
public interface IUserService { GetUserResponse GetUser(int id); } public class BaseService { //protected SqlInstance sqlInstance; public BaseService() { //sqlInstance=new SqlInstance(); //執行個體化資料庫連接 //... } //... } public class UserService:BaseService,IUserService { public GetUserResponse GetUser(int id) { //連結資料庫擷取資料 //... throw new NotImplementedException(); } }
Security類庫程式碼範例
類庫只是處理了安全性問題,在api請求入口處添加上許可權判斷。使用重寫Http請求的方式。
程式碼範例
public class MyHandler : DelegatingHandler { protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { IEnumerable<string> keyEnumerable; var t1 = request.Headers.TryGetValues("key", out keyEnumerable); var key = keyEnumerable.FirstOrDefault(); if (!true)//驗證類似於token的許可權 { return await Task.Factory.StartNew<HttpResponseMessage>( () => new HttpResponseMessage(HttpStatusCode.Forbidden) { Content = new StringContent("error message") }); } //如果有signature,判斷,並加結果標誌,沒有的話,清除signature相關資訊,防止偽造。 //..... return await base.SendAsync(request, cancellationToken); } }
抽象出來的許可權判斷,可直接調用到webapi端,添加到路由配置代碼中。
WebApi項目樣本
作為介面的實際定義,webapi定義了介面檔案的實際規則,並做出相應的安全管理及介面的許可權控制。學習的許可權控制,大概確定了幾種介面:
介面許可權.png
這些許可權的判斷都放在了Security做了集中管理。介面定義只需要在相應的邏輯上使用判斷合法性即可。
程式碼範例:
public class UserController : ApiController { private IUserService userService; public UserController() { userService=new UserService(); } [Signature]//安全簽名過濾器判斷 [HttpPost] public GetUserResponse GetUser(GetUserRequest request) { //參數判斷,安全性判斷等等 var ret = userService.GetUser(request.Id); return ret; } }
以上是一個擷取使用者資訊的樣本介面,而作為介面入口的路由配置,則需要對請求的合法性進行判斷,路由配置代碼如下:
public static void Register(HttpConfiguration config){ // Web API configuration and services // Configure Web API to use only bearer token authentication. config.SuppressDefaultHostAuthentication(); config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType)); // Web API routes config.MapHttpAttributeRoutes(); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{action}", defaults: new { id = RouteParameter.Optional } ); //添加的代碼,添加http請求的入口處理 config.MessageHandlers.Add(new MyHandler());}
Client類庫程式碼範例
Client類庫定義了介面調用的公用方法。
1、利用泛型介面,將請求類和返回類進行了封裝,簡化調用的代碼書寫。
2、並使得消費者調用介面需要通過代理類,避開了跨域的問題。
3、消費者的調用都同意使用統一類庫,是的日誌的處理統一,返回的錯誤也可以進行一致化定義。
程式碼範例如下:
public interface IClient { T Execute<T>(IRequest<T> request) where T : AbstractResponse; }public class DefaultClient:IClient { private readonly string appKey; private readonly string appSecret; private readonly string baseUrl = "http://localhost:16469/api/"; private readonly bool isNeedLogFile = false; private readonly LogFile logFile; public static readonly string SecureHeaderAppKey = "secure_head_appkey"; public static readonly string SecureHeaderSignature = "secure_head_signature"; public DefaultClient() { baseUrl = ConfigurationManager.AppSettings["service_base_url"]; appKey = ConfigurationManager.AppSettings["app_key"]; appSecret = ConfigurationManager.AppSettings["app_secret"]; isNeedLogFile = "1".Equals(ConfigurationManager.AppSettings["client_log_file"]); logFile = new LogFile("client_log_path"); logFile.SubPath = appKey; } public DefaultClient(string serviceBase, string code, string key) { baseUrl = serviceBase; appKey = code; appSecret = key; } public T Execute<T>(IRequest<T> request) where T : AbstractResponse { var webRequest = (HttpWebRequest)WebRequest.Create(baseUrl + request.GetApiName()); webRequest.Method = "POST"; string reqJson; string sign; using (Stream rs = webRequest.GetRequestStream()) { reqJson = JsonConvert.SerializeObject(request); byte[] reqBytes = Encoding.UTF8.GetBytes(reqJson); rs.Write(reqBytes, 0, reqBytes.Length); rs.Close(); } webRequest.ContentType = "application/json"; webRequest.Headers.Add(SecureHeaderAppKey, appKey); sign = ComputeHash(appKey, appSecret, reqJson); webRequest.Headers.Add(SecureHeaderSignature, sign); //記錄日誌 if (isNeedLogFile) { logFile.Log(string.Format("[{0}] 請求內容: {1}", request.GetApiCode(), reqJson)); logFile.Log(string.Format("[{0}] 請求籤名: {1}", request.GetApiCode(), sign)); } try { using (var resp = (HttpWebResponse)webRequest.GetResponse()) { try { Stream respStream = resp.GetResponseStream(); if (respStream == null) { throw new WebException("GetResponseStream returned null"); } var streamReader = new StreamReader(respStream); string respStr = streamReader.ReadToEnd(); //記錄日誌 if (isNeedLogFile) { logFile.Log(string.Format("[{0}] 響應內容: {1}", request.GetApiCode(), respStr)); } return JsonConvert.DeserializeObject<T>(respStr); } catch (Exception e) { //記錄日誌 if (isNeedLogFile) { logFile.Log(string.Format("[{0}] 響應錯誤: {1}", request.GetApiCode(), e.Message)); } throw new ApplicationException(e.Message, e); } } } catch (WebException e) { var errMsg = new StreamReader(e.Response.GetResponseStream()).ReadToEnd(); //記錄日誌 if (isNeedLogFile) { logFile.Log(string.Format("[{0}] 請求錯誤: {1}", request.GetApiCode(), errMsg)); } throw new APIServiceException(errMsg); } } private string ComputeHash(string key, string secret, string body) { return Convert.ToBase64String( SHA1.Create().ComputeHash(Encoding.Default.GetBytes(string.Concat(key, secret, body.Trim())))); } }