SpringBoot2.x笔记——整合Web开发

MVC框架

SpringMVC是一种基于Java,实现了Web MVC设计模式,请求驱动类型的轻量级Web框架,即使用了MVC架构模式的思想,将Web层进行职责解耦。基于请求驱动指的就是使用请求-响应模型,框架的目的就是帮助我们简化开发,SpringMVC也是要简化我们日常Web开发。

引入依赖

SpringBoot 集成SpringMVC框架并实现自动配置,只需要在pom中添加以下依赖即可,不需要其他任何配置:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

JSON配置

JSON是目前主流的前后端数据传输方式,SpringMVC中使用消息转换器HttpMessageConverter对JSON 的转换提供了很好的支持。添加上述的依赖后,我们的项目就可以返回一段JSON了。

默认使用

@Data
public class Table {
    //主键
    private Integer id;
    //名称
    private String name;
    //时间
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date date;
}

@RestController
public class TableController {
    @Resource
    private TableDao tableDao;

    /**
     * @return java.util.List<com.fengfan.web.entity.Table>
     * @description 查询所有数据
     * @author fengfan
     * @date 2022/5/10 14:23
     */
    @PostMapping("/queryAll")
    public List<Table> queryAll() {
        return tableDao.queryAll(new Table());
    }
}

@SpringBootTest
public class TableControllerTest {
    @Resource
    private WebApplicationContext wac;
    private MockMvc mockMvc;

    @BeforeEach
    public void init(){
        //初始化MockMvc对象
        mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
    }

    @Test
    public void queryAll() throws Exception {
        String str = mockMvc.perform(MockMvcRequestBuilders.post("/queryAll"))
                .andExpect(status().isOk())
                .andReturn().getResponse().getContentAsString(StandardCharsets.UTF_8);
        System.out.println(str);
    }
}

在这里插入图片描述
这是SpringBoot自带的处理方式,通过Spring中默认提供的MappingJackson2HttpMessageConvert类来实现的。如果采用这种方式,那么对于字段忽略、日期格式化等常见需求都可以通过注解来解决。

自定义转换器

常见的JSON处理器除了jackson-databind外,还有 Gson、fastjson等。

使用Gson
  1. 添加依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
        <exclusions>
            <exclusion>
                <groupId>com.fasterxml.jackson.core</groupId>
                <artifactId>jackson-databind</artifactId>
            </exclusion>
        </exclusions>
</dependency>
<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
</dependency>
  1. 由于SpringBoot中默认提供了Gson的配置类GsonHttpMessageConvertersConfiguration,因此Gson的依赖添加成功后,可以像使用jackson-databind那样直接使用。但是如果想对日期数据进行格式化,那么还需要自定义HttpMessageConverter,代码如下:
@Configuration
public class WebBeanConfig {
    /**
     * Gson配置
     * @return
     */
    @Bean
    GsonHttpMessageConverter gsonHttpMessageConverter() {
        GsonHttpMessageConverter converter = new GsonHttpMessageConverter();
        GsonBuilder gsonBuilder = new GsonBuilder();
        gsonBuilder.setDateFormat("yyyy-MM-dd HH:mm:ss");
        gsonBuilder.excludeFieldsWithModifiers(Modifier.PROTECTED);
        converter.setGson(gsonBuilder.create());
        return converter;
    }
}

在这里插入图片描述

使用fastjson

fastjson是阿里巴巴的一个开源JSON框架是目前JSON解析速度最快的开源框架,该框架也可以集成到 Spring Boot 中。不同于Gson, fastjson承完成之后并不能立马使用, 需要提供相应的httpMessageConverter后才能使用,配置如下:

  1. 添加依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
        <exclusions>
            <exclusion>
                <groupId>com.fasterxml.jackson.core</groupId>
                <artifactId>jackson-databind</artifactId>
            </exclusion>
        </exclusions>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.70</version>
