SpringBoot基础笔记(上)

基于Spring 5和 Spring Boot 2
开发环境 Spring Tool Suite


Spring概览

  • Spring的核心是提供了一个容器container,通常称为Spring应用上下文(Spring application context)。用来创建和管理应用组件。
  • 这些组件称为bean,在Spring的应用上下文中装配在一起,形成完整的应用。
  • 通过一种基于依赖注入(dependency injection, DI)的模式将bean装配在一起。

过去Spring应用上下文使用一个或多个XML文件将bean装配在一起。
现在基于java的配置更常见,这样的基于java的配置类是和XML配置等价的。

@configuration
public class ServiceConfiguration{
    @Bean
    public InventoryService inventoryService(){
       return new InventoryService();
    }
    @Bean
    public ProductService productService(){
       return new ProductService(inventoryService());
    }
}

@configuration注解会告诉Spring这是一个配置类,给Spring应用上下文提供bean。


Spring Initializr

Spring Initializr是一个基于浏览器的Web应用,也是一个REST API,能够生成一个Spring项目结构的骨架。
使用Spring Initializr的方式:

  • 通过地址为https://start.spring.io/的web应用
  • 在命令行中使用curl命令
  • 在命令行中使用Spring boot命令行接口
  • 在Spring Tool Suite中创建新项目
  • 在IntelliJ IDEA中创建新项目
  • 在NetBeans中创建新项目

Spring项目结构


这是一个典型的Maven或Gradle项目结构。
应用的源代码放到了src/main/java中,测试代码在test中,非java的资源放到了src/main/resources。

另外:

  • mvnw和mvnm.cmd: 这是Maven包装器(Wrapper)脚本。
  • pom.xml: 这是Maven构建规范
  • TacoCloudApplication.java是Spring Boot主类,它会启动该项目
  • application.properties: 这个文件提供了指定配置属性的地方
  • static: 存放为浏览器提供服务的静态内容(图片、样式表、JavaScript等)
  • templates: 存放用来渲染内容到浏览器的模板文件。
  • ……test.java 就是测试类

构建规范

这个项目使用Maven来构建,pom.xml就是Maven的构建规范。

<artifactId>taco-cloud</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>
	<name>taco-cloud</name>
  • < packaging >的地方选择将应用构建成一个可执行的JAR文件,而不是WAR文件。
    传统的Java Web应用都是打包成WAR文件,打包JAR是基于云思维,对于云平台JAR更友好。
<version>2.3.2.RELEASE</version>
  • 表明要使用Spring Boot 2.3.2

引导应用

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication    //表明这是一个Spring Boot应用
public class TacoCloudApplication {

	public static void main(String[] args) {
		SpringApplication.run(TacoCloudApplication.class, args);
	}

}

@SpringBootApplication 注解表明这是一个Spring Boot应用。
@SpringBootApplication 是一个组合注解,组合了3个其他的注解

  • @SpringBootConfiguration:将该类声明为配置类
  • @EnableAutoConfiguration:启用Spring Boot的自动配置
  • @ComponentScan:启用组件扫描
SpringApplication.run(TacoCloudApplication.class, args)

SpringApplication里的run()方法,执行应用的引导过程,创建Spring的应用上下文。
两个参数:配置类、命令行参数
(传递给run()的配置类不一定要和引导类相同)


测试应用

package com.example.demo;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.junit.runner.RunWith;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)

//使用spring的运行器
@SpringBootTest

//spring boot测试
class TacoCloudApplicationTests {

	@Test
	void contextLoads() {
	}

}
@RunWith(SpringRunner.class)

@RunWith是JUnit的注解,它会提供一个测试运行器runner来指导JUnit如何运行测试。
可以想象给JUnit一个插件提供自定义的测试行为,整理提供的是SpringRunner,这是一个Spring提供的测试运行器,它会创建测试运行所需的Spring应用上下文。

@SpringBootTest

@SpringBootTest告诉JUnit在启动测试的时候要加上Spring boot的功能。



应用

创建两个代码构件
一个控制器类、一个视图模板


处理Web请求


Spring MVC

Spring自带了一个强大的Web框架:Spring MVC。
Spring MVC的核心是控制器(controller)的理念。

编写一个简单的控制器类:

package com.example.demo;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller  //控制器
public class HomeController {
	@GetMapping("/")   //处理对根路径“/”的请求
	public String home() {
		return "home";  //返回视图名
	}
}

