【.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方法实现从数据库里取就可以了,既然有思路了,

  1. public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder builder, OcelotPipelineConfiguration pipelineConfiguration)
  2. {
  3. //创建配置信息
  4. var configuration = await CreateConfiguration(builder);
  5. ConfigureDiagnosticListener(builder);
  6. return CreateOcelotPipeline(builder, pipelineConfiguration);
  7. }

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

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

  1. private static async Task<IInternalConfiguration> CreateConfiguration(IApplicationBuilder builder)
  2. {
  3. // make configuration from file system?
  4. // earlier user needed to add ocelot files in startup configuration stuff, asp.net will map it to this
  5. var fileConfig = builder.ApplicationServices.GetService<IOptionsMonitor<FileConfiguration>>();
  6. // now create the config
  7. var internalConfigCreator = builder.ApplicationServices.GetService<IInternalConfigurationCreator>();
  8. var internalConfig = await internalConfigCreator.Create(fileConfig.CurrentValue);
  9. //Configuration error, throw error message
  10. if (internalConfig.IsError)
  11. {
  12. ThrowToStopOcelotStarting(internalConfig);
  13. }
  14. // now save it in memory
  15. var internalConfigRepo = builder.ApplicationServices.GetService<IInternalConfigurationRepository>();
  16. internalConfigRepo.AddOrReplace(internalConfig.Data);
  17. fileConfig.OnChange(async (config) =>
  18. {
  19. var newInternalConfig = await internalConfigCreator.Create(config);
  20. internalConfigRepo.AddOrReplace(newInternalConfig.Data);
  21. });
  22. var adminPath = builder.ApplicationServices.GetService<IAdministrationPath>();
  23. var configurations = builder.ApplicationServices.GetServices<OcelotMiddlewareConfigurationDelegate>();
  24. // 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?
  25. foreach (var configuration in configurations)
  26. {
  27. await configuration(builder);
  28. }
  29. if(AdministrationApiInUse(adminPath))
  30. {
  31. //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
  32. //admin api it works...boy this is getting a spit spags boll.
  33. var fileConfigSetter = builder.ApplicationServices.GetService<IFileConfigurationSetter>();
  34. await SetFileConfig(fileConfigSetter, fileConfig);
  35. }
  36. return GetOcelotConfigAndReturn(internalConfigRepo);
  37. }

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

  1. public OcelotBuilder(IServiceCollection services, IConfiguration configurationRoot)
  2. {
  3. Configuration = configurationRoot;
  4. Services = services;
  5. //服务注册,可以使用IOptions<FileConfiguration>调用
  6. Services.Configure<FileConfiguration>(configurationRoot);
  7. ....
  8. }

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

  1. using System.Threading.Tasks;
  2. using Ocelot.Configuration.File;
  3. using Ocelot.Responses;
  4. namespace Ocelot.Configuration.Repository
  5. {
  6. public interface IFileConfigurationRepository
  7. {
  8. Task<Response<FileConfiguration>> Get();
  9. Task<Response> Set(FileConfiguration fileConfiguration);
  10. }
  11. }

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

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

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

开发的思路就是顶层开始一步一步往下实现,最后完成我们的扩展。现在回到我们自己的代码,修改配置信息代码如下,是不是精简很多了,但是有2个问题未解决,一是需要实现IFileConfigurationRepository,二是还没实现动态更新。

  1. private static async Task<IInternalConfiguration> CreateConfiguration(IApplicationBuilder builder)
  2. {
  3. //提取文件配置信息
  4. var fileConfig = await builder.ApplicationServices.GetService<IFileConfigurationRepository>().Get();
  5. var internalConfigCreator = builder.ApplicationServices.GetService<IInternalConfigurationCreator>();
  6. var internalConfig = await internalConfigCreator.Create(fileConfig.Data);
  7. //如果配置文件错误直接抛出异常
  8. if (internalConfig.IsError)
  9. {
  10. ThrowToStopOcelotStarting(internalConfig);
  11. }
  12. //配置信息缓存,这块需要注意实现方式,因为后期我们需要改造下满足分布式架构,这篇不做讲解
  13. var internalConfigRepo = builder.ApplicationServices.GetService<IInternalConfigurationRepository>();
  14. internalConfigRepo.AddOrReplace(internalConfig.Data);
  15. return GetOcelotConfigAndReturn(internalConfigRepo);
  16. }

