重複造輪子系列——基於Ocelot實現類似支付寶介面模式的閘道器
引言
重複造輪子系列是自己平時的一些總結。有的輪子依賴社群提供的輪子為基礎,這裡把使用過程的一些覺得有意思的做個分享。有些思路或者方法在大神看來可能會比較low。但是能解決實際問題,相信有需要的人也在尋找類似的解決方案。這裡可以算作是提供了一種思路,類似問題如果有讀者能有更好的解決方案,願聞其詳。
若有閱讀後引起內心衝突或者憤怒等不適以及自覺被誤導者,不需要切換到抖音等歡樂頻道進行綜合調理,直接就可以在評論區吐槽。
閘道器簡介
什麼是閘道器,為什麼用閘道器。這些問題網上有很多文章,講解的非常全面。這裡就不做重複的講解了。
但後面的內容至少需要了解閘道器下面兩點。
API閘道器是一個伺服器,是系統的唯一入口。
API閘道器方式的核心要點是,所有的客戶端和消費端都通過統一的閘道器接入微服務,在閘道器層處理所有的非業務功能(提供監控、鑑權、負載均衡等)。
預設實現
下面演示的專案使用vs2019,Asp.Net Core 2.1開發
1、建立一個ASP.NET Core API專案Agile.Demo1.API,使用Swagger作為線上UI展示
專案結構如圖1
圖1
釋出並且執行,為了方面啟動執行,寫了個批處理指令碼,如圖2
圖2
直接雙擊start執行如圖3
圖3
瀏覽器開啟顯示效果如圖4
圖4
直接Swagger文件線上測試各個介面正常。
2、建立一個ASP.NET Core API專案Agile.Demo2.API 與Agile.Demo1.API專案類似。
3、建立一個基於ocelot的閘道器服務,專案結構如圖5
圖5
這裡使用Ocelot來做閘道器,Ocelot是一堆特定順序的中介軟體
配置ocelot.json,配置內容如下
{ "ReRoutes": [ //API01 業務介面1 { "DownstreamPathTemplate": "/{url}", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "127.0.0.1", "Port": 9001 } ], "UpstreamPathTemplate": "/demo1/{url}", "UpstreamHttpMethod": [ "Post", "Get" ], "ReRoutesCaseSensitive": false }, //API02 業務介面2 { "DownstreamPathTemplate": "/{url}", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "127.0.0.1", "Port": 9002 } ], "UpstreamPathTemplate": "/demo2/{url}", "UpstreamHttpMethod": [ "Post", "Get" ], "ReRoutesCaseSensitive": false } ] }
這個配置比較簡單,就配置了兩個下游的業務介面。
把兩個業務介面站點和閘道器站點都執行起來,如圖6
圖6
使用postman直接測試demo1 裡面的 saveorder介面,如圖7
圖7
使用postman直接測試demo2 裡面的 saveorder介面,如圖8
圖8
使用postman通過閘道器訪問demo1,如圖9
圖9
能正常返回資料,說明閘道器的轉發正常。
通過閘道器訪問demo2也類似,這裡就不截圖了。下面提供demo程式碼可以下載自己測試下。
這裡只介紹,通過閘道器的轉發,其他閘道器方面的更多應用不在這裡做介紹。
新的問題
有一次,我們提供介面和其他部門對接。按照慣例把介面以及閘道器部署好,文件提供,讓他們按照文件規定的傳就可以了。
結果,他們看了文件後提出了疑問,這是什麼閘道器。每個介面請求地址還得拼接出來作為完整的請求,我們程式碼要做很多調整啊。能不能做成支付寶那種,就一個地址固定不變,然後公共引數,業務引數封裝的模式。因為這種模式封裝的東西都有現成的,這樣我們就不用很大的改動就可以快速對接了。看下支付寶介面,如圖10
圖10
我想你這公共引數還不是動態的,相當於原來我們提供的閘道器地址後面加的就是對應的動態資料,道理都一樣的啊,但受阿里系影響,他們介面的開發還是對接都是習慣按照支付寶這種模式來的,封裝的公共引數什麼的都做好了,要調整很麻煩。介面不按照他們的樣子來就彆扭,增加他們工作量。
當時我想這怎麼辦,我出介面應該按照我們的要求來啊,但沒辦法不夠強勢,還得按照他們阿里系規則來,那就想辦法吧。
想到ocelot也是一系列的中介軟體處理 的,我想那就增加一箇中介軟體,把請求給攔截了,重新組合資料,再下發。
這樣可以保證我們內部的呼叫不變,對外相容這種請求方式。說幹就幹,先做個demo試驗下能否行得通。
增加一箇中介軟體GatewayMiddleware,程式碼如下,既然要按照支付寶介面的來,那乾脆把公共引數這塊整體搬過來。
// You may need to install the Microsoft.AspNetCore.Http.Abstractions package into your project
public class GatewayMiddleware
{
private readonly RequestDelegate _next;
public GatewayMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext)
{
//支援類似支付寶的閘道器模式
//公共請求引數
//app_id method format charset sign_type sign timestamp version app_auth_token biz_content
//請求引數(業務),建議json格式
//trade_no out_trade_no operator_id
if (httpContext.Request.Path.ToString().ToLower() == "/gateway"|| httpContext.Request.Path.ToString().ToLower() == "/gateway.do")
{
//呼叫方post form提交,獲取公共請求引數,處理做轉發
if (httpContext.Request.Method.ToLower() == "get")
{
httpContext.Response.ContentType = "text/plain;charset=utf-8";
await httpContext.Response.WriteAsync("除錯錯誤,請回到請求來源地,重新發起請求");
return;
}
var reqForm = httpContext.Request.Form;
if (reqForm == null || reqForm.Count == 0)
{
httpContext.Response.ContentType = "text/plain;charset=utf-8";
await httpContext.Response.WriteAsync("除錯錯誤,請回到請求來源地,重新發起請求");
return;
}
var app_id = reqForm.ContainsKey("appid") ? reqForm["appid"].ToString() : "";
var method = reqForm.ContainsKey("method") ? reqForm["method"].ToString() : "";//介面名稱(格式:模組.控制器.方法) 比如demo1.Values.SaveOrder
var format = reqForm.ContainsKey("format") ? reqForm["format"].ToString() : "json";
var charset = reqForm.ContainsKey("charset") ? reqForm["charset"].ToString() : "utf-8";
var sign_type = reqForm.ContainsKey("sign_type") ? reqForm["sign_type"].ToString() : "md5";
var sign = reqForm.ContainsKey("sign") ? reqForm["sign"].ToString() : "";
var timestamp = reqForm.ContainsKey("timestamp") ? reqForm["timestamp"].ToString() : "";
var version = reqForm.ContainsKey("version") ? reqForm["version"].ToString() : "";
var app_auth_token = reqForm.ContainsKey("app_auth_token") ? reqForm["app_auth_token"].ToString() : "";
var biz_content = reqForm.ContainsKey("biz_content") ? reqForm["biz_content"].ToString() : "";//業務介面引數 json格式
//通過method引數拆分出 模組 控制器 方法
var methods = method.Split('.');
var moduleName = method.Length > 0 ? methods[0] : "";
var controllerName = method.Length > 1 ? methods[1] : "";
var actionName = method.Length > 2 ? methods[2] : "";
//區分有版本和無版本兩種情況,version不傳或傳空就是無版本
var nextPath = string.IsNullOrEmpty(version) ? $"/{moduleName}/api/{controllerName}/{actionName}" : $"/{moduleName}/api/v{version}/{controllerName}/{actionName}";
//下游業務介面暫時只支援post json格式的請求
byte[] postData = Encoding.GetEncoding(charset).GetBytes(biz_content);
httpContext.Request.Path = nextPath;
httpContext.Request.ContentType = "application/json";
httpContext.Request.ContentLength = postData.Length;
httpContext.Request.Body = new MemoryStream(postData);
await _next(httpContext);
}
else
{
await _next(httpContext);
}
}
}
// Extension method used to add the middleware to the HTTP request pipeline.
public static class GatewayMiddlewareExtensions
{
public static void UseGatewayMiddleware(this IApplicationBuilder app)
{
app.UseMiddleware<GatewayMiddleware>();
}
}
Startup.cs增加如下程式碼,如圖11
測試攔截成功,重新組裝下發。能夠正常返回,測試成功。
具體的操作看程式碼的說明,這裡就不再贅述。
這裡有一點要特別說明。因為公共引數是form表單post提交,所以呼叫方請求過來肯定是post方式。轉到下游的時候這個請求型別沒有改變,所有暫時只支援下游是post的介面。不過可以增加個引數或者使用format引數值來做區分下游具體是get還是post。因為現在format是json肯定只能是支援post。
測試聯調
以訪問demo1為例,這裡有三種方式訪問demo1,使用postman測試如下
1、直接訪問,如上面圖7
2、通過閘道器轉發方式1,如上面圖9
3、通過閘道器轉發方式2,如圖12
圖12
使用這種方式還是有優勢的,比如引數簽名這塊就可以從業務裡面獨立出來,在閘道器處理了。
總結
說服不了,就多幹活,多想方案。
由於一個檔案最大10M,這裡拆開上傳
APIDemo1程式碼下載,APIDemo2程式碼下載,APIGateway程式碼下載
感謝閱讀,希望這篇文章能給你帶來幫助!