【.NET Core项目实战-统一认证平台】第三章 网关篇-数据库存储配置(1)

【.NET Core项目实战-统一认证平台】开篇及目录索引

本篇将介绍如何扩展Ocelot中间件实现自定义网关,并使用2种不一样数据库来演示Ocelot配置信息存储和动态更新功能,内容也是从实际设计出发来编写咱们本身的中间件,本文内容涵盖设计思想内容和代码内容,我但愿园友们最好跟着我这个文章的思路先理解好后再看源代码,这样有利于融会贯通,本篇的文档及源码将会在GitHub上开源,每篇的源代码我将用分支的方式管理,本篇使用的分支为course1html

附文档及源码下载地址:[https://github.com/jinyancao/CtrAuthPlatform/tree/course1]mysql

1、数据库设计

上一篇中咱们介绍了Ocelot中要知足咱们需求,咱们须要把配置信息转到数据库存储,今天咱们就从数据库设计开始,数据库设计我采用的是PowerDesigner,首先打开软件,新建一个概念模型。根据Ocelot的配置文件,咱们能够发现,配置信息由全局配置信息和路由信息组成,这时候咱们能够设计表结构以下,为了知足后续多个路由的切换,增长了网关和路由多对多关系,之后咱们能够随时根据不一样规则切换,详细的表字段能够自行根据Ocelot配置文档和设计文档对照查看,这里我移除了限流的字段,由于咱们后续须要自定义限流,用不上原来的方法。
git

生成物理模型
数据库设计好后,咱们须要把概念模型转成物理模型,使用Ctrl+Shift+P快捷键,咱们默认使用MSSQL2008R2实现配置存储,全部在弹出的对话框中选择,而后点击确认后会自动生成MSSQL2008R2的物理模型,能够看到数据类型和表之间的关连关系都生成好了,奈斯,一切都是那么完美,若是主键为自增类型,手动标记下便可。


github

如今咱们须要生成咱们建立数据库的SQL脚本了,别忘了保存下刚才生成的物理模型,由于之后还须要用到。sql

生成数据库脚本数据库

如图所示,可使用快捷键Ctrl+G生成数据库脚本,点击确认生成并保存,而后把生成的脚本在咱们新建的数据库里执行,这样咱们的数据库就设计完成了。

json

2、搭建并测试中间件

咱们使用VS2017新建一个.NETCORE2.1项目,而后新建一个类库来实现咱们Ocelot定制版中间件,建好后项目结构以下,如今开始咱们第一个AhphOcelot定制中间件编写。
c#

首先咱们回顾下【.NET Core项目实战-统一认证平台】第二章网关篇-重构Ocelot来知足需求的源码解析,关于配置信息的读取以下,咱们只须要重写下CreateConfiguration方法实现从数据库里取就能够了,既然有思路了,api

public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder builder, OcelotPipelineConfiguration pipelineConfiguration)
{  
    //建立配置信息
    var configuration = await CreateConfiguration(builder);
    ConfigureDiagnosticListener(builder);
    return CreateOcelotPipeline(builder, pipelineConfiguration);
}

那就开始改造吧,咱们新建一个Ctr.AhphOcelot类库,来实现这个中间件,首先新建自定义中间件扩展,这个扩展是在原有的Ocelot的基础上进行改造,因此须要先在Nuget中安装Ocelot,这系列课程咱们以最新的Ocelot 12.0.1版本进行扩展。
浏览器

首先咱们要了解,Ocelot的配置信息是怎么加载进来的呢?