1、实现IFileConfigurationRepository接口

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

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

  1. namespace Ctr.AhphOcelot.Configuration
  2. {
  3. /// <summary>
  4. /// 金焰的世界
  5. /// 2018-11-11
  6. /// 自定义配置信息
  7. /// </summary>
  8. public class AhphOcelotConfiguration
  9. {
  10. /// <summary>
  11. /// 数据库连接字符串
  12. /// </summary>
  13. public string DbConnectionStrings { get; set; }
  14. }
  15. }

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

  1. using Ctr.AhphOcelot.Configuration;
  2. using Ctr.AhphOcelot.Model;
  3. using Dapper;
  4. using Ocelot.Configuration.File;
  5. using Ocelot.Configuration.Repository;
  6. using Ocelot.Responses;
  7. using System;
  8. using System.Collections.Generic;
  9. using System.Data.SqlClient;
  10. using System.Text;
  11. using System.Threading.Tasks;
  12. namespace Ctr.AhphOcelot.DataBase.SqlServer
  13. {
  14. /// <summary>
  15. /// 金焰的世界
  16. /// 2018-11-11
  17. /// 使用SqlServer来实现配置文件仓储接口
  18. /// </summary>
  19. public class SqlServerFileConfigurationRepository : IFileConfigurationRepository
  20. {
  21. private readonly AhphOcelotConfiguration _option;
  22. public SqlServerFileConfigurationRepository(AhphOcelotConfiguration option)
  23. {
  24. _option = option;
  25. }
  26. /// <summary>
  27. /// 从数据库中获取配置信息
  28. /// </summary>
  29. /// <returns></returns>
  30. public async Task<Response<FileConfiguration>> Get()
  31. {
  32. #region 提取配置信息
  33. var file = new FileConfiguration();
  34. //提取默认启用的路由配置信息
  35. string glbsql = "select * from AhphGlobalConfiguration where IsDefault=1 and InfoStatus=1";
  36. //提取全局配置信息
  37. using (var connection = new SqlConnection(_option.DbConnectionStrings))
  38. {
  39. var result = await connection.QueryFirstOrDefaultAsync<AhphGlobalConfiguration>(glbsql);
  40. if (result != null)
  41. {
  42. var glb = new FileGlobalConfiguration();
  43. //赋值全局信息
  44. glb.BaseUrl = result.BaseUrl;
  45. glb.DownstreamScheme = result.DownstreamScheme;
  46. glb.RequestIdKey = result.RequestIdKey;
  47. glb.HttpHandlerOptions = result.HttpHandlerOptions?.ToObject<FileHttpHandlerOptions>();
  48. glb.LoadBalancerOptions = result.LoadBalancerOptions?.ToObject<FileLoadBalancerOptions>();
  49. glb.QoSOptions = result.QoSOptions?.ToObject<FileQoSOptions>();
  50. glb.ServiceDiscoveryProvider = result.ServiceDiscoveryProvider?.ToObject<FileServiceDiscoveryProvider>();
  51. file.GlobalConfiguration = glb;
  52. //提取所有路由信息
  53. string routesql = "select T2.* from AhphConfigReRoutes T1 inner join AhphReRoute T2 on T1.ReRouteId=T2.ReRouteId where AhphId=@AhphId and InfoStatus=1";
  54. var routeresult = (await connection.QueryAsync<AhphReRoute>(routesql, new { result.AhphId }))?.AsList();
  55. if (routeresult != null && routeresult.Count > 0)
  56. {
  57. var reroutelist = new List<FileReRoute>();
  58. foreach (var model in routeresult)
  59. {
  60. var m = new FileReRoute();
  61. m.AuthenticationOptions = model.AuthenticationOptions?.ToObject<FileAuthenticationOptions>();
  62. m.FileCacheOptions = model.CacheOptions?.ToObject<FileCacheOptions>();
  63. m.DelegatingHandlers = model.DelegatingHandlers?.ToObject<List<string>>();
  64. m.LoadBalancerOptions = model.LoadBalancerOptions?.ToObject<FileLoadBalancerOptions>();
  65. m.QoSOptions = model.QoSOptions?.ToObject<FileQoSOptions>();
  66. m.DownstreamHostAndPorts = model.DownstreamHostAndPorts?.ToObject<List<FileHostAndPort>>();
  67. //开始赋值
  68. m.DownstreamPathTemplate = model.DownstreamPathTemplate;
  69. m.DownstreamScheme = model.DownstreamScheme;
  70. m.Key = model.RequestIdKey;
  71. m.Priority = model.Priority ?? 0;
  72. m.RequestIdKey = model.RequestIdKey;
  73. m.ServiceName = model.ServiceName;
  74. m.UpstreamHost = model.UpstreamHost;
  75. m.UpstreamHttpMethod = model.UpstreamHttpMethod?.ToObject<List<string>>();
  76. m.UpstreamPathTemplate = model.UpstreamPathTemplate;
  77. reroutelist.Add(m);
  78. }
  79. file.ReRoutes = reroutelist;
  80. }
  81. }
  82. else
  83. {
  84. throw new Exception("未监测到任何可用的配置信息");
  85. }
  86. }
  87. #endregion
  88. if (file.ReRoutes == null || file.ReRoutes.Count == 0)
  89. {
  90. return new OkResponse<FileConfiguration>(null);
  91. }
  92. return new OkResponse<FileConfiguration>(file);
  93. }
  94. //由于数据库存储可不实现Set接口直接返回
  95. public async Task<Response> Set(FileConfiguration fileConfiguration)
  96. {
  97. return new OkResponse();
  98. }
  99. }
  100. }

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

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

  1. using Ctr.AhphOcelot.Configuration;
  2. using Ctr.AhphOcelot.DataBase.SqlServer;
  3. using Microsoft.Extensions.DependencyInjection;
  4. using Microsoft.Extensions.Options;
  5. using Ocelot.Configuration.Repository;
  6. using Ocelot.DependencyInjection;
  7. using System;
  8. namespace Ctr.AhphOcelot.Middleware
  9. {
  10. /// <summary>
  11. /// 金焰的世界
  12. /// 2018-11-11
  13. /// 扩展Ocelot实现的自定义的注入
  14. /// </summary>
  15. public static class ServiceCollectionExtensions
  16. {
  17. /// <summary>
  18. /// 添加默认的注入方式,所有需要传入的参数都是用默认值
  19. /// </summary>
  20. /// <param name="builder"></param>
  21. /// <returns></returns>
  22. public static IOcelotBuilder AddAhphOcelot(this IOcelotBuilder builder, Action<AhphOcelotConfiguration> option)
  23. {
  24. builder.Services.Configure(option);
  25. //配置信息
  26. builder.Services.AddSingleton(
  27. resolver => resolver.GetRequiredService<IOptions<AhphOcelotConfiguration>>().Value);
  28. //配置文件仓储注入
  29. builder.Services.AddSingleton<IFileConfigurationRepository, SqlServerFileConfigurationRepository>();
  30. return builder;
  31. }
  32. }
  33. }

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

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

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Threading.Tasks;
  5. using Microsoft.AspNetCore.Builder;
  6. using Microsoft.AspNetCore.Hosting;
  7. using Microsoft.AspNetCore.Http;
  8. using Microsoft.AspNetCore.Mvc;
  9. using Microsoft.Extensions.Configuration;
  10. using Microsoft.Extensions.DependencyInjection;
  11. using Ocelot.DependencyInjection;
  12. using Ctr.AhphOcelot.Middleware;
  13. namespace Ctr.AuthPlatform.Gateway
  14. {
  15. public class Startup
  16. {
  17. public Startup(IConfiguration configuration)
  18. {
  19. Configuration = configuration;
  20. }
  21. public IConfiguration Configuration { get; }
  22. public void ConfigureServices(IServiceCollection services)
  23. {
  24. services.AddOcelot().AddAhphOcelot(option=>
  25. {
  26. option.DbConnectionStrings = "Server=.;Database=Ctr_AuthPlatform;User ID=sa;Password=bl123456;";
  27. });
  28. }
  29. public void Configure(IApplicationBuilder app, IHostingEnvironment env)
  30. {
  31. if (env.IsDevelopment())
  32. {
  33. app.UseDeveloperExceptionPage();
  34. }
  35. else
  36. {
  37. app.UseExceptionHandler("/Error");
  38. }
  39. app.UseAhphOcelot().Wait();
  40. }
  41. }
  42. }

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

  1. --插入全局测试信息
  2. insert into AhphGlobalConfiguration(GatewayName,RequestIdKey,IsDefault,InfoStatus)
  3. values('测试网关','test_gateway',1,1);
  4. --插入路由分类测试信息
  5. insert into AhphReRoutesItem(ItemName,InfoStatus) values('测试分类',1);
  6. --插入路由测试信息
  7. insert into AhphReRoute values(1,'/ctr/values','[ "GET" ]','','http','/api/Values','[{"Host": "localhost","Port": 9000 }]',
  8. '','','','','','','',0,1);
  9. --插入网关关联表
  10. insert into dbo.AhphConfigReRoutes values(1,1);

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

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

