java guide spring boot 常用注解 答疑

2.5. @Configuration
一般用来声明配置类,可以使用 @Component注解替代,不过使用@Configuration注解声明配置类更加语义化。
————————@Configuration可以完全用@Component替代吗

虽然@Configuration和@Component在Spring中都可以用来声明一个类为Spring容器的一个bean,但是它们之间有一些关键的区别,特别是在处理bean定义和配置类时。

@Configuration vs@Component

 1.@Configuration

- **语义化**:@Configuration明确表示这个类是一个配置类,用于定义bean和配置Spring容器。
- **全局单例**:@Configuration类中的bean方法会被CGLIB代理,以确保这些方法返回的bean是单例的,即使在同一个配置类中多次调用这些方法,也会返回同一个bean实例。这称为**Full Mode**。
- **配置增强**:@Configuration可以与其他Spring配置注解(如@PropertySource、@Import等)一起使用,以更好地组织和管理Spring应用程序的配置。

 2.@Component

- **通用性**:@Component是一个通用注解,用于标记一个类为Spring管理的bean。它没有特定的语义,可以用于任何需要被Spring管理的类。
- **局部单例**:如果将@Configuration替换为@Component,方法返回的bean将不会被CGLIB代理,这意味着在同一个类中多次调用这些方法时,每次都会返回一个新的bean实例。这称为**Lite Mode**。

 示例对比

 使用@Configuration

java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    public MyService myService() {
        return new MyService();
    }

    @Bean
    public MyController myController() {
        return new MyController(myService());
    }
}


在这个例子中,myService()方法会被CGLIB代理,确保在myController()方法中调用时返回同一个MyService实例。

 使用@Component

java
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

@Component
public class AppConfig {

    @Bean
    public MyService myService() {
        return new MyService();
    }

    @Bean
    public MyController myController() {
        return new MyController(myService());
    }
}


在这个例子中,myService()方法不会被CGLIB代理,因此在myController()方法中调用时,每次都会返回一个新的MyService实例。


================
一定一定不要忘记在类上加上 @Validated 注解了,这个参数可以告诉 Spring 去校验方法参数。
-------就用@valid就行了吧,不一定非要同时在类上加@Validated吧

@Valid和@Validated在Spring中都有用于校验的方法参数或类字段的功能,但它们在使用场景和功能上有一些区别。

@Valid vs@Validated

@Valid

- **标准注解**:@Valid是Java Bean Validation(JSR-380)的标准注解,用于触发标准的Bean Validation机制。
- **嵌套校验**:@Valid可以触发嵌套校验,即如果一个对象包含另一个对象,@Valid可以触发对嵌套对象的校验。
- **常用于方法参数**:通常用于方法参数校验和类字段校验。

@Validated

- **Spring特定注解**:@Validated是Spring特定的注解,提供了分组校验的功能。
- **分组校验**:@Validated可以指定校验组,从而对不同场景下的校验规则进行分组和管理。
- **类级别和方法级别**:可以在类级别和方法级别使用,特别是在AOP切面中进行参数校验时非常有用。

 示例对比

@Valid 示例

用于方法参数的校验:

java
import javax.validation.Valid;
import javax.validation.constraints.NotNull;

public class MyController {

    public void createUser(@Valid @NotNull User user) {
        // 方法实现
    }
}


用于嵌套对象的校验:

java
import javax.validation.Valid;
import javax.validation.constraints.NotNull;

public class User {
    @NotNull
    private String name;

    @Valid
    private Address address;
}

public class Address {
    @NotNull
    private String street;
}


@Validated 示例

用于分组校验:

java
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.NotNull;

@Validated
public class MyService {

    public void createUser(@Validated(CreateGroup.class) User user) {
        // 方法实现
    }
}

public class User {
    @NotNull(groups = CreateGroup.class)
    private String name;
}

public interface CreateGroup {}


用于类级别的校验:

java
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.NotNull;

@Validated
public class MyService {

    public void createUser(@NotNull User user) {
        // 方法实现
    }
}


 总结

- **@Valid**@Valid:适用于标准的Bean Validation,常用于方法参数和嵌套对象的校验。如果只是需要基本的校验功能,@Valid通常就足够了。
@Validated:适用于需要分组校验或在类级别进行校验的场景。如果您的校验逻辑需要分组管理或者需要在类级别进行全局的校验,使用@Validated会更合适


