日志门面统一技术

日志门面统一技术

1、日志门面介绍

1、什么是日志门面技术

日志门面技术借鉴了 JDBC 的思想,为日志系统也提供了一套门面,开发者就可以面向这些接口规范来开发,避免了直接依赖具体日志框架,因为不同的日志框架有不同的用法,所以导致在更换日志框架时就需要改动大量的代码,所以为了减少开发量,就有了日志门面技术,门面技术简单地理解就是说你的日志框架变了,但是不需要改代码,只修改依赖和配置文件即可,提高了开发效率

  • 常见的日志门面: JCL、SLF4J
  • 常见的日志实现: JUL、Log4j、Logback、Log4j2
  • 日志框架出现的历史顺序:Log4j » JUL » JCL » SLF4J » Logback » Log4j2
  • 常用门面及实现组合:slf4j + logback、slf4j + log4j2

因为JCL支持的日志框架比较少,只支持目前比较主流的日志框架,且不支持扩展,需要扩展其他的日志实现技术的话需要用户自己修改源代码,但是随着技术的发展,以后肯定会发展出更多更好的日志框架,但JCL却不支持这些新出的框架,因此JCL在2014年就已经被 Apache 给淘汰了,目前使用最广泛的是SLF4J

2、为什么要用门面技术

  1. 面向接口开发,不在依赖具体的实现类,减少代码的耦合
  2. 项目通过导入不同的日志实现类,可以灵活的切换日志框架
  3. 统一API,方便开发者学习和使用
  4. 统一配置便于项目日志的管理

2、日志门面SLF4J

官网:https://www.slf4j.org/、Maven 仓库地址:https://search.maven.org/artifact/org.slf4j/slf4j-api

简单日志门面(Simple Logging Facade For Java)主要是为了给Java日志访问提供一套标准、规范的API框架,其主要意义在于提供接口,具体的实现可以交由其它日志框架,例如log4j和logback等。当然slf4j自己也提供了功能较为简单的实现,但是一般很少用到。对于一般的java项目而言,日志框架会选择slf4j-api作为门面,配上具体的实现框架(log4j、logback等),中间使用桥接器完成桥接。并且类路径要绑定具体的日志实现框架(Logback、Log4j、Log4j2、JUL),否则无操作去实现,因为SLF4J只是接口。

SLF4J是目前市面上最流行的日志门面。现在的项目中基本上都是使用SLF4J。主要提供两大功能:

  1. 日志框架的绑定(Binding):绑定其他日志的实现
  2. 日志框架的桥接(Bridging):桥接旧的日志框架
                         ┌       日志桥接绑定                 日志实现组件
                         ├  loback-classic-version.jar   »»»   logback
           日志门面       ├  slf4j-log4j12-version.jar    »»»   log4j
client »»» slf4j-api »»» ├  slf4j-jdk14-version.jar      »»»   jul(jdk)
                         ├  slf4j-jcl-version.jar        »»»   jcl
                         ├  slf4j-simple-version.jar     »»»   slf4j simple
                         └  slf4j-nop-version.jar        »»»   slf4j nop

1、核心接口或类 & 日志级别

核心接口或类:

  1. org.slf4j.Logger:核心接口(包含了日志操作的方法)
  2. org.slf4j.LoggerFactory:工厂类,获取日志实例

LoggerFactory:位于slf4j-api.jar包中,是一个用于生产各种Logger的工厂,有3个公共方法。

方法名称含义
getILoggerFactory返回logger工厂,getLogger方法内部会调用该方法
getLogger(Class<?> clazz)通过clazz查找具体的logger
getLogger(String name)通过name查找具体的logger

日志级别有5类,分别是ERROR、WARN、INFO、DEBUG、TRACE。源码如下:

ERROR(ERROR_INT, "ERROR"), 
WARN(WARN_INT, "WARN"), 
INFO(INFO_INT, "INFO"), 
DEBUG(DEBUG_INT, "DEBUG"), 
TRACE(TRACE_INT, "TRACE");

final public int TRACE_INT = 00;
final public int DEBUG_INT = 10;
final public int INFO_INT = 20;
final public int WARN_INT = 30;
final public int ERROR_INT = 40;

2、绑定默认日志实现 Binding

1、单独使用SLF4J

直接使用SLF4J日志门面,不加日志实现框架

