隐藏页面特效
办一襟风月看升平,吟春色。

Spring Boot 2.x快速上手(四)Spring Boot框架中的日志框架(转载)

日志文件是用于记录系统操作事件的记录文件或者是文件集合,可以分为事件日志和消息日志。具有处理历史数据、诊断问题的追踪以及理解系统的活动等重要作用。日志框架就是更好的记录日志时使用的,记录日志是为了我们在工作中更好的查找相应的问题,也算是对我们操作的留痕吧。刚刚开始学习Java的时候我们都是使用System.out.prinfln()的方式在控制台进行打印的,而且这样的方式伴随了我们很长的一段时间,后来使用debug打断点的方式进行调试,但是不论是哪一种方式,也不论这样的日志是否有用,都应该去记录。


一、日志框架

市面上有许多的日志框架,比如 JUL( java.util.logging), JCL( Apache Commons Logging), Log4j, Log4j2, Logback、 SLF4j、 jboss-logging等等。

Spring Boot 2.x默认采用了slf4j+logback的形式 ,slf4j是个通用的日志门面,logback就是个具体的日志框架了,我们记录日志的时候采用slf4j的方法去记录日志,底层的实现就是根据引用的不同日志jar去判定了。所以Spring Boot也能自动适配JCL、JUL、Log4J等日志框架,它的内部逻辑就是通过特定的JAR包去适配各个不同的日志框架。

二、Logback的使用

Spring Boot 2.x默认采用了slf4j+logback的形式,我们就对Logback进行简单学习,在application.properties文件中进行相关的配置:

如果我们仅仅在application.properties文件中进行相关的配置,实际上是不能满足我们在项目中的实际要求的,所以我们可以使用logback自带的日志配置文件,在Resource目录下创建一个logback.xml(也可命名为logback-spring.xml )文件:

<?xml version="1.0" encoding="UTF-8"?> <configuration scan="true" scanPeriod="60 seconds" debug="false"> &lt;!--定义日志保存的路径--&gt; &lt;!-- 1.更改默认的logback.xml为logback-spring.xml,SpringBoot当看到logback-spring.xml文件存在的时候,才会启动日志的环境切换,logback.xml文件没法做到 2. 在需要切换的环境上增加springProfile标签 --&gt; &lt;springProfile name=<span class="hljs-string">"dev"</span>&gt; &lt;property name=<span class="hljs-string">"LOG_HOME"</span> value=<span class="hljs-string">"d:/logs/dev/"</span> /&gt; &lt;/springProfile&gt; &lt;springProfile name=<span class="hljs-string">"prd"</span>&gt; &lt;property name=<span class="hljs-string">"LOG_HOME"</span> value=<span class="hljs-string">"d:/logs/prd/"</span> /&gt; &lt;/springProfile&gt; &lt;!--定义一个控制台输出器,名为console--&gt; &lt;appender name=<span class="hljs-string">"console"</span> class=<span class="hljs-string">"ch.qos.logback.core.ConsoleAppender"</span>&gt; &lt;!--按pattern指定的格式输出日志,编码为UTF-8--&gt; &lt;encoder class=<span class="hljs-string">"ch.qos.logback.classic.encoder.PatternLayoutEncoder"</span>&gt; &lt;pattern&gt;%d{yyyy-MM-dd HH:mm:ss} %-5level [%thread] %logger{30} - %msg%n&lt;/pattern&gt; &lt;charset&gt;UTF-8&lt;/charset&gt; &lt;/encoder&gt; &lt;/appender&gt; &lt;!--定义一个日滚动(每天生成一份)的日志文件--&gt; &lt;appender name=<span class="hljs-string">"file"</span> class=<span class="hljs-string">"ch.qos.logback.core.rolling.RollingFileAppender"</span>&gt; &lt;!--按pattern指定的格式输出日志,编码为UTF-8--&gt; &lt;encoder class=<span class="hljs-string">"ch.qos.logback.classic.encoder.PatternLayoutEncoder"</span>&gt; &lt;pattern&gt;%d{yyyy-MM-dd HH:mm:ss} %-5level [%thread] %logger{30} - %msg%n&lt;/pattern&gt; &lt;charset&gt;UTF-8&lt;/charset&gt; &lt;/encoder&gt; &lt;!-- 定义保存的文件名 --&gt; &lt;rollingPolicy class=<span class="hljs-string">"ch.qos.logback.core.rolling.TimeBasedRollingPolicy"</span>&gt; &lt;!--%d{yyyy-MM-dd}代表每天生成一个新的日志--&gt; &lt;fileNamePattern&gt;<span class="hljs-variable">${LOG_HOME}</span>/mysprintboot_%d{yyyy-MM-dd}.log.zip&lt;/fileNamePattern&gt; &lt;!--日志最多保存90天,也就是90份--&gt; &lt;maxHistory&gt;90&lt;/maxHistory&gt; &lt;!--总大小--&gt; &lt;totalSizeCap&gt;1GB&lt;/totalSizeCap&gt; &lt;/rollingPolicy&gt; &lt;!-- 级别过滤:在日滚动文件中,强制只保存错误级别以上信息 --&gt; &lt;filter class=<span class="hljs-string">"ch.qos.logback.classic.filter.LevelFilter"</span>&gt; &lt;level&gt;INFO&lt;/level&gt; &lt;onMatch&gt;ACCEPT&lt;/onMatch&gt; &lt;onMismatch&gt;DENY&lt;/onMismatch&gt; &lt;/filter&gt; &lt;/appender&gt; &lt;!-- 定义日志全局最低输出级别是INFO,同时向控制台和日滚动文件输出 --&gt; &lt;root level=<span class="hljs-string">"INFO"</span>&gt; &lt;appender-ref ref=<span class="hljs-string">"console"</span> /&gt; &lt;appender-ref ref=<span class="hljs-string">"file"</span> /&gt; &lt;/root&gt; &lt;loger name=<span class="hljs-string">"com.setalone.myspringboot.MyspringbootApplicationTests"</span>/&gt; &lt;!--定义日志保存的路径--&gt; &lt;!-- 1.更改默认的logback.xml为logback-spring.xml,SpringBoot当看到logback-spring.xml文件存在的时候,才会启动日志的环境切换,logback.xml文件没法做到 2. 在需要切换的环境上增加springProfile标签 --&gt; &lt;springProfile name=<span class="hljs-string">"dev"</span>&gt; &lt;property name=<span class="hljs-string">"LOG_HOME"</span> value=<span class="hljs-string">"d:/logs/dev/"</span> /&gt; &lt;/springProfile&gt; &lt;springProfile name=<span class="hljs-string">"prd"</span>&gt; &lt;property name=<span class="hljs-string">"LOG_HOME"</span> value=<span class="hljs-string">"d:/logs/prd/"</span> /&gt; &lt;/springProfile&gt; &lt;!--定义一个控制台输出器,名为console--&gt; &lt;appender name=<span class="hljs-string">"console"</span> class=<span class="hljs-string">"ch.qos.logback.core.ConsoleAppender"</span>&gt; &lt;!--按pattern指定的格式输出日志,编码为UTF-8--&gt; &lt;encoder class=<span class="hljs-string">"ch.qos.logback.classic.encoder.PatternLayoutEncoder"</span>&gt; &lt;pattern&gt;%d{yyyy-MM-dd HH:mm:ss} %-5level [%thread] %logger{30} - %msg%n&lt;/pattern&gt; &lt;charset&gt;UTF-8&lt;/charset&gt; &lt;/encoder&gt; &lt;/appender&gt; &lt;!--定义一个日滚动(每天生成一份)的日志文件--&gt; &lt;appender name=<span class="hljs-string">"file"</span> class=<span class="hljs-string">"ch.qos.logback.core.rolling.RollingFileAppender"</span>&gt; &lt;!--按pattern指定的格式输出日志,编码为UTF-8--&gt; &lt;encoder class=<span class="hljs-string">"ch.qos.logback.classic.encoder.PatternLayoutEncoder"</span>&gt; &lt;pattern&gt;%d{yyyy-MM-dd HH:mm:ss} %-5level [%thread] %logger{30} - %msg%n&lt;/pattern&gt; &lt;charset&gt;UTF-8&lt;/charset&gt; &lt;/encoder&gt; &lt;!-- 定义保存的文件名 --&gt; &lt;rollingPolicy class=<span class="hljs-string">"ch.qos.logback.core.rolling.TimeBasedRollingPolicy"</span>&gt; &lt;!--%d{yyyy-MM-dd}代表每天生成一个新的日志--&gt; &lt;fileNamePattern&gt;<span class="hljs-variable">${LOG_HOME}</span>/mysprintboot_%d{yyyy-MM-dd}.log.zip&lt;/fileNamePattern&gt; &lt;!--日志最多保存90天,也就是90份--&gt; &lt;maxHistory&gt;90&lt;/maxHistory&gt; &lt;!--总大小--&gt; &lt;totalSizeCap&gt;1GB&lt;/totalSizeCap&gt; &lt;/rollingPolicy&gt; &lt;!-- 级别过滤:在日滚动文件中,强制只保存错误级别以上信息 --&gt; &lt;filter class=<span class="hljs-string">"ch.qos.logback.classic.filter.LevelFilter"</span>&gt; &lt;level&gt;INFO&lt;/level&gt; &lt;onMatch&gt;ACCEPT&lt;/onMatch&gt; &lt;onMismatch&gt;DENY&lt;/onMismatch&gt; &lt;/filter&gt; &lt;/appender&gt; &lt;!-- 定义日志全局最低输出级别是INFO,同时向控制台和日滚动文件输出 --&gt; &lt;root level=<span class="hljs-string">"INFO"</span>&gt; &lt;appender-ref ref=<span class="hljs-string">"console"</span> /&gt; &lt;appender-ref ref=<span class="hljs-string">"file"</span> /&gt; &lt;/root&gt; &lt;loger name=<span class="hljs-string">"com.setalone.myspringboot.MyspringbootApplicationTests"</span>/&gt; </configuration>

