当前位置: 移动技术网 > IT编程>开发语言>.net > 【.NET Core项目实战-统一认证平台】第三章 网关篇-数据库存储配置(1)

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

2018年11月12日  | 移动技术网IT编程  | 我要评论

夜鹭眼睛被戳瞎,买什么狗好,万能键盘记录器 9.0

【.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 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://www.lhsxpumps.com/_localhost:9000/api/values地址,测试地址正常访问。

然后使用测试网关路由地址访问http://www.lhsxpumps.com/_localhost:7777/ctr/values,显示内容和本地访问一样,证明网关路由生效,是不是有点小激动呢?我们完成了从配置信息中取网关路由信息扩展。

三、下篇预告

最后我们回顾下这篇内容,我是从设计到实现一步一步讲解和实现的,而且实现过程是根据需求慢慢剖析再局部实现的,我发现现在很多人在平时学习基本都是结果未导向,很少去关心中间的实现过程,久而久之基本就会丧失解决问题的思路,写的这么详细,也是希望给大家一个解决问题的思路,目前我们实现了从数据库中提取配置信息并在网关中生效,但是还未实现动态更新和扩展其他数据库存储,大家也可以先自己尝试如何实现。

下一篇我们将会实现网关路由的动态更新,会提供几种更新思路,根据实际情况择优选择。然后在使用mysql数据库来存储配置信息,并扩展此网关实现很优雅的配置,为什么使用mysql扩展实现呢?因为.netcore已经跨平台啦,后期我们准备在centos下实现容器化部署,这时我们就准备以mysql为例进行讲解,本网关所有内容源码都会实现sqlserver和mysql两种方式,其他存储方式可自行扩展即可。

最后项目所有的文档在源码的文档目录,文档按照课程源码文件夹区分,本文的文档标识course1

我的博客即将同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=l0q6lfr3asgg

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网