zoukankan      html  css  js  c++  java
  • 重复造轮子系列——基于Ocelot实现类似支付宝接口模式的网关

    重复造轮子系列——基于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代码下载

     

     

    感谢阅读,希望这篇文章能给你带来帮助!

  • 相关阅读:
    360安全桌面的屏保图片在哪个目录下?
    IntelliJ 注解@Slf4j后找不到log问题解决
    运行springboot项目报错:Field userMapper in XX required a bean of type 'xx' that could not be found.
    springsecurity
    Error creating bean with name 'sqlSessionFactory' defined in class path resource [config/spring/applicationContext.xml]: Invocation of init method failed;
    idea热部署
    数据结构导论 三 线性表-顺序存储
    数据结构导论 二 时空性
    树莓派安装、卸载、更新软件
    数据结构导论第一章
  • 原文地址:https://www.cnblogs.com/sylla/p/11248027.html
Copyright © 2011-2022 走看看