===========
分组校验啥意思

分组校验是Java Bean Validation(JSR-380)中的一个功能,允许您根据不同的场景对同一个对象进行不同的校验。通过分组校验,您可以定义不同的校验组,并在不同的场景中选择性地应用这些校验规则。这在需要对同一个对象在不同的操作(如创建、更新、删除)中进行不同的校验时非常有用。

 分组校验的基本概念

1. **校验组(Validation Group)**:一个标记接口,用于标识一组校验规则。
2. **分组注解(Group Annotation)**:在校验注解上指定校验组,以便在特定的场景中应用这些校验规则。
3. **分组校验(Group Validation)**:在方法参数或类上指定校验组,以便在调用时触发特定的校验规则。

 示例

 1. 定义校验组

首先,定义两个校验组接口:

java
public interface CreateGroup {}
public interface UpdateGroup {}


 2. 在Bean中使用校验组

在需要校验的Bean类中,使用校验注解并指定校验组:

java
import javax.validation.constraints.NotNull;

public class User {

    @NotNull(groups = CreateGroup.class)
    private String name;

    @NotNull(groups = UpdateGroup.class)
    private String id;

    // getters and setters
}


在这个例子中:
-name字段在CreateGroup校验组中是必需的。
-id字段在UpdateGroup校验组中是必需的。

 3. 在服务类中使用分组校验

在服务类的方法中,使用@Validated注解指定校验组:

java
import org.springframework.validation.annotation.Validated;
import javax.validation.Valid;

@Validated
public class UserService {

    public void createUser(@Validated(CreateGroup.class) User user) {
        // 创建用户的逻辑
    }

    public void updateUser(@Validated(UpdateGroup.class) User user) {
        // 更新用户的逻辑
    }
}


在这个例子中:
-createUser方法会触发CreateGroup校验组中的校验规则。
-updateUser方法会触发UpdateGroup校验组中的校验规则。

 使用场景

分组校验非常适合以下场景:

1. **

==============
设置属性 userName 对应的数据库字段名为 user_name,长度为 32,非空@Column(name = "user_name", nullable = false, length=32)
private String userName;
设置字段类型并且加默认值,这个还是挺常用的。@Column(columnDefinition = "tinyint(1) default 1")
private Boolean enabled;
------
columnDefinition 是啥

columnDefinition是JPA(Java Persistence API)中的一个属性,用于在实体类的字段上指定数据库列的详细定义。它允许您直接在实体类中定义列的SQL类型、默认值、约束等,这些定义会在生成数据库表时被应用。

columnDefinition 的用法

columnDefinition属性允许您在实体类的字段上指定自定义的SQL片段,用于定义该字段在数据库中的具体细节。以下是一些常见的用法示例:

 1. 指定列类型和默认值

例如,您希望在数据库中将enabled字段定义为TINYINT(1)类型,并设置默认值为1:

java
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;

@Entity
public class User {

    @Id
    private Long id;

    @Column(name = "user_name", nullable = false, length = 32)
    private String userName;

    @Column(columnDefinition = "TINYINT(1) DEFAULT 1")
    private Boolean enabled;

    // getters and setters
}


在这个例子中,enabled字段在数据库中将被创建为TINYINT(1)类型,并且默认值为1。

 2. 指定列类型和其他约束

例如,您希望在数据库中将age字段定义为INT类型,并且不允许为NULL:

java
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;

@Entity
public class User {

    @Id
    private Long id;

    @Column(name = "user_name", nullable = false, length = 32)
    private String userName;

    @Column(columnDefinition = "INT NOT NULL")
    private Integer age;

    // getters and setters
}


在这个例子中,age字段在数据库中将被创建为INT类型,并且不允许为NULL。

 使用注意事项

- **数据库依赖**:columnDefinition的内容是数据库特定的,这意味着它可能依赖于您使用的具体数据库管理系统(如MySQL、PostgreSQL等)。在不同的数据库中,列定义的语法可能有所不同。
- **可移植性**:由于columnDefinition直接使用SQL片段,它可能会影响应用程序的数据库可移植性。如果您计划在不同的数据库之间切换,使用columnDefinition时需要特别注意。

 总结