private static async Task<IInternalConfiguration> CreateConfiguration(IApplicationBuilder builder)
{
    // make configuration from file system?
    // earlier user needed to add ocelot files in startup configuration stuff, asp.net will map it to this
    var fileConfig = builder.ApplicationServices.GetService<IOptionsMonitor<FileConfiguration>>();

    // now create the config
    var internalConfigCreator = builder.ApplicationServices.GetService<IInternalConfigurationCreator>();
    var internalConfig = await internalConfigCreator.Create(fileConfig.CurrentValue);
    //Configuration error, throw error message
    if (internalConfig.IsError)
    {
        ThrowToStopOcelotStarting(internalConfig);
    }

    // now save it in memory
    var internalConfigRepo = builder.ApplicationServices.GetService<IInternalConfigurationRepository>();
    internalConfigRepo.AddOrReplace(internalConfig.Data);

    fileConfig.OnChange(async (config) =>
                        {
                            var newInternalConfig = await internalConfigCreator.Create(config);
                            internalConfigRepo.AddOrReplace(newInternalConfig.Data);
                        });

    var adminPath = builder.ApplicationServices.GetService<IAdministrationPath>();

    var configurations = builder.ApplicationServices.GetServices<OcelotMiddlewareConfigurationDelegate>();

    // Todo - this has just been added for consul so far...will there be an ordering problem in the future? Should refactor all config into this pattern?
    foreach (var configuration in configurations)
    {
        await configuration(builder);
    }

    if(AdministrationApiInUse(adminPath))
    {
        //We have to make sure the file config is set for the ocelot.env.json and ocelot.json so that if we pull it from the 
        //admin api it works...boy this is getting a spit spags boll.
        var fileConfigSetter = builder.ApplicationServices.GetService<IFileConfigurationSetter>();

        await SetFileConfig(fileConfigSetter, fileConfig);
    }

    return GetOcelotConfigAndReturn(internalConfigRepo);
}

查看源码后发现是是从OcelotBuilder加载的配置文件,也就是最先的AddOcelot()方法时注入的。

public OcelotBuilder(IServiceCollection services, IConfiguration configurationRoot)
{
    Configuration = configurationRoot;
    Services = services;
    //服务注册,可使用IOptions<FileConfiguration>调用
    Services.Configure<FileConfiguration>(configurationRoot);
    ....
}

如今咱们要实现从数据库提取配置信息,能够查看下Ocelot是否给咱们提供了相关扩展接口,经过Ctrl+F查找FileConfiguration实体在哪些地方能够返回,IFileConfigurationRepository接口一眼就能认出,配置文件仓储类,咱们能够重写这个接口实现便可完成配置文件从数据库提取,果真Ocelot是为定制而生,其实若是没有这个接口问题也不大,咱们本身去定义和实现这个接口也同样能够完成。

using System.Threading.Tasks;
using Ocelot.Configuration.File;
using Ocelot.Responses;

namespace Ocelot.Configuration.Repository
{
    public interface IFileConfigurationRepository
    {
        Task<Response<FileConfiguration>> Get();
        Task<Response> Set(FileConfiguration fileConfiguration);
    }
}

咱们看看这个接口是否有默认实现,DiskFileConfigurationRepository方法实现了这个接口,经过名称就知道是直接从配置文件提取配置信息,再看下这个接口应用到哪里,继续Ctrl+F找到,FileConfigurationPollerFileAndInternalConfigurationSetter两个地方用到了这个接口,其中FileConfigurationPoller实现了IHostedService后台任务,咱们不难看出,这个是一个定时更新任务,实际咱们配置信息变动,确定由管理员本身修改测试无误后发起,这里咱们用不上,可是实现思路能够了解下。FileAndInternalConfigurationSetter是配置文件更新方法,这里咱们若是使用数据库存储,更新确定由咱们本身管理界面更新,因此也用不上,这时有人会问,那若是配置文件发生变动了,咱们怎么去更新。这时候咱们须要了解配置信息在哪里使用,是否使用了缓存。其实上面也给出了答案,就是IInternalConfiguration.

// now create the config
var internalConfigCreator = builder.ApplicationServices.GetService<IInternalConfigurationCreator>();
var internalConfig = await internalConfigCreator.Create(fileConfig.CurrentValue);

