重复造轮子系列是本身平时的一些总结。有的轮子依赖社区提供的轮子为基础,这里把使用过程的一些以为有意思的作个分享。有些思路或者方法在大神看来可能会比较low。可是能解决实际问题,相信有须要的人也在寻找相似的解决方案。这里能够算做是提供了一种思路,相似问题若是有读者能有更好的解决方案,愿闻其详。json
如有阅读后引发心里冲突或者愤怒等不适以及自觉被误导者,不须要切换到抖音等欢乐频道进行综合调理,直接就能够在评论区吐槽。api
什么是网关,为何用网关。这些问题网上有不少文章,讲解的很是全面。这里就不作重复的讲解了。浏览器
但后面的内容至少须要了解网关下面两点。服务器
API网关是一个服务器,是系统的惟一入口。app
API网关方式的核心要点是,全部的客户端和消费端都经过统一的网关接入微服务,在网关层处理全部的非业务功能(提供监控、鉴权、负载均衡等)。负载均衡
下面演示的项目使用vs2019,Asp.Net Core 2.1开发async
一、建立一个ASP.NET Core API项目Agile.Demo1.API,使用Swagger做为在线UI展现微服务
项目结构如图1post
图1测试
发布而且运行,为了方面启动运行,写了个批处理脚本,如图2
图2
直接双击start运行如图3
图3
浏览器打开显示效果如图4
图4
直接Swagger文档在线测试各个接口正常。
二、建立一个ASP.NET Core API项目Agile.Demo2.API 与Agile.Demo1.API项目相似。
三、建立一个基于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测试以下
一、直接访问,如上面图7
二、经过网关转发方式1,如上面图9
三、经过网关转发方式2,如图12
图12
使用这种方式仍是有优点的,好比参数签名这块就能够从业务里面独立出来,在网关处理了。
说服不了,就多干活,多想方案。
因为一个文件最大10M,这里拆开上传
APIDemo1代码下载,APIDemo2代码下载,APIGateway代码下载
感谢阅读,但愿这篇文章能给你带来帮助!