ASP.NET Core 2.2 : 二十一. 内容协商与自定义IActionResult和格式化类

上一章的结尾留下了一个问题:同样是ObjectResult,在执行的时候又是如何被转换成string和JSON两种格式的呢?

本章来解答这个问题,这里涉及到一个名词:”内容协商”。除了这个,本章将通过两个例子来介绍如何自定义IActionResult和格式化类。 (ASP.NET Core 系列目录)

一、内容协商

依然以返回Book类型的Action为例,看看它是怎么被转换为JSON类型的。

public Book GetModel()
{
    return new Book() { Code = "1001", Name = "ASP" };
}

这个Action执行后被封装为ObjectResult,接下来就是这个ObjectResult的执行过程。

ObjectResult的代码如下:

public class ObjectResult : ActionResult, IStatusCodeActionResult
{
     //部分代码略
     public override Task ExecuteResultAsync(ActionContext context)
     {
          var executor = context.HttpContext.RequestServices.GetRequiredService>();
          return executor.ExecuteAsync(context, this);
     }
}

它是如何被执行的呢?首先会通过依赖注入获取ObjectResult对应的执行者,获取到的是ObjectResultExecutor,然后调用ObjectResultExecutor的ExecuteAsync方法。代码如下:

public class ObjectResultExecutor : IActionResultExecutor
{
     //部分代码略
     public virtual Task ExecuteAsync(ActionContext context, ObjectResult result)
     {
          //部分代码略
          var formatterContext = new OutputFormatterWriteContext(
               context.HttpContext,
               WriterFactory,
               objectType,
               result.Value);

            var selectedFormatter = FormatterSelector.SelectFormatter(
                formatterContext,
                (IList)result.Formatters ?? Array.Empty(),
                result.ContentTypes);
            if (selectedFormatter == null)
            {
                // No formatter supports this.

                Logger.NoFormatter(formatterContext);
                context.HttpContext.Response.StatusCode = StatusCodes.Status406NotAcceptable;
                return Task.CompletedTask;
            }

            result.OnFormatting(context);
            return selectedFormatter.WriteAsync(formatterContext);
     }
}

核心代码就是FormatterSelector.SelectFormatter()方法,它的作用是选择一个合适的Formatter。Formatter顾名思义就是一个用于格式化数据的类。系统默认提供了4种Formatter,如下图 1

ASP.NET Core 2.2 : 二十一. 内容协商与自定义IActionResult和格式化类

图 1

它们都实现了IOutputFormatter接口,继承关系如下图 2:

ASP.NET Core 2.2 : 二十一. 内容协商与自定义IActionResult和格式化类

图 2

IOutputFormatter代码如下:

public interface IOutputFormatter
{
    bool CanWriteResult(OutputFormatterCanWriteContext context);
    Task WriteAsync(OutputFormatterWriteContext context);
}

又是非常熟悉的方式,就像在众多 _XXX_ResultExecutor中筛选一个合适的Action的执行者一样,首先将它们按照一定的顺序排列,然后开始遍历,逐一执行它们的Can _XXX_方法,若其中一个的执行结果为true,则它就会被选出来。例如StringOutputFormatter的代码如下:

public class StringOutputFormatter : TextOutputFormatter
{
    public StringOutputFormatter()
    {
        SupportedEncodings.Add(Encoding.UTF8);
        SupportedEncodings.Add(Encoding.Unicode);
        SupportedMediaTypes.Add("text/plain");
    }

    public override bool CanWriteResult(OutputFormatterCanWriteContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        if (context.ObjectType == typeof(string) || context.Object is string)
        {
            return base.CanWriteResult(context);
        }

         return false;
    }
    //省略部分代码
}

从StringOutputFormatter的CanWriteResult方法中可以知道它能处理的是string类型的数据。它的构造方法中标识它可以处理的字符集为UTF8和Unicode。对应的数据格式标记为”text/plain”。同样查看HttpNoContentOutputFormatter和HttpNoContentOutputFormatter对应的是返回值为void或者task的,StreamOutputFormatter对应的是Stream类型的。