如今问题都梳理清楚了,如今咱们实现的思路就是,首先经过数据库实现IFileConfigurationRepository接口内容(更新不须要实现,前面说过了),而后再咱们数据库里修改了配置,更新IInternalConfiguration配置信息,便可完成咱们的自定义任何地方的存储。

开发的思路就是顶层开始一步一步往下实现,最后完成咱们的扩展。如今回到咱们本身的代码,修改配置信息代码以下,是否是精简不少了,可是有2个问题未解决,一是须要实现IFileConfigurationRepository,二是还没实现动态更新。

private static async Task<IInternalConfiguration> CreateConfiguration(IApplicationBuilder builder)
{
    //提取文件配置信息
    var fileConfig = await builder.ApplicationServices.GetService<IFileConfigurationRepository>().Get();
    var internalConfigCreator = builder.ApplicationServices.GetService<IInternalConfigurationCreator>();
    var internalConfig = await internalConfigCreator.Create(fileConfig.Data);
    //若是配置文件错误直接抛出异常
    if (internalConfig.IsError)
    {
        ThrowToStopOcelotStarting(internalConfig);
    }
    //配置信息缓存,这块须要注意实现方式,由于后期咱们须要改造下知足分布式架构,这篇不作讲解
    var internalConfigRepo = builder.ApplicationServices.GetService<IInternalConfigurationRepository>();
    internalConfigRepo.AddOrReplace(internalConfig.Data);
    return GetOcelotConfigAndReturn(internalConfigRepo);
}

一、实现IFileConfigurationRepository接口

本系列全部课程都是基于轻量级的ORM框架dapper实现

首先须要NuGet包里添加Dapper,而后咱们须要把设计的表生成实体,至于如何生成这里就不介绍了,实现方式不少,相关的帖子不少。使用Dapper时,咱们须要知道知道链接方式,这时须要在中间件的基础上扩充一个配置文件接收配置数据,这样咱们才能使用配置的信息内容。

namespace Ctr.AhphOcelot.Configuration
{
    /// <summary>
    /// 金焰的世界
    /// 2018-11-11
    /// 自定义配置信息
    /// </summary>
    public class AhphOcelotConfiguration
    {
        /// <summary>
        /// 数据库链接字符串
        /// </summary>
        public string DbConnectionStrings { get; set; }
    }
}

如今能够实现接口了,详细代码以下,代码很简单,就是从数据库查询出录入的内容,使用dapper实现。

using Ctr.AhphOcelot.Configuration;
using Ctr.AhphOcelot.Model;
using Dapper;
using Ocelot.Configuration.File;
using Ocelot.Configuration.Repository;
using Ocelot.Responses;
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Text;
using System.Threading.Tasks;

namespace Ctr.AhphOcelot.DataBase.SqlServer
{
    /// <summary>
    /// 金焰的世界
    /// 2018-11-11
    /// 使用SqlServer来实现配置文件仓储接口
    /// </summary>
    public class SqlServerFileConfigurationRepository : IFileConfigurationRepository
    {
        private readonly AhphOcelotConfiguration _option;
        public SqlServerFileConfigurationRepository(AhphOcelotConfiguration option)
        {
            _option = option;
        }