==============
可以使用枚举类型的字段,不过枚举字段要用@Enumerated注解修饰。public enum Gender {
    MALE("男性"),
    FEMALE("女性");

    private String value;
    Gender(String str){
        value=str;
    }
}
@Entity
@Table(name = "role")
public class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String description;
    @Enumerated(EnumType.STRING)
    private Gender gender;
    省略getter/setter......
}
数据库里面对应存储的是 MALE/FEMALE。
------
对应的数据库里面字段类型也是枚举类型吗?不推荐数据库用枚举类型吗?

在JPA中使用枚举类型字段时,数据库中的对应字段类型通常不是数据库本身的枚举类型,而是字符串或整数。这是因为JPA通过@Enumerated注解来控制如何将枚举类型映射到数据库字段。

@Enumerated 注解

@Enumerated注解用于指定枚举类型在数据库中的存储方式。它有两个可选值:

1. **EnumType.STRING**:将枚举的名称(如MALE,FEMALE)存储为字符串。
2. **EnumType.ORDINAL**:将枚举的序数(即枚举在定义时的顺序,从0开始)存储为整数。

 示例

 1. 使用EnumType.STRING

在数据库中存储枚举的名称:

java
import javax.persistence.*;

public enum Gender {
    MALE("男性"),
    FEMALE("女性");

    private String value;
    Gender(String str){
        value = str;
    }
}

@Entity
@Table(name = "role")
public class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private String description;

    @Enumerated(EnumType.STRING)
    private Gender gender;

    // Getters and setters
}


在这种情况下,数据库中的gender字段将存储为字符串,例如'MALE'或'FEMALE'。

 2. 使用EnumType.ORDINAL

在数据库中存储枚举的序数:

java
@Entity
@Table(name = "role")
public class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private String description;

    @Enumerated(EnumType.ORDINAL)
    private Gender gender;

    // Getters and setters
}


在这种情况下,数据库中的gender字段将存储为整数,例如0(对应MALE)或1(对应FEMALE)。

 数据库中的枚举类型

虽然某些数据库支持枚举类型(如PostgreSQL),但在实际应用中,使用数据库枚举类型并不是非常推荐,原因如下:

1. **可移植性**:不同数据库对枚举类型的支持和实现方式不同,使用数据库枚举类型会降低应用程序的数据库可移植性。
2. **灵活性**:数据库枚举类型在添加、删除或修改枚

=============

只要继承了 AbstractAuditBase的类都会默认加上下面四个字段。
@Data
@AllArgsConstructor
@NoArgsConstructor
@MappedSuperclass
@EntityListeners(value = AuditingEntityListener.class)
public abstract class AbstractAuditBase {

    @CreatedDate
    @Column(updatable = false)
    @JsonIgnore
    private Instant createdAt;

    @LastModifiedDate
    @JsonIgnore
    private Instant updatedAt;

    @CreatedBy
    @Column(updatable = false)
    @JsonIgnore
    private String createdBy;

    @LastModifiedBy
    @JsonIgnore
    private String updatedBy;
}

我们对应的审计功能对应地配置类可能是下面这样的(Spring Security 项目):
@Configuration
@EnableJpaAuditing
public class AuditSecurityConfiguration {
    @Bean
    AuditorAware<String> auditorAware() {
        return () -> Optional.ofNullable(SecurityContextHolder.getContext())
                .map(SecurityContext::getAuthentication)
                .filter(Authentication::isAuthenticated)
                .map(Authentication::getName);
    }
}
简单介绍一下上面涉及到的一些注解:@CreatedDate: 表示该字段为创建时间字段,在这个实体被 insert 的时候,会设置值@CreatedBy :表示该字段为创建人,在这个实体被 insert 的时候,会设置值@LastModifiedDate、@LastModifiedBy同理。@EnableJpaAuditing:开启 JPA 审计功能。

这个怎么用的,具体插入到数据库那个字段呢?

 3. 如何使用(续)

 继承AbstractAuditBase 类

任何实体类继承了AbstractAuditBase类后,都会自动拥有审计字段,并且在插入和更新时,这些字段会自动填充。

java
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class User extends AbstractAuditBase {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    // Getters and setters
}


 4. 数据库中的字段

当您继承了AbstractAuditBase类并在数据库中创建相应的表时,表结构将自动包含审计字段。以User实体为例,数据库表user可能会包含以下字段:

-id:用户ID
-name:用户名
-created_at:创建时间
-updated_at:最后修改时间
-created_by:创建人
-updated_by:最后修改人

 5. 插入和更新操作

当您对继承了AbstractAuditBase的实体类进行插入和更新操作时,Spring Data JPA会自动填充这些审计字段。例如:

 插入操作

java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    public User createUser(String name) {
        User user = new User();
        user.setName(name);
        return userRepository.save(user);
    }
}


在插入操作中,createdAt、createdBy、updatedAt和updatedBy字段会被自动填充。例如,createdAt和updatedAt会被设置为当前时间,createdBy和updatedBy会被设置为当前登录的用户名。

 更新操作

java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    public User updateUser(Long id, String name) {
        User user = userRepository.findById(id).orElseThrow(() -> new RuntimeException("User not found"));
        user.setName(name);
        return userRepository.save(user);
    }
}


在更新操作中,updatedAt和updatedBy字段会被自动更新为当前时间和当前登录的用户名,而created

==========
我必须在建表的时候添加created_at等等字段吗?还是它会自动添加字段?

在使用Spring Data JPA和Hibernate时,实体类中的字段映射到数据库表的列通常是自动处理的。这意味着,当您定义了包含审计字段的实体类并使用JPA/Hibernate进行数据库表创建时,这些字段会自动添加到相应的数据库表中。

 自动添加字段

如果您启用了自动DDL(数据定义语言)生成功能,Hibernate会根据您的实体类自动创建或更新数据库表结构,包括添加审计字段。自动DDL生成功能通常由spring.jpa.hibernate.ddl-auto属性控制。

 示例配置

在application.properties或application.yml中配置自动DDL生成:

properties
spring.jpa.hibernate.ddl-auto=update


或者在application.yml中:

yaml
spring:
  jpa:
    hibernate:
      ddl-auto: update


 自动DDL生成选项

- **create**:每次启动应用程序时,都会删除现有的表并重新创建表。
- **update**:每次启动应用程序时,都会根据实体类更新表结构(推荐用于开发环境)。
- **validate**:每次启动应用程序时,都会验证表结构是否与实体类匹配,但不会修改表。
- **none**:不执行任何DDL操作。

 示例

假设您有以下实体类:

java
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class User extends AbstractAuditBase {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    // Getters and setters
}


以及包含审计字段的基类:

java
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import com.fasterxml.jackson.annotation.JsonIgnore;
import javax.persistence.Column;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import java.time.Instant;

@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class AbstractAuditBase {

    @CreatedDate
    @Column(updatable = false)
    @JsonIgnore
    private Instant createdAt;

    @LastModifiedDate
    @JsonIgnore
    private Instant updatedAt;

    @CreatedBy
    @Column(updatable = false)
    @JsonIgnore
    private String createdBy;

    @LastModifiedBy
    @Json


======================
@Repository
public interface UserRepository extends JpaRepository<User, Integer> {

    @Modifying
    @Transactional(rollbackFor = Exception.class)
    void deleteByUserName(String userName);
}
不加@Modifying不行吗?

在Spring Data JPA中,@Modifying注解用于标识一个修改数据库状态的方法,例如INSERT、UPDATE或DELETE操作。默认情况下,Spring Data JPA的查询方法(如findBy)是只读的,不会修改数据库状态。因此,对于会修改数据库状态的方法,必须使用@Modifying注解来明确指示。

 为什么需要@Modifying

1. **标识修改操作**:@Modifying注解告诉Spring Data JPA,这个查询不是一个普通的SELECT查询,而是一个会修改数据库状态的操作。
2. **事务管理**:@Modifying注解通常与@Transactional一起使用,以确保修改操作在事务中执行,并在出现异常时进行回滚。

 示例

以下是一个使用@Modifying和@Transactional注解的示例:

java
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.transaction.annotation.Transactional;

@Repository
public interface UserRepository extends JpaRepository<User, Integer> {

    @Modifying
    @Transactional(rollbackFor = Exception.class)
    void deleteByUserName(String userName);
}


在这个示例中:

- **@Modifying**:标识deleteByUserName方法是一个修改数据库状态的方法。
- **@Transactional**:确保方法在事务中执行,并在出现异常时进行回滚。

 不加@Modifying会发生什么

如果不加@Modifying注解,Spring Data JPA会将该方法视为一个普通的查询方法(即SELECT查询)。由于DELETE操作不是一个查询操作,Spring Data JPA会抛出异常,提示该方法不是一个合法的查询方法。

 总结

- **必须加@Modifying**:对于任何会修改数据库状态的操作(如INSERT、UPDATE、DELETE),都必须加@Modifying注解。
- **事务管理**:通常与@Transactional一起使用,以确保操作在事务中执行,并在出现异常时进行回滚。

因此,在您的deleteByUserName方法中,@Modifying注解是必须的,以明确指示这是一个修改数据库状态的方法。
============
@JsonFormat一般用来格式化 json 数据。比如:@JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone="GMT")
private Date date;
------
@JsonFormat这个是jpa中的吧,不是把请求参数映射到实体类吧,那这个具体干嘛的呢


