zoukankan      html  css  js  c++  java
  • 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);
            }
        }

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

    1540470343082

    代码重写好了。由于我们服务注册时通过扩展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的定制相关的·内容可以看我一个朋友写的系列博客,博客地址:https://www.cnblogs.com/jackcao/p/9928879.html 【.NET Core微服务实战-统一身份认证】网关篇,这里给你详细介绍了如何进行自定义限流以及客户端授权并重写Ocelot缓存成Redis!有兴趣的可以看下!

     

    作者:依乐祝

    出处:https://www.cnblogs.com/yilezhu

    联系:1015657114@qq.com .NET Core实战项目交流群:637326624

    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。如有问题或建议,请多多赐教,非常感谢。
    DotNetCore实战
  • 相关阅读:
    Kafka单机环境部署
    kafka启动出现:Unsupported major.minor version 52.0 错误
    CentOs7.3 搭建 Redis-4.0.1 Cluster 集群服务
    Python ZKPython 安装
    zookeeper伪集群安装
    系统吞吐量(TPS)、用户并发量、性能测试概念和公式
    XDebug安装配置教程
    48 条高效率的 PHP 优化写法
    待处理bug
    phpstudy composer 使用安装
  • 原文地址:https://www.cnblogs.com/bigmango/p/10393108.html
Copyright © 2011-2022 走看看