</dependency>
  1. 对于FastJsonHttpMessageConverter的配置,有一个WebMvcAutoConfiguration类提供了对
    SpringMVC最基本的配置,如果某一项自动化配置不满足开发需求,可以针对该项自定义配置,只需要实现WebMvcConfigurer对应的接口即可,配置如下:
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    /**
     * fastJson配置
     *
     * @param converters
     */
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        //创建fastJson消息转换器
        FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
        //新版本(当前版本1.2.70)需要配置支持的MediaType, 不然就会报错
        List<MediaType> supportedMediaTypes = new ArrayList<>();
        supportedMediaTypes.add(MediaType.APPLICATION_JSON);
        supportedMediaTypes.add(MediaType.APPLICATION_ATOM_XML);
        supportedMediaTypes.add(MediaType.APPLICATION_FORM_URLENCODED);
        supportedMediaTypes.add(MediaType.APPLICATION_OCTET_STREAM);
        supportedMediaTypes.add(MediaType.APPLICATION_PDF);
        supportedMediaTypes.add(MediaType.APPLICATION_RSS_XML);
        supportedMediaTypes.add(MediaType.APPLICATION_XHTML_XML);
        supportedMediaTypes.add(MediaType.APPLICATION_XML);
        supportedMediaTypes.add(MediaType.IMAGE_GIF);
        supportedMediaTypes.add(MediaType.IMAGE_JPEG);
        supportedMediaTypes.add(MediaType.IMAGE_PNG);
        supportedMediaTypes.add(MediaType.TEXT_EVENT_STREAM);
        supportedMediaTypes.add(MediaType.TEXT_HTML);
        supportedMediaTypes.add(MediaType.TEXT_MARKDOWN);
        supportedMediaTypes.add(MediaType.TEXT_PLAIN);
        supportedMediaTypes.add(MediaType.TEXT_XML);
        fastConverter.setSupportedMediaTypes(supportedMediaTypes);
        FastJsonConfig config = new FastJsonConfig();
        config.setDateFormat("yyyy-MM-dd HH:mm:ss");
        //格式化的规则
        config.setSerializerFeatures(
                SerializerFeature.WriteDateUseDateFormat,
                SerializerFeature.WriteNullListAsEmpty,
                SerializerFeature.WriteMapNullValue
        );
        fastConverter.setFastJsonConfig(config);
        converters.add(fastConverter);
    }
}

在这里插入图片描述

静态资源访问

在SpringMVC中,对于静态资源都需要手动配置静态资源过滤。SpringBoot中对此也提供自动化配置,可以简化静态资源过滤配置。

默认配置

在WebMvcAutoConfiguration类中有一个静态内部类WebMvcAutoConfigurationAdapter实现了
WebMvcConfigurer接口。WebMvcConfigurer接口中有一个个方法addRsourceHandlers,
是用来配置静态资源过滤的。方法在WebMvcAutoConfigurationAdapter中得到了实现。
在这里插入图片描述
其中默认添加了下面几个文件路径:
在这里插入图片描述
在registration.addResourceLocations(resource)中添加了"/“路径
在这里插入图片描述
综上可以得到,SpringBoot过滤所有的静态资源,而静态资源的位置一共有5个,分别是"classpath:/META-INF/resources”、“classpath: /resources/”、“classpath: /static/”、“classpath :/public/”、“/”。也就是可以将静态资源放到这5个位置中的任意一个,其中按照定义的顺序优先级依次降低。一般情况下SpringBoot项目不需要webapp目录,所以第5个"/"可以暂不考虑。

自定义配置

如果默认静态资源过滤策略不能满足需求 ,也可以自定义静态资源过滤策略。

  1. 在配置文件中配置
