重複造輪子系列——基於Ocelot實現類似支付寶介面模式的閘道器

sylla發表於2019-07-26

重複造輪子系列——基於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程式碼下載

 

 

感謝閱讀,希望這篇文章能給你帶來幫助!

相關文章