logback--进阶--04--配置

logback–进阶–04–配置


代码位置
https://gitee.com/DanShenGuiZu/learnDemo/tree/master/logback-learn

1、加载配置的步骤

1.1、步骤

步骤1

Logback 尝试在 classpath 中找一个名为 logback-test.xml 的文件 。

步骤2

如果找不到此类文件,则 logback 尝试在 classpath 中找一个名为 logback.groovy 的文件 。

步骤3

如果找不到这样的文件,它将在 classpath 中找一个名为 logback.xml 的文件。

步骤4

如果还没有找到这样的文件, ServiceLoader会通过 META-INF\services\ch.qos.logback.classic.spi.Configurator 加载 com.qos.logback.classic.spi.Configurator 接口的实现类。

步骤5

  1. 如果以上方法均未成功,则 logback 将使用 BasicConfigurator 进行自动配置,这会将日志输出定向到控制台。
    1. 调用 BasicConfigurator ,创建一个最小化配置。
    2. 最小化配置由一个关联到根 logger 的 ConsoleAppender 组成。输出用模式为 %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 的 PatternLayoutEncoder 进行格式化。
  2. 默认情况下,为 root logger 分配了 DEBUG 级别。

1.2、案例1

演示没有配置文件,logback 使用默认配置的效果

1.2.1、代码

public class MyApp1 {
    private static final Logger LOGGER = LoggerFactory.getLogger(MyApp1.class);
    public static void main(String[] args) {
        LOGGER.info("我是在 MyApp1 类中,使用info级别打印日志");
        User user = new User();
        user.say();
    }
}


public class User {
    private static final Logger LOGGER = LoggerFactory.getLogger(User.class);

    public void say() {
        LOGGER.debug("我是在 User 类中,使用debug级别打印日志");
    }
}


1.2.2、运行程序,控制台输出日志如下

10:30:25.543 [main] INFO fei.zhou.logbacklearn.business.test.MyApp1 - 我是在 MyApp1 类中,使用info级别打印日志
10:30:25.563 [main] DEBUG fei.zhou.logbacklearn.business.test.User - 我是在 User 类中,使用debug级别打印日志

1.3、案例2

演示使用 logback.xml 进行配置

1.3.1、 logback.xml

在这里插入图片描述

<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <!-- encoders are assigned the type
                   ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <root level="debug">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>

1.3.2、 再运行上面的程序,控制台会输出如下信息

10:36:33.698 [main] INFO  f.z.l.business.test.MyApp1 - 我是在 MyApp1 类中,使用info级别打印日志
10:36:33.705 [main] DEBUG f.z.logbacklearn.business.test.User - 我是在 User 类中,使用debug级别打印日志

在这里插入图片描述

2、打印内部状态

2.1、介绍

如果程序在解析配置文件期间发生警告或错误,则 logback 会自动在控制台上打印其内部状态信息。如果在没有警告或错误时,你也希望检查 logback 的内部状态,你可以指示通过调用 StatusPrinter 类的 print() 方法。如下所示:

LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
StatusPrinter.print(lc);

如果你不想在程序中编写打印 logback 的内部状态,那可以在配置文件 configuration 元素的 debug 属性设置为 true,同样也可以在程序启动时打印 logback 内部状态。

<configuration debug = "true">
 <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
  <!-- encoders are assigned the type
             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
  <encoder>
   <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
  </encoder>
 </appender>

 <root level="debug">
  <appender-ref ref="STDOUT" />
 </root>
</configuration>

2.2、案例

在这里插入图片描述

3、直接指定配置文件的位置

  1. 通过配置系统属性(logback.configurationFile)的方式,指定 logback配置文件的位置。
  2. 系统属性的值
    1. 可以是 URL
    2. 可以是 类路径上的资源
    3. 可以是 应用程序外部文件的路径。

3.1、案例

java -Dlogback.configurationFile=/path/to/logback.xml fei.zhou.logbacklearn.business.test.MyApp1

4、自动重新加载配置文件

如果开启了自动重新加载配置文件,logback-classic 会扫描配置文件中的更改,并在配置文件更改时自动重新配置自身。在 < configuration> 标签中将 scan 属性设置为 true 即可开启。