spring:
  mvc:
    static-path-pattern: /static/**

过滤规则为/static/**,静态资源位置为classpath:/static/

  1. 代码定义
    只需要实WebMvcConfigurer接口即可,然后实现接口中的addResourceHandlers方法,代码如下:
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    /**
     * 静态资源过滤配置
     *
     * @param registry
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
    }
}

异常处理

在SpringBoot默认情况下,如果用户在发起请求时发生了错误,SpringBoot 会有一些默认的页面展示给用户,如下所示:
在这里插入图片描述

Controller异常处理

  1. @RestControllerAdvice或者最常见的使用场景:当我们的controller抛出一个Exception,此时可以通过@RestControllerAdvice结合@ExceptionHandler定义全局异常捕获机制,然后返回给客户端一个页面或者json描述。缺点:只能捕获请求进入了controller的异常,再之前的异常和一些Filter的异常无法捕获
@Data
public class WebResponse <T> implements Serializable {
    private int code;
    private String type;
    private String message;
    private T data;
}

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public WebResponse exceptionHandler(Exception e) {
        WebResponse webResponse = new WebResponse();
        webResponse.setCode(-1);
        webResponse.setType("error");
        webResponse.setMessage(e.getMessage());
        return webResponse;
    }
}

在这里插入图片描述

  1. 在SpringMVC中HandlerExceptionResolver接口负责统一异常处理,这里使用其实现类AbstractHandlerExceptionResolver来实现。
@Configuration
public class WebHandlerExceptionResolver extends AbstractHandlerExceptionResolver {

    @Override
    protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        ModelAndView mv = new ModelAndView(new MappingJackson2JsonView());
        WebResponse webResponse = new WebResponse();
        webResponse.setCode(-1);
        webResponse.setType("error");
        webResponse.setMessage(ex.getMessage());
        mv.addAllObjects(webResponse.toMap());
        return mv;
    }
}

public class WebBeanConfig {

    /**
     * 异常处理类配置
     * @return
     */
    @Bean
    public WebHandlerExceptionResolver webHandlerExceptionResolver() {
        return new WebHandlerExceptionResolver();
    }
}

在这里插入图片描述

非Controller抛出的异常

上述两种方法一般用来处理应用级别的异常,有一些不是controller抛出的异常就处理不了,例如Filter中抛出异常等。因此,SpringBoot提供BasicErrorControll作为默认的ErrorController,我们如果需要更灵活地对Error视图和数据进行处理,那么只需要提供自己的ErrorController即可。

@RestController
public class WebErrorController extends BasicErrorController {

    public WebErrorController(ErrorAttributes errorAttributes) {
        super(errorAttributes, new ErrorProperties());
    }

    @Override
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        ModelAndView mv = new ModelAndView(new MappingJackson2JsonView());
        Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
        WebResponse webResponse = new WebResponse();
        webResponse.setCode(-1);
        webResponse.setType("error");
        webResponse.setMessage((String) body.get("message"));
        mv.addAllObjects(webResponse.toMap());
        mv.setStatus(getStatus(request));
        return mv;
    }

    @Override
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
        WebResponse webResponse = new WebResponse();
        webResponse.setCode(-1);
        webResponse.setType("error");
        webResponse.setMessage((String) body.get("message"));
        return new ResponseEntity<Map<String, Object>>(webResponse.toMap(), getStatus(request));
    }

    @Override
    protected ErrorAttributeOptions getErrorAttributeOptions(HttpServletRequest request, MediaType mediaType) {
        ErrorAttributeOptions options = ErrorAttributeOptions.of(ErrorAttributeOptions.Include.EXCEPTION,
                ErrorAttributeOptions.Include.STACK_TRACE, ErrorAttributeOptions.Include.MESSAGE,
                ErrorAttributeOptions.Include.BINDING_ERRORS);
        if (getErrorProperties().isIncludeException()) {
            options = options.including(ErrorAttributeOptions.Include.EXCEPTION);
        }
        if (isIncludeStackTrace(request, mediaType)) {
            options = options.including(ErrorAttributeOptions.Include.STACK_TRACE);
        }
        if (isIncludeMessage(request, mediaType)) {
            options = options.including(ErrorAttributeOptions.Include.MESSAGE);
        }
        if (isIncludeBindingErrors(request, mediaType)) {
            options = options.including(ErrorAttributeOptions.Include.BINDING_ERRORS);
        }
        return options;
    }
}