        /// <summary>
        /// 从数据库中获取配置信息
        /// </summary>
        /// <returns></returns>
        public async Task<Response<FileConfiguration>> Get()
        {
            #region 提取配置信息
            var file = new FileConfiguration();
            //提取默认启用的路由配置信息
            string glbsql = "select * from AhphGlobalConfiguration where IsDefault=1 and InfoStatus=1";
            //提取全局配置信息
            using (var connection = new SqlConnection(_option.DbConnectionStrings))
            {
                var result = await connection.QueryFirstOrDefaultAsync<AhphGlobalConfiguration>(glbsql);
                if (result != null)
                {
                    var glb = new FileGlobalConfiguration();
                    //赋值全局信息
                    glb.BaseUrl = result.BaseUrl;
                    glb.DownstreamScheme = result.DownstreamScheme;
                    glb.RequestIdKey = result.RequestIdKey;
                    glb.HttpHandlerOptions = result.HttpHandlerOptions?.ToObject<FileHttpHandlerOptions>();
                    glb.LoadBalancerOptions = result.LoadBalancerOptions?.ToObject<FileLoadBalancerOptions>();
                    glb.QoSOptions = result.QoSOptions?.ToObject<FileQoSOptions>();
                    glb.ServiceDiscoveryProvider = result.ServiceDiscoveryProvider?.ToObject<FileServiceDiscoveryProvider>();
                    file.GlobalConfiguration = glb;

                    //提取全部路由信息
                    string routesql = "select T2.* from AhphConfigReRoutes T1 inner join AhphReRoute T2 on T1.ReRouteId=T2.ReRouteId where AhphId=@AhphId and InfoStatus=1";
                    var routeresult = (await connection.QueryAsync<AhphReRoute>(routesql, new { result.AhphId }))?.AsList();
                    if (routeresult != null && routeresult.Count > 0)
                    {
                        var reroutelist = new List<FileReRoute>();
                        foreach (var model in routeresult)
                        {
                            var m = new FileReRoute();
                            m.AuthenticationOptions = model.AuthenticationOptions?.ToObject<FileAuthenticationOptions>();
                            m.FileCacheOptions = model.CacheOptions?.ToObject<FileCacheOptions>();
                            m.DelegatingHandlers = model.DelegatingHandlers?.ToObject<List<string>>();
                            m.LoadBalancerOptions = model.LoadBalancerOptions?.ToObject<FileLoadBalancerOptions>();
                            m.QoSOptions = model.QoSOptions?.ToObject<FileQoSOptions>();
                            m.DownstreamHostAndPorts = model.DownstreamHostAndPorts?.ToObject<List<FileHostAndPort>>();
                            //开始赋值
                            m.DownstreamPathTemplate = model.DownstreamPathTemplate;
                            m.DownstreamScheme = model.DownstreamScheme;
                            m.Key = model.RequestIdKey;
                            m.Priority = model.Priority ?? 0;
                            m.RequestIdKey = model.RequestIdKey;
                            m.ServiceName = model.ServiceName;
                            m.UpstreamHost = model.UpstreamHost;
                            m.UpstreamHttpMethod = model.UpstreamHttpMethod?.ToObject<List<string>>();
                            m.UpstreamPathTemplate = model.UpstreamPathTemplate;
                            reroutelist.Add(m);
                        }
                        file.ReRoutes = reroutelist;
                    }
                }
                else
                {
                    throw new Exception("未监测到任何可用的配置信息");
                }
            }
            #endregion
            if (file.ReRoutes == null || file.ReRoutes.Count == 0)
            {
                return new OkResponse<FileConfiguration>(null);
            }
            return new OkResponse<FileConfiguration>(file);
        }

        //因为数据库存储可不实现Set接口直接返回
        public async Task<Response> Set(FileConfiguration fileConfiguration)
        {
            return new OkResponse();
        }
    }
}

如今又延伸出两个问题.第一个是AhphOcelotConfiguration这个信息从哪读取的?第二是SqlServerFileConfigurationRepository在哪注入。

其实读过我前面中间件源码解析的同窗可能已经知道了,就是在AddOcelot里注入的,如今咱们就可使用相同的方式实现本身的扩展。添加本身的ServiceCollectionExtensions扩展。

using Ctr.AhphOcelot.Configuration;
using Ctr.AhphOcelot.DataBase.SqlServer;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Ocelot.Configuration.Repository;
using Ocelot.DependencyInjection;
using System;