这个类带有@controller,让组件扫描这个类识别为一个组件。
所以Spring的组件扫描功能会自动发现它,并创建一个HomeController实例作为Spring应用上下文中的bean。

    @GetMapping("/")   //处理对根路径“/”的请求
	public String home() {
		return "home";  //返回视图名
	}

home()是一个简单的控制器方法。
@GetMapping注解表明如果针对“/”发送HTTP GET请求,这个方法会处理请求。


Thymeleaf

使用Thymeleaf作为模板引擎,没有选择JSP和FreeMarker。
这些都是Java Web页面技术。
详情见补充说明。

模板名称是由逻辑视图名派生而来的,再加上“/templates/”前缀和“.html”后缀,
最终模板路径是"templates/home.html"。


定义视图

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
  <head>
    <title>Taco Cloud</title>
  </head>
  <body>
    <h1>Welcome to ...</h1>
    <img th:src="@{/images/Taco.png}" height="100" width="100"/>
  </body>      
</html>

< img >标签那里,使用了Thymeleaf的th:src属性和@{…}表达式,以便于引用相对于上下文路径的图片。


测试控制器

测试需要针对根路径"/"发送一个HTTP GET请求并期望得到成功结果。

package com.example.demo;

import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
import org.junit.*;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

@RunWith(SpringRunner.class) 
@WebMvcTest(HomeController.class)  //针对HomeController的测试
public class HomeControllerTest {
	@Autowired
	private MockMvc mockMvc; //注入MockMvc
	@Test
	public void testHomePage()throws Exception{
		mockMvc.perform(get("/"))   //发起对/的GET
		.andExpect(status().isOk())  //期望得到HTTP 200
		.andExpect(view().name("home"))  //期望得到home视图
		.andExpect(content()
		.string(containsString("Welcome to ..."))); //期望包含Welcome巴拉巴拉
	}
}

HomeControllerTest没有使用@SpringBootTest标记,而是添加了@WebMvcTest注解。这是Spring Boot所提供的一个特殊测试注解,它会让整个测试在Spring MVC应用上下文中执行。
它会将HomeController注册到Spring MVC中,这样就可以发送请求了。
@WebMvcTest同样为测试Spring MVC应用提供Spring环境的支持。

测试类被注入了一个MockMvc,能够让测试实现mockup。(模型、视觉稿?)

通过testHomePage()方法,定义了针对主页想要执行的测试。首先使用MockMvc对象对"/"(根路径)发起HTTP GET请求。

  • 响应应该具备HTTP 200(OK)状态
  • 视图的逻辑名应该是home
  • 渲染后的视图应该包含文本Welcome to ……


Spring Boot DevTools


DevTools在初始化项目的时候作为一个依赖加入进来了,在pom.xml文件里有一个对应的依赖项。

DevTools提供了一些便利的开发期工具

  • 代码变更后应用会自动重启
  • 当面向浏览器的资源,如模板、JavaScript、样式表等发生变化时,会自动刷新浏览器
  • 自动禁用模板缓存
  • 如果使用H2数据库,内置H2控制台

DevTools并不是IDE插件,并且仅仅用于开发,能够很智能地在生产环境中把自己禁用掉。


应用自动重启

如果将DevTools作为项目的一部分,对项目中的Java代码和属性文件做出修改后,这些变更稍后就能发挥作用。DevTools会监控变更。

DevTools运行的时候,应用程序会被加载到Java虚拟机两个独立的类加载器中。

其中一个类加载器会加载你的Java代码、属性文件以及项目中"src/main"路径下几乎所有的内容。这些条目很可能会经常发生变化,另外一个类加载器会加载依赖的库,这些库不太可能经常发生变化。

当检测到变更的时候,DevTools只会重新加载包含项目代码的类加载器,并重启Spring应用上下文,在这个过程中另外一个类加载器和JVM会原封不动。这个策略非常精细,但是它能减少应用启动时间。

不足之处是自动重启无法翻译依赖项的变化。变更依赖需要重新启动应用。


浏览器自动刷新和禁用模板缓存

默认情况,Thymeleaf模板方案在配置时会缓存解析的结果,在位每个请求提供服务的时候就不用重新解析。在生产环境中可以带来一定的性能收益。

但是在开发期,缓存模板会使我们看不到模板变更的效果。

所以DevTools通过禁用所有模板缓存解决了这个问题。



Web开发


展示信息


构建领域类

package com.example.demo;
import lombok.Data;
import lombok.RequiredArgsConstructor;