1、根节点<cofiguration>

  • scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true
  • scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟
  • debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false

2、设置变量<property>

  • 用来定义变量值的标签, 有两个属性,name和value;其中name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量

3、子节点<appender>

  • appender用来格式化日志输出节点,有俩个属性name和class,class用来指定哪种输出策略,常用就是控制台输出策略和文件输出策略

4、<filter>

  • filter是一个过滤器,表示对输出到控制台的日记进行过滤。有两种过滤器,分别为LevelFilter 和ThresholdFilter。执行一个过滤器会有返回个枚举值,即DENY,NEUTRAL,ACCEPT其中之一。返回DENY,日志将立即被抛弃不再经过其他过滤器;返回NEUTRAL,有序列表里的下个过滤器接着处理日志;返回ACCEPT,日志会被立即处理,不再经过剩余过滤器。
  • 其中LevelFilter 为级别过滤器,根据日志级别进行过滤。其下有三个子节点,level表示过滤的级别,用于配置符合过滤条件的操作,ACCEPT符合级别的输出到控制台,用于配置不符合过滤条件的操作,DENY不符合的拒绝输出到控制台。
  • ThresholdFilter为临界值过滤器,过滤掉低于指定临界值的日志。当日志级别等于或高于临界值时,过滤器返回NEUTRAL;当日志级别低于临界值时,日志会被拒绝。
     

5、对日志进行格式化

  • %d{HH:mm:ss.SSS} :日志的输出时间
  • %contextName : 上下文名称
  • %thread : 输出日志的进程名字,这在Web应用以及异步任务处理中很有用
  • %-5level : 日志级别,并且使用5个字符靠左对齐
  • %logger{30} : 日志输出者的名字(一般为类名),名字最长30个字符,否则按照句点分割
  • %msg : 具体的日志消息
  • %n :换行符