namespace Ctr.AhphOcelot.Middleware
{
    /// <summary>
    /// 金焰的世界
    /// 2018-11-11
    /// 扩展Ocelot实现的自定义的注入
    /// </summary>
    public static class ServiceCollectionExtensions
    {
        /// <summary>
        /// 添加默认的注入方式,全部须要传入的参数都是用默认值
        /// </summary>
        /// <param name="builder"></param>
        /// <returns></returns>
        public static IOcelotBuilder AddAhphOcelot(this IOcelotBuilder builder, Action<AhphOcelotConfiguration> option)
        {
            builder.Services.Configure(option);
            //配置信息
            builder.Services.AddSingleton(
                resolver => resolver.GetRequiredService<IOptions<AhphOcelotConfiguration>>().Value);
            //配置文件仓储注入
            builder.Services.AddSingleton<IFileConfigurationRepository, SqlServerFileConfigurationRepository>();
            return builder;
        }
    }
}

有木有很简单呢?到这里从数据库中提取配置信息都完成啦,如今咱们开始来测试下,看是否知足了咱们的需求。

新建一个Ctr.AuthPlatform.Gateway网关项目,添加咱们的中间件项目引用,修改Startup.cs代码以下

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Ocelot.DependencyInjection;
using Ctr.AhphOcelot.Middleware;
namespace Ctr.AuthPlatform.Gateway
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddOcelot().AddAhphOcelot(option=>
            {
                option.DbConnectionStrings = "Server=.;Database=Ctr_AuthPlatform;User ID=sa;Password=bl123456;";
            });
        }
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
            }
            app.UseAhphOcelot().Wait();
        }
    }
}

就实现了自定义的网关,是否是很优雅呢?可是是否达到了咱们预期的网关效果了,咱们来直接从数据库里插入测试数据,并新建一个测试项目。测试数据脚本以下

--插入全局测试信息
insert into AhphGlobalConfiguration(GatewayName,RequestIdKey,IsDefault,InfoStatus)
values('测试网关','test_gateway',1,1);

--插入路由分类测试信息
insert into AhphReRoutesItem(ItemName,InfoStatus) values('测试分类',1);

--插入路由测试信息 
insert into AhphReRoute values(1,'/ctr/values','[ "GET" ]','','http','/api/Values','[{"Host": "localhost","Port": 9000 }]',
'','','','','','','',0,1);

--插入网关关联表
insert into dbo.AhphConfigReRoutes values(1,1);

测试项目结构以下,就是默认的一个api项目,修改下启动端口为9000。

为了方便调试.NETCORE项目,我建议使用dotnet run方式,分别启动网关(7777端口)和测试服务(9999端口)。优先启动网关项目,想想还有点小激动呢,开始运行项目,纳尼,尽然报错,并且是熟悉的未将对象引用到实例化错误,根据异常内容能够看到是在验证的时候报错,咱们能够查看下Ocelot对应的源代码,发现问题所在了。

咱们在一些未定义的配置项目使用了为空的赋值。而Ocleot里面对于很多配置项目未作非空验证。好比RateLimitOptionsCreator对于FileGlobalConfiguration未作非空验证,相似这样的地方还有很多,我但愿下次Ocelot更新时最好增长这类非空验证,这样便于自定义扩展,而Ocelot内部实现了默认实例化,因此咱们以前从数据库取值赋值时写法须要改进,修改后的代码以下。

using Ctr.AhphOcelot.Configuration;
using Ctr.AhphOcelot.Model;
using Dapper;
using Ocelot.Configuration.File;
using Ocelot.Configuration.Repository;
using Ocelot.Responses;
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Text;
using System.Threading.Tasks;

namespace Ctr.AhphOcelot.DataBase.SqlServer
{
    /// <summary>
    /// 金焰的世界
    /// 2018-11-11
    /// 使用SqlServer来实现配置文件仓储接口
    /// </summary>
    public class SqlServerFileConfigurationRepository : IFileConfigurationRepository
    {
        private readonly AhphOcelotConfiguration _option;
        public SqlServerFileConfigurationRepository(AhphOcelotConfiguration option)
        {
            _option = option;
        }