@Data
@RequiredArgsConstructor
public class Ingredient {
	private final String id;
	private final String name;
	private final Type type;
	//包装、蛋白质、蔬菜、奶酪、沙司
	public static enum Type{
		WRAP,PROTEIN,VEGGIES,CHEESE,SAUCE
	}
}

这个Ingredient类没有getter和setter方法,以及equals()、hashCode()、toString()等方法。
这里使用了Lombok库,@Data注解是由Lombok提供的,它会告诉Lombok生成所有缺失的方法,同时还会生成所有以final属性作为参数的构造器。

不过Lombok不是Spring库,需要自己添加依赖。
并且将Lombok作为扩展添加到IDE上。
总之有点点麻烦吧(以前觉得),,详情可以看本人以前写的博客:STS导入Lombok相关


创建控制器类

在Spring MVC框架中,控制器是最重要的参与者,主要职责是处理HTTP请求,要么将请求传递给视图以便于渲染HTML,要么直接将数据写入响应体RESTful。

package com.example.demo;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import javax.validation.Valid;

import com.example.demo.Ingredient.Type;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import lombok.extern.slf4j.*;

//SLF4J只是各种日志实现的一个接口抽象(facade)
@Slf4j
@Controller
@RequestMapping("/design")   
public class DesignTacoController {
	/**
	 * 处理GET请求视图渲染
	 */
	@GetMapping
	public String showDesignForm(Model model) {
		//Ingredient:(烹饪)原料
		//构建一个列表
		List<Ingredient> ingredients = Arrays.asList(
				new Ingredient("FLTO","Flour Tortilla",Type.WRAP), //面粉薄饼
				new Ingredient("COTO","Corn Tortilla",Type.WRAP), //玉米饼
				new Ingredient("GRBF","Ground Beef",Type.PROTEIN), //碎牛肉
				new Ingredient("CARN","Carnitas",Type.PROTEIN), //卡尼塔斯
				new Ingredient("TMTO","Diced Tomatoes",Type.VEGGIES), //西红柿丁
				new Ingredient("LETC","Lettuce",Type.VEGGIES), //生菜
				new Ingredient("CHED","Cheddar",Type.CHEESE), //切达干酪
				new Ingredient("JACK","Monterrey Jack",Type.CHEESE),  //蒙特雷杰克
				new Ingredient("SLSA","Salsa",Type.SAUCE), //萨尔萨
				new Ingredient("SRCR","Sour Cream",Type.SAUCE) //酸奶油
		);
		
		Type[] types = Ingredient.Type.values();
		for(Type type: types) {
			//向前台传送数据
			model.addAttribute(type.toString().toLowerCase(),filterByType(ingredients,type));
		}
		model.addAttribute("design",new Taco());
		//视图的逻辑名称
		return "design";
	}
	
	
	/**
	 * 处理表单提交
	  * 当表单提交的时候,表单的输入域会绑定到Taco对象的属性中
	  * 该对象会以参数的形式传递给该方法
	 */
	//指定方法要处理针对/design的POST请求
	@PostMapping
	public String processDesign(@Valid Taco design,	Errors errors) {
		// save the taco design 
		// do it in future
		if(errors.hasErrors()) {
			return "design";
		}
		
		log.info("Processing Design:" + design);
		//返回值还是代表一个要展示给用户的视图
		//redirect:表明这是一个重定向视图,完成之后用户的浏览器会重定向到/orders/current
		/**
		 * 这里的想法是创建好Taco,用户将被重定向到一个订单页面
		 * 在这里用户可以创建一个订单
		 * 目前还没有处理这个请求的控制器
		 */
		return "redirect:/orders/current";
	}

	
	/**
	 * 过滤List对象
	 */
	private List<Ingredient> filterByType (List<Ingredient> ingredients, Type type){
		//.stream().filter() 过滤List对象
		//x -> x.getType().equals(type) 满足这个条件的
		//Collectors.toList()用来结束Stream流
		return ingredients.stream().filter(x -> x.getType().equals(type)).collect(Collectors.toList());
	}
}

@Slf4j 是Lombok所提供的注解,在运行时,它会在这个类中自动生成一个SLF4J(Simple Logging Facade for Java)Logger。这个简单的注解也可以显式声明:

private static final org.slf4j.Logger log =
org.slf4j.LoggerFactory.getLogger(DesignTacoController.class);

设计视图

Spring提供了多种定义视图的方法,包括JavaServer Pages(JSP),Thymeleaf,FreeMarker,Mustache和基于Groovy的模板。

这里使用Thymeleaf,需要添加依赖到pom.xml文件中。

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