没错,@JsonFormat注解与JPA无关,它主要用于控制层的返回响应格式,具体来说是控制JSON数据的序列化和反序列化。它属于Jackson库的一部分,而Jackson是Spring Boot默认的JSON处理库。

 主要用途

- **控制JSON输出格式**:用于指定Java对象在序列化为JSON时的格式。
- **控制JSON输入格式**:用于指定JSON数据在反序列化为Java对象时的格式。

 使用场景

 1. 格式化日期和时间

在控制层返回响应时,如果需要特定的日期和时间格式,可以使用@JsonFormat:

java
import com.fasterxml.jackson.annotation.JsonFormat;
import java.util.Date;

public class Event {

    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone = "GMT")
    private Date eventDate;

    // Getters and setters
}


在控制层中:

java
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class EventController {

    @GetMapping("/event")
    public Event getEvent() {
        Event event = new Event();
        event.setEventDate(new Date());
        return event;
    }
}


当访问/event端点时,返回的JSON响应将包含格式化的日期:

json
{
    "eventDate": "2024-08-07T02:09:33.000Z"
}


 2. 格式化枚举类型

如果需要将枚举类型以字符串形式返回,可以使用@JsonFormat:

java
import com.fasterxml.jackson.annotation.JsonFormat;

public enum Status {
    ACTIVE,
    INACTIVE
}

public class User {

    private String name;

    @JsonFormat(shape = JsonFormat.Shape.STRING)
    private Status status;

    // Getters and setters
}


在控制层中:

java
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

    @GetMapping("/user")
    public User getUser() {
        User user = new User();
        user.setName("John Doe");
        user.setStatus(Status.ACTIVE);
        return user;
    }
}


当访问/user端点时,返回的JSON响应将包含枚举的字符串表示:

json
{
    "name": "John Doe",
    "status": "ACTIVE"
}

==============
你的意思@JsonFormat  不能处理URL参数、路径变量或表单数据?

是的,正如你所理解的,@JsonFormat主要用于处理JSON格式的数据,而不是URL参数、路径变量或表单数据。@DateTimeFormat则专门用于处理这些非JSON格式的数据。

 具体解释

@JsonFormat

- **主要用途**:控制Java对象在序列化(转换为JSON)和反序列化(从JSON转换)时的格式。
- **适用场景**:处理JSON请求体和响应体。
- **局限性**:不能直接用于解析URL参数、路径变量或表单数据。

@DateTimeFormat

- **主要用途**:格式化和解析Spring MVC中的日期和时间字段。
- **适用场景**:处理HTTP请求参数、路径变量和表单数据。
- **优势**:专门用于处理非JSON格式的数据。

 示例对比

 使用@JsonFormat处理JSON数据

java
import com.fasterxml.jackson.annotation.JsonFormat;
import java.util.Date;

public class Event {

    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone = "GMT")
    private Date eventDate;

    // Getters and setters
}


在控制器中处理JSON请求体:

java
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class EventController {

    @PostMapping("/event")
    public void createEvent(@RequestBody Event event) {
        // event.getEventDate() will be parsed according to the specified format
    }
}


 使用@DateTimeFormat处理URL参数

java
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate;

public class Event {
    
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate eventDate;

    // Getters and setters
}


在控制器中处理查询参数:

java
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class EventController {

    @GetMapping("/event")
    public Event getEvent(@RequestParam("date") @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate date) {
        Event event = new Event();
        event.setEventDate(date);
        return event;
    }
}


 总结

- **@JsonFormat**:适用于处理JSON请求体和

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值