        /// <summary>
        /// 从数据库中获取配置信息
        /// </summary>
        /// <returns></returns>
        public async Task<Response<FileConfiguration>> Get()
        {
            #region 提取配置信息
            var file = new FileConfiguration();
            //提取默认启用的路由配置信息
            string glbsql = "select * from AhphGlobalConfiguration where IsDefault=1 and InfoStatus=1";
            //提取全局配置信息
            using (var connection = new SqlConnection(_option.DbConnectionStrings))
            {
                var result = await connection.QueryFirstOrDefaultAsync<AhphGlobalConfiguration>(glbsql);
                if (result != null)
                {
                    var glb = new FileGlobalConfiguration();
                    //赋值全局信息
                    glb.BaseUrl = result.BaseUrl;
                    glb.DownstreamScheme = result.DownstreamScheme;
                    glb.RequestIdKey = result.RequestIdKey;
                    //glb.HttpHandlerOptions = result.HttpHandlerOptions?.ToObject<FileHttpHandlerOptions>();
                    //glb.LoadBalancerOptions = result.LoadBalancerOptions?.ToObject<FileLoadBalancerOptions>();
                    //glb.QoSOptions = result.QoSOptions?.ToObject<FileQoSOptions>();
                    //glb.ServiceDiscoveryProvider = result.ServiceDiscoveryProvider?.ToObject<FileServiceDiscoveryProvider>();
                    if (!String.IsNullOrEmpty(result.HttpHandlerOptions))
                    {
                        glb.HttpHandlerOptions = result.HttpHandlerOptions.ToObject<FileHttpHandlerOptions>();
                    }
                    if (!String.IsNullOrEmpty(result.LoadBalancerOptions))
                    {
                        glb.LoadBalancerOptions = result.LoadBalancerOptions.ToObject<FileLoadBalancerOptions>();
                    }
                    if (!String.IsNullOrEmpty(result.QoSOptions))
                    {
                        glb.QoSOptions = result.QoSOptions.ToObject<FileQoSOptions>();
                    }
                    if (!String.IsNullOrEmpty(result.ServiceDiscoveryProvider))
                    {
                        glb.ServiceDiscoveryProvider = result.ServiceDiscoveryProvider.ToObject<FileServiceDiscoveryProvider>();
                    }
                    file.GlobalConfiguration = glb;

                    //提取全部路由信息
                    string routesql = "select T2.* from AhphConfigReRoutes T1 inner join AhphReRoute T2 on T1.ReRouteId=T2.ReRouteId where AhphId=@AhphId and InfoStatus=1";
                    var routeresult = (await connection.QueryAsync<AhphReRoute>(routesql, new { result.AhphId }))?.AsList();
                    if (routeresult != null && routeresult.Count > 0)
                    {
                        var reroutelist = new List<FileReRoute>();
                        foreach (var model in routeresult)
                        {
                            var m = new FileReRoute();
                            //m.AuthenticationOptions = model.AuthenticationOptions?.ToObject<FileAuthenticationOptions>();
                            //m.FileCacheOptions = model.CacheOptions?.ToObject<FileCacheOptions>();
                            //m.DelegatingHandlers = model.DelegatingHandlers?.ToObject<List<string>>();
                            //m.LoadBalancerOptions = model.LoadBalancerOptions?.ToObject<FileLoadBalancerOptions>();
                            //m.QoSOptions = model.QoSOptions?.ToObject<FileQoSOptions>();
                            //m.DownstreamHostAndPorts = model.DownstreamHostAndPorts?.ToObject<List<FileHostAndPort>>();
                            if (!String.IsNullOrEmpty(model.AuthenticationOptions))
                            {
                                m.AuthenticationOptions = model.AuthenticationOptions.ToObject<FileAuthenticationOptions>();
                            }
                            if (!String.IsNullOrEmpty(model.CacheOptions))
                            {
                                m.FileCacheOptions = model.CacheOptions.ToObject<FileCacheOptions>();
                            }
                            if (!String.IsNullOrEmpty(model.DelegatingHandlers))
                            {
                                m.DelegatingHandlers = model.DelegatingHandlers.ToObject<List<string>>();
                            }
                            if (!String.IsNullOrEmpty(model.LoadBalancerOptions))
                            {
                                m.LoadBalancerOptions = model.LoadBalancerOptions.ToObject<FileLoadBalancerOptions>();
                            }
                            if (!String.IsNullOrEmpty(model.QoSOptions))
                            {
                                m.QoSOptions = model.QoSOptions.ToObject<FileQoSOptions>();
                            }
                            if (!String.IsNullOrEmpty(model.DownstreamHostAndPorts))
                            {
                                m.DownstreamHostAndPorts = model.DownstreamHostAndPorts.ToObject<List<FileHostAndPort>>();
                            }
                            //开始赋值
                            m.DownstreamPathTemplate = model.DownstreamPathTemplate;
                            m.DownstreamScheme = model.DownstreamScheme;
                            m.Key = model.RequestIdKey;
                            m.Priority = model.Priority ?? 0;
                            m.RequestIdKey = model.RequestIdKey;
                            m.ServiceName = model.ServiceName;
                            m.UpstreamHost = model.UpstreamHost;
                            m.UpstreamHttpMethod = model.UpstreamHttpMethod?.ToObject<List<string>>();
                            m.UpstreamPathTemplate = model.UpstreamPathTemplate;
                            reroutelist.Add(m);
                        }
                        file.ReRoutes = reroutelist;
                    }
                }
                else
                {
                    throw new Exception("未监测到任何可用的配置信息");
                }
            }
            #endregion
            if (file.ReRoutes == null || file.ReRoutes.Count == 0)
            {
                return new OkResponse<FileConfiguration>(null);
            }
            return new OkResponse<FileConfiguration>(file);
        }

