008_logback配置语法

1. 配置文件的基本结构

1.1. Logback配置文件的语法非常灵活。正因为灵活, 所以无法用DTD或XML schema进行定义。尽管如此, 可以这样描述配置文件的基本结构: 以<configuration>开头, 后面有零个或多个<appender>元素, 有零个或多个<logger>元素, 有最多一个<root>元素。

2. 配置logger或<logger>元素

2.1. Logger是用<logger>元素配置的。<logger>元素有且仅有一个name属性、一个可选的level属性和一个可选的additivity属性。Level属性的值大小写无关, 其值为: TRACE、DEBUG、INFO、 WARN、ERROR、ALL和OFF中的一个值。还可以是一个特殊的字符串"INHERITED"或其同义词"NULL", 表示强制继承上级的级别。

2.2. <logger>元素可以包含零个或多个<appender-ref>元素, 表示这个appender会被添加到该logger。强调一下, 每个用<logger>元素声明的logger, 首先会移除所有的appender, 然后添加引用的appender, 所以如果logger没有引用任何appender, 就会失去所有appender。

2.3. 例子

2.3.1. 新建一个名为LogbackCfgGrammer的Java项目, 同时添加相关jar包

2.3.2. 编辑LoggerCfg.java

package com.zr.cfg;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;

public class LoggerCfg {
	private static final Logger logger = LoggerFactory.getLogger(LoggerCfg.class);
	
	public static void main(String[] args) {
		// 上下文
		LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
		// Joran配置文件处理类
		JoranConfigurator cfg = new JoranConfigurator();
		cfg.setContext(lc);
		// 上下文已经读取配置文件或使用默认配置文件, 这里进行重置操作。
		lc.reset();
		try {
			cfg.doConfigure("cfg/logger.xml");
		} catch (JoranException e) {
			e.printStackTrace();
		}
		
		logger.info("配置logger或<logger>元素");
		logger.debug("配置logger或<logger>元素");
	}
}

2.3.3. 新建cfg文件夹, 在该文件夹下编辑logger.xml

<configuration>

	<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">  
        <encoder charset="UTF-8">  
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>  
        </encoder>  
  	</appender> 

	<logger name="com.zr.cfg.LoggerCfg" level="info">
		<appender-ref ref="stdout" />
	</logger>

</configuration>

2.3.4. 只会打印info和高于info级别的日志

3. 配置根logger或<root>元素 

3.1. <root>元素配置根logger。该元素有一个level属性。没有name属性, 因为已经被命名为"ROOT"。Level属性的值大小写无关, 其值为: TRACE、DEBUG、INFO、 WARN、ERROR、ALL和OFF中的一个值。注意不能设置为"INHERITED"或"NULL"。

3.2. <logger>元素可以包含零个或多个<appender-ref>元素。与<logger>元素类似, 声明<root>元素后, 会先关闭然后移除全部当前appender, 只引用声明的appender。如果root元素没有引用任何appender, 就会失去所有appender。

3.3. 基本选择规则依赖于被调用的logger的有效级别, 而不是appender所关联的logger的级别。Logback首先判断记录语句是否被启用, 如果启用, 则调用logger等级里的appender, 同时如果有继承的话, 还会调用祖先的appender, 无视logger的级别。

3.4. 例子

3.4.1. 编辑RootLoggerCfg.java

package com.zr.cfg;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;

public class RootLoggerCfg {
	private static final Logger logger = LoggerFactory.getLogger(RootLoggerCfg.class);
	
	public static void main(String[] args) {
		// 上下文
		LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
		// Joran配置文件处理类
		JoranConfigurator cfg = new JoranConfigurator();
		cfg.setContext(lc);
		// 上下文已经读取配置文件或使用默认配置文件, 这里进行重置操作。
		lc.reset();
		try {
			cfg.doConfigure("cfg/rootlogger.xml");
		} catch (JoranException e) {
			e.printStackTrace();
		}
		
		logger.info("配置根logger或<root>元素");
		logger.debug("配置根logger或<root>元素");
	}
}

3.4.2. 在cfg文件夹下编辑rootlogger.xml

<configuration>

	<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">  
        <encoder charset="UTF-8">  
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>  
        </encoder>  
  	</appender> 
  	
  	<logger name="com.zr.cfg.RootLoggerCfg" level="info"></logger>

	<root level="off">
		<appender-ref ref="stdout" />
	</root>
</configuration>

3.4.3. 各个logger的级别

3.4.4. 配置里唯一的appender"stdout", 被关联到级别为OFF的根logger, 根logger的级别不起任何作用。com.zr.cfg.RootLoggerCfg类的INFO级别是启用的, 因此会输出com.zr.cfg.RootLoggerCfg类INFO级别及其更高级别的日志:

4. 配置Appender

4.1. Appender用<appender>元素配置, 该元素必要属性name和class。name属性指定appender的名称, class属性指定appender类的全限定名。

4.2. <appender>元素可以包含零个或一个<layout>元素或者零个或一个<encoder>元素和零个或多个<filter>元素。除了这三个常用元素之外, 还可以包含任意数量的javabean属性。

4.3. 下图演示了常用结构, 注意对javabean属性的支持在图中不可见。

4.4. <layout>元素的class属性是必要的, 表示将被实例化的layout类的全限定名。因为太常用了, 所以当layout是PatternLayout 时, 可以省略class属性。

4.5. <encoder>元素class属性是必要的, 表示将被实例化的encoder类的全限定名。因为太常用了, 所以当encoder是PatternLayoutEncoder时, 可以省略class属性。

4.6. 记录输出到多个appender很简单, 先定义各种appender, 然后在logger里进行引用就行了。

4.7. 注意每个appender都有自己的encoder。Encoder通常不能被多个appender共享, layout也是。

4.8. 例子

4.8.1. 编辑AppenderCfg.java

package com.zr.cfg;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;

public class AppenderCfg {
	private static final Logger logger = LoggerFactory.getLogger(AppenderCfg.class);
	
	public static void main(String[] args) {
		// 上下文
		LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
		// Joran配置文件处理类
		JoranConfigurator cfg = new JoranConfigurator();
		cfg.setContext(lc);
		// 上下文已经读取配置文件或使用默认配置文件, 这里进行重置操作。
		lc.reset();
		try {
			cfg.doConfigure("cfg/appender.xml");
		} catch (JoranException e) {
			e.printStackTrace();
		}
		
		logger.info("Appender用<appender>元素配置, 该元素必要属性name和class。");
		logger.debug("name属性指定appender的名称, class属性指定appender类的全限定名。");
	}
}

4.8.2. 在cfg文件夹下编辑appender.xml

<configuration>

	<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
		<layout class="ch.qos.logback.classic.PatternLayout">
			<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
		</layout>
	</appender>

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

	<root level="debug">
		<appender-ref ref="file" />
		<appender-ref ref="stdout" />
	</root>

</configuration>

4.8.3. 运行结果

4.9. Appender累积

4.9.1. 默认情况下, appender是可累积的: logger会把记录输出到它自身的appender和它所有祖先的appender。因此, 把同一appender关联到多个logger会导致重复输出。

4.9.2. Appender的叠加性对新手来说并不是陷阱, 反而是非常方便的。举例来说, 你可以让某些系统里所有logger的记录信息出现在控制台, 却让某些特定logger的记录信息发到一个特定的appender。

<configuration>

	<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
		<layout class="ch.qos.logback.classic.PatternLayout">
			<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
		</layout>
	</appender>

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

	<logger name="com.zr.cfg.AccumulateCfg" level="error">
		<appender-ref ref="file" />
	</logger>

	<root>
		<appender-ref ref="stdout" />
	</root>

</configuration>

4.9.3. 如果你觉得默认的累积行为不合适, 可以设置叠加性标识为false以关闭它。这样的话, logger树里的某个分支可以输出到与其他logger不同的appender。

<configuration>

	<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
		<layout class="ch.qos.logback.classic.PatternLayout">
			<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
		</layout>
	</appender>

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

	<logger name="com.zr.cfg.AccumulateCfg" level="error" additivity="false">
		<appender-ref ref="file" />
	</logger>

	<root>
		<appender-ref ref="stdout" />
	</root>

</configuration>

4.9.4. 创建AccumulateCfg.java

package com.zr.cfg;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;

public class AccumulateCfg {
	private static final Logger logger = LoggerFactory.getLogger(AccumulateCfg.class);
	private static final Logger root = LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
	
	public static void main(String[] args) {
		// 上下文
		LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
		// Joran配置文件处理类
		JoranConfigurator cfg = new JoranConfigurator();
		cfg.setContext(lc);
		// 上下文已经读取配置文件或使用默认配置文件, 这里进行重置操作。
		lc.reset();
		try {
			cfg.doConfigure("cfg/accumulate.xml");
		} catch (JoranException e) {
			e.printStackTrace();
		}
		
		logger.error("默认情况下, appender是可累积的: logger会把记录输出到它自身的appender和它所有祖先的appender。");
		logger.error("因此, 把同一appender关联到多个logger会导致重复输出。");
		logger.error("Appender的叠加性对新手来说并不是陷阱, 反而是非常方便的。");
		root.warn("举例来说, 你可以让某些系统里所有logger的记录信息出现在控制台, 却让某些特定logger的记录信息发到一个特定的appender。");
		root.info("如果你觉得默认的累积行为不合适, 可以设置叠加性标识为false以关闭它。");
		root.debug("这样的话, logger树里的某个分支可以输出到与其他logger不同的appender。");
	}
}

