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); } }
这其中最主要的代码就是,重写配置文件获取这块。我在下面进行了截图,并圈出来了,大家自行查看吧。
代码重写好了。由于我们服务注册时通过扩展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的源码中。