JsonOutputFormatter没有重写CanWriteResult方法,采用的是OutputFormatter的CanWriteResult方法,代码如下:

public abstract class OutputFormatter : IOutputFormatter, IApiResponseTypeMetadataProvider
{
   //部分代码略
    protected virtual bool CanWriteType(Type type)
    {
        return true;
    }

    ///
    public virtual bool CanWriteResult(OutputFormatterCanWriteContext context)
    {
        if (SupportedMediaTypes.Count == 0)
        {
            var message = Resources.FormatFormatter_NoMediaTypes(
                GetType().FullName,
                nameof(SupportedMediaTypes));

             throw new InvalidOperationException(message);
        }

         if (!CanWriteType(context.ObjectType))
        {
            return false;
        }

        if (!context.ContentType.HasValue)
        {
            context.ContentType = new StringSegment(SupportedMediaTypes[0]);
            return true;
        }
        else
        {
            var parsedContentType = new MediaType(context.ContentType);

            for (var i = 0; i < SupportedMediaTypes.Count; i++)
            {
                var supportedMediaType = new MediaType(SupportedMediaTypes[i]);
                if (supportedMediaType.HasWildcard)
                {
                    if (context.ContentTypeIsServerDefined
                        && parsedContentType.IsSubsetOf(supportedMediaType))
                    {
                        return true;
                    }
                }
                else
                {
                    if (supportedMediaType.IsSubsetOf(parsedContentType))
                    {
                        context.ContentType = new StringSegment(SupportedMediaTypes[i]);
                        return true;
                    }
                }
            }
        }

         return false;
    }
}

通过代码可以看出它主要是利用SupportedMediaTypes和context.ContentType做一系列的判断,它们分别来自客户端和服务端:

SupportedMediaTypes:它是客户端在请求的时候给出的,标识客户端期望服务端按照什么样的格式返回请求结果。

context.ContentType:它来自ObjectResult.ContentTypes,是由服务端在Action执行后给出的。

二者的值都是类似”application/json”、”text/plain”这样的格式,当然也有可能为空,即客户端或服务端未对请求做数据格式的设定。通过上面的代码可以知道,如果这两个值均未做设置或者只有一方做了设置并且设置为JSON时,这个CanWriteResult方法的返回值都是true。所以这样的情况下除了前三种Formatter对应的特定类型外的ObjectResult都会交由JsonOutputFormatter处理。这也就是为什么同样是ObjectResult,但string类型的Action返回结果是String类型,而Book类型的Action返回的结果是JSON类型。这个JsonOutputFormatter有点像当其他的Formatter无法处理时用来”保底”的。

那么SupportedMediaTypes和context.ContentType这两个值又是在什么时候被设置的呢? 在讲请求的模型参数绑定的时候,可以通过在请求Request的Header中添加”content-type: application/json”这样的标识来说明请求中包含的数据的格式是JSON类型的。同样,在请求的时候也可以添加”accept: xxx“这样的标识,来表明期望服务端对本次请求返回的数据的格式。例如期望是JSON格式”accept:application/json”,文本格式”accept: text/plain”等。这个值就是SupportedMediaTypes。

在服务端,也可以对返回的数据格式做设置,例如下面的代码:

[Produces("application/json")]
 public Book GetModel()
 {
     return new Book() { Code = "1001", Name = "ASP" };
 }

通过这个ProducesAttribute设置的值最终就会被赋值给ObjectResult.ContentTypes,最终传递给context.ContentType。ProducesAttribute实际是一个IResultFilter,代码如下:

public class ProducesAttribute : Attribute, IResultFilter, IOrderedFilter, IApiResponseMetadataProvider
    {
        //部分代码省略
        public virtual void OnResultExecuting(ResultExecutingContext context)
        {
            //部分代码省略
           SetContentTypes(objectResult.ContentTypes);
        }

        public void SetContentTypes(MediaTypeCollection contentTypes)
        {
            contentTypes.Clear();
            foreach (var contentType in ContentTypes)
            {
                contentTypes.Add(contentType);
            }
        }

        private MediaTypeCollection GetContentTypes(string firstArg, string[] args)
        {
            var completeArgs = new List<string>();
            completeArgs.Add(firstArg);
            completeArgs.AddRange(args);
            var contentTypes = new MediaTypeCollection();
            foreach (var arg in completeArgs)
            {
                var contentType = new MediaType(arg);
                if (contentType.HasWildcard)
                {
                    throw new InvalidOperationException(                     Resources.FormatMatchAllContentTypeIsNotAllowed(arg));
                }

                contentTypes.Add(arg);
            }

            return contentTypes;
        }
    }

在执行OnResultExecuting的时候,会将设置的”application/json”赋值给ObjectResult.ContentTypes。所以请求最终返回结果的数据格式是由二者”协商”决定的。下面回到Formatter的筛选方法FormatterSelector.SelectFormatter(),这个方法写在DefaultOutputFormatterSelector.cs中。精简后的代码如下:

public class DefaultOutputFormatterSelector : OutputFormatterSelector
{
    //部分代码略
    public override IOutputFormatter SelectFormatter(OutputFormatterCanWriteContext context, IList formatters, MediaTypeCollection contentTypes)
    {
        //部分代码略
        var request = context.HttpContext.Request;
        var acceptableMediaTypes = GetAcceptableMediaTypes(request);
        var selectFormatterWithoutRegardingAcceptHeader = false;
        IOutputFormatter selectedFormatter = null;
        if (acceptableMediaTypes.Count == 0)
        {
            //客户端未设置Accept标头的情况
            selectFormatterWithoutRegardingAcceptHeader = true;
        }
        else
        {
            if (contentTypes.Count == 0)
            {
                //服务端未指定数据格式的情况
                selectedFormatter = SelectFormatterUsingSortedAcceptHeaders(
                    context,
                    formatters,
                    acceptableMediaTypes);
            }
            else
            {
                //客户端和服务端均指定了数据格式的情况
                selectedFormatter = SelectFormatterUsingSortedAcceptHeadersAndContentTypes(
                    context,
                    formatters,
                    acceptableMediaTypes,
                    contentTypes);
            }

            if (selectedFormatter == null)
            {
                //如果未找到合适的,由系统参数ReturnHttpNotAcceptable决定直接返回错误
                //还是忽略客户端的Accept设置再筛选一次
                if (!_returnHttpNotAcceptable)
                {
                    selectFormatterWithoutRegardingAcceptHeader = true;
                }
            }
        }

        if (selectFormatterWithoutRegardingAcceptHeader)
        {
            //Accept标头未设置或者被忽略的情况
            if (contentTypes.Count == 0)
            {
                //服务端也未指定数据格式的情况
                selectedFormatter = SelectFormatterNotUsingContentType(
                    context,
                    formatters);
            }
            else
            {
                //服务端指定数据格式的情况
                selectedFormatter = SelectFormatterUsingAnyAcceptableContentType(
                    context,
                    formatters,
                    contentTypes);
            }
        }

        if (selectedFormatter == null)
        {
            // No formatter supports this.

            _logger.NoFormatter(context);
            return null;
        }

        _logger.FormatterSelected(selectedFormatter, context);
        return selectedFormatter;
    }

    // 4种情况对应的4个方法略
    // SelectFormatterNotUsingContentType
    // SelectFormatterUsingSortedAcceptHeaders
    // SelectFormatterUsingAnyAcceptableContentType
    // SelectFormatterUsingSortedAcceptHeadersAndContentTypes
}

DefaultOutputFormatterSelector根据客户端和服务端关于返回数据格式的设置的4种不同情况作了分别处理,优化了查找顺序,此处就不详细讲解了。

