1. 程式人生 > >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 
[email protected]
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簡易教程目錄