4.9.5. 创建accumulate.xml, 使用步骤4.9.2.的配置文件

4.9.6. 运行AccumulateCfg.java

4.9.7. 修改accumulate.xml配置, 使用步骤4.9.3.的配置文件, 再次运行AccumulateCfg.java

5. 设置上下文名称 

5.1. 每个logger都关联到logger上下文。默认情况下, logger上下文名为"default"。但是你可以借助配置指令<contextName>设置成其他名字。注意一旦设置logger上下文名称后, 不能再改。设置上下文名称后, 可以方便地区分来自不同应用程序的记录。

5.2. 例子

5.2.1. 编辑ContextName.java

package com.zr.cfg;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;

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

	public static void main(String[] args) {
		// 上下文
		LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
		// Joran配置文件处理类
		JoranConfigurator cfg = new JoranConfigurator();
		cfg.setContext(lc);
		// 上下文已经读取配置文件或使用默认配置文件, 这里进行重置操作。
		lc.reset();
		try {
			cfg.doConfigure("cfg/contextName.xml");
		} catch (JoranException e) {
			e.printStackTrace();
		}

		logger.info("每个logger都关联到logger上下文。");
		logger.debug("你可以借助配置指令<contextName>设置上下文名字。");
	}
}

5.2.2. 在cfg文件夹下编辑contextName.xml

<configuration debug="true">

	<contextName>日志服务器</contextName>
	
	<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">  
        <encoder charset="UTF-8">  
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>  
        </encoder>  
  	</appender> 
  	
	<root>
		<appender-ref ref="stdout" />
	</root>
</configuration>

5.2.3. 运行结果

6. 变量替换 

6.1. 原则上, 指定变量的地方就能够发生变量替换。变量替换的语法与Unix shell中的变量替换相似。位于"${"与"}"之间的字符串是键(key), 取代键的值可以在同一配置文件里指定, 也可以在外部文件或通过系统属性进行指定。

6.2. 属性被插入logger上下文

6.2.1. 注意通过<property>元素定义的值实际上会被插入logger上下文。换句话说, 这些值变成了logger上下文的属性。所以, 它们对所有记录事件都可用, 包括通过序列化方式被发送到远程主机的记录事件。

6.2.2. 下面的例子在配置文件的开头声明了一个变量又名替换属性, 它代表输出文件的位置, 然后在后面的配置文件里使用它。

<property name="fileName" value="my.log" />

6.3. 属性文件

6.3.1. 当需要很多变量时, 更方便的做法是在一个单独的文件里声明所有变量。

<property file="variables.properties" /> 

6.3.2. 还可以不引用文件, 而是引用class path上的资源。

<property resource="variables.properties" />

6.3.3. variables.properties文件内容类似于:

pathPre=/log
fileName=my.log
fullPath=/log/my.log

6.4. 嵌套变量替换

6.4.1. Logback支持嵌套变量替换。这里的嵌套是指变量的值里包含对其他变量的引用。

pathPre=/log
fileName=my.log
fullPath=${pathPre}/${fileName}

6.5. 变量的默认替换值 

6.5.1. 在某些特定情况下, 最好给变量一个默认值, 以免变量未被声明或值为null。Bash shell用":-"指定默认值。例如, 假设"appName"未被声明, 那么"${appName:-zrApp}"将被解释为"zrApp"。

6.6. Logback自动定义了一个常用变量"${HOSTNAME}"。

7. 配置文件里的条件化处理 

7.1. 开发者经常需要针对不同的环境在不同的配置文件里换来换去, 比如开发、测试和生产环境。这些配置文件大同小异。为避免重复劳动, logback支持在配置文件里进行条件化处理, 用<if>、<then>和<else>这些元素可以让一个配置文件适用于多个环境。

7.2. 条件语句一般格式如下

<configuration> 
 
 	<!-- if-then form -->  
	<if condition="some conditional expression">   
		<then>     ...   </then>  
	</if> 
 
 	<!-- if-then-else form -->  
 	<if condition="some conditional expression">   
 		<then>     ...   </then>   
 		<else>     ... 	 </else>  
   </if> 
 
</configuration

7.3. 其中"condition"是java表达式, 只允许访问上下文属性和系统属性。对于作为参数传入的键, property()方法或其等价的p()方法将返回属性的字符串值。例如, 想访问属性键为"k"的值, 你可以用property("k")或等价的 p("k")。如果键为"k"的属性未被定义, property方法将返回空字符串而不是null, 这样避免了检查null值。

7.4. 例子