我们在一些未定义的配置项目使用了为空的赋值。而Ocleot里面对于不少配置项目未做非空验证。比如RateLimitOptionsCreator对于FileGlobalConfiguration未做非空验证,类似这样的地方还有不少,我希望下次Ocelot更新时最好增加这类非空验证,这样便于自定义扩展,而Ocelot内部实现了默认实例化,所以我们之前从数据库取值赋值时写法需要改进,修改后的代码如下。

  1. using Ctr.AhphOcelot.Configuration;
  2. using Ctr.AhphOcelot.Model;
  3. using Dapper;
  4. using Ocelot.Configuration.File;
  5. using Ocelot.Configuration.Repository;
  6. using Ocelot.Responses;
  7. using System;
  8. using System.Collections.Generic;
  9. using System.Data.SqlClient;
  10. using System.Text;
  11. using System.Threading.Tasks;
  12. namespace Ctr.AhphOcelot.DataBase.SqlServer
  13. {
  14. /// <summary>
  15. /// 金焰的世界
  16. /// 2018-11-11
  17. /// 使用SqlServer来实现配置文件仓储接口
  18. /// </summary>
  19. public class SqlServerFileConfigurationRepository : IFileConfigurationRepository
  20. {
  21. private readonly AhphOcelotConfiguration _option;
  22. public SqlServerFileConfigurationRepository(AhphOcelotConfiguration option)
  23. {
  24. _option = option;
  25. }
  26. /// <summary>
  27. /// 从数据库中获取配置信息
  28. /// </summary>
  29. /// <returns></returns>
  30. public async Task<Response<FileConfiguration>> Get()
  31. {
  32. #region 提取配置信息
  33. var file = new FileConfiguration();
  34. //提取默认启用的路由配置信息
  35. string glbsql = "select * from AhphGlobalConfiguration where IsDefault=1 and InfoStatus=1";
  36. //提取全局配置信息
  37. using (var connection = new SqlConnection(_option.DbConnectionStrings))
  38. {
  39. var result = await connection.QueryFirstOrDefaultAsync<AhphGlobalConfiguration>(glbsql);
  40. if (result != null)
  41. {
  42. var glb = new FileGlobalConfiguration();
  43. //赋值全局信息
  44. glb.BaseUrl = result.BaseUrl;
  45. glb.DownstreamScheme = result.DownstreamScheme;
  46. glb.RequestIdKey = result.RequestIdKey;
  47. //glb.HttpHandlerOptions = result.HttpHandlerOptions?.ToObject<FileHttpHandlerOptions>();
  48. //glb.LoadBalancerOptions = result.LoadBalancerOptions?.ToObject<FileLoadBalancerOptions>();
  49. //glb.QoSOptions = result.QoSOptions?.ToObject<FileQoSOptions>();
  50. //glb.ServiceDiscoveryProvider = result.ServiceDiscoveryProvider?.ToObject<FileServiceDiscoveryProvider>();
  51. if (!String.IsNullOrEmpty(result.HttpHandlerOptions))
  52. {
  53. glb.HttpHandlerOptions = result.HttpHandlerOptions.ToObject<FileHttpHandlerOptions>();
  54. }
  55. if (!String.IsNullOrEmpty(result.LoadBalancerOptions))
  56. {
  57. glb.LoadBalancerOptions = result.LoadBalancerOptions.ToObject<FileLoadBalancerOptions>();
  58. }
  59. if (!String.IsNullOrEmpty(result.QoSOptions))
  60. {
  61. glb.QoSOptions = result.QoSOptions.ToObject<FileQoSOptions>();
  62. }
  63. if (!String.IsNullOrEmpty(result.ServiceDiscoveryProvider))
  64. {
  65. glb.ServiceDiscoveryProvider = result.ServiceDiscoveryProvider.ToObject<FileServiceDiscoveryProvider>();
  66. }
  67. file.GlobalConfiguration = glb;
  68. //提取所有路由信息
  69. string routesql = "select T2.* from AhphConfigReRoutes T1 inner join AhphReRoute T2 on T1.ReRouteId=T2.ReRouteId where AhphId=@AhphId and InfoStatus=1";
  70. var routeresult = (await connection.QueryAsync<AhphReRoute>(routesql, new { result.AhphId }))?.AsList();
  71. if (routeresult != null && routeresult.Count > 0)
  72. {
  73. var reroutelist = new List<FileReRoute>();
  74. foreach (var model in routeresult)
  75. {
  76. var m = new FileReRoute();
  77. //m.AuthenticationOptions = model.AuthenticationOptions?.ToObject<FileAuthenticationOptions>();
  78. //m.FileCacheOptions = model.CacheOptions?.ToObject<FileCacheOptions>();
  79. //m.DelegatingHandlers = model.DelegatingHandlers?.ToObject<List<string>>();
  80. //m.LoadBalancerOptions = model.LoadBalancerOptions?.ToObject<FileLoadBalancerOptions>();
  81. //m.QoSOptions = model.QoSOptions?.ToObject<FileQoSOptions>();
  82. //m.DownstreamHostAndPorts = model.DownstreamHostAndPorts?.ToObject<List<FileHostAndPort>>();
  83. if (!String.IsNullOrEmpty(model.AuthenticationOptions))
  84. {
  85. m.AuthenticationOptions = model.AuthenticationOptions.ToObject<FileAuthenticationOptions>();
  86. }
  87. if (!String.IsNullOrEmpty(model.CacheOptions))
  88. {
  89. m.FileCacheOptions = model.CacheOptions.ToObject<FileCacheOptions>();
  90. }
  91. if (!String.IsNullOrEmpty(model.DelegatingHandlers))
  92. {
  93. m.DelegatingHandlers = model.DelegatingHandlers.ToObject<List<string>>();
  94. }
  95. if (!String.IsNullOrEmpty(model.LoadBalancerOptions))
  96. {
  97. m.LoadBalancerOptions = model.LoadBalancerOptions.ToObject<FileLoadBalancerOptions>();
  98. }
  99. if (!String.IsNullOrEmpty(model.QoSOptions))
  100. {
  101. m.QoSOptions = model.QoSOptions.ToObject<FileQoSOptions>();
  102. }
  103. if (!String.IsNullOrEmpty(model.DownstreamHostAndPorts))
  104. {
  105. m.DownstreamHostAndPorts = model.DownstreamHostAndPorts.ToObject<List<FileHostAndPort>>();
  106. }
  107. //开始赋值
  108. m.DownstreamPathTemplate = model.DownstreamPathTemplate;
  109. m.DownstreamScheme = model.DownstreamScheme;
  110. m.Key = model.RequestIdKey;
  111. m.Priority = model.Priority ?? 0;
  112. m.RequestIdKey = model.RequestIdKey;
  113. m.ServiceName = model.ServiceName;
  114. m.UpstreamHost = model.UpstreamHost;
  115. m.UpstreamHttpMethod = model.UpstreamHttpMethod?.ToObject<List<string>>();
  116. m.UpstreamPathTemplate = model.UpstreamPathTemplate;
  117. reroutelist.Add(m);
  118. }
  119. file.ReRoutes = reroutelist;
  120. }
  121. }
  122. else
  123. {
  124. throw new Exception("未监测到任何可用的配置信息");
  125. }
  126. }
  127. #endregion
  128. if (file.ReRoutes == null || file.ReRoutes.Count == 0)
  129. {
  130. return new OkResponse<FileConfiguration>(null);
  131. }
  132. return new OkResponse<FileConfiguration>(file);
  133. }
  134. //由于数据库存储可不实现Set接口直接返回
  135. public async Task<Response> Set(FileConfiguration fileConfiguration)
  136. {
  137. return new OkResponse();
  138. }
  139. }
  140. }

然后重新运行,网关启动成功。

接着我们启动我们测试的服务,然后浏览器先访问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

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

  1. 【.NET Core项目实战-统一认证平台】第二章网关篇-定制Ocelot来满足需求

    [.NET Core项目实战-统一认证平台]开篇及目录索引 这篇文章,我们将从Ocelot的中间件源码分析,目前Ocelot已经实现那些功能,还有那些功能在我们实际项目中暂时还未实现,如果我们要使用这 ...

  2. 【.NET Core项目实战-统一认证平台】第九章 授权篇-使用Dapper持久化IdentityServer4

    [.NET Core项目实战-统一认证平台]开篇及目录索引 上篇文章介绍了IdentityServer4的源码分析的内容,让我们知道了IdentityServer4的一些运行原理,这篇将介绍如何使用d ...

  3. 【.NET Core项目实战-统一认证平台】第十章 授权篇-客户端授权

    [.NET Core项目实战-统一认证平台]开篇及目录索引 上篇文章介绍了如何使用Dapper持久化IdentityServer4(以下简称ids4)的信息,并实现了sqlserver和mysql两种 ...

  4. 【.NET Core项目实战-统一认证平台】第八章 授权篇-IdentityServer4源码分析

    [.NET Core项目实战-统一认证平台]开篇及目录索引 上篇文章我介绍了如何在网关上实现客户端自定义限流功能,基本完成了关于网关的一些自定义扩展需求,后面几篇将介绍基于IdentityServer ...

  5. 【.NET Core项目实战-统一认证平台】第一章 功能及架构分析

    [.NET Core项目实战-统一认证平台]开篇及目录索引 从本文开始,我们正式进入项目研发阶段,首先我们分析下统一认证平台应该具备哪些功能性需求和非功能性需求,在梳理完这些需求后,设计好系统采用的架 ...

  6. 【.NET Core项目实战-统一认证平台】第十六章 网关篇-Ocelot集成RPC服务

    [.NET Core项目实战-统一认证平台]开篇及目录索引 一.什么是RPC RPC是"远程调用(Remote Procedure Call)"的一个名称的缩写,并不是任何规范化的 ...

  7. 【.NET Core项目实战-统一认证平台】第十五章 网关篇-使用二级缓存提升性能

    [.NET Core项目实战-统一认证平台]开篇及目录索引 一.背景 首先说声抱歉,可能是因为假期综合症(其实就是因为懒哈)的原因,已经很长时间没更新博客了,现在也调整的差不多了,准备还是以每周1-2 ...

  8. 【.NET Core项目实战-统一认证平台】第十四章 授权篇-自定义授权方式

    [.NET Core项目实战-统一认证平台]开篇及目录索引 上篇文章我介绍了如何强制令牌过期的实现,相信大家对IdentityServer4的验证流程有了更深的了解,本篇我将介绍如何使用自定义的授权方 ...

  9. 【.NET Core项目实战-统一认证平台】第十三章 授权篇-如何强制有效令牌过期

    [.NET Core项目实战-统一认证平台]开篇及目录索引 上一篇我介绍了JWT的生成验证及流程内容,相信大家也对JWT非常熟悉了,今天将从一个小众的需求出发,介绍如何强制令牌过期的思路和实现过程. ...

随机推荐

  1. h5页面转图片长按保存

    5页面经常会遇到此类需求.将最后的结果页转换为图片长按保存.下面介绍一下实现此需求的过程 1,依赖安装 cnpm install html2canvas --save 2,依赖引入,使用 绑定 初始化 ...

  2. How to Reset VW Steering Assist 1S1909144P with OBDSTAR X300 DP

    Vehicle model:VW Polo 2015 (or other Audi, Seat, Skoda, VW with unit 1S1 909 144 P) Module:Control u ...

  3. Python学习心得--变量类型篇

    1.Python允许同时为多个变量赋值.例如:a = b = c = 1 2.Python允许多个对象指定多个变量.例如:a1, b1, c1 = 1, 2, "john" 3.使 ...

  4. Eclipse Maven: Cannot change version of project facet Dynamic web to 3.0 的解决方法

    在 Eclipse 中创建 Maven web 项目的时候使用 maven-artchetype-webapp 骨架,但是这个 catalog 比较老,用的 servlet 还是 2.3 在 Proj ...

  5. python 数据可视化 -- matplotlib02

    import matplotlib.pyplot as plt import numpy as np x = np.linspace(start=0.5, stop=3.5, num=100) y = ...

  6. [Hadoop]Hadoop章3 NameNode的ZKFC机制

    基本概念 首先我们要明确ZKFC 是什么,有什么作用: zkfc是什么? ZooKeeperFailoverController 它是什么?是Hadoop中通过ZK实现FC功能的一个实用工具. 主要作 ...

  7. 深入理解JVM(七)JVM类加载机制

    7.1JVM类加载机制 虚拟机把数据从Class文件加载到内存,并且校验.转换解析和初始化最终形成可以被虚拟机使用的Java类型,这就是虚拟机的类加载机制. 7.2类加载的时机 1.类加载的步骤开始的 ...

  8. OO第一单元单元总结

    总述 三周的时间一晃而过,也到了和表达式说再见的时候了.想起来,现在已经能够优雅地在互测“攻击”别人,然后笑对被别人“攻击”,就觉得OO这三周还是很有意义,也多多少少改变了我.周六已经快习惯早上背着包 ...

  9. java将图片传为设定编码值显示(可做刺绣)

    import java.awt.Color; import java.awt.image.BufferedImage;import java.io.File;import java.io.IOExce ...

  10. API setContentType(MIME) 参数说明

    HttpServletResponse的setContentType(MIME) API主要用以告诉浏览器服务器所传递的数据类型或服务器希望浏览器以何种方式解析和展示这些数据 其由两部分构成,如:te ...