总结一下这个规则:

  1. 只有在Action返回类型为ObjectResult的时候才会进行”协商”。如果返回类型为JsonResult、ContentResult、ViewResult等特定ActionResult,无论请求是否设置了accept标识,都会被忽略,会固定返回 JSON、String,Html类型的结果。
  2. 当系统检测到请求是来自浏览器时,会忽略 其Header中Accept 的设置,所以会由服务器端设置的格式决定(未做特殊配置时,系统默认为JSON)。 这是为了在使用不同浏览器使用 API 时提供更一致的体验。系统提供了参数RespectBrowserAcceptHeader,即尊重浏览器在请求的Header中关于Accept的设置,默认值为false。将其设置为true的时候,浏览器请求中的Accept 标识才会生效。注意这只是使该Accept 标识生效,依然不能由其决定返回格式,会进入”协商”阶段。
  3. 若二者均未设置,采用默认的JSON格式。
  4. 若二者其中有一个被设置,采用该设置值。
  5. 若二者均设置且不一致,即二者值不相同且没有包含关系(有通配符的情况),会判断系统参数ReturnHttpNotAcceptable(返回不可接受,默认值为false),若ReturnHttpNotAcceptable值为false,则忽略客户端的Accept设置,按照无Accept设置的情况再次筛选一次Formatter。如果该值为true,则直接返回状态406。

涉及的两个系统参数RespectBrowserAcceptHeader和ReturnHttpNotAcceptable的设置方法是在 Startup.cs 中通过如下代码设置:

services.AddMvc(
    options =>
    {
        options.RespectBrowserAcceptHeader = true;
        options.ReturnHttpNotAcceptable = true;
    }
 )

最终,通过上述方法找到了合适的Formatter,接着就是通过该Formatter的WriteAsync方法将请求结果格式化后写入HttpContext.Response中。JsonOutputFormatter重写了OutputFormatter的WriteResponseBodyAsync方法(WriteAsync方法会调用WriteResponseBodyAsync方法),代码如下:

public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
{
    if (context == null)
    {
        throw new ArgumentNullException(nameof(context));
    }

    if (selectedEncoding == null)
    {
        throw new ArgumentNullException(nameof(selectedEncoding));
    }

    var response = context.HttpContext.Response;
    using (var writer = context.WriterFactory(response.Body, selectedEncoding))
    {
        WriteObject(writer, context.Object);
        // Perf: call FlushAsync to call WriteAsync on the stream with any content left in the TextWriter's
        // buffers. This is better than just letting dispose handle it (which would result in a synchronous
        // write).

        await writer.FlushAsync();
    }
}

这个方法的功能就是将结果数据转换为JSON并写入HttpContext.Response. Body中。至此,请求结果就按照JSON的格式返回给客户端了。

在实际项目中,如果上述的几种格式均不能满足需求,比如某种数据经常需要通过特殊的格式传输,想自定义一种格式,该如何实现呢?通过本节的介绍,可以想到两种方式,即自定义一种IActionResult或者自定义一种IOutputFormatter。

二、自定义IActionResult

举个简单的例子,以第一节的第3个例子为例,该例通过 “return new JsonResult(new Book() { Code = “1001”, Name = “ASP” })”返回了一个JsonResult。

返回的JSON值为:

{"code":"1001","name":"ASP"}

假如对于Book这种类型,希望用特殊的格式返回,例如这样的格式:

Book Code:[1001]|Book Name:

可以通过自定义一个类似JsonResult的类来实现。代码如下:

public class BookResult : ActionResult
{
        public BookResult(Book content)
        {
            Content = content;
        }
        public Book Content { get; set; }
        public string ContentType { get; set; }
        public int? StatusCode { get; set; }

        public override async Task ExecuteResultAsync(ActionContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

             var executor = context.HttpContext.RequestServices.GetRequiredService>();
            await executor.ExecuteAsync(context, this);
        }
    }

定义了一个名为BookResult的类,为了方便继承了ActionResult。由于是为了处理Book类型,在构造函数中添加了Book类型的参数,并将该参数赋值给属性Content。重写ExecuteResultAsync方法,对应JsonResultExecutor,还需要自定义一个BookResultExecutor。代码如下:

public class BookResultExecutor : IActionResultExecutor
{
    private const string DefaultContentType = "text/plain; charset=utf-8";
    private readonly IHttpResponseStreamWriterFactory _httpResponseStreamWriterFactory;

    public BookResultExecutor(IHttpResponseStreamWriterFactory httpResponseStreamWriterFactory)
    {
        _httpResponseStreamWriterFactory = httpResponseStreamWriterFactory;
    }

    private static string FormatToString(Book book)
    {
        return string.Format("Book Code:[{0}]|Book Name:", book.Code, book.Name);
    }

     ///
    public virtual async Task ExecuteAsync(ActionContext context, BookResult result)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        if (result == null)
        {
            throw new ArgumentNullException(nameof(result));
        }

        var response = context.HttpContext.Response;
        string resolvedContentType;
        Encoding resolvedContentTypeEncoding;
     ResponseContentTypeHelper.ResolveContentTypeAndEncoding(
            result.ContentType,
            response.ContentType,
            DefaultContentType,
            out resolvedContentType,
            out resolvedContentTypeEncoding);
        response.ContentType = resolvedContentType;

        if (result.StatusCode != null)
        {
            response.StatusCode = result.StatusCode.Value;
        }

        string content = FormatToString(result.Content);
        if (result.Content != null)
        {
            response.ContentLength = resolvedContentTypeEncoding.GetByteCount(content);
            using (var textWriter = _httpResponseStreamWriterFactory.CreateWriter(response.Body, resolvedContentTypeEncoding))
            {
                await textWriter.WriteAsync(content);
                await textWriter.FlushAsync();
            }
        }
    }
}

这里定义了默认的ContentType 类型,采用了文本格式,即”text/plain; charset=utf-8″,这会在请求结果的Header中出现。为了特殊说明这个格式,也可以自定义一个特殊类型,例如”text/book; charset=utf-8″,这需要项目中提前约定好。定义了一个FormatToString方法用于将Book类型的数据格式化。最终将格式化的数据写入Response.Body中。

这个BookResultExecutor定义之后,需要在依赖注入中(Startup文件中的ConfigureServices方法)注册:

public void ConfigureServices(IServiceCollection services)
{
    //省略部分代码
    services.TryAddSingleton, BookResultExecutor>();
}

至此,这个自定义的BookResult就可以被使用了,例如下面代码所示的Action:

public BookResult GetBookResult()
{
    return new BookResult(new Book() { Code = "1001", Name = "ASP" });
}

用Fiddler访问这个Action测试一下,返回结果如下:

Book Code:[1001]|Book Name:

Header值:

Content-Length: 32
Content-Type: text/book; charset=utf-8

这是自定义了Content-Type的结果。

三、 自定义格式化类

对于上一节的例子,也可以对照JsonOutputFormatter来自定义一个格式化类来实现。将新定义一个名为BookOutputFormatter的类,也如同JsonOutputFormatter一样继承TextOutputFormatter。代码如下:

public class BookOutputFormatter : TextOutputFormatter
    {
        public BookOutputFormatter()
        {
            SupportedEncodings.Add(Encoding.UTF8);
            SupportedEncodings.Add(Encoding.Unicode);
            SupportedMediaTypes.Add("text/book");
        }

        public override bool CanWriteResult(OutputFormatterCanWriteContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            if (context.ObjectType == typeof(Book) || context.Object is Book)
            {
                return base.CanWriteResult(context);
            }

             return false;
        }

         private static string FormatToString(Book book)
        {
            return string.Format("Book Code:[{0}]|Book Name:",book.Code,book.Name);
        }

        public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

             if (selectedEncoding == null)
            {
                throw new ArgumentNullException(nameof(selectedEncoding));
            }

            var valueAsString = FormatToString(context.Object as Book);
            if (string.IsNullOrEmpty(valueAsString))
            {
                await Task.CompletedTask;
            }

            var response = context.HttpContext.Response;
            await response.WriteAsync(valueAsString, selectedEncoding);
        }
    }