7.4.1. 条件语句需要两个额外的包commons-compiler-3.1.3.jar和janino-3.1.3.jar

7.4.2. 编辑Condition.java

package com.zr.cfg;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;

public class Condition {
	private static final Logger logger = LoggerFactory.getLogger(Condition.class);
	
	public static void main(String[] args) {
		// 上下文
		LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
		// Joran配置文件处理类
		JoranConfigurator cfg = new JoranConfigurator();
		cfg.setContext(lc);
		// 上下文已经读取配置文件或使用默认配置文件, 这里进行重置操作。
		lc.reset();
		try {
			cfg.doConfigure("cfg/condition.xml");
		} catch (JoranException e) {
			e.printStackTrace();
		}
		
		logger.error("错误信息");
		logger.warn("警告信息");
		logger.info("信息");
		logger.debug("测试信息");
	}
}

7.4.3. 在cfg文件夹下编辑condition.xml

<configuration>
	
	<property name="development" value="dev" />
	
	<contextName>${pathPre:-zrApp}</contextName>
	
	<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">  
    	<encoder charset="UTF-8">  
           <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>  
       </encoder>  
	</appender> 
  	
  	<if condition='p("development").contains("test")'> 
		<then> 
			<root level="debug">
				<appender-ref ref="stdout" />
			</root>
		</then>
		<else>  
			<root level="warn">
				<appender-ref ref="stdout" />
			</root>
		</else>
	</if>
</configuration>

7.4.4. 运行结果

8. 从JNDI获取变量

8.1. 在某些特定情况下, 你也许利用JNDI里存储的env项, <insertFromJNDI>指令会从JNDI里取得env项, 然后用as属性把它们作为变量。

8.2. 新建一个名为JNDIGetVariable的动态Web工程, 同时添加相关jar包

8.3. 创建JNDIAction.java

package com.fj;

import java.io.IOException;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class JNDIAction extends HttpServlet {
	private static final long serialVersionUID = 1L;
	
	private static final Logger logger = LoggerFactory.getLogger(JNDIAction.class);
	
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		try {
			InitialContext ctx = new InitialContext();
			String contextName = (String) ctx.lookup("java:comp/env/logback/context-name");
			
			logger.error("从JNDI获取变量: {}", contextName);
		} catch (NamingException e) {
			e.printStackTrace();
		}
	}
	
	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		doGet(req, resp);
	}
}

8.4. 在src目录下添加logback.xml

<configuration>

	<insertFromJNDI env-entry-name="java:comp/env/logback/context-name" as="context-name" />
	<contextName>${context-name}</contextName>

	<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
		<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
			<pattern>%contextName %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
		</encoder>
	</appender>
	
	<root level="debug">
		<appender-ref ref="stdout" />
	</root>

</configuration>

8.5. 配置web.xml

8.6. 运行项目

9. 文件包含 

9.1. Joran支持在配置文件里包含其他文件。方法是声明<include>元素, 被包含的内容可以是文件、资源或URL。

9.2. 作为文件, 用"file"属性包含一个文件。可以用相对路径, 但是需要注意, 当前目录是由应用程序决定的, 与配置文件的路径必要的联系。

<include file="includedConfig.xml" /> 

9.3. 作为资源, 用"resource"属性包含一个资源, 也就是在class path上的文件。

<include resource="includedConfig.xml" />

9.4. 作为URL, 用"url"属性包括一个URL。

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

9.5. 被包含的文件必须把它的元素嵌套在<included>元素里。请注意<included>元素是必需的。

9.6. 例子

9.6.1. 编辑IncludeCfg.java

package com.zr.cfg;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;

public class IncludeCfg {
	private static final Logger logger = LoggerFactory.getLogger(IncludeCfg.class);
	
	public static void main(String[] args) {
		// 上下文
		LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
		// Joran配置文件处理类
		JoranConfigurator cfg = new JoranConfigurator();
		cfg.setContext(lc);
		// 上下文已经读取配置文件或使用默认配置文件, 这里进行重置操作。
		lc.reset();
		try {
			cfg.doConfigure("cfg/containingcfg.xml");
		} catch (JoranException e) {
			e.printStackTrace();
		}
		
		logger.error("错误信息");
		logger.warn("警告信息");
		logger.info("信息");
		logger.debug("测试信息");
	}
}

9.6.2. 在cfg文件夹下编辑containingcfg.xml

<included>
	<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">  
        <encoder charset="UTF-8">  
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>  
        </encoder>  
  	</appender> 
</included>

9.6.3. 在cfg文件夹下编辑includecfg.xml

<configuration>
  	<include file="cfg/includecfg.xml"></include>
  	
	<root>
		<appender-ref ref="stdout" />
	</root>
</configuration>

9.6.4. 运行结果

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值