欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Ocelot简易教程(七)之配置文件数据库存储插件源码解析

程序员文章站 2022-06-21 11:27:39
作者:依乐祝 原文地址:https://www.cnblogs.com/yilezhu/p/9852711.html 上篇文章给大家分享了如何集成我写的一个Ocelot扩展插件把Ocelot的配置存储到数据库中。并没有对实现原理进行相应的阐述。今天抽空把实现的原理给大家说道说道。明白原理后,大家就可 ......

作者:依乐祝
原文地址:https://www.cnblogs.com/yilezhu/p/9852711.html

上篇文章给大家分享了如何集成我写的一个ocelot扩展插件把ocelot的配置存储到数据库中。并没有对实现原理进行相应的阐述。今天抽空把实现的原理给大家说道说道。明白原理后,大家就可以自行改写进行扩展来满足自身需要了!
再次感觉张队的审稿,并给出的修改意见!

源码解析过程

大家可以自行分析ocelot的源码,我通过分析ocelot的源码得出,如果要实现重写配置文件的方式,只需要写一个类来实现ifileconfigurationrepository这个接口即可。

代码如下:

 /// <summary>
    /// yilezhu
    /// 2018.10.22
    /// 实现从sqlserver数据库中提取配置信息
    /// </summary>
    public class sqlserverfileconfigurationrepository : ifileconfigurationrepository
    {
        private readonly iocelotcache<fileconfiguration> _cache;
        private readonly iocelotlogger _logger;
        private readonly configauthlimitcacheoptions _option;
        public sqlserverfileconfigurationrepository(configauthlimitcacheoptions option, iocelotcache<fileconfiguration> cache, iocelotloggerfactory loggerfactory)
        {
            _option = option;
            _cache = cache;
            _logger = loggerfactory.createlogger<sqlserverfileconfigurationrepository>();
        }

        public task<response> set(fileconfiguration fileconfiguration)
        {
            _cache.addanddelete(_option.cacheprefix + "fileconfiguration", fileconfiguration, timespan.fromseconds(1800), "");
            return task.fromresult((response)new okresponse());
        }

        /// <summary>
        /// 提取配置信息
        /// </summary>
        /// <returns></returns>
        public async task<response<fileconfiguration>> get()
        {
            var config = _cache.get(_option.cacheprefix + "fileconfiguration", "");

            if (config != null)
            {
                return new okresponse<fileconfiguration>(config);
            }
            #region 提取配置信息
            var file = new fileconfiguration();
            string glbsql = "select top 1 * from ocelotglobalconfiguration where isdefault=1";
            //提取全局配置信息
            using (var connection = new sqlconnection(_option.dbconnectionstrings))
            {
                var result = await connection.queryfirstordefaultasync<ocelotglobalconfiguration>(glbsql);
                if (result != null)
                {
                    var glb = new fileglobalconfiguration();
                    glb.baseurl = result.baseurl;
                    glb.downstreamscheme = result.downstreamscheme;
                    glb.requestidkey = result.requestidkey;
                    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 * from ocelotreroutes where ocelotglobalconfigurationid=@ocelotglobalconfigurationid and isstatus=1";
                    var routeresult = (await connection.queryasync<ocelotreroutes>(routesql, new { ocelotglobalconfigurationid=result.id })).aslist();
                    if (routeresult != null && routeresult.count > 0)
                    {
                        var reroutelist = new list<filereroute>();
                        foreach (var model in routeresult)
                        {
                            var m = new filereroute();
                            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.key;
                            m.priority = model.priority ?? 0;
                            m.requestidkey = model.requestidkey;
                            m.servicename = model.servicename;
                            m.timeout = model.timeout ?? 0;
                            m.upstreamhost = model.upstreamhost;
                            if (!string.isnullorempty(model.upstreamhttpmethod))
                            {
                                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);
        }
    }

当然,既然我们已经重新实现了这个接口,那么就得进行相应的di了。这里我们扩展下iocelotbuilder方法,代码如下,主要就是进行相应的服务的di:

/// <summary>
    /// yilezhu
    /// 2018.10.22
    /// 基于ocelot扩展的依赖注入
    /// </summary>
    public static class servicecollectionextensions
    {
        /// <summary>
        /// 添加默认的注入方式,所有需要传入的参数都是用默认值
        /// </summary>
        /// <param name="builder"></param>
        /// <returns></returns>
        public static iocelotbuilder addauthlimitcache(this iocelotbuilder builder, action<configauthlimitcacheoptions> option)
        {
            builder.services.configure(option);
            builder.services.addsingleton(
                resolver => resolver.getrequiredservice<ioptions<configauthlimitcacheoptions>>().value);
            #region 注入其他配置信息
            //重写提取ocelot配置信息,
            builder.services.addsingleton(databaseconfigurationprovider.get);
            //builder.services.addhostedservice<fileconfigurationpoller>();
            builder.services.addsingleton<ifileconfigurationrepository, sqlserverfileconfigurationrepository>();
            //注入自定义限流配置
            //注入认证信息
            #endregion
            return builder;
        }
    }

接下来就是重写,ocelotbuild里面配置文件的获取方式了。这里我选择的是对iapplicationbuilder进行扩展,因为这样方便做一些其他的事情,比如,重写限流,集成自定义的验证等等。具体代码如下:

   /// <summary>
    /// yilezhu
    /// 2018.10.22
    /// 扩展iapplicationbuilder,新增use方法
    /// </summary>
    public static class ocelotmiddlewareextensions
    {
        /// <summary>
        /// 扩展useocelot
        /// </summary>
        /// <param name="builder"></param>
        /// <returns></returns>
        public static async task<iapplicationbuilder> useahphocelot(this iapplicationbuilder builder)
        {
            await builder.useahphocelot(new ocelotpipelineconfiguration());
            return builder;
        }

        /// <summary>
        /// 重写ocelot,带参数
        /// </summary>
        /// <param name="builder"></param>
        /// <param name="pipelineconfiguration"></param>
        /// <returns></returns>
        public static async task<iapplicationbuilder> useahphocelot(this iapplicationbuilder builder, ocelotpipelineconfiguration pipelineconfiguration)
        {
            var configuration = await createconfiguration(builder);

            configurediagnosticlistener(builder);

            return createocelotpipeline(builder, pipelineconfiguration);
        }

        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>>();
            var fileconfig = await builder.applicationservices.getservice<ifileconfigurationrepository>().get();
            // now create the config
            var internalconfigcreator = builder.applicationservices.getservice<iinternalconfigurationcreator>();
            var internalconfig = await internalconfigcreator.create(fileconfig.data);
            //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.data);
            }

            return getocelotconfigandreturn(internalconfigrepo);
        }

        private static bool administrationapiinuse(iadministrationpath adminpath)
        {
            return adminpath != null;
        }

        //private static async task setfileconfig(ifileconfigurationsetter fileconfigsetter, ioptionsmonitor<fileconfiguration> fileconfig)
        //{
        //    var response = await fileconfigsetter.set(fileconfig.currentvalue);

        //    if (iserror(response))
        //    {
        //        throwtostopocelotstarting(response);
        //    }
        //}

        private static bool iserror(response response)
        {
            return response == null || response.iserror;
        }

        private static iinternalconfiguration getocelotconfigandreturn(iinternalconfigurationrepository provider)
        {
            var ocelotconfiguration = provider.get();

            if (ocelotconfiguration?.data == null || ocelotconfiguration.iserror)
            {
                throwtostopocelotstarting(ocelotconfiguration);
            }

            return ocelotconfiguration.data;
        }

        private static void throwtostopocelotstarting(response config)
        {
            throw new exception($"unable to start ocelot, errors are: {string.join(",", config.errors.select(x => x.tostring()))}");
        }

        private static iapplicationbuilder createocelotpipeline(iapplicationbuilder builder, ocelotpipelineconfiguration pipelineconfiguration)
        {
            var pipelinebuilder = new ocelotpipelinebuilder(builder.applicationservices);

            //重写自定义管道
            pipelinebuilder.buildahphocelotpipeline(pipelineconfiguration);

            var firstdelegate = pipelinebuilder.build();

            /*
            inject first delegate into first piece of asp.net middleware..maybe not like this
            then because we are updating the http context in ocelot it comes out correct for
            rest of asp.net..
            */

            builder.properties["analysis.nextmiddlewarename"] = "transitiontoocelotmiddleware";

            builder.use(async (context, task) =>
            {
                var downstreamcontext = new downstreamcontext(context);
                await firstdelegate.invoke(downstreamcontext);
            });

            return builder;
        }

        private static void configurediagnosticlistener(iapplicationbuilder builder)
        {
            var env = builder.applicationservices.getservice<ihostingenvironment>();
            var listener = builder.applicationservices.getservice<ocelotdiagnosticlistener>();
            var diagnosticlistener = builder.applicationservices.getservice<diagnosticlistener>();
            diagnosticlistener.subscribewithadapter(listener);
        }
    }

这其中最主要的代码就是,重写配置文件获取这块。我在下面进行了截图,并圈出来了,大家自行查看吧。

Ocelot简易教程(七)之配置文件数据库存储插件源码解析

代码重写好了。由于我们服务注册时通过扩展iocelotbuilder,所以,我们需要在configureservices方法引入ocelot服务的时候比ocelot多写一个方法,并传入相关的配置信息,如下所示:

services.addocelot()//注入ocelot服务
                    .addauthlimitcache(option=> {
                        option.dbconnectionstrings = "server=.;database=ocelot;user id=sa;password=1;";
                    });

这里的目的就是为了注入我们实现了ifileconfigurationrepository接口的sqlserverfileconfigurationrepository这个类。

接下来就是在管道中使用我们重写的ocelot服务了。如下所示,在configure方法中按如下代码进行使用:

app.useahphocelot().wait();

好了,以上就是实现的整个过程了。经过这么一分析是不是觉得很简单呢。当然具体为什么按照上面处理就能够从数据库获取配置了呢,这个还需要你分析了源码后才能了解。我也只是给你引路,传达我实现的思路。

源码

https://github.com/yilezhu/ocelot.configauthlimitcache

总结

今天抽空对上篇文章进行了补充说明,目的是给大家阐述下,配置文件存储到数据库中的实现过程及原理。让你能够根据自身需要来进行改写来满足你的业务需求。当然我也只是给你引路,具体为什么这样实现下就能够成功呢?答案在ocelot的源码中。

ocelot简易教程目录

  1. ocelot简易教程(一)之ocelot是什么
  2. ocelot简易教程(二)之快速开始1
  3. ocelot简易教程(二)之快速开始2
  4. ocelot简易教程(三)之主要特性及路由详解
  5. ocelot简易教程(四)之请求聚合以及服务发现
  6. ocelot简易教程(五)之集成identityserver认证以及授权
  7. ocelot简易教程(六)之重写配置文件存储方式并优化响应数据
  8. ocelot简易教程(七)之配置文件数据库存储插件源码解析