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

【.NETCore项目实战-统一认证平台】开篇及目录索引本篇将介绍如何扩展Ocelot中间件实现自定义网关,并使用2种不同数据库来演示Ocelot配置信息存储和动态更新功能,内容也是从实际设计出发来编写我们自己的中间件,本文内容涵盖设计思想内容和代码内容,我希望园友们最好跟着我这个文章的思路先理解好后再看源代码,这样有利于融会贯通,本篇的文档及源码将会在GitHub上开源,每篇的源代码我将用分支的...

【.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 configvar 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<IInternalConfigurationReposi
源文地址:https://www.guoxiongfei.cn/cntech/4156.html