当将 scan 属性设置为 true 时,在后台 ReconfigureOnChangeTask 会在单独的线程中运行,它会检查配置文件是否已更改。

由于在编辑配置文件时很容易出错,因此如果最新版本的配置文件具有 XML 语法错误,则它将回退到先前没有 XML 语法错误的配置文件。

<configuration scan="true"> 
  ... 
</configuration> 

默认情况下,每1分钟扫描一次配置文件是否有更改。我们可以设置 < configuration> 标签中的 scanPeriod 属性来指定扫描周期。单位可以为毫秒,秒,分钟或小时。如果未指定时间单位,则时间单位默认为毫秒

<configuration debug = "true" scan="true" scanPeriod="30 seconds">
 <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
  <!-- encoders are assigned the type
             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
  <encoder>
   <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
  </encoder>
 </appender>

 <root level="debug">
  <appender-ref ref="STDOUT" />
 </root>
</configuration>

5、在堆栈跟踪中启用包数据

注意从版本1.1.4开始,包装数据默认为禁用。可按如下配置开启包数据:

<configuration packagingData="true">
  ...
</configuration>

当然,也可以在程序中进行配置,如下:

LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
lc.setPackagingDataEnabled(true);

如果开启了,logback 会在输出的堆栈行中显示它是属于哪个 jar 或者哪个类的。

此信息由 jar 文件的名称和版本组成,表明堆栈信息来源于此。

此机制对于识别软件版本问题非常有用。但是,计算成本相当昂贵,尤其是在经常引发异常的应用程序中。以下演示开启的结果,即多了 [] 括号内的信息。

14:28:48.835 [btpool0-7] INFO  c.q.l.demo.prime.PrimeAction - 99 is not a valid value
java.lang.Exception: 99 is invalid
  at ch.qos.logback.demo.prime.PrimeAction.execute(PrimeAction.java:28) [classes/:na]
  at org.apache.struts.action.RequestProcessor.processActionPerform(RequestProcessor.java:431) [struts-1.2.9.jar:1.2.9]
  at org.apache.struts.action.RequestProcessor.process(RequestProcessor.java:236) [struts-1.2.9.jar:1.2.9]
  at org.apache.struts.action.ActionServlet.doPost(ActionServlet.java:432) [struts-1.2.9.jar:1.2.9]
  at javax.servlet.http.HttpServlet.service(HttpServlet.java:820) [servlet-api-2.5-6.1.12.jar:6.1.12]
  at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:502) [jetty-6.1.12.jar:6.1.12]
  at ch.qos.logback.demo.UserServletFilter.doFilter(UserServletFilter.java:44) [classes/:na]
  at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1115) [jetty-6.1.12.jar:6.1.12]
  at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:361) [jetty-6.1.12.jar:6.1.12]
  at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:417) [jetty-6.1.12.jar:6.1.12]
  at org.mortbay.jetty.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:230) [jetty-6.1.12.jar:6.1.12]

6、停止 logback-classic

为了释放 logback-classic 资源,停止 logback context 是一个好主意。如果停止,会关闭所有 loggers 关联的 appenders,并有序的停止所有活动线程。

import org.sflf4j.LoggerFactory;
import ch.qos.logback.classic.LoggerContext;
...

LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
loggerContext.stop();

7、配置文件语法

7.1、 元素

一个 logger 记录器可以使用 元素配置。

< logger> 元素中,name 属性是必须的,level 级别属性是可选的,additivity 可叠加性属性也是可以选的(它的值是 true 或 false)。级别 level 属性的值是不区分大小写的字符串 TRACE,DEBUG,INFO,WARN,ERROR,ALL,OFF。还有不区分大小写的值 INHERITED 或其同义词 NULL,代表将强制从层次结构中较高的层次继承记录器继承级别。

< logger> 元素里面可包含0或多个 < appender-ref> 元素,这样引用的 appender 会关联到此 logger。不同 log4j,即便你在配置文件配置了 logger 关联的 appender,logback-classic 也不会关闭或者移除之前关联的 appender。

7.2、配置 root logger,< root> 元素

