【.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 thisvar fileConfig = builder.ApplicationServices.GetService<IOptionsMonitor<FileConfiguration>>();// now create the configvar internalConfigCreator = builder.ApplicationServices.GetService<IInternalConfigurationCreator>();var internalConfig = await internalConfigCreator.Create(fileConfig.CurrentValue);//Configuration error, throw error messageif (internalConfig.IsError){ThrowToStopOcelotStarting(internalConfig);}// now save it in memoryvar 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 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("未监测到任何可用的配置信息");}}#endregionif (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("未监测到任何可用的配置信息");}}#endregionif (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

posted on 2018-11-20 22:53 NET未来之路 阅读(...) 评论(...) 编辑 收藏

转载于:https://www.cnblogs.com/lonelyxmas/p/9992353.html

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

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

    上篇文章介绍了IdentityServer4的源码分析的内容,让我们知道了IdentityServer4的一些运行原理,这篇将介绍如何使用dapper来持久化Identityserver4,让我们对I ...

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

    上篇文章介绍了如何使用Dapper持久化IdentityServer4(以下简称ids4)的信息,并实现了sqlserver和mysql两种方式存储,本篇将介绍如何使用ids4进行客户端授权. .ne ...

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

    上篇文章我介绍了如何在网关上实现客户端自定义限流功能,基本完成了关于网关的一些自定义扩展需求,后面几篇将介绍基于IdentityServer4(后面简称Ids4)的认证相关知识,在具体介绍ids4实现 ...

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

    从本文开始,我们正式进入项目研发阶段,首先我们分析下统一认证平台应该具备哪些功能性需求和非功能性需求,在梳理完这些需求后,设计好系统采用的架构来满足已有的需求和未来的扩展应用. 1 功能性需求 统一认 ...

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

    上篇文章我介绍了如何强制令牌过期的实现,相信大家对IdentityServer4的验证流程有了更深的了解,本篇我将介绍如何使用自定义的授权方式集成老的业务系统验证,然后根据不同的客户端使用不同的认证方 ...

  6. 【.NET Core项目实战-统一认证平台】第十二章 授权篇-深入理解JWT生成及验证流程...

    上篇文章介绍了基于Ids4密码授权模式,从使用场景.原理分析.自定义帐户体系集成完整的介绍了密码授权模式的内容,并最后给出了三个思考问题,本篇就针对第一个思考问题详细的讲解下Ids4是如何生成acce ...

  7. 【.NET Core项目实战-统一认证平台】第十一章 授权篇-密码授权模式

    上篇文章介绍了基于Ids4客户端授权的原理及如何实现自定义的客户端授权,并配合网关实现了统一的授权异常返回值和权限配置等相关功能,本篇将介绍密码授权模式,从使用场景.源码剖析到具体实现详细讲解密码授权 ...

  8. 【.NET Core项目实战-统一认证平台】第七章 网关篇-自定义客户端限流

    上篇文章我介绍了如何在网关上增加自定义客户端授权功能,从设计到编码实现,一步一步详细讲解,相信大家也掌握了自定义中间件的开发技巧了,本篇我们将介绍如何实现自定义客户端的限流功能,来进一步完善网关的基础 ...

  9. 【.NET Core项目实战-统一认证平台】第六章 网关篇-自定义客户端授权

    上篇文章[.NET Core项目实战-统一认证平台]第五章 网关篇-自定义缓存Redis 我们介绍了网关使用Redis进行缓存,并介绍了如何进行缓存实现,缓存信息清理接口的使用.本篇我们将介绍如何实现 ...

最新文章

  1. 【luogu3398】 仓鼠找sugar [LCA 倍增]
  2. idea uml图怎么画_有了IDEA中的这款插件,流程图、类图轻松搞定,简直神器
  3. Linux端日志加密,Linux学习--第十三天--日志、系统运行级别、grub加密
  4. 详细讲解设计LOGO思维方式和方法【转】
  5. step3 . day2 数据结构之线性表链表
  6. 1.4编程基础之逻辑表达式与条件分支 01判断数正负
  7. python字典是什么数据_python---基本数据类型 dict(字典)
  8. 检察日报:需从预防犯罪视角对私人数字货币实施刑法规制
  9. 5.网页中增加新的内容
  10. 激光干涉仪测量五轴机床旋转轴精度的方法
  11. 电脑链接wifi显示无法连接服务器,电脑连接wifi出现感叹号_电脑连接wifi成功但上不了网怎么办?-192路由网...
  12. 50万粉丝单场带货破200万!这些快手美妆黑马主播是如何在双十一前夕涨粉又爆单的?
  13. 取模、乘法和除法运算在CPU和GPU上的效率
  14. java 数据类型cher,java期末考试题96534-(20008)
  15. 遨博机械臂——末端工具ROS驱动
  16. 【已解决】Mac OS 配置host域名映射不生效
  17. 计算机专业论文docx,计算机专业毕业大学生论文.docx
  18. oracle raise 含义,Oracle中RAISE异常详解
  19. 电脑屏幕变小了左右黑_记者走进温州市中心粮仓,看“黑科技”助力守护“米袋子”——每一粒粮食都在视线范围内...
  20. 台式计算机清洁除尘内容,解决方案:教您如何清洁计算机主机灰尘,计算机除尘教程...

热门文章

  1. 字符串常量前面的L的作用
  2. 每天一道LeetCode-----实现LFU置换算法
  3. 内联函数的声明和定义
  4. php server 域名,php 关于如何获取域名或者IP地址的$_SERVER['']
  5. 8086汇编-实验9-字符打印
  6. Linux软件源apt,仓库,包的概念
  7. 冒死揭秘安利_赵丽颖新剧开播备受争议?冒死安利
  8. 15.4.2:可变参数与泛型化方法
  9. linux文件类型elf,[Linux]四种ELF对象文件类型
  10. autosys虚拟机定义