定制Ocelot来满足需求

这篇文章,我们将从Ocelot的中间件源码分析,目前Ocelot已经实现那些功能,还有那些功能在我们实际项目中暂时还未实现,如果我们要使用这些功能,应该如何改造等方面来说明。

一、Ocelot源码解读

在使用一个组件前,最好我们要了解其中的一些原理,否则在使用过程中遇到问题,也无从下手,今天我带着大家一起来解读下Ocelot源码,并梳理出具体实现的原理和流程,便于我们根据需求扩展应用。
Ocelot源码地址[https://github.com/ThreeMammals/Ocelot],
Ocelot文档地址[https://ocelot.readthedocs.io/en/latest/]

查看.NETCORE相关中间件源码,我们优先找到入口方法,比如Ocelot中间件使用的是app.UseOcelot(),我们直接搜索UserOcelot,我们会找到OcelotMiddlewareExtensions方法,里面是Ocelot中间件实际运行的方式和流程。
640?wx_fmt=png

然后继续顺藤摸瓜,查看详细的实现,我们会发现如下代码

public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder builder, OcelotPipelineConfiguration pipelineConfiguration)        {   //创建配置信息var configuration = await CreateConfiguration(builder);            //监听配置信息ConfigureDiagnosticListener(builder);            //创建执行管道return CreateOcelotPipeline(builder, pipelineConfiguration);}

然后我们继续跟踪到创建管道方法,可以发现Ocelot的执行流程已经被找到,现在问题变的简单了,直接查看

private static IApplicationBuilder CreateOcelotPipeline(IApplicationBuilder builder, OcelotPipelineConfiguration pipelineConfiguration){    var pipelineBuilder = new OcelotPipelineBuilder(builder.ApplicationServices);    //详细创建的管道顺序在此方法pipelineBuilder.BuildOcelotPipeline(pipelineConfiguration);    var firstDelegate = pipelineBuilder.Build();    /*inject first delegate into first piece of asp.net middleware..maybe not like thisthen because we are updating the http context in ocelot it comes out correct forrest of asp.net..*/builder.Properties["analysis.NextMiddlewareName"] = "TransitionToOcelotMiddleware";builder.Use(async (context, task) =>{                    var downstreamContext = new DownstreamContext(context);                    await firstDelegate.Invoke(downstreamContext);});    return builder;
}

管道创建流程及实现,会不会感觉到摸到大动脉了,核心的功能及原理基本找到了,那以后动手术也就可以避开一些坑了,我们可以对着这个执行顺序,再查看详细的源码,按照这个执行顺序查看源码,您就会发现整个思路非常清晰,每一步的实现一目了然。为了更直观的介绍源码的解读方式,这里我们就拿我们后续要操刀的中间件来讲解下中间件的具体实现。

public static class OcelotPipelineExtensions{        public static OcelotRequestDelegate BuildOcelotPipeline(this IOcelotPipelineBuilder builder,OcelotPipelineConfiguration pipelineConfiguration){            // This is registered to catch any global exceptions that are not handled// It also sets the Request Id if anything is set globallybuilder.UseExceptionHandlerMiddleware();            // If the request is for websockets upgrade we fork into a different pipelinebuilder.MapWhen(context => context.HttpContext.WebSockets.IsWebSocketRequest,app =>{                    app.UseDownstreamRouteFinderMiddleware();                    app.UseDownstreamRequestInitialiser();                    app.UseLoadBalancingMiddleware();                    app.UseDownstreamUrlCreatorMiddleware();                    app.UseWebSocketsProxyMiddleware();});            // Allow the user to respond with absolutely anything they want.builder.UseIfNotNull(pipelineConfiguration.PreErrorResponderMiddleware);            // This is registered first so it can catch any errors and issue an appropriate responsebuilder.UseResponderMiddleware();            // Then we get the downstream route informationbuilder.UseDownstreamRouteFinderMiddleware();            // This security module, IP whitelist blacklist, extended security mechanismbuilder.UseSecurityMiddleware();            //Expand other branch pipesif (pipelineConfiguration.MapWhenOcelotPipeline != null){                foreach (var pipeline in pipelineConfiguration.MapWhenOcelotPipeline){                    builder.MapWhen(pipeline);}}            // Now we have the ds route we can transform headers and stuff?builder.UseHttpHeadersTransformationMiddleware();            // Initialises downstream requestbuilder.UseDownstreamRequestInitialiser();            // We check whether the request is ratelimit, and if there is no continue processingbuilder.UseRateLimiting();            // This adds or updates the request id (initally we try and set this based on global config in the error handling middleware)// If anything was set at global level and we have a different setting at re route level the global stuff will be overwritten// This means you can get a scenario where you have a different request id from the first piece of middleware to the request id middleware.builder.UseRequestIdMiddleware();            // Allow pre authentication logic. The idea being people might want to run something custom before what is built in.builder.UseIfNotNull(pipelineConfiguration.PreAuthenticationMiddleware);            // Now we know where the client is going to go we can authenticate them.// We allow the ocelot middleware to be overriden by whatever the// user wantsif (pipelineConfiguration.AuthenticationMiddleware == null){                builder.UseAuthenticationMiddleware();}            else{                builder.Use(pipelineConfiguration.AuthenticationMiddleware);}            // The next thing we do is look at any claims transforms in case this is important for authorisationbuilder.UseClaimsToClaimsMiddleware();            // Allow pre authorisation logic. The idea being people might want to run something custom before what is built in.builder.UseIfNotNull(pipelineConfiguration.PreAuthorisationMiddleware);            // Now we have authenticated and done any claims transformation we // can authorise the request// We allow the ocelot middleware to be overriden by whatever the// user wantsif (pipelineConfiguration.AuthorisationMiddleware == null){//使用自定义认证,移除默认的认证方式//builder.UseAuthorisationMiddleware();}else{                builder.Use(pipelineConfiguration.AuthorisationMiddleware);}            // Now we can run the claims to headers transformation middlewarebuilder.UseClaimsToHeadersMiddleware();            // Allow the user to implement their own query string manipulation logicbuilder.UseIfNotNull(pipelineConfiguration.PreQueryStringBuilderMiddleware);            // Now we can run any claims to query string transformation middlewarebuilder.UseClaimsToQueryStringMiddleware();            // Get the load balancer for this requestbuilder.UseLoadBalancingMiddleware();            // This takes the downstream route we retrieved earlier and replaces any placeholders with the variables that should be usedbuilder.UseDownstreamUrlCreatorMiddleware();            // Not sure if this is the best place for this but we use the downstream url // as the basis for our cache key.builder.UseOutputCacheMiddleware();            //We fire off the request and set the response on the scoped data repobuilder.UseHttpRequesterMiddleware();            return builder.Build();}    private static void UseIfNotNull(this IOcelotPipelineBuilder builder,Func<DownstreamContext, Func<Task>, Task> middleware){            if (middleware != null){                builder.Use(middleware);}}}

限流中间件实现解析

实现代码如下builder.UseRateLimiting();,我们转到定义,得到如下代码,详细的实现逻辑在ClientRateLimitMiddleware方法里,继续转定义到这个方法,我把方法里用到的内容注释了下。

public static class RateLimitMiddlewareExtensions{    public static IOcelotPipelineBuilder UseRateLimiting(this IOcelotPipelineBuilder builder)    {        return builder.UseMiddleware<ClientRateLimitMiddleware>();}
}public class ClientRateLimitMiddleware : OcelotMiddleware{        private readonly OcelotRequestDelegate _next;        private readonly IRateLimitCounterHandler _counterHandler;        private readonly ClientRateLimitProcessor _processor;        public ClientRateLimitMiddleware(OcelotRequestDelegate next,IOcelotLoggerFactory loggerFactory,IRateLimitCounterHandler counterHandler):base(loggerFactory.CreateLogger<ClientRateLimitMiddleware>())        {_next = next;_counterHandler = counterHandler;_processor = new ClientRateLimitProcessor(counterHandler);}        //熟悉的Tnvoke方法,所有的逻辑都在此方法里。public async Task Invoke(DownstreamContext context)        {            var options = context.DownstreamReRoute.RateLimitOptions;            // 校验是否启用限流配置if (!context.DownstreamReRoute.EnableEndpointEndpointRateLimiting){//未启用直接进入下一个中间件Logger.LogInformation($"EndpointRateLimiting is not enabled for {context.DownstreamReRoute.DownstreamPathTemplate.Value}");                await _next.Invoke(context);                return;}            // 获取配置的校验客户端的方式var identity = SetIdentity(context.HttpContext, options);            // 校验是否为白名单if (IsWhitelisted(identity, options)){//白名单直接放行Logger.LogInformation($"{context.DownstreamReRoute.DownstreamPathTemplate.Value} is white listed from rate limiting");                await _next.Invoke(context);                return;}            var rule = options.RateLimitRule;            if (rule.Limit > 0){//限流数是否大于0// 获取当前客户端请求情况,这里需要注意_processor是从哪里注入的,后续重var counter = _processor.ProcessRequest(identity, options);                // 校验请求数是否大于限流数if (counter.TotalRequests > rule.Limit){                    //获取下次有效请求的时间,就是避免每次请求,都校验一次var retryAfter = _processor.RetryAfterFrom(counter.Timestamp, rule);                    // 写入日志LogBlockedRequest(context.HttpContext, identity, counter, rule, context.DownstreamReRoute);                    var retrystring = retryAfter.ToString(System.Globalization.CultureInfo.InvariantCulture);                    // 抛出超出限流异常并把下次可请求时间写入header里。await ReturnQuotaExceededResponse(context.HttpContext, options, retrystring);                    return;}}            //如果启用了限流头部if (!options.DisableRateLimitHeaders){                var headers = _processor.GetRateLimitHeaders(context.HttpContext, identity, options);context.HttpContext.Response.OnStarting(SetRateLimitHeaders, state: headers);}            //进入下一个中间件await _next.Invoke(context);}        public virtual ClientRequestIdentity SetIdentity(HttpContext httpContext, RateLimitOptions option)        {            var clientId = "client";            if (httpContext.Request.Headers.Keys.Contains(option.ClientIdHeader)){clientId = httpContext.Request.Headers[option.ClientIdHeader].First();}            return new ClientRequestIdentity(clientId,httpContext.Request.Path.ToString().ToLowerInvariant(),httpContext.Request.Method.ToLowerInvariant());}        public bool IsWhitelisted(ClientRequestIdentity requestIdentity, RateLimitOptions option)        {            if (option.ClientWhitelist.Contains(requestIdentity.ClientId)){                return true;}            return false;}        public virtual void LogBlockedRequest(HttpContext httpContext, ClientRequestIdentity identity, RateLimitCounter counter, RateLimitRule rule, DownstreamReRoute downstreamReRoute)        {Logger.LogInformation(                $"Request {identity.HttpVerb}:{identity.Path} from ClientId {identity.ClientId} has been blocked, quota {rule.Limit}/{rule.Period} exceeded by {counter.TotalRequests}. Blocked by rule { downstreamReRoute.UpstreamPathTemplate.OriginalValue }, TraceIdentifier {httpContext.TraceIdentifier}.");}        public virtual Task ReturnQuotaExceededResponse(HttpContext httpContext, RateLimitOptions option, string retryAfter)        {            var message = string.IsNullOrEmpty(option.QuotaExceededMessage) ? $"API calls quota exceeded! maximum admitted {option.RateLimitRule.Limit} per {option.RateLimitRule.Period}." : option.QuotaExceededMessage;            if (!option.DisableRateLimitHeaders){httpContext.Response.Headers["Retry-After"] = retryAfter;}httpContext.Response.StatusCode = option.HttpStatusCode;            return httpContext.Response.WriteAsync(message);}        private Task SetRateLimitHeaders(object rateLimitHeaders)        {            var headers = (RateLimitHeaders)rateLimitHeaders;headers.Context.Response.Headers["X-Rate-Limit-Limit"] = headers.Limit;headers.Context.Response.Headers["X-Rate-Limit-Remaining"] = headers.Remaining;headers.Context.Response.Headers["X-Rate-Limit-Reset"] = headers.Reset;            return Task.CompletedTask;}}

通过源码解析,发现实现一个限流还是很简单的吗!再进一步解析,IRateLimitCounterHandler ClientRateLimitProcessor里的相关接口又是怎么实现的呢?这时候我们就需要了解下.NETCORE 的运行原理,其中ConfigureServices方法实现了依赖注入(DI)的配置。这时候我们看下Ocelot是在哪里进行注入的呢?

services.AddOcelot()是不是印象深刻呢?原来所有的注入信息都写在这里,那么问题简单了,Ctrl+F查找AddOcelot方法,马上就能定位到ServiceCollectionExtensions方法,然后再转到定义OcelotBuilder

public static class ServiceCollectionExtensions{    public static IOcelotBuilder AddOcelot(this IServiceCollection services)    {        var service = services.First(x => x.ServiceType == typeof(IConfiguration));        var configuration = (IConfiguration)service.ImplementationInstance;        return new OcelotBuilder(services, configuration);}    public static IOcelotBuilder AddOcelot(this IServiceCollection services, IConfiguration configuration)    {        return new OcelotBuilder(services, configuration);}
}

又摸到大动脉啦,现在问题迎刃而解,原来所有的注入都写在这里,从这里可以找下我们熟悉的几个接口注入。

public OcelotBuilder(IServiceCollection services, IConfiguration configurationRoot)
{Configuration = configurationRoot;Services = services;Services.Configure<FileConfiguration>(configurationRoot);Services.TryAddSingleton<IOcelotCache<FileConfiguration>, InMemoryCache<FileConfiguration>>();Services.TryAddSingleton<IOcelotCache<CachedResponse>, InMemoryCache<CachedResponse>>();Services.TryAddSingleton<IHttpResponseHeaderReplacer, HttpResponseHeaderReplacer>();Services.TryAddSingleton<IHttpContextRequestHeaderReplacer, HttpContextRequestHeaderReplacer>();Services.TryAddSingleton<IHeaderFindAndReplaceCreator, HeaderFindAndReplaceCreator>();Services.TryAddSingleton<IInternalConfigurationCreator, FileInternalConfigurationCreator>();Services.TryAddSingleton<IInternalConfigurationRepository, InMemoryInternalConfigurationRepository>();Services.TryAddSingleton<IConfigurationValidator, FileConfigurationFluentValidator>();Services.TryAddSingleton<HostAndPortValidator>();Services.TryAddSingleton<IReRoutesCreator, ReRoutesCreator>();Services.TryAddSingleton<IAggregatesCreator, AggregatesCreator>();Services.TryAddSingleton<IReRouteKeyCreator, ReRouteKeyCreator>();Services.TryAddSingleton<IConfigurationCreator, ConfigurationCreator>();Services.TryAddSingleton<IDynamicsCreator, DynamicsCreator>();Services.TryAddSingleton<ILoadBalancerOptionsCreator, LoadBalancerOptionsCreator>();Services.TryAddSingleton<ReRouteFluentValidator>();Services.TryAddSingleton<FileGlobalConfigurationFluentValidator>();Services.TryAddSingleton<FileQoSOptionsFluentValidator>();Services.TryAddSingleton<IClaimsToThingCreator, ClaimsToThingCreator>();Services.TryAddSingleton<IAuthenticationOptionsCreator, AuthenticationOptionsCreator>();Services.TryAddSingleton<IUpstreamTemplatePatternCreator, UpstreamTemplatePatternCreator>();Services.TryAddSingleton<IRequestIdKeyCreator, RequestIdKeyCreator>();Services.TryAddSingleton<IServiceProviderConfigurationCreator,ServiceProviderConfigurationCreator>();Services.TryAddSingleton<IQoSOptionsCreator, QoSOptionsCreator>();Services.TryAddSingleton<IReRouteOptionsCreator, ReRouteOptionsCreator>();Services.TryAddSingleton<IRateLimitOptionsCreator, RateLimitOptionsCreator>();Services.TryAddSingleton<IBaseUrlFinder, BaseUrlFinder>();Services.TryAddSingleton<IRegionCreator, RegionCreator>();Services.TryAddSingleton<IFileConfigurationRepository, DiskFileConfigurationRepository>();Services.TryAddSingleton<IFileConfigurationSetter, FileAndInternalConfigurationSetter>();Services.TryAddSingleton<IServiceDiscoveryProviderFactory, ServiceDiscoveryProviderFactory>();Services.TryAddSingleton<ILoadBalancerFactory, LoadBalancerFactory>();Services.TryAddSingleton<ILoadBalancerHouse, LoadBalancerHouse>();Services.TryAddSingleton<IOcelotLoggerFactory, AspDotNetLoggerFactory>();Services.TryAddSingleton<IRemoveOutputHeaders, RemoveOutputHeaders>();Services.TryAddSingleton<IClaimToThingConfigurationParser, ClaimToThingConfigurationParser>();Services.TryAddSingleton<IClaimsAuthoriser, ClaimsAuthoriser>();Services.TryAddSingleton<IScopesAuthoriser, ScopesAuthoriser>();Services.TryAddSingleton<IAddClaimsToRequest, AddClaimsToRequest>();Services.TryAddSingleton<IAddHeadersToRequest, AddHeadersToRequest>();Services.TryAddSingleton<IAddQueriesToRequest, AddQueriesToRequest>();Services.TryAddSingleton<IClaimsParser, ClaimsParser>();Services.TryAddSingleton<IUrlPathToUrlTemplateMatcher, RegExUrlMatcher>();Services.TryAddSingleton<IPlaceholderNameAndValueFinder, UrlPathPlaceholderNameAndValueFinder>();Services.TryAddSingleton<IDownstreamPathPlaceholderReplacer, DownstreamTemplatePathPlaceholderReplacer>();Services.TryAddSingleton<IDownstreamRouteProvider, DownstreamRouteFinder>();Services.TryAddSingleton<IDownstreamRouteProvider, DownstreamRouteCreator>();Services.TryAddSingleton<IDownstreamRouteProviderFactory, DownstreamRouteProviderFactory>();Services.TryAddSingleton<IHttpRequester, HttpClientHttpRequester>();Services.TryAddSingleton<IHttpResponder, HttpContextResponder>();Services.TryAddSingleton<IErrorsToHttpStatusCodeMapper, ErrorsToHttpStatusCodeMapper>();Services.TryAddSingleton<IRateLimitCounterHandler, MemoryCacheRateLimitCounterHandler>();Services.TryAddSingleton<IHttpClientCache, MemoryHttpClientCache>();Services.TryAddSingleton<IRequestMapper, RequestMapper>();Services.TryAddSingleton<IHttpHandlerOptionsCreator, HttpHandlerOptionsCreator>();Services.TryAddSingleton<IDownstreamAddressesCreator, DownstreamAddressesCreator>();Services.TryAddSingleton<IDelegatingHandlerHandlerFactory, DelegatingHandlerHandlerFactory>();Services.TryAddSingleton<IHttpRequester, HttpClientHttpRequester>();// see this for why we register this as singleton http://stackoverflow.com/questions/37371264/invalidoperationexception-unable-to-resolve-service-for-type-microsoft-aspnetc// could maybe use a scoped data repositoryServices.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();Services.TryAddSingleton<IRequestScopedDataRepository, HttpDataRepository>();Services.AddMemoryCache();Services.TryAddSingleton<OcelotDiagnosticListener>();Services.TryAddSingleton<IMultiplexer, Multiplexer>();Services.TryAddSingleton<IResponseAggregator, SimpleJsonResponseAggregator>();Services.TryAddSingleton<ITracingHandlerFactory, TracingHandlerFactory>();Services.TryAddSingleton<IFileConfigurationPollerOptions, InMemoryFileConfigurationPollerOptions>();Services.TryAddSingleton<IAddHeadersToResponse, AddHeadersToResponse>();Services.TryAddSingleton<IPlaceholders, Placeholders>();Services.TryAddSingleton<IResponseAggregatorFactory, InMemoryResponseAggregatorFactory>();Services.TryAddSingleton<IDefinedAggregatorProvider, ServiceLocatorDefinedAggregatorProvider>();Services.TryAddSingleton<IDownstreamRequestCreator, DownstreamRequestCreator>();Services.TryAddSingleton<IFrameworkDescription, FrameworkDescription>();Services.TryAddSingleton<IQoSFactory, QoSFactory>();Services.TryAddSingleton<IExceptionToErrorMapper, HttpExeptionToErrorMapper>();//add security this.AddSecurity();//add asp.net services..var assembly = typeof(FileConfigurationController).GetTypeInfo().Assembly;Services.AddMvcCore().AddApplicationPart(assembly).AddControllersAsServices().AddAuthorization().AddJsonFormatters();Services.AddLogging();Services.AddMiddlewareAnalysis();Services.AddWebEncoders();
}

至此Ocelot源码解析就到这里了,其他的具体实现代码就根据流程一个一个查看即可,这里就不详细讲解了,因为我们已经掌握整个Ocelot代码的运行原理和实现方式及流程,项目里其他的一大堆的代码都是围绕这个流程去一步一步实现的。

有没有感觉添加一个中间件不是很复杂呢,是不是都跃跃欲试,准备尝试开发自己的自定义中间件啦,本篇就不介绍中间件的具体开发流程了,后续实战中会包含部分项目中需要用到的中间件,到时候会详细讲解如何规划和开发一个满足自己项目需求的中间件。

二、结合项目梳理功能

在完整学习完Ocelot文档和源码后,我们基本掌握了Ocelot目前已经实现的功能,再结合我们实际项目需求,我们梳理下还有哪些功能可能需要自己扩展实现。

项目设计网关基本需求包括路由、认证、授权、限流、缓存,仔细学习文档和源码后发现功能都已经存在,那是不是我们就可以直接拿来使用呢?这时候我们需要拿出一些复杂业务场景来对号入座,看能否实现复杂场景的一些应用。

1、授权

能否为每一个客户端设置独立的访问权限,如果客户端A可以访问服务A、服务B,客户端B只能访问服务A,从网关层面直接授权,不满足需求不路由到具体服务。从文档和代码分析后发现暂时未实现。

2、限流

能否为每一个客户端设置不能限流规则,例如客户端A为我们内容应用,我希望对服务A不启用限流,客户端B为第三方接入应用,我需要B访问服务A访问进行单独限流(30次/分钟),看能否通过配置实现自定义限流。从文档和代码分析后发现暂时未实现。

3、缓存

通过代码发现目前缓存实现的只是Dictionary方式实现的缓存,不能实现分布式结构的应用。

通过分析我们发现列举的5个基本需求,尽然有3个在我们实际项目应用中可能会存在问题,如果不解决这些问题,很难直接拿这个完美的网关项目应用到正式项目,所以我们到通过扩展Ocelot方法来实现我们的目的。

如何扩展呢

为了满足我们项目应用的需要,我们需要为每一个路由进行单独设置,如果还采用配置文件的方式,肯定无法满足需求,且后续网关动态增加路由、授权、限流等无法控制,所以我们需要把网关配置信息从配置文件中移到数据库中,由数据库中的路由表、限流表、授权表等方式记录当前网关的应用,且后续扩展直接在数据库中增加或减少相关配置,然后动态更新网关配置实现网关的高可用。

想一想是不是有点小激动,原来只要稍微改造下宝骏瞬间变宝马,那接下来的课程就是网关改造之旅,我会从设计、思想、编码等方面讲解下如何实现我们的第一辆宝马。

本系列文章我也是边想边写边实现,如果发现中间有任何描述或实现不当的地方,也请各位大神批评指正,我会第一时间整理并修正,避免让后续学习的人走弯路。

相关文章:

  • AspNetCore中使用Ocelot之 IdentityServer4

  • Ocelot-基于.NET Core的开源网关实现

  • .NET Core微服务之基于Ocelot+IdentityServer实现统一验证与授权

  • Swagger如何访问Ocelot中带权限验证的API

  • Ocelot.JwtAuthorize:一个基于网关的Jwt验证包

  • .NET Core微服务之基于Ocelot实现API网关服务

  • .NET Core微服务之基于Ocelot实现API网关服务(续)

  • .NET微服务体系结构中为什么使用Ocelot实现API网关

  • Ocelot简易教程(一)之Ocelot是什么

  • Ocelot简易教程(二)之快速开始1

  • Ocelot简易教程(二)之快速开始2

  • Ocelot简易教程(三)之主要特性及路由详解

  • Ocelot简易教程(四)之请求聚合以及服务发现

  • Ocelot简易教程(五)之集成IdentityServer认证以及授权

  • Ocelot简易教程(六)之重写配置文件存储方式并优化响应数据

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

  • ASP.NET Core中Ocelot的使用:API网关的应用

  • ASP.NET Core中Ocelot的使用:基于Spring Cloud Netflix Eureka的动态路由

  • ASP.NET Core中Ocelot的使用:基于服务发现的负载均衡

原文地址: https://www.cnblogs.com/jackcao/p/9937213.html


.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com

640?wx_fmt=jpeg

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/319067.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【.NET Core项目实战-统一认证平台】第一章 功能及架构分析

从本文开始&#xff0c;我们正式进入项目研发阶段&#xff0c;首先我们分析下统一认证平台应该具备哪些功能性需求和非功能性需求&#xff0c;在梳理完这些需求后&#xff0c;设计好系统采用的架构来满足已有的需求和未来的扩展应用。1 功能性需求统一认证平台应该具备以下基本…

Shift and Reverse

题目链接 题意&#xff1a; 一个序列a1&#xff0c;a2&#xff0c;a3…an 选择一个i&#xff0c;然后将序列改成ai,ai-1,…a1,an,an-1,…ai1 可以进行无数次这样的操作 问&#xff1a;最多有多少不同的序列产生&#xff1f;&#xff08;答案mod1e97&#xff09; 题解&#xf…

Redis基本使用及百亿数据量中的使用技巧分享

作者&#xff1a;依乐祝原文地址&#xff1a;https://www.cnblogs.com/yilezhu/p/9941208.html作者&#xff1a;大石头时间&#xff1a;2018-11-10 晚上20&#xff1a;00地点&#xff1a;钉钉群&#xff08;组织代码BKMV7685&#xff09;QQ群&#xff1a;1600800内容&#xff1…

Subsequence Pair

题目 题目描述 题意&#xff1a; X和Y两个字符串&#xff0c;两个字符串各取子序列X1和Y1&#xff0c;问X1<Y1的情况下X1和Y1的长度和最长是多少&#xff1f; 比如例子&#xff1a; zazxwabzczazazd abcaa 第一个字符串选取子序列为azxwabzczazazd 第二个为bcaa azxwabzc…

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

本篇将介绍如何扩展Ocelot中间件实现自定义网关&#xff0c;并使用2种不同数据库来演示Ocelot配置信息存储和动态更新功能&#xff0c;内容也是从实际设计出发来编写我们自己的中间件&#xff0c;本文内容涵盖设计思想内容和代码内容&#xff0c;我希望园友们最好跟着我这个文章…

一个技术管理者的苦逼【技术管理漫谈】

希望给你3-5分钟的碎片化学习&#xff0c;可能是坐地铁、等公交&#xff0c;积少成多&#xff0c;水滴石穿&#xff0c;谢谢关注。角色转变 从工程师转技术管理这两年&#xff0c;好比头马变成车夫&#xff0c;除了角色认知的转变&#xff0c;还要看方向&#xff0c;定计划。不…

[机器翻译]参与 Microsoft 开放源代码软件项目的方式

下面是一个事实&#xff1a;Microsoft 托管在 GitHub&#xff0c;包括.NET 编译器平台&#xff0c;也称为"Roslyn"具有多达 4 万行代码等一些相当大的大约 2,000 开放源代码软件 (OSS) 存储库。很多开发人员的代码将更改提交到数以百万计的计算机运行的项目可能会令人…

【蓝桥杯】 2018年国赛 矩阵求和

题目 题目&#xff1a; 经过重重笔试面试的考验&#xff0c;小明成功进入 Macrohard 公司工作。 今天小明的任务是填满这么一张表&#xff1a; 表有 n 行 n 列&#xff0c;行和列的编号都从1算起。 其中第 i 行第 j 个元素的值是 gcd(i, j)的平方&#xff0c; gcd 表示最大公…

被低估的.net(上) - 微软MonkeyFest 2018广州分享会活动回顾

前天, 2018年11月10日, 广州图书馆\微软云开发者社区\广东职业教育信息化研究会\珠三角技术沙龙在广州图书馆负一层1号报告厅搞了一场”微软最有价值专家(MVP)广州分享会 - MonkeyFest 2018广州分享会”. 这是在广州图书馆官方微信公众号上的活动报名链接: https://mp.weixin.q…

C#的RSA加密解密签名,就为了支持PEM PKCS#8格式密钥对的导入导出

差点造了一整个轮子.Net Framework 4.5 里面的RSA功能&#xff0c;并未提供简单对PEM密钥格式的支持&#xff08;.Net Core有咩&#xff1f;&#xff09;&#xff0c;差点&#xff08;还远着&#xff09;造了一整个轮子&#xff0c;就为了支持PEM PKCS#8、PKCS#1格式密钥对的导…

福州首届.NET开源社区技术交流会圆满成功

活动总结2018年11月10日周六的下午&#xff0c;在福州蒲公英创新工场举办了福州首届.NET开源社区技术交流会&#xff0c;来自福建省各大科技公司的技术小伙伴齐聚一堂&#xff0c;为了就是能在现场学习到微软跨平台技术.NET Core、微服务以及Azure云服务。在交流会现场&#xf…

.NET Core开发者的福音之玩转Redis的又一傻瓜式神器推荐

引子为什么写这篇文章呢&#xff1f;因为.NET Core的生态越来越好了&#xff01;之前玩转.net的时候操作Redis相信大伙都使用过一些组件&#xff0c;但都有一些缺点&#xff0c;如ServiceStack.Redis 是商业版&#xff0c;免费版有限制&#xff1b;StackExchange.Redis 是免费版…

P2495 [SDOI2011]消耗战(树形dp+虚树)

P2495 [SDOI2011]消耗战 树形dp 状态表示&#xff1a;fuf_ufu​表示以uuu为根的子树中&#xff0c;uuu节点与子树中的关键的“隔开”所需要的最小代价 状态转移&#xff1a; 考虑uuu的一个儿子vvv vvv是关键点&#xff1a;fufuwu→vf_uf_uw_{u\to v}fu​fu​wu→v​vvv不是关键…

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

上篇文章我们介绍了如何扩展Ocelot网关&#xff0c;并实现数据库存储&#xff0c;然后测试了网关的路由功能&#xff0c;一切都是那么顺利&#xff0c;但是有一个问题未解决&#xff0c;就是如果网关配置信息发生变更时如何生效&#xff1f;以及我使用其他数据库存储如何快速实…

计算机提示找不到vcruntime140.dll,无法继续执行代码怎么办?如何修复

“找不到vcruntime140.dll&#xff0c;无法继续执行代码”。这个问题可能会让你感到困惑&#xff0c;不知道如何解决。那么&#xff0c;vcruntime140.dll是什么文件&#xff1f;它为什么会丢失&#xff1f;又该如何解决这个问题呢&#xff1f;本文将为你详细介绍vcruntime140.d…

codeforces1486 F. Pairs of Paths(倍增+树上数数)

F. Pairs of Paths syksykCCC题解 iamhpp题解 首先说明&#xff0c;下面图片来自第一篇博客&#xff0c;下面代码照抄第二篇博客 对没有啥是自己写的&#xff08;因为我太菜~~ 从上图可以看出两条链只有一个交点可能有两种情况 交点是两条链的LCA交点是一条链的LCA而不是另一…

学习Raft算法的笔记

Raft是一种为了管理日志复制的一致性算法。它提供了和Paxos算法相同的功能和性能&#xff0c;但是它的算法结构和Paxos不同&#xff0c;使得Raft算法更加容易理解并且更容易构建实际的系统。为了提升可理解性&#xff0c;Raft将一致性算法分解成几个关键的模块&#xff0c;例如…

.NET Core 必备安全措施

.NET Core大大简化了.NET应用程序的开发。它的自动配置和启动依赖大大减少了开始一个应用所需的代码和配置量&#xff0c;本文目的是介绍如何创建更安全的.NET Core应用程序。1.在生产中使用HTTPS传输层安全性&#xff08;TLS&#xff09;是HTTPS的官方名称&#xff0c;你可能听…

[翻译] C# 8.0 新特性

原文: Building C# 8.0[译注:原文主标题如此&#xff0c;但内容大部分为新特性介绍&#xff0c;所以意译标题为 "C# 8.0 新特性"]C# 的下一个主要版本是 8.0。我们已经为它工作了很长一段时间&#xff0c;即使我们构建并发布了次要版本 C# 7.1, 7.2 和 7.3&#xff0…

[蓝桥杯][2018年第九届真题]搭积木

[[蓝桥杯][2018年第九届真题]搭积木](https://www.dotcpp.com/oj/problem2292.html)题目&#xff1a; 小明对搭积木非常感兴趣。他的积木都是同样大小的正立方体。 在搭积木时&#xff0c;小明选取 m 块积木作为地基&#xff0c;将他们在桌子上一字排开&#xff0c;中间不留空…