< root>元素用来配置 root logger。
它支持单个属性,即 level 级别属性。
它没有其他属性,因为可叠加性标志不适用于根记录器。
由于根记录器已被命名为 " ROOT" ,因此它也不允许使用 name 属性。
level 属性的值可以是不区分大小写的字符串 TRACE,DEBUG,INFO,WARN,ERROR,ALL或OFF之一。但是,根记录器的级别不能设置为 INHERITED 或 NULL。

与 < logger> 元素类似,< root> 元素也可以包含零个或多个 < appender-ref>元素。如此引用的每个附加程序都会添加到根记录器中。不同 log4j,即便你在配置文件配置了 root logger 关联的 appender,logback-classic 也不会关闭或者移除之前关联的 appender。

7.2.1、案例1

下面演示个 demo,假设我们不想打印 “com.nobody.entity” 包下任何组件的任何 DEBUG 消息。可以按如下配置:

<configuration>

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <!-- encoder 默认被分配 ch.qos.logback.classic.encoder.PatternLayoutEncoder 类 -->
    <!-- 当然你也可以通过 class 属性 显示指定,即 <encoder class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> -->
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>

  <logger name="com.nobody.entity" level="INFO"/>

  <!-- 其实此level属性设置也可以去除,因为默认就是 DEBUG 级别 -->
  <root level="DEBUG">          
    <appender-ref ref="STDOUT"/>
  </root>  
  
</configuration>

7.2.2、案例2

我们可以根据需要配置任意数量的记录器。如下,我们将 com.nobody.A 记录器的级别设置 为 INFO,但同时将 com.nobody.B 记录器的级别设置为DEBUG。

<configuration>

  ...

  <logger name="com.nobody.A" level="INFO"/>
  <logger name="com.nobody.B" level="DEBUG"/>
 
  ... 
 
</configuration>

7.3、配置 Appenders

一个 appender 使用 < appender> 元素配置,该元素具有两个必填属性 name 和 class。
1. name 属性指定 appender 的名称
2. class 属性指定实例化此 appender 的类。

< appender> 元素可包含零个或一个 < layout> 元素,零个或多个 < encoder> 元素,零个或多个层 < filter> 元素。

除了这三个公共元素之外,< appender> 可以包含任意数量的与 appender 类的JavaBean属性相对应的元素。

< layout> 有个必填的属性指定实例化此对象的全限定类名。和 < appender>一样,它也有自己的相关属性。PatternLayout 有默认的属性值,所以可以不指定属性值。

< encoder>有个必填的属性指定实例化此对象的全限定类名。PatternLayoutEncoder 有默认的属性值,所以可以不指定属性值。

<configuration debug="false" scan="true" scanPeriod="30 seconds" packagingData="true">
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--   <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>-->
            <pattern>%-4relative [%thread] %-5level %logger{32} - %msg%n</pattern>
        </encoder>
    </appender>
    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
  <file>myApp.log</file>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <root level="INFO">
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="FILE"/>
    </root>
</configuration>

上述配置文件定义了两个命名为 FILE 和STDOUT 的 appender 。
1. FILE appender 将日志输出到 myApp.log 文件。
2. STDOUT appender 将日志输出到控制台。

默认情况下,附加程序是累积式的:记录器将记录到附加到其自身的附加程序(如果有)以及附加到其祖先的所有附加程序。因此,将同一附加程序附加到多个记录器将导致记录输出重复。

默认情况下,appender 是累积式的:一个 logger 会将日志输出到它自己关联的所有 appender 和 它上层级(祖先)所关联的所有 appender。所以,如果将同一个 appender 关联到不同的 logger,有可能会导致输出的日志会重复。例如下面这个例子:

<configuration debug="false" scan="true" scanPeriod="30 seconds" packagingData="true">
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--   <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>-->
            <pattern>%-4relative [%thread] %-5level %logger{32} - %msg%n</pattern>
        </encoder>
    </appender>

    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
        <file>myApp.log</file>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <logger name="fei.zhou.logbacklearn.business.test.Main">
        <appender-ref ref="STDOUT"/>
    </logger>

    <root level="INFO">
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="FILE"/>
    </root>
</configuration>

public class Main {
    private static final Logger logger = LoggerFactory.getLogger(Main.class);