6、字节点root

  • root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性,默认是DEBUG
  • 其中可以包含零个或多个元素,表示我们定义的appender将会添加到我们定义的loger子节点中

7、字节点logger

  1. <loger>用来设置某一个包或者具体的某一个类的日志打印级别、以及指定<appender>
  2. <loger>仅有一个name属性,一个可选的level和一个可选的addtivity属性

name:用来指定受此loger约束的某一个包或者具体的某一个类。

level:用来设置打印级别,如果未设置此属性,那么当前loger将会继承上级的级别。

addtivity:是否向上级loger传递打印信息。默认是true。

    3、<loger> 的实际使用有两种情况
 

三、Log4j2的使用

Spring Boot虽然默认使用Logback日志框架,但其内部也集成了Log4j2框架。要知道的是,在Java中,Log4j2框架的性能是最强的,所以我们一般在程序中使用Log4j2框架。下面介绍一下Log4j2的使用及其内部属性的含义。如果我们要使用Log4j2,还是要先去除logbcak的包,引入Log4j2的包。

引包结束之后,创建一个log4j2.xml文件:

<?xml version="1.0" encoding="UTF-8"?> <!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL --> <!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出--> <!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数--> <configuration status="WARN" monitorInterval="30"> &lt;Properties&gt; &lt;Property name=<span class="hljs-string">"log.path"</span>&gt;<span class="hljs-built_in">log</span>&lt;/Property&gt; &lt;/Properties&gt; &lt;!--先定义所有的appender--&gt; &lt;appenders&gt; &lt;!--这个输出控制台的配置--&gt; &lt;console name=<span class="hljs-string">"Console"</span> target=<span class="hljs-string">"SYSTEM_OUT"</span>&gt; &lt;!--输出日志的格式--&gt; &lt;PatternLayout pattern=<span class="hljs-string">"[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"</span>/&gt; &lt;/console&gt; &lt;File name=<span class="hljs-string">"log"</span> fileName=<span class="hljs-string">"<span class="hljs-variable">${log.path}</span>/test.log"</span> append=<span class="hljs-string">"false"</span>&gt; &lt;PatternLayout pattern=<span class="hljs-string">"[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"</span>/&gt; &lt;/File&gt; &lt;RollingFile name=<span class="hljs-string">"RollingFileInfo"</span> fileName=<span class="hljs-string">"<span class="hljs-variable">${log.path}</span>/info.log"</span> filePattern=<span class="hljs-string">"<span class="hljs-variable">${log.path}</span>/logs/<span class="hljs-variable">${date:yyyy-MM}</span>/info-%d{yyyy-MM-dd}.log.zip"</span>&gt; &lt;!--只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)--&gt; &lt;ThresholdFilter level=<span class="hljs-string">"info"</span> onMatch=<span class="hljs-string">"ACCEPT"</span> onMismatch=<span class="hljs-string">"DENY"</span>/&gt; &lt;PatternLayout pattern=<span class="hljs-string">"[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"</span>/&gt; &lt;Policies&gt; &lt;TimeBasedTriggeringPolicy modulate=<span class="hljs-string">"true"</span> interval=<span class="hljs-string">"1"</span>/&gt; &lt;/Policies&gt; &lt;/RollingFile&gt; &lt;/appenders&gt; &lt;!--然后定义logger,只有定义了logger并引入appender,appender才会生效--&gt; &lt;loggers&gt; &lt;!--过滤掉spring和mybatis的一些无用的DEBUG信息--&gt; &lt;logger name=<span class="hljs-string">"org.springframework"</span> level=<span class="hljs-string">"INFO"</span>/&gt; &lt;logger name=<span class="hljs-string">"org.mybatis"</span> level=<span class="hljs-string">"INFO"</span>/&gt; &lt;logger name=<span class="hljs-string">"com.baiding"</span> level=<span class="hljs-string">"INFO"</span>/&gt; &lt;root level=<span class="hljs-string">"info"</span>&gt; &lt;appender-ref ref=<span class="hljs-string">"Console"</span>/&gt; &lt;appender-ref ref=<span class="hljs-string">"RollingFileInfo"</span>/&gt; &lt;/root&gt; &lt;/loggers&gt; &lt;Properties&gt; &lt;Property name=<span class="hljs-string">"log.path"</span>&gt;<span class="hljs-built_in">log</span>&lt;/Property&gt; &lt;/Properties&gt; &lt;!--先定义所有的appender--&gt; &lt;appenders&gt; &lt;!--这个输出控制台的配置--&gt; &lt;console name=<span class="hljs-string">"Console"</span> target=<span class="hljs-string">"SYSTEM_OUT"</span>&gt; &lt;!--输出日志的格式--&gt; &lt;PatternLayout pattern=<span class="hljs-string">"[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"</span>/&gt; &lt;/console&gt; &lt;File name=<span class="hljs-string">"log"</span> fileName=<span class="hljs-string">"<span class="hljs-variable">${log.path}</span>/test.log"</span> append=<span class="hljs-string">"false"</span>&gt; &lt;PatternLayout pattern=<span class="hljs-string">"[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"</span>/&gt; &lt;/File&gt; &lt;RollingFile name=<span class="hljs-string">"RollingFileInfo"</span> fileName=<span class="hljs-string">"<span class="hljs-variable">${log.path}</span>/info.log"</span> filePattern=<span class="hljs-string">"<span class="hljs-variable">${log.path}</span>/logs/<span class="hljs-variable">${date:yyyy-MM}</span>/info-%d{yyyy-MM-dd}.log.zip"</span>&gt; &lt;!--只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)--&gt; &lt;ThresholdFilter level=<span class="hljs-string">"info"</span> onMatch=<span class="hljs-string">"ACCEPT"</span> onMismatch=<span class="hljs-string">"DENY"</span>/&gt; &lt;PatternLayout pattern=<span class="hljs-string">"[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"</span>/&gt; &lt;Policies&gt; &lt;TimeBasedTriggeringPolicy modulate=<span class="hljs-string">"true"</span> interval=<span class="hljs-string">"1"</span>/&gt; &lt;/Policies&gt; &lt;/RollingFile&gt; &lt;/appenders&gt; &lt;!--然后定义logger,只有定义了logger并引入appender,appender才会生效--&gt; &lt;loggers&gt; &lt;!--过滤掉spring和mybatis的一些无用的DEBUG信息--&gt; &lt;logger name=<span class="hljs-string">"org.springframework"</span> level=<span class="hljs-string">"INFO"</span>/&gt; &lt;logger name=<span class="hljs-string">"org.mybatis"</span> level=<span class="hljs-string">"INFO"</span>/&gt; &lt;logger name=<span class="hljs-string">"com.baiding"</span> level=<span class="hljs-string">"INFO"</span>/&gt; &lt;root level=<span class="hljs-string">"info"</span>&gt; &lt;appender-ref ref=<span class="hljs-string">"Console"</span>/&gt; &lt;appender-ref ref=<span class="hljs-string">"RollingFileInfo"</span>/&gt; &lt;/root&gt; &lt;/loggers&gt; </configuration>

