" 【.NET Core項目實戰 統一認證平臺】開篇及目錄索引 " 本篇將介紹如何擴展Ocelot中間件實現自定義網關,並使用2種不同資料庫來演示Ocelot配置信息存儲和動態更新功能,內容也是從實際設計出發來編寫我們自己的中間件,本文內容涵蓋設計思想內容和代碼內容,我希望園友們最好跟著我這個文章的 ...
【.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
找到,FileConfigurationPoller
和FileAndInternalConfigurationSetter
兩個地方用到了這個介面,其中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 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
,顯示內容和本地訪問一樣,證明網關路由生效,是不是有點小激動呢?我們完成了從配置信息中取網關路由信息擴展。
三、下篇預告
最後我們回顧下這篇內容,我是從設計到實現一步一步講解和實現的,而且實現過程是根據需求慢慢剖析再局部實現的,我發現現在很多人在平時學習基本都是結果未導向,很少去關心中間的實現過程,久而久之基本就會喪失解決問題的思路,寫的這麼詳細,也是希望給大家一個解決問題的思路,目前我們實現了從資料庫中提取配置信息併在網關中生效,但是還未實現動態更新和擴展其他資料庫存儲,大家也可以先自己嘗試如何實現。
下一篇我們將會實現網關路由的動態更新,會提供幾種更新思路,根據實際情況擇優選擇。然後在使用Mysql資料庫來存儲配置信息,並擴展此網關實現很優雅的配置,為什麼使用mysql擴展實現呢?因為.netcore已經跨平臺啦,後期我們準備在Centos下實現容器化部署,這時我們就準備以mysql為例進行講解,本網關所有內容源碼都會實現sqlserver和mysql兩種方式,其他存儲方式可自行擴展即可。
最後項目所有的文檔在源碼的文檔目錄,文檔按照課程源碼文件夾區分,本文的文檔標識course1
。