    public static void main(String[] args) {

        logger.info("Hello Logback!");

        Logger rootLogger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
        rootLogger.info("rootLogger:{}", rootLogger.getName());
    }
}

会在控制台输出如下结果,因为名为 fei.zhou.logbacklearn.business.test.Main 的 logger 的可叠加性标识默认为 true,所以会将日志输出到它上级的 logger 关联的 appender 中,所以输出2遍。而 root logger 没有上级,输出1遍。

790  [main] INFO  f.z.l.business.test.Main - Hello Logback!
790  [main] INFO  f.z.l.business.test.Main - Hello Logback!
803  [main] INFO  ROOT - rootLogger:ROOT

当然,你可以将 com.nobody.Main 的 logger 的可叠加性标识默认为 false,那它的日志就不会输送到上层级中。

<logger name="fei.zhou.logbacklearn.business.test.Main" additivity="false">
    <appender-ref ref="STDOUT"/>
</logger>

8、设置上下文名称

每个 logger 记录器都附加到一个记录器上下文。默认情况下,它的名称为 “default”。
通过 < contextName> 配置可以更改其名称,使用此值打印到日志中,用于区分不同应用程序的记录。但一旦设置,它的名称就无法变更。

<configuration>
  <contextName>myAppName</contextName>
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>

  <root level="INFO">
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

9、变量定义和替换

9.1、介绍

logback 配置文件支持变量的定义和替换。
变量具有作用域。
变量可以在配置文件中,在外部文件中,在外部资源中,甚至可以即时计算和定义。

变量替换可以发生在配置文件中可以指定值的任何位置。语法是 ${variableName}。

考虑到常用性,HOSTNAME 和 CONTEXT_NAME 变量默认已定义,并具有上下文作用域。考虑到在某些环境中可能需要花费一些时间来计算主机名,因此它的值是延迟计算的(仅在需要时)。

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
        <pattern>%d{HH:mm:ss.SSS} ${HOSTNAME} ${CONTEXT_NAME} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
</appender>

变量可以在 logback 自己的配置文件中定义,也可以从外部属性文件或外部资源中批量加载。由于历史原因,用于定义变量用 。

<configuration>

  <property name="LOG_HOME" value="./logs"/>

  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <file>${LOG_HOME}/myApp.log</file>
    <encoder>
      <pattern>%msg%n</pattern>
    </encoder>
  </appender>

  <root level="debug">
    <appender-ref ref="FILE" />
  </root>
</configuration>

它下面的效果一样,logback 将在 System 属性中查找它。

java -LOG_HOME="./logs" MyApp

如果你定义的变量太多时,可以创建单独的文件来保存,方便管理。

<configuration>

  <property file="src/main/java/resources/logback-variables.properties"/>

  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <file>${LOG_HOME}/myApp.log</file>
    <encoder>
      <pattern>%msg%n</pattern>
    </encoder>
  </appender>

  <root level="debug">
    <appender-ref ref="FILE" />
  </root>
</configuration>

此配置会读取logback-variables.properties文件中的变量,然后在本地范围内使用。logback-variables.properties文件定义的变量如下:

LOG_HOME=./logs

当然,也可以写成引入类路径上的资源文件的形式。

<configuration>

  <property resource="logback-variables.properties"/>

  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <file>${LOG_HOME}/myApp.log</file>
    <encoder>
      <pattern>%msg%n</pattern>
    </encoder>
  </appender>

  <root level="debug">
    <appender-ref ref="FILE" />
  </root>
</configuration>

9.2、变量作用域

9.2.1、变量作用域

  1. Local Scope(本地作用域)
    1. 从配置文件中定义的本地变量即在本地配置文件使用。每次解析和执行配置文件时,都会重新定义本地作用域中的变量。
  2. Context Scope(上下文作用域)
    1. 一个拥有上下文作用域的变量存在于上下文中,于上下文共存,直到被清除。在所有记录事件中都可用到,包括那些通过序列化发送到远程主机的事件。
  3. System Scope(系统级作用域)
    1. 系统级作用域的变量被插入到JVM的系统属性中,生命周期和JVM一致,直到被清除。
  4. 默认是本地作用域。从操作系统环境中读取变量很容易,但是无法写入到操作系统环境中。

