簡介
Api作為業(yè)務(wù)邏輯提供方艳汽,承載了項(xiàng)目的核心邏輯州袒,因而具有相對高的邏輯復(fù)雜性勋功。在這樣的前提下如何簡化代碼編寫蹬音,如何規(guī)范統(tǒng)一書寫風(fēng)格和邏輯規(guī)范,如何提高代碼的維護(hù)性和擴(kuò)展性休玩。項(xiàng)目的搭建的高內(nèi)聚低耦合變得重要著淆。
示例的是一個(gè)企業(yè)級項(xiàng)目劫狠,框架圖如下
Security:重寫了Http請求(Override DelegatingHandler),在請求的切面進(jìn)行合法性判斷永部,順便進(jìn)行簽名要求的預(yù)處理独泞。
Client:定義了統(tǒng)一的接口調(diào)用方式共調(diào)用端使用,簡化及統(tǒng)一了接口使用苔埋。
Ctrl層:作為服務(wù)的直接提供方懦砂,在服務(wù)器上直接提供類似于RestFul風(fēng)格的接口(感覺嚴(yán)格的RestFul風(fēng)格,需要有完備的領(lǐng)域模型驅(qū)動(dòng)组橄,實(shí)際上的情況總是不盡如人意荞膘,領(lǐng)域抽象能力不夠。)玉工,獲取請求數(shù)據(jù)羽资,按需調(diào)用Filter過濾器,進(jìn)一步判斷遵班,調(diào)用
Model層:作為業(yè)務(wù)模型層屠升,提供業(yè)務(wù)邏輯的實(shí)際操作。使用統(tǒng)一的實(shí)體模型狭郑,并聯(lián)系到Ibatis上腹暖,進(jìn)行數(shù)據(jù)操作。
具體的代碼結(jié)構(gòu)如下圖:
下面是各個(gè)模塊的詳細(xì)介紹和代碼示例:
Entity library項(xiàng)目代碼示例
項(xiàng)目結(jié)構(gòu)如下圖:
Domain模塊翰萨,作為實(shí)體模型脏答,簡易代碼如下
public class User
{
public int Id { get; set; }
public string NickName { get; set; }
public string Avatar { get; set; }
}
Request,請求結(jié)構(gòu)模型缨历,利用了泛型接口以蕴,將請求類和返回類聯(lián)系,起到了控制倒轉(zhuǎn)的作用辛孵。
public abstract class AbstractRequest
{
public bool ValidateParameters()
{
//公用方法示例丛肮,驗(yàn)證參數(shù)合法性
}
}
public interface IRequest<T> where T:AbstractResponse
{
//獲取接口名稱
string GetApiName();
//獲取接口編碼
string GetApiCode();
}
//獲取User信息的請求結(jié)構(gòu)定義
public class GetUserRequest:AbstractRequest,IRequest<GetUserResponse>
{
public int Id { get; set; }
public string GetApiName()
{
return "User.GetUserDetail";
}
public string GetApiCode()
{
return "User001";
}
}
Response模塊,作為請求的返回類型魄缚,定義統(tǒng)一的返回結(jié)構(gòu)宝与,便于消費(fèi)者進(jìn)行一致性返回碼判斷處理。
public abstract class AbstractResponse
{
//返回碼
public int Code { get; set; }
//報(bào)錯(cuò)信息
public string Message { get; set; }
}
public class GetUserResponse:AbstractResponse
{
public User User { get; set; }
}
Service項(xiàng)目代碼示例
項(xiàng)目結(jié)構(gòu)如下圖:
代碼示例:
public interface IUserService
{
GetUserResponse GetUser(int id);
}
public class BaseService
{
//protected SqlInstance sqlInstance;
public BaseService()
{
//sqlInstance=new SqlInstance(); //實(shí)例化數(shù)據(jù)庫連接
//...
}
//...
}
public class UserService:BaseService,IUserService
{
public GetUserResponse GetUser(int id)
{
//鏈接數(shù)據(jù)庫獲取數(shù)據(jù)
//...
throw new NotImplementedException();
}
}
Security類庫代碼示例
類庫只是處理了安全性問題冶匹,在api請求入口處添加上權(quán)限判斷习劫。使用重寫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)//驗(yàn)證類似于token的權(quán)限
{
return await Task.Factory.StartNew<HttpResponseMessage>(
() => new HttpResponseMessage(HttpStatusCode.Forbidden)
{
Content = new StringContent("error message")
});
}
//如果有signature嚼隘,判斷诽里,并加結(jié)果標(biāo)志,沒有的話飞蛹,清除signature相關(guān)信息谤狡,防止偽造灸眼。
//.....
return await base.SendAsync(request, cancellationToken);
}
}
抽象出來的權(quán)限判斷,可直接調(diào)用到webapi端墓懂,添加到路由配置代碼中焰宣。
WebApi項(xiàng)目示例
作為接口的實(shí)際定義,webapi定義了接口文件的實(shí)際規(guī)則捕仔,并做出相應(yīng)的安全管理及接口的權(quán)限控制匕积。學(xué)習(xí)微信的權(quán)限控制,大概確定了幾種接口:
這些權(quán)限的判斷都放在了Security做了集中管理榜跌。接口定義只需要在相應(yīng)的邏輯上使用判斷合法性即可闪唆。
代碼示例:
public class UserController : ApiController
{
private IUserService userService;
public UserController()
{
userService=new UserService();
}
[Signature]//安全簽名過濾器判斷
[HttpPost]
public GetUserResponse GetUser(GetUserRequest request)
{
//參數(shù)判斷,安全性判斷等等
var ret = userService.GetUser(request.Id);
return ret;
}
}
以上是一個(gè)獲取用戶信息的示例接口斜做,而作為接口入口的路由配置苞氮,則需要對請求的合法性進(jìn)行判斷,路由配置代碼如下:
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類庫定義了接口調(diào)用的公共方法笼吟。
1、利用泛型接口霸旗,將請求類和返回類進(jìn)行了封裝贷帮,簡化調(diào)用的代碼書寫。
2诱告、并使得消費(fèi)者調(diào)用接口需要通過代理類撵枢,避開了跨域的問題。
3精居、消費(fèi)者的調(diào)用都同意使用統(tǒng)一類庫锄禽,是的日志的處理統(tǒng)一,返回的錯(cuò)誤也可以進(jìn)行一致化定義靴姿。
代碼示例如下:
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}] 請求內(nèi)容: {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}] 響應(yīng)內(nèi)容: {1}", request.GetApiCode(), respStr));
}
return JsonConvert.DeserializeObject<T>(respStr);
}
catch (Exception e)
{
//記錄日志
if (isNeedLogFile)
{
logFile.Log(string.Format("[{0}] 響應(yīng)錯(cuò)誤: {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}] 請求錯(cuò)誤: {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()))));
}
}
以上就是Api項(xiàng)目端的各個(gè)核心環(huán)節(jié)的詳細(xì)介紹沃但。
接下來會(huì)對調(diào)用端即前端進(jìn)行簡單的介紹。