@WebFilter("/*")
public class TestFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) {
        System.out.println("init");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("doFilter");
        throw new RuntimeException("我是Filter抛出的异常,测试BasicErrorController的作用");
        // filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {

    }

}

在这里插入图片描述

CORS支持

CORS (Cross-Origin Resource Sharing)是由W3C制定的一种跨域资源共享技术标准,其目的
就是为了解决前端的跨域请求,一般我们都是使用代理来解决跨域问题,一般不需要在代码中配置。

@CrossOrigin

@PostMapping("/queryAll")
@CrossOrigin(value = "http://localhost:8080", maxAge = 1800, allowedHeaders = "*")
public List<Table> queryAll() {
  return tableDao.queryAll(new Table());
}
  • value:表示支持的域,这里表示来自localhost:8080域的请求是支持跨域的
  • maxAge:请求的有效期
  • allowedHeaders:允许的请求头,*表示所有的请求头都被允许

代码配置

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    /**
     * 跨域配置
     *
     * @param registry
     */
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/web/**")
                .allowedHeaders("*")
                .allowedMethods("*")
                .maxAge(1800)
                .allowedOrigins("http://localhost:8080");
    }

}

注册拦截器

public class InterceptorTest implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("InterceptorTest-------preHandle");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("InterceptorTest-------postHandle");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
                                Exception ex) throws Exception {
        System.out.println("InterceptorTest-------afterCompletion");
    }
}

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    /**
     * 拦截器配置
     *
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new InterceptorTest())
                .addPathPatterns("/**")
                .excludePathPatterns("/query");
    }
}

拦截器中的方法将按 preHandle→Controller→postHandle→afterCompletion的顺序执行。注意,只有 preHandle方法返回true时后面的方法才会执行。当拦截器链内存在多个拦截器时,postHandler在拦截器链内的所有拦截器返回成功时才会调用,而afterCompletion只有preHandle返回true才调用,但若拦截器链内的第一个拦截器preHandle方法返回false后面的方法都不会执行。
在这里插入图片描述

启动系统任务

有一些特殊的任务需要在系统启动时执行,例如配置文件加载、数据库初始化等操作。如果没有使用SpringBoot,这些问题可以在Listener中解决。SpringBoot对此提供了两种解决方案:CommandLineRunner和ApplicationRunner。CommandLineRunner和ApplicationRunner基本一致
差别主要体现在参数上。

CommandlineRunner

@Component
@Order(1)
public class WebCommandlineRunner1 implements CommandLineRunner {

    @Override
    public void run(String... args) throws Exception {
        System.out.println("我是Runner1,参数为" + Arrays.toString(args));
    }
}

@Component
@Order(1)
public class WebCommandlineRunner2 implements CommandLineRunner {

    @Override
    public void run(String... args) throws Exception {
        System.out.println("我是Runner2,参数为" + Arrays.toString(args));
    }
}
  • Order:用来描述CommandLineRunner的执行顺序,数字越小越先执行
  • run方法:是调用的核心逻辑,参数是系统启动时传入的参数,即入口类main方法的参数
    ![在这里插入图片描述](https://img-blog.csdnimg.cn/9b83b072e490409fa3814c262c595e6d.png

ApplicationRunner

@Component
@Order(1)
public class WebApplicationRunner1 implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("我是WebApplicationRunner1,参数为" + args.getNonOptionArgs());
    }
}

@Component
@Order(2)
public class WebApplicationRunner2 implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("我是WebApplicationRunner2,参数为" + args.getNonOptionArgs());
    }
}
  • Order:执行顺序 ,数字越小越优先执行
  • 这里run方法的参数是ApplicationArguments对象,如果想从ApplicationArguments对象中获取入口类中 main方法接收的参数,调 ApplicationArguments中的getNonOptionArgs方法即可。ApplicationArguments中的getOptionNam方法用来获取项目启动命令行中参数key,getOptionNames方法获取到的是name,getOptionValues则是获取相应的value。
    在这里插入图片描述

Servlet、Filter、Listener配置

@WebServlet("/testServlet")
public class TestServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("Servlet-------测试servlet的使用");
    }
}

@WebFilter("/*")
public class TestFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("Filterr-------测试Filter的使用");
//        throw new RuntimeException("我是Filter抛出的异常,测试BasicErrorController的作用");
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {

    }
}

@WebListener
public class TestListener implements ServletRequestListener {
    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        System.out.println("Listener-------requestDestroyed");
    }

    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        System.out.println("Listener-------requestInitialized");
    }
}

在项目入口类上添加@ServletComponentScan注解,实现对 Servlet、Filter以及Listener的扫描
在这里插入图片描述

REST

REST(Representational State Transfer)是Web软件架构风格,它是一种风格,而不是标准,匹配或兼容这种架构风格的网络服务称为REST服务。REST服务简洁并且有层次,REST通常基于 HTTP、URI和XML以及HTML这些现有的广泛流行的协议和标准。在 REST 中,资源是URI 来指定的,对资源的增删改查操作可以通过HTTP协议提供的GET、POST、PUT、DELETE等方法实现。使用REST可以更高效地利用缓存来提高响应速度,同时REST中的通信会话状态由客户端来维护,这可以让不同的服务器 处理一系列请求中的不同请求,进而提高服务器的扩展性。前后端分离项目中,一个设计良好的Web软件架构必然要满足REST风格。SpringMVC框架中,开发者可以通过@RestController注解开发一个RESTful 服务。

标识资源

将资源名称放到 也中,如果资源有层级关系,则放入层级关系:

http://localhost:8080/web/api/table

确定HTTP Method

在REST中,HTTP Method常常对应以下含义:

  • POST:代表增加资源;
  • PUT:代表更改资源,客户端提供需完整的资源属性;
  • GET:代表查询资源;
  • PATCH:更新资源,客户端提供仅需要更改的资源属性;
  • DELETE:通常用于删除资源;
  • HEAD:类似GET,但仅仅只有 HTTP 头信息,头信息包含了需要查找的信息;
  • OPTIONS:用于获取URI所支持的方法,响应信息会在HTTP头中包含一个个名为Allow
    的头,值是所支持的方法,如GET、POST;

确定HTTP Status

服务器向客户端返回HTPP Status以表示操作是否成功,常用的如下:

  • 200:0K,用户请求成功,如查询数据成功返回;
  • 400:错误的的请求,URI匹配上SpringBoot中的Controller,但方法参
    数匹配错误,就会抛出错误;
  • 404:NOT Found,用户发出的请求针对的资源不存在;
  • 405:用来访问本页面的HTTP Method不被允许,比如通过HTTP GET方式访问了一个@PostMapping Controller 方法;
  • 406:表示无法使用请求的内容特性来响应请求的资源,比如在SpringBoot中,请求后缀以html结尾,但同时请求的HTTP头中又包含了Accept:application/json;
  • 500:服务器内部错误,无法完成请求,通常是 Controller抛出的异常。

SpringBoot集成REST

依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

  <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

代码实现

server:
  servlet:
    context-path: /rest
  port: 8080

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
  jpa:
    hibernate:
      ddl-auto: none
    database: mysql
    show-sql: true
  mvc:
    hiddenmethod:
      filter:
        enabled: true

@Data
@Entity
public class Student {
    //主键
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    //名称
    private String name;
    //时间
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date time;
}

@RepositoryRestResource
public interface StudentRepository extends JpaRepository<Student, Integer> {

}

在这里插入图片描述

自定义路径

@RepositoryRestResource(path = "teachers", collectionResourceRel = "t", itemResourceRel = "teacher")
public interface StudentRepository extends JpaRepository<Student, Integer> {

}

@RepositoryRestResource注解:

  • path:表示将所有请求路径中的students都修改为teachers;
  • collectionResourceRel:表示将返回的JSON集合中students集合的key修改为t
  • itemResourceRel:表示将返回JSON集合中的单个student的key修改为teacher

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值