在运行时,Spring Boot的自动配置功能会发现Thymeleaf在类路径中,因此会为Spring MVC创建支撑Thymeleaf视图的bean。

像Thymeleaf这样的视图库在设计的时候是与特定的Web框架解耦的。
这样的话,它们无法感知Spring的模型抽象,因此无法与控制器放到Model中的数据协同工作。
但是它们可以与Servlet的request属性协作。所以,在Spring将请求转移到视图之前,会把模型数据复制到request属性中,这样Thymeleaf和其他的视图模板方案就能访问到。

Thymeleaf模板就是增加一些额外元素属性的HTML,这些属性能够指导模板如何渲染request数据。
这个建议去翻专门讲Thymeleaf的手册……

<!DOCTYPE html>
<!-- xmlns 属性可以在文档中定义一个或多个可供选择的命名空间 -->
<!-- xmlns:th="http://www.thymeleaf.org" 在文档的开头处定义具有前缀的命名空间,后面可以直接用-->
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
      <head>
      <title>Taco Cloud</title>
      <!-- 链接一个外部样式表 -->
      <link rel="stylesheet" th:href="@{/styles.css}"/>
      </head>
      <body>
      	<h1>Design your taco!</h1>
      	<img th:src="@{/images/TacoYummy.jpg}"/>
      	<form method="POST" th:object="${design}" th:modelAttribute="design">
      	<div class="grid">
		    <div class="ingredient-group" id="wraps">
      		<h3>Designate your wrap:</h3>
      		<!-- 在每次迭代时,配料元素都会绑定到ingredient变量上 -->
      		<div th:each="ingredient : ${wrap}">
      		    <!-- 复选框元素 -->
      			<input name="ingredients" type="checkbox" th:value="${ingredient.id}"/>
      			<!-- th:text是Thymeleaf命名空间中的属性 -->
      			<!-- ${}会告诉它要使用某个请求属性 也就是ingredient.name的值 -->
      			<!-- 为复选框提供标签 -->
      			<span th:text="${ingredient.name}">INGREDIENT</span><br/>
      		</div>
      		</div>
      		……
      	<h3>Name your taco creation:</h3>
      	<input type="text" th:field="*{name}"/>
      	<br/><br/>
      	<button>Submit your taco</button>
      	</div>
      	</form>
      </body>
</html>

视图中的<form>标签,method属性被设置成了POST,form并没有声明action属性,当表单提交的时候,浏览器会收集表单中的的所有数据。并以HTTP POST请求的形式将其发送至服务器端。

在POST请求的接收端,需要有一个控制器方法处理针对/design的POST请求。

package com.example.demo;

import javax.validation.Valid;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import lombok.extern.slf4j.Slf4j;

//使用Lombok的slf4j注释在允许期创建一个SLF4J Logger对象
//我们将会使用这个Logger记录所提交订单的详细信息
@Slf4j
@Controller
@RequestMapping("/orders")
public class OrderController {
	//类级别RequestMapping和方法级别的GetMapping结合使用
	//指定方法处理针对"/orders/current"的HTTP GET请求
	@GetMapping("/current")
	public String orderForm(Model model) {
		model.addAttribute("order", new Order());
		return "orderForm";
	}
	
	@PostMapping
	public String processOrder(@Valid Order order,Errors errors) {
		if(errors.hasErrors()) {
			return "orderForm";
		}
		log.info("Order submitted:"+order);
		return "redirect:/";
	}
}


校验表单输入

Spring支持Java的Bean校验API(Bean Validation API, 也被称为JSR-303)。

package com.example.demo;

import java.util.List;

//SpringBoot 新版本没有自动导入validation 程序包  需要加依赖
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import lombok.Data;

@Data
public class Taco {
	//design.html 中的checkbox元素 名字都是ingredients,还有一个name的文本输入元素
	//表单中这些输入域直接对应Taco类的属性
	@NotNull
	@Size(min=5, message="Name must be at least 5 characters long")
	private String name;
	@Size(min=1, message="You must choose at least 1 ingredient")
	private List<String> ingredients;
	
	//将对象持久化到数据库的时候,通常最好有一个字段作为对象的唯一标识
	private long id;
}

package com.example.demo;

import javax.validation.Valid;
import javax.validation.constraints.Digits;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;

import org.hibernate.validator.constraints.CreditCardNumber;

import lombok.Data;