首先在构造函数中定义它所支持的字符集和Content-type类型。重写CanWriteResult方法,这是用于确定它是否能处理对应的请求返回结果。可以在此方法中做多种判断,最终返回bool类型的结果。本例比较简单,仅是判断返回的结果是否为Book类型。同样定义了FormatToString方法用于请求结果的格式化。最后重写WriteResponseBodyAsync方法,将格式化后的结果写入Response.Body中。

BookOutputFormatter定义之后也需要注册到系统中去,例如如下代码:

services.AddMvc(
    options =>
    {
        options.OutputFormatters.Insert(0,new BookOutputFormatter());
    }
 )

这里采用了Insert方法,也就是将其插入了OutputFormatters集合的第一个。所以在筛选OutputFormatters的时候,它也是第一个。此时的OutputFormatters如下图 3

ASP.NET Core 2.2 : 二十一. 内容协商与自定义IActionResult和格式化类

图 3

通过Fiddler测试一下,以第一节返回Book类型的第4个例子为例:

public Book GetModel()
{
    return new Book() { Code = "1001", Name = "ASP" };
}

当设定accept: text/book或者未设定accept的时候,采用了自定义的BookOutputFormatter,返回结果为:

Book Code:[1001]|Book Name:

Content-Type值是:Content-Type: text/book; charset=utf-8。

当设定accept: application/json的时候,返回JSON,值为:

{“code”:”1001″,”name”:”ASP”}

Content-Type值是:Content-Type: application/json; charset=utf-8。

这是由于BookOutputFormatter类型排在了JsonOutputFormatter的前面,所以对于Book类型会首先采用BookOutputFormatter,当客户端通过Accept方式要求返回结果为JSON的时候,才采用了JSON类型。测试一下服务端的要求。将这个Action添加Produces设置,代码如下:

[Produces("application/json")]
 public Book GetModel()
 {
     return new Book() { Code = "1001", Name = "ASP" };
 }

此时无论设定accept: text/book或者未设定accept的情况,都会按照JSON的方式返回结果。这也验证了第二节关于服务端和客户端”协商”的规则。

四、添加XML类型支持

第三、四节通过自定义的方式实现了特殊格式的处理,在项目中常见的格式还有XML,这在ASP.NET Core中没有做默认支持。如果需要XML格式的支持,可以通过NuGet添加相应的包。

在NuGet中搜索并安装Microsoft.AspNetCore.Mvc.Formatters.Xml,如下图 4

ASP.NET Core 2.2 : 二十一. 内容协商与自定义IActionResult和格式化类

图 4

不需要像BookOutputFormatter那样都注册方式,系统提供了注册方法:

services.AddMvc().AddXmlSerializerFormatters();

或者

services.AddMvc().AddXmlDataContractSerializerFormatters();

分别对应了两种格式化程序:

System.Xml.Serialization.XmlSerializer;
System.Runtime.Serialization.DataContractSerializer;

二者的区别就不在这里描述了。注册之后,就可以通过在请求的Header中通过设置”accept: application/xml”来获取XML类型的结果了。访问上一节的返回结果类型为Book的例子,返回的结果如下:

1001

Content-Type值是:Content-Type: application/xml; charset=utf-8。

本文地址:asp.net core: 二十一. 内容协商与自定义iactionresult和格式化类

(ASP.NET Core 系列目录)