1、 根节点属性

  • 根节点Configuration有两个属性,status和monitorinterval
  • status : status用来指定log4j2本身的日志的级别
  • monitorinterval : monitorinterval用于指定log4j自动重新配置的监测间隔时间,单位是s,最小是5s

2、 Properties 标签

  • 在xml文件中,可以使用Properties 标签来自定义变量,方便其他地方的引用。

3、Appenders 节点

和logback一样,Appender是用来定义日志输出点的,一般常用有三个子节点,分别为Console、RollingFile、File。

Console节点用来定义输出到控制台的Appender:

  • name : 指定Appender的名字,用于Logger节点引用
  • target : SYSTEM_OUT 或 SYSTEM_ERR,一般设置为:SYSTEM_OUT
  • PatternLayout : 指定日志输出格式,默认为%m%n

File节点用来定义输出到指定位置的文件的Appender:

  • name : 指定Appender的名字,用于Logger节点引用
  • fileName : 指定输出日志的目的文件带全路径的文件名
  • append : 是否追加,默认为ture。ture是将新日志追加到原日志文件尾部,false则是删除已有文件,重建新文件
  • PatternLayout : 指定日志输出格式,默认为%m%n

RollingFile节点用来定义输出到指定位置的文件的Appender,但其记录的内容可拆分:

  • File对文件的约束很简单,而RollingFile则比较灵活,其可根据文件大小来分拆,还可以根据时间来分拆
  • name : 指定Appender的名字,用于Logger节点引用
  • fileName : 指定输出日志的目的文件带全路径的文件名
  • filePattern:指定拆分出去的日志文件的全路径的文件名以及格式
  • PatternLayout : 指定日志输出格式,默认为%m%n
  • Policies : 指定滚动日志的策略,就是什么时候进行新建日志文件输出日志.
  • TimeBasedTriggeringPolicy : 基于时间进行日志的滚动
  • SizeBasedTriggeringPolicy : 基于文件大小进行日志的滚动
  • ThresholdFilter : 日志过滤器