@Valid
@Data
public class Order {
	@NotBlank(message="Name is required")
	private String name;
	@NotBlank(message="Street is required")
	private String street;
	@NotBlank(message="City is required")
	private String city;
	@NotBlank(message="State is required")
	private String state;
	@NotBlank(message="Zip is required")
	private String zip;
	@CreditCardNumber(message="Not a valid credit card number")
	private String ccNumber;
	@Pattern(regexp="^(0[1-9]|1[0-2])([\\/])([1-9][0-9])$",
			message="Must be formatted MM/YY")
	private String ccExpiration;
	@Digits(integer=3, fraction=0, message="Invalid CVV")
	private String ccCVV;
	
	private long id;
}

除了上面的,还要给控制器方法加@Valid注解

此外Thymeleaf提供了便捷访问Error对象的方法,借助fields及其th:errors属性。举例来说,为了展现信用卡字段的校验错误,可以添加一个<span>元素,该元素会将对错误的引用用到订单表单模板上,详情见上面的html代码。

<label for="ccNumber">Credit Card#:</label>
<input type="text" th:field="*{ccNumber}"/>
<span class="validationError"
	  th:if="${#fields.hasError('ccNumber')}"
	  th:errors="*{ccNumber}">CC num Error</span>

<span>元素使用class属性来为错误添加样式,使用th:if属性来决定是否要显示该元素。
fields属性的hasError()方法会检查ccNumber域是否存在错误,如果存在,将会渲染<span>
th:errors属性引用了ccNumber输入域,如果该输入域存在错误,那么它会将<span>元素的占位符内容替换为校验信息。


视图控制器

如果一个控制器非常简单,不需要填充模型或者处理输入,还有一种方式可以定义控制器

package com.example.demo;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer{
	@Override
	//registry:登记
	/**用这个注册一个或多个视图控制器*/
	public void addViewControllers(ViewControllerRegistry registry) {
		//将"/"传递进去,视图控制器将会针对该路径执行GET请求
		//这个方法会返回ViewControllerRegistration对象
		//基于这个对象调用setViewName方法
		//用它指明当请求“/”时要转发到home视图上
		registry.addViewController("/").setViewName("home");
	}
}

WebMvcConfigurer接口定义了多个方法来配置Spring MVC。尽管只是一个接口,但是提供了所有方法的默认实现,只需要覆盖所需的方法即可。



补充说明


REST API

REST指一组架构约束条件和原则,满足约束条件和原则的应用程序设计。

架构,软件体系结构分为三部分:构建,用于描述计算机;连接器,用于描述构建的链接部分;配置将构建和连接器组成有机整体。

web基本技术:
URI(统一资源标示符)HTTP(超文本传输协议)(post、get、put、delete)

Hypertext。
1、每个资源都应该有唯一的一个标识
2、使用标准的方法更改资源的状态
3、request和response的自描述
4、资源多重表述
5、无状态服务


关于Maven

  1. Maven于java有点像pip于python。

  2. Maven是Apache下的一个开源项目,用于项目的构建。使用Maven可以对项目的依赖包进行管理,它也支持构建脚本的继承。

  3. Maven本身的插件机制很灵活,可以配置各种Maven插件来完成各种事情。如果觉得插件不够用,还可以编写自己的Maven插件。Maven为使用者提供了一个统一的依赖仓库,各种开源项目的发布包都可以在上面找到。

  4. 在Maven的众多特性中,最重要的是对依赖包的管理。Maven将项目所使用的依赖包的信息放到pom.xml的dependencies节点。只需在pom.xml中配置该模块的依赖信息,Maven会自动将模块引入到环境变量中。


JUnit

JUnit是一个java测试框架,允许定义单元测试来验证方法和类的行为。


Thymeleaf

Thymeleaf是一个现代化的服务器端Java模板引擎,适用于Web和独立环境,能够处理HTML、XML、JavaScript、CSS甚至纯文本。

Thymeleaf的主要目标是提供一种优雅和高度可维护的创建模板的方法。
它建立在自然模板的概念上,将其逻辑注入到模板文件中,其方式不会影响模板被用作设计原型。


测试的时候端口被占用


Servlet

处理请求和发送响应的过程是由一种叫做Servlet的程序来完成的,Servlet是为了解决实现动态页面而衍生的东西
Tomcat是Web应用服务器,是一个Servlet/JSP容器。
Tomcat作为Servlet容器,负责处理客户请求,把请求传送给Servlet,并将Servlet的响应传送回给客户。
Servlet是一种运行在支持Java语言的服务器上的组件。
Servlet最常见的用途是扩展Java Web服务器功能,提供非常安全的,可移植的,易于使用的CGI替代品。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值