Original: https://www.cnblogs.com/FlyLolo/p/ASPNETCore2_21.html
Author: FlyLolo
Title: ASP.NET Core 2.2 : 二十一. 内容协商与自定义IActionResult和格式化类

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/581860/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • CPU架构对redis的性能影响

    CPU架构对redis的性能影响 主流CPU架构 一个CPU处理器中通常有多个运行核心,每一个运行核心称为一个物理核,每个物理核都可以运行应用程序。每个物理核都拥有 私有的一级缓存…

    Linux 2023年5月28日
    0113
  • JDK8以上提高开发效率

    1 接口的默认方法和静态方法 1.1 接口中可以定义默认方法和静态方法。 默认方法使用default修饰,静态方法和默认方法可以多个; 静态方法通过接口直接调用,默认方法通过接口实…

    Linux 2023年6月13日
    0101
  • short, int, long, long long各个类型的范围

    类型名称 字节数 取值范围 signed char 1 -2^7 ~ 2^7-1 -128~+127 short int 2 -2^14 ~ 2^14-1 -32768~+3276…

    Linux 2023年6月8日
    0112
  • 实验2:Open vSwitch虚拟交换机实践

    实验2:Open vSwitch虚拟交换机实践 一、实验目的 能够对Open vSwitch进行基本操作; 能够通过命令行终端使用OVS命令操作Open vSwitch交换机,管理…

    Linux 2023年6月7日
    0125
  • JavaScript 的闭包(closure)

    关于JavaScript 的闭包(closure)的笔记 以下内容为本人的学习笔记,如需要转载,请声明原文链接微信公众号「englyf」 https://www.cnblogs.c…

    Linux 2023年6月6日
    0122
  • Ubuntu20.04 命令行 修改系统IP地址

    Ubuntu 修改IP地址(静态IP) 配置文件修改 — 命令行修改 ifconfig的安装及使用,ip 命令的使用 0. 前言 1. 修改配置文件 1.1 输入(修改…

    Linux 2023年6月6日
    0192
  • jdk 安装(图形界面版)

    在这里为大家提供jdk8的Linux版安装包,下载链接: 提前将jdk安装包放入U盘中,插入U盘,VMware会自动识别,选择将U盘接入虚拟机 打开终端 为避免权限不足,开始之前确…

    Linux 2023年6月8日
    0114
  • 编程入门之字符编码与乱码

    ——”为什么服务器收到的请求或者打开的文本文件有时会乱码?” ——”因为编码不对。” ——”编码的本质是什么?为什么编码…

    Linux 2023年6月13日
    093
  • Redis设置密码

    设置密码有两种方式。 运行cmd切换到redis根目录,先启动服务端 ><span class="hljs-selector-tag">red…

    Linux 2023年5月28日
    086
  • prometheus监控

    介绍 Prometheus是一个开源监控系统,它前身是SoundCloud的警告工具包。从2012年开始,许多公司和组织开始使用Prometheus。该项目的开发人员和用户社区非常…

    Linux 2023年6月6日
    0104
  • 版本控制gitlab

    版本控制gitlab 版本控制gitlab 版本控制介绍 常用的版本控制工具: gitlab部署 gitlab网页界面的基本使用 使用命令行的方式进行上传文件 版本控制介绍 版本控…

    Linux 2023年6月6日
    0126
  • 常用命令记录

    npm仓库查看和修改 npm config set registry https://registry.npm.taobao.org #设置使用淘宝提供的npm仓库 npm con…

    Linux 2023年6月14日
    076
  • Tomcat 实现双向SSL认证

    大概思路: 使用openssl生产CA证书,使用keytool生产密钥库 1、生成CA密钥 genrsa [产生密钥命令] -des3 [加密算法] -out[密钥文件输出路径] …

    Linux 2023年6月14日
    084
  • linux神器sed快速入门,不好用你打我!

    为什么要学习sed sed(Stream EDitor)被誉为Linux三剑客之一,负责过滤和转换文本,功能灵活又强大,搭配正则表达式更是如虎添翼。 如果你需要向1000行文本中的…

    Linux 2023年6月7日
    093
  • 幸运的袋子 附加动图演示!

    幸运的袋子_牛客题霸_牛客网 (nowcoder.com) 厄运的袋子 用到了深度遍历 递归回溯法 这里假设一个例子: 1 1 1 2 2 3 4 5 7 8 因为要确认是否辛运,…

    Linux 2023年6月13日
    090
  • Spring 进入Controller前参数校验

    在进入Controller前完成参数的校验,针对对象参数 分为两个验证方式 (1)直接使用已定义的校验方式 1、在需要进行校验的属性上增加校验类型注解 import java.ut…

    Linux 2023年6月7日
    0131
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球