在进行属性替换时,查找变量的顺序为:local scope,context scope,system

<configuration>

  <property scope="context" name="LOG_HOME" value="./logs"/>

  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <file>${LOG_HOME}/myApp.log</file>
    <encoder>
      <pattern>%msg%n</pattern>
    </encoder>
  </appender>

  <root level="debug">
    <appender-ref ref="FILE" />
  </root>
</configuration>

9.2.2、引入变量时,可能变量未定义或者值为null,我们可以使用":-"符号指定默认值。例如

${LOG_HOME:-./logs}。

9.2.3、支持变量嵌套,默认值和值定义都可以引用其他变量。例如:

LOG_HOME=./logs
FILE_NAME=myApp.log
destination=${LOG_HOME}/${FILE_NAME}

9.2.4、名称嵌套

引用变量时,变量名称可能包含对另一个变量的引用。例如

如果为名为" userid"的变量分配了值" alice",则" $ {$ {userid} .password}"引用名称为" alice.password"的变量。

9.2.5、默认值嵌套

变量的默认值可以引用另一个变量。例如

假设未分配变量" id",并且为变量" userid"分配了值" alice",则表达式" $ {id :- $ {userid}}"将返回" alice"。

9.2.6、有条件地处理配置文件

我们可能需要在不同的环境(例如dev,test,prod)切换不同的logback配置文件。然而这些配置文件大部分内容是一样的,极少内容是不同的。为了减少多个配置文件,可以使用条件处理标签,< if>, < then> 和 < else>,根据不同环境进行配置。不过,需要引入Janino 库。

   <!-- if-then 形式 -->
   <if condition="表达式">
    <then>
      ...
    </then>
  </if>
  
  <!-- if-then-else 形式 -->
  <if condition="表达式">
    <then>
      ...
    </then>
    <else>
      ...
    </else>    
  </if>

condition 条件只能是上下文属性或系统属性的Java表达式。对于通过参数传递的键,可以通过 property() 或简写的 p() 方法返回属性的字符串值。例如,property(“k”) 或 p(“k”) 访问键" k"的值。如果键" k"的属性未定义,则属性方法将返回空字符串,而不是null。这能避免判断null值。

isDefined()方法可用于检查是否定义了属性。例如,isDefined(“k”) 。
如果需要检查属性是否为null,则可以使用 isNull() 方法。例如,isNull(“k”)。

<configuration debug="true">

  <if condition='property("HOSTNAME").contains("torino")'>
    <then>
      <appender name="CON" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
          <pattern>%d %-5level %logger{35} - %msg %n</pattern>
        </encoder>
      </appender>
      <root>
        <appender-ref ref="CON" />
      </root>
    </then>
  </if>

  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <file>${randomOutputDir}/conditional.log</file>
    <encoder>
      <pattern>%d %-5level %logger{35} - %msg %n</pattern>
   </encoder>
  </appender>

  <root level="ERROR">
     <appender-ref ref="FILE" />
  </root>
</configuration>

在范围内都可以使用条件处理语句。还支持嵌套的if-then-else语句。但是,XML语法非常繁琐,为以后后续其他开发者以及自己能快速理解,尽量少用。

9.2.7、文件包含

Joran支持将配置文件的一部分包含在另一个文件中。这是通过声明一个 include元素来完成的,如下所示:

可以通过标签< include>来引入另一个配置文件。

<configuration>
  <include file="src/main/java/resources/includedConfig.xml"/>

  <root level="DEBUG">
    <appender-ref ref="includedConsole" />
  </root>

</configuration>

includedConfig.xml文件定义了被引用的内容:

<included>
  <appender name="includedConsole" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>"%d - %m%n"</pattern>
    </encoder>
  </appender>
</included>

标签引入的文件可以为一个文件,一个类路径上的资源,或者一个URL。如下:

<include file="src/main/java/resources/includedConfig.xml"/>

<include resource="includedConfig.xml"/>

<include url="http://xxx.com/includedConfig.xml"/>

如果被引用的文件不存在,logback会打印内部的状态信息。如果包含的文件是可选的,可以通过optional属性设置为true来进制打印显示警告信息。

<include optional="true" ..../>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值