1. 程式人生 > >【.NET Core專案實戰-統一認證平臺】第三章 閘道器篇-資料庫儲存配置(1)

【.NET Core專案實戰-統一認證平臺】第三章 閘道器篇-資料庫儲存配置(1)

原文: 【.NET Core專案實戰-統一認證平臺】第三章 閘道器篇-資料庫儲存配置(1)

【.NET Core專案實戰-統一認證平臺】開篇及目錄索引

本篇將介紹如何擴充套件Ocelot中介軟體實現自定義閘道器,並使用2種不同資料庫來演示Ocelot配置資訊儲存和動態更新功能,內容也是從實際設計出發來編寫我們自己的中介軟體,本文內容涵蓋設計思想內容和程式碼內容,我希望園友們最好跟著我這個文章的思路先理解好後再看原始碼,這樣有利於融會貫通,本篇的文件及原始碼將會在GitHub上開源,每篇的原始碼我將用分支的方式管理,本篇使用的分支為course1

附文件及原始碼下載地址:[https://github.com/jinyancao/CtrAuthPlatform/tree/course1

]

一、資料庫設計

上一篇中我們介紹了Ocelot中要滿足我們需求,我們需要把配置資訊轉到資料庫儲存,今天我們就從資料庫設計開始,資料庫設計我採用的是PowerDesigner,首先開啟軟體,新建一個概念模型。根據Ocelot的配置檔案,我們可以發現,配置資訊由全域性配置資訊和路由資訊組成,這時候我們可以設計表結構如下,為了滿足後續多個路由的切換,增加了閘道器和路由多對多關係,以後我們可以隨時根據不同規則切換,詳細的表字段可以自行根據Ocelot配置文件和設計文件對照檢視,這裡我移除了限流的欄位,因為我們後續需要自定義限流,用不上原來的方法。

生成物理模型
資料庫設計好後,我們需要把概念模型轉成物理模型,使用Ctrl+Shift+P

快捷鍵,我們預設使用MSSQL2008R2實現配置儲存,所有在彈出的對話方塊中選擇,然後點選確認後會自動生成MSSQL2008R2的物理模型,可以看到資料型別和表之間的關連關係都生成好了,奈斯,一切都是那麼完美,如果主鍵為自增型別,手動標記下即可。


現在我們需要生成我們建立資料庫的SQL指令碼了,別忘了儲存下剛才生成的物理模型,因為以後還需要用到。

生成資料庫指令碼

如圖所示,可以使用快捷鍵Ctrl+G生成資料庫指令碼,點選確認生成並儲存,然後把生成的指令碼在我們新建的資料庫裡執行,這樣我們的資料庫就設計完成了。

二、搭建並測試中介軟體

我們使用VS2017新建一個.NETCORE2.1

專案,然後新建一個類庫來實現我們Ocelot定製版中介軟體,建好後項目結構如下,現在開始我們第一個AhphOcelot定製中介軟體編寫。

首先我們回顧下【.NET Core專案實戰-統一認證平臺】第二章閘道器篇-重構Ocelot來滿足需求的原始碼解析,關於配置資訊的讀取如下,我們只需要重寫下CreateConfiguration方法實現從資料庫裡取就可以了,既然有思路了,

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);
}

1、實現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 [email protected] 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 [email protected] 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,顯示內容和本地訪問一樣,證明閘道器路由生效,是不是有點小激動呢?我們完成了從配置資訊中取閘道器路由資訊擴充套件。

三、下篇預告

最後我們回顧下這篇內容,我是從設計到實現一步一步講解和實現的,而且實現過程是根據需求慢慢剖析再區域性實現的,我發現現在很多人在平時學習基本都是結果未導向,很少去關心中間的實現過程,久而久之基本就會喪失解決問題的思路,寫的這麼詳細,也是希望給大家一個解決問題的思路,目前我們實現了從資料庫中提取配置資訊並在閘道器中生效,但是還未實現動態更新和擴充套件其他資料庫儲存,大家也可以先自己嘗試如何實現。

下一篇我們將會實現閘道器路由的動態更新,會提供幾種更新思路,根據實際情況擇優選擇。然後在使用Mysql資料庫來儲存配置資訊,並擴充套件此閘道器實現很優雅的配置,為什麼使用mysql擴充套件實現呢?因為.netcore已經跨平臺啦,後期我們準備在Centos下實現容器化部署,這時我們就準備以mysql為例進行講解,本閘道器所有內容原始碼都會實現sqlserver和mysql兩種方式,其他儲存方式可自行擴充套件即可。

最後專案所有的文件在原始碼的文件目錄,文件按照課程原始碼資料夾區分,本文的文件標識course1

我的部落格即將同步至騰訊雲+社群,邀請大家一同入駐:https://cloud.tencent.com/developer/support-plan?invite_code=l0q6lfr3asgg