        //因为数据库存储可不实现Set接口直接返回
        public async Task<Response> Set(FileConfiguration fileConfiguration)
        {
            return new OkResponse();
        }
    }
}

而后从新运行,网关启动成功。

接着咱们启动咱们测试的服务,而后浏览器先访问http://localhost:9000/api/values地址,测试地址正常访问。

而后使用测试网关路由地址访问http://localhost:7777/ctr/values,显示内容和本地访问同样,证实网关路由生效,是否是有点小激动呢?咱们完成了从配置信息中取网关路由信息扩展。

3、下篇预告

最后咱们回顾下这篇内容,我是从设计到实现一步一步讲解和实现的,并且实现过程是根据需求慢慢剖析再局部实现的,我发现如今不少人在平时学习基本都是结果未导向,不多去关心中间的实现过程,长此以往基本就会丧失解决问题的思路,写的这么详细,也是但愿给你们一个解决问题的思路,目前咱们实现了从数据库中提取配置信息并在网关中生效,可是还未实现动态更新和扩展其余数据库存储,你们也能够先本身尝试如何实现。

下一篇咱们将会实现网关路由的动态更新,会提供几种更新思路,根据实际状况择优选择。而后在使用Mysql数据库来存储配置信息,并扩展此网关实现很优雅的配置,为何使用mysql扩展实现呢?由于.netcore已经跨平台啦,后期咱们准备在Centos下实现容器化部署,这时咱们就准备以mysql为例进行讲解,本网关全部内容源码都会实现sqlserver和mysql两种方式,其余存储方式可自行扩展便可。

最后项目全部的文档在源码的文档目录,文档按照课程源码文件夹区分,本文的文档标识course1

个人博客即将同步至腾讯云+社区,邀请你们一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=l0q6lfr3asgg

相关文章
相关标签/搜索