<!--  slf4j 日志门面  -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.32</version>
</dependency>
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SLF4JTest {
    public static void main(String[] args) {
        Logger logger = LoggerFactory.getLogger(SLF4JTest.class);
        logger.error("error test");
        logger.warn("warn test");
        logger.info("info {}", "test");
        logger.debug("debug test ");
        logger.trace("trace test");
    }
}

因为目前只有接口,还没有具体的实现,此时还不会打印内容,会打印提示您没有实现类

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
2、SLF4J日志开关

slf4j-nop是slf4j开关,加上该依赖,重新执行Test,既不会打印内容,也不会提示

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-nop</artifactId>
    <version>1.7.32</version>
</dependency>
3、绑定默认日志框架

slf4j-simple是slf4j内置的简单日志实现框架,功能比较简单

<!--slf4j 日志门面-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.32</version>
</dependency>
<!--slf4j 内置的简单实现-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-simple</artifactId>
    <version>1.7.32</version>
</dependency>

运行上述代码,控制台打印的结果如下

[main] ERROR SLF4JTest - error test
[main] WARN SLF4JTest - warn test
[main] INFO SLF4JTest - info test

3、绑定其他日志实现 Binding

如前所述,SLF4J支持各种日志框架。SLF4J发行版附带了几个称为“SLF4J绑定”的jar文件,每个绑定对应一个受支持的框架。使用SLF4J的日志绑定流程**😗*

  1. 添加slf4j-api的依赖
  2. 使用slf4j的API在项目中进行统一的日志记录
  3. 绑定具体的日志实现框架
    • 绑定已经实现了slf4j的日志框架,直接添加对应依赖
    • 绑定没有实现slf4j的日志框架,先添加日志的适配器,再添加实现类的依赖
  4. slf4j有且仅有一个日志实现框架的绑定(如果出现多个默认使用第一个依赖日志实现)

打开官网的用户手册查看流程图:https://www.slf4j.org/manual.html

img

1、绑定JUL日志实现
<!--slf4j 日志门面-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.32</version>
</dependency>
<!--JUL适配器依赖,因为JUL是JDK内置的,所以不需要额外导入JUL实现的依赖-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-jdk14</artifactId>
    <version>1.7.32</version>
</dependency>

运行上述代码,控制台打印的结果如下

3月 31, 2022 10:07:14 上午 SLF4JTest main
严重: error test
3月 31, 2022 10:07:14 上午 SLF4JTest main
警告: warn test
3月 31, 2022 10:07:14 上午 SLF4JTest main
信息: info test
2、绑定Log4j日志框架
<!--slf4j 日志门面-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.32</version>
</dependency>
<!--导入log4j适配器依赖-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.32</version>
</dependency>
<!--导入log4j依赖-->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

除此之外,还需要加上log4j的配置文件(main/resources目录下):log4j.properties

# 使用根节点的logger对象, 将日志信息输出到控制台
log4j.rootLogger = info,console
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.conversionPattern = [%p]%r %c%t%d{yyyy-mm-dd HH:mm:ss:SSS} %m%n

运行上述代码,控制台打印的结果如下

[ERROR]0 SLF4JTestmain2022-16-31 10:16:50:961 error test
[WARN]0 SLF4JTestmain2022-16-31 10:16:50:961 warn test
[INFO]10 SLF4JTestmain2022-16-31 10:16:50:971 info test
3、日志实现框架切换

要切换日志框架,只需替换类路JUL切换到Log4j,只需将 slf4j-jdk14-1.7.32.jar 替换为 slf4j-log4j12-1.7.32.jar 即可。

SLF4J不依赖于任何特殊的类装载。实际上,每个SLF4J绑定在编译时都是硬连线的, 以使用一个且只有一个特定的日志记录框架。例如:slf4j-log4j12-1.7.32.jar绑定在编译时绑定以使用log4j。

4、桥接旧的日志框架 Bridging

通常,您依赖的某些组件依赖于SLF4J以外的日志记录API。您也可以假设这些组件在不久的将来不会切换到SLF4J。为了解决这种情况,SLF4J附带了几个桥接模块,这些模块将对log4j、JCL和java.util.loggingAPI的调用重定向,就好像它们是SLF4J的API一样。