这里说一下TimeBasedTriggeringPolicy这个滚动策略的属性interval,它是指日志进行滚动的间隔,那么它的单位具体是什么呢?关键点在于filePattern的日志文件名所含有的日期格式%d{yyyy-MM-dd},这里日期格式具体到了天,那么以天为单位,若是日期具体到%d{yyyy-MM-dd-HH-mm}分钟的话,那么就是以分钟为单位。

这里还提到了日志过滤器,Log4j提供了许多的日志过滤器,具体可以看下文档 Filters。但我们一般采用ThresholdFilter,这个过滤器一般用来过滤掉所有级别低于它定义的级别的日志。

4、Loggers节点
Loggers节点下一般有root和logger节点:

root节点用来指定项目的根日志,如果没有单独指定logger,那么就会默认使用该root日志输出。

  • level :日志输出级别,共有8个级别,按照从低到高为:All < Trace < Debug < Info < Warn < Error < Fatal < OFF.
  • appender-ref :root的子节点,用来指定该日志输出到哪个Appender

Logger节点用来单独指定日志的形式,比如要为某个包下所有的class或者某个具体的class指定不同的日志级别等。

  • level : 日志输出级别
  • name : 用来指定该Logger所适用的类或者包.
  • AppenderRef :Logger的子节点,用来指定该日志输出到哪个Appender,如果没有指定,就会默认继承自Root
  • additivity :是否向上级传递日志 true(默认)或false

若我们为logger指定了AppenderRef ,别忘了将logger的additivity 属性设置为false,要不然日志可能会在指定的Appender中输出两遍

四、Log4j2异步输出日志
Log4j2有个突出的功能就是支持高效低延迟的异步化写日志。日志异步输出的好处在于,使用单独的进程来执行日志打印的功能,可以提高日志执行效率,减少日志功能对正常业务的影响。

日志的异步输出使用了disruptor这个开源的并发框架,所以首先得导入disruptor.jar包


异步输出分为两种情况,一种为全异步模式,一种为异步和非异步混合输出模式。通过官方的性能对比,一般线程数在2-16之间的话,混合使用同步和异步的logger来打印日志,性能是最好的。

异步和非异步混合模式

这种模式的启用,主要在于两个节点的使用,分别为AsyncRoot和AsyncLogger,这两个节点可以和Root 或 Logger节点混合使用。在这里修改一下上述的log4j2.xml文件中Loggers节点就可以了。

<loggers> <AsyncRoot level="info"> <appender-ref ref="Console"/> <appender-ref ref="RollingFileInfo"/> </AsyncRoot> <AsyncLogger name="com.baiding" level="INFO" /> <AsyncLogger name="org.mybatis" level="INFO" /> <AsyncLogger name="org.springframework" level="INFO" /> </loggers>


 


__EOF__

本文作者最喜欢夏天了
本文链接https://www.cnblogs.com/scar1et/p/17285471.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   就永远夏天  阅读(117)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!
点击右上角即可分享
微信分享提示