桥接解决的是项目中日志的遗留问题,当系统中存在之前的日志API,可以通过桥接转换到slf4j的实现:

  1. 先去除之前老的日志框架的依赖,必须去掉
  2. 添加SLF4J提供的桥接组件,这个组件就是模仿之前老的日志写了一套相同的api,只不过这个api是在调用SLF4J的api
  3. 为项目添加SLF4J的具体实现(如Logback)
1、SLF4J桥接器介绍

旧日志迁移方式:

<!--slf4j 日志门面-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.32</version>
</dependency>
<!--桥接的组件,加上这个依赖后,虽然import上是log4j,但是底层调用的其实是logback的日志实现;-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>log4j-over-slf4j</artifactId>
    <version>1.7.32</version>
</dependency>
<!--Logback 日志实现依赖-->
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.5</version>
</dependency>

SLF4J提供的桥接器:

<!-- log4j-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>log4j-over-slf4j</artifactId>
    <version>1.7.32</version>
</dependency>
<!-- jul -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jul-to-slf4j</artifactId>
    <version>1.7.32</version>
</dependency>
<!--jcl -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jcl-over-slf4j</artifactId>
    <version>1.7.32</version>
</dependency>

注意问题:

  1. log4j-over-slf4j 桥接器 和 slf4j-jcl.jar 适配器 不能同时部署,否则会导致无限循环
    前一个jar文件将导致JCL将日志系统的选择委托给SLF4J,后一个jar文件将导致SLF4J将日志系统的选择委托给JCL,从而导致无限循环
  2. log4j-over-slf4j 桥接器 和 slf4j-log4j 适配器 不能同时出现
  3. jul-to-slf4j.jar 桥接器和 slf4j-jdk14.jar 适配器 不能同时出现
  4. 所有的桥接器都只对Logger日志记录器对象有效,如果程序中调用了内部的配置类或者是Appender、Filter等对象,将无法产生效果

模拟同时导入log4j 适配器与桥接器 导致死循环 示例:

<!--slf4j日志门面-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.32</version>
</dependency>
<!--配置log4j的桥接器-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>log4j-over-slf4j</artifactId>
    <version>1.7.32</version>
</dependency>
<!--配置log4j的适配器-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.32</version>
</dependency>
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SLF4JTest {
    public static void main(String[] args) {
        Logger logger = LoggerFactory.getLogger(SLF4JTest.class);
        logger.error("error test");
        logger.warn("warn test");
        logger.info("info test");
        logger.debug("debug test ");
        logger.trace("trace test");
    }
}
SLF4J: Detected both log4j-over-slf4j.jar AND bound slf4j-log4j12.jar on the class path, preempting StackOverflowError. 
SLF4J: See also http://www.slf4j.org/codes.html#log4jDelegationLoop for more details.
Exception in thread "main" java.lang.ExceptionInInitializerError
	at org.slf4j.impl.StaticLoggerBinder.<init>(StaticLoggerBinder.java:72)
	at org.slf4j.impl.StaticLoggerBinder.<clinit>(StaticLoggerBinder.java:45)
	at org.slf4j.LoggerFactory.bind(LoggerFactory.java:150)
	at org.slf4j.LoggerFactory.performInitialization(LoggerFactory.java:124)
	at org.slf4j.LoggerFactory.getILoggerFactory(LoggerFactory.java:417)
	at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:362)
	at org.apache.log4j.Category.<init>(Category.java:57)
	at org.apache.log4j.Logger.<init>(Logger.java:37)
	at org.apache.log4j.Log4jLoggerFactory.getLogger(Log4jLoggerFactory.java:43)
	at org.apache.log4j.Logger.getLogger(Logger.java:41)
	at org.apache.log4j.Logger.getLogger(Logger.java:49)
	at SLF4JTest.main(SLF4JTest.java:6)
Caused by: java.lang.IllegalStateException: Detected both log4j-over-slf4j.jar AND bound slf4j-log4j12.jar on the class path, preempting StackOverflowError. See also http://www.slf4j.org/codes.html#log4jDelegationLoop for more details.
	at org.slf4j.impl.Log4jLoggerFactory.<clinit>(Log4jLoggerFactory.java:54)
	... 12 more
2、日志框架桥接示例

使用场景:老项目使用的日志框架是Log4j,现在想要使用Logback, 但是一大堆的Java文件中都有Log4j的导入,并且代码还是Log4j的api代码。因为要使用Logback,就一定要将Log4j相关的maven依赖删除,依赖删除后,这个import导入就一定会报错,一个个去改把,太麻烦了,搞不好改错了,更麻烦,于是就有了日志桥接器,只需要加上桥接器的maven依赖即可,代码一行都不需要改。

1、先模拟老项目使用Log4j日志框架,导入Log4j依赖,编写测试代码

<!--导入log4j依赖-->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;

public class SLF4JTest {
    public static void main(String[] args) {
        BasicConfigurator.configure();
        Logger logger = Logger.getLogger(SLF4JTest.class);
        logger.error("error test");
        logger.warn("warn test");
        logger.info("info test");
        logger.debug("debug test ");
        logger.trace("trace test");
    }
}

// 输出内容
0 [main] ERROR SLF4JTest  - error test
0 [main] WARN SLF4JTest  - warn test
0 [main] INFO SLF4JTest  - info test
0 [main] DEBUG SLF4JTest  - debug test

2、修改依赖为Logback的日志框架(先注释掉Log4j的依赖,增加Slf4j-api和Logback的依赖):

<!--配置slf4j日志门面-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.32</version>
</dependency>
<!--配置logback-->
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.5</version>
</dependency>

3、此时由于没有了log4j的依赖包,代码就会提示错误(如果整个项目各个地方用到,每个地方都去修改就会工作量很大)在原有的依赖配置上添加如下依赖:

<!--配置log4j的桥接器-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>log4j-over-slf4j</artifactId>
    <version>1.7.32</version>
</dependency>

4、报错消失,运行上面的代码结果如下(表面上看,导入的是log4j的包,实际上执行效果却是logback的日志打印)

12:14:36.179 [main] ERROR SLF4JTest - error test
12:14:36.179 [main] WARN SLF4JTest - warn test
12:14:36.179 [main] INFO SLF4JTest - info test
12:14:36.179 [main] DEBUG SLF4JTest - debug test

5、SLF4J注意事项与原理解析

1、maven依赖中,尽量保证只有一个日志实现,如果由多个日志实现的话默认使用第一个(maven依赖靠前的那个),并且在控制台会显示以下警告信息

# 提示有多个日志实现
SLF4J: Class path contains multiple SLF4J bindings.
# 第一个日志实现
SLF4J: Found binding in [jar:file:/D:/repository/org/slf4j/slf4j-simple/1.7.25/ slf4j-simple-1.7.25.jar!/org/slf4j/impl/StaticLoggerBin
# 第二个日志实现
SLF4J: Found binding in [jar:file:/D:/repository/org/slf4j/slf4j-log4j12/1.7.12/slf4j-log4j12-1.7.12.jar!/org/slf4j/impl/StaticLoggerBinder.class]
# 默认绑定第一个
SLF4J: Actual binding is of type [org.slf4j.impl.SimpleLoggerFactory]

2、SLF4J原理解析

  1. SLF4J通过LoggerFactory加载日志具体的实现对象。

  2. LoggerFactory在初始化的过程中,会通过performInitialization()方法绑定具体的日志实现。

  3. 在绑定具体实现的时候,通过类加载器,加载org/slf4j/impl/StaticLoggerBinder.class

  4. 所以只要是一个日志实现框架,在org.slf4j.impl包中提供一个自己的StaticLoggerBinder类,在其中提供具体日志实现的LoggerFactory就可以被SLF4J所加载

6、参考文献 & 鸣谢

  • java日志:四、slf4j使用【CSDN:小徐也要努力鸭】https://blog.csdn.net/a232884c/article/details/121160183
  • java日志(三)–slf4j和logback使用【CSDN:panda-star】https://blog.csdn.net/chinabestchina/article/details/104743907

3、日志门面JCL

1、JCL简单入门

1、JCL简介

JCL全称为Jakarta Commons Logging,是Apache提供的一个通用日志API。 该日志门面的使用并不是很广泛。它是为 "所有的Java日志实现"提供一个统一的接口,它自身也提供一个日志的实现(SimpleLog)但是功能非常常弱 。所以一般不会单独使用它。它允许开发人员使用不同的具体日志实现工具:Log4j,JDK自带的日志(JUL)

JCL 有两个基本的抽象类:Log(基本记录器)和LogFactory(负责创建Log实例)

APP(Java应用)=> JCL => Log4j、JUL、SimpleLog

日志实现优先级排列:JCL门面技术在获取Log实现时会按照以下顺序依次获取Logger的实现,可以看到优先使用Log4j,如果匹配就不会继续找了

  1. log4j: 优先使用log4j
  2. jdk14Logger:jdk自带logger
  3. jdk13LumberjackLogger:也是jdk自带logger
  4. SimpleLog: jcl自带的日志框架,功能比较简单
2、JCL示例

1、先添加apache门面的pom依赖

<!--配置JCL日志门面-->
<dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>1.2</version>
</dependency>

2、测试代码打印日志

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class JCLTest {
    public static void main(String[] args) {
        // 创建日志对象
        Log log = LogFactory.getLog(JCLTest.class);
        // 日志记录输出
        log.fatal("fatal");
        log.error("error");
        log.warn("warn");
        log.info("info");
        log.debug("debug");
    }
}

3、打印结果如下,发现默认情况下使用的是jdk(jdk14)自带的日志框架

3月 31, 2022 9:44:45 下午 JCLTest main
严重: fatal
3月 31, 2022 9:44:45 下午 JCLTest main
严重: error
3月 31, 2022 9:44:45 下午 JCLTest main
警告: warn
3月 31, 2022 9:44:45 下午 JCLTest main
信息: info

4、接下来我们加上Log4j的依赖

<!--配置JCL日志门面-->
<dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>1.2</version>
</dependency>
<!--配置log4j日志实现-->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

5、再加上Log4j的配置文件 log4j.properties,放在resources目录下

# 使用根节点的logger对象, 将日志信息输出到控制台
log4j.rootLogger = info,console
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.conversionPattern = [%p]%r %c%t%d{yyyy-mm-dd HH:mm:ss:SSS} %m%n

6、其他代码不变,再次运行main方法后打印结果如下 ,会发现自动使用了log4j进行输出日志

[FATAL]0 JCLTestmain2022-46-31 22:46:47:699 fatal
[ERROR]0 JCLTestmain2022-46-31 22:46:47:699 error
[WARN]0 JCLTestmain2022-46-31 22:46:47:699 warn
[INFO]0 JCLTestmain2022-46-31 22:46:47:699 info

2、JCL原理

1、通过LogFactory动态加载Log实现类

                               Log
                                ⬆
     ┌───────────────┬──────────────────────┬──────────────────┐
Log4JLogger     JDK14Logger      Jdk13LumberjackLogger      SimpleLog

2、日志门面支持的日志实现数组(在LogFactoryImpl类中)

private static final String[] classesToDiscover = {
    "org.apache.commons.logging.impl.Log4JLogger",
    "org.apache.commons.logging.impl.Jdk14Logger",
    "org.apache.commons.logging.impl.Jdk13LumberjackLogger",
    "org.apache.commons.logging.impl.SimpleLog"
};

3、获取具体的日志实现(也在LogFactoryImpl类中)

for(int i = 0; i < classesToDiscover.length && result == null; ++i) {
    result = this.createLogFromClass(classesToDiscover[i], logCategory, true);
}
if (result == null) {
    throw new LogConfigurationException("No suitable Log implementation");
}
return result;

源码可知,读取到一个日志的结果,result不为空,就会跳出for循环,直接返回。

以之前代码为例。这里首先会在 classesToDiscover 属性中寻找,按数组下标顺序进行寻找,开始我们没有加入log4j依赖和配置文件,这里 “org.apache.commons.logging.impl.Log4JLogger”,就找不到,会继续在数组中寻找,第二个"org.apache.commons.logging.impl.Jdk14Logger",是JDK自带的,此时就使用它。当我们加入log4j依赖和配置文件后,就直接找到org.apache.commons.logging.impl.Log4JLogger,并使用它。如果需要再加入其它的日志,需要修改原码,并且在log中实现它,扩展性很不好。JCL已经被apache淘汰了。

3、参考文献 & 鸣谢

  • java日志:三、JCL使用【CSDN:小徐也要努力鸭】https://blog.csdn.net/a232884c/article/details/121157039
  • 日志技术之JCL学习【CSDN:StudyWinter】https://blog.csdn.net/Zhouzi_heng/article/details/107971135
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值