Jackson注解详解

随着fastjson的壮大,漏洞也越来越多,是时候考虑下Jackson了,下面对Jackson近30个注解逐个解释。

引入依赖

    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.11.2</version>
    </dependency>

序列化注解

@JsonAnyGetter

允许将map的字段变成标准字段.

看一个bean

public class Bean01 {
    public String name;
    private Map<String, String> properties;

    public Bean01(String name, Map<String, String> properties) {
        this.name = name;
        this.properties = properties;
    }

    @JsonAnyGetter(enabled = false)
    public Map<String, String> getProperties() {
        return properties;
    }
}

再看看测试:

@Test
public void testBean01() throws JsonProcessingException {
    final Bean01 bean01 = new Bean01("jimo", new HashMap<>());
    bean01.getProperties().put("k1", "v1");
    bean01.getProperties().put("k2", "v2");

    final String result1 = new ObjectMapper().writeValueAsString(bean01);
    System.out.println(result1);
    assertThat(result1, containsString("k1"));
    assertThat(result1, containsString("v1"));
}

当enabled=false时,结果如下:

{
  "name": "jimo",
  "properties": {
    "k1": "v1",
    "k2": "v2"
  }
}

当enabled=true时,结果如下:

{
  "name": "jimo",
  "k1": "v1",
  "k2": "v2"
}

可以看到,map被拉平了。

@JsonGetter

public class Bean02 {
    public int id;
    private String name;

    public Bean02(int id, String name) {
        this.id = id;
        this.name = name;
    }

    // @JsonGetter("name")
    public String getTheName() {
        return name;
    }
}

看测试

@Test
public void testBean02() throws JsonProcessingException {
    final Bean02 b = new Bean02(1, "jimo");

    final String result = new ObjectMapper().writeValueAsString(b);
    System.out.println(result);
    assertThat(result, containsString("jimo"));
    assertThat(result, containsString("name"));
    assertThat(result, containsString("1"));
}

如果没有 @JsonGetter 的注解,得到的json是:

{
  "id": 1,
  "theName": "jimo"
}

加上后才是name:

{
  "id": 1,
  "name": "jimo"
}

@JsonPropertyOrder

就是指定生成的json字符串中属性的顺序,默认是按照声明顺序。

@JsonPropertyOrder(value = {"name", "id"})
public class Bean03 {

    public int id;
    public String name;

    public Bean03(int id, String name) {
        this.id = id;
        this.name = name;
    }
}

测试:

@Test
public void testBean03() throws JsonProcessingException {
    final Bean03 b = new Bean03(1, "jimo");

    final String s = new ObjectMapper().writeValueAsString(b);
    assertEquals("{\"name\":\"jimo\",\"id\":1}", s);
}

也支持按字母序排列: @JsonPropertyOrder(value = {"name", "id"}, alphabetic = true)

@JsonRawValue

直接将字符串里的json写成json格式.

public class Bean04 {
    public String name;

    @JsonRawValue
    public String json;

    public Bean04(String name, String json) {
        this.name = name;
        this.json = json;
    }
}

测试:

@Test
public void testBean04() throws JsonProcessingException {
    final Bean04 b = new Bean04("jimo", "{\"attr\":false}");

    final String s = new ObjectMapper().writeValueAsString(b);
    assertEquals("{\"name\":\"jimo\",\"json\":{\"attr\":false}}", s);
}

结果:

{
  "name": "jimo",
  "json": {
    "attr": false
  }
}

JsonValue

public enum Bean05 {
    USER1(1, "JIMO"), USER2(2, "HEHE");

    private int id;
    private String name;

    Bean05(int id, String name) {
        this.id = id;
        this.name = name;
    }

    // @JsonValue
    public String getName() {
        return name;
    }
}

看上面的枚举类,我们序列化时,其结果: “USER1”

@Test
public void testBean05() throws JsonProcessingException {
    final String s = new ObjectMapper().writeValueAsString(Bean05.USER1);
    // s== "USER1"
}

但是我们想只用name这个字段来代表枚举类,于是加上 @JsonValue.

得到: assertEquals("\"JIMO\"", s);

@JsonRootName

@JsonRootName(value = "user")
public class Bean06 {
    public int id;
    public String name;

    public Bean06(int id, String name) {
        this.id = id;
        this.name = name;
    }
}

测试:需要启用 WRAP_ROOT_VALUE

@Test
public void testBean06() throws JsonProcessingException {
    final Bean06 b = new Bean06(1, "jimo");

    final String s = new ObjectMapper().enable(SerializationFeature.WRAP_ROOT_VALUE).writeValueAsString(b);
    assertEquals("{\"user\":{\"id\":1,\"name\":\"jimo\"}}", s);
}

可以看到user作为了根节点:

{"user":{"id":1,"name":"jimo"}}

@JsonSerialize

自定义序列化。

public class Bean07 {

    public String name;

    @JsonSerialize(using = MyLocalDateSerializer.class)
    public LocalDate date;

    public Bean07(String name, LocalDate date) {
        this.name = name;
        this.date = date;
    }
}
public class MyLocalDateSerializer extends StdSerializer<LocalDate> {

    private static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");

    public MyLocalDateSerializer() {
        this(null);
    }

    protected MyLocalDateSerializer(Class<LocalDate> t) {
        super(t);
    }

    @Override
    public void serialize(LocalDate localDate, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        jsonGenerator.writeString(formatter.format(localDate));
    }
}

测试:

@Test
public void testBean07() throws JsonProcessingException {
    final Bean07 b = new Bean07("jimo", LocalDate.now());
    final String s = new ObjectMapper().writeValueAsString(b);
    System.out.println(s);
}

在没有自定义序列化时:

{"name":"jimo","date":{"year":2020,"month":"SEPTEMBER","dayOfMonth":6,"monthValue":9,"chronology":{"id":"ISO","calendarType":"iso8601"},"dayOfWeek":"SUNDAY","dayOfYear":250,"era":"CE","leapYear":true}}

定义了之后:

{"name":"jimo","date":"2020-09-06"}

反序列化注解

@JsonCreator

用在构造方法或工厂方法,调节反序列化的过程。

比如,下面将 theName属性映射到 name 属性上:

public class Bean08 {
    public int id;
    public String name;

    @JsonCreator
    public Bean08(@JsonProperty("id") int id,
                  @JsonProperty("theName") String name) {
        this.id = id;
        this.name = name;
    }
}

测试:

@Test
public void testBean08() throws JsonProcessingException {
    String json = "{\"id\":1,\"theName\":\"jimo\"}";

    final Bean08 b = new ObjectMapper().readValue(json, Bean08.class);
    assertEquals("jimo", b.name);
}

JacksonInject

表示属性的值不从json里读,而是由注解指定。

public class Bean09 {
    @JacksonInject
    public int id;
    public String name;
}

测试:

@Test
public void testBean09() throws IOException {
    String json = "{\"name\":\"jimo\"}";

    final InjectableValues.Std inject = new InjectableValues.Std().addValue(int.class, 1);
    final Bean09 b = new ObjectMapper().reader(inject).readValue(json, Bean09.class);

    assertEquals(1, b.id);
    assertEquals("jimo", b.name);
}

JsonAnySetter

和JsonAnyGetter相对应,将json里的k-v映射回map:

public class Bean10 {
    public String name;
    private Map<String, String> properties;

    public Bean10() {
        this.properties = new HashMap<>();
    }

    @JsonAnySetter
    public void add(String key, String value) {
        properties.put(key, value);
    }

    public Map<String, String> getProperties() {
        return properties;
    }
}

测试:

@Test
public void testBean10() throws JsonProcessingException {
    String json = "{\"name\":\"jimo\",\"attr2\":\"val2\",\"attr1\":\"val1\"}";

    final Bean10 b = new ObjectMapper().readerFor(Bean10.class).readValue(json);

    assertEquals("jimo", b.name);
    assertEquals("val1", b.getProperties().get("attr1"));
    assertEquals("val2", b.getProperties().get("attr2"));
}

JsonSetter

将任意方法变成一个setter方法

public class Bean11 {
    public int id;
    private String name;

    @JsonSetter("name")
    public void setTheName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

测试:

@Test
public void testBean11() throws JsonProcessingException {
    String json = "{\"id\":1,\"name\":\"jimo\"}";

    final Bean11 b = new ObjectMapper().readValue(json, Bean11.class);

    assertEquals("jimo", b.getName());
}

JsonDeserialize

自定义反序列化

public class Bean12 {
    public String name;

    @JsonDeserialize(using = MyLocalDateDeserializer.class)
    public LocalDate date;
}
public class MyLocalDateDeserializer extends StdDeserializer<LocalDate> {

    public static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");

    public MyLocalDateDeserializer() {
        this(null);
    }

    protected MyLocalDateDeserializer(Class<?> vc) {
        super(vc);
    }

    @Override
    public LocalDate deserialize(JsonParser p, DeserializationContext ctx) throws IOException,
            JsonProcessingException {
        return LocalDate.parse(p.getText(), formatter);
    }
}

测试:

@Test
public void testBean12() throws JsonProcessingException {
    String json = "{\"name\":\"jimo\",\"date\":\"2020-09-06\"}";

    final Bean12 b = new ObjectMapper().readValue(json, Bean12.class);

    assertEquals("2020-09-06", b.date.format(MyLocalDateDeserializer.formatter));
}

JsonAlias

别名

public class Bean13 {
    @JsonAlias({"fName", "f_name"})
    private String firstName;
    private String lastName;

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }
}

测试:


@Test
public void testBean13() throws JsonProcessingException {
    String json = "{\"fName\":\"jimo\",\"lastName\":\"hehe\"}";

    final Bean13 b = new ObjectMapper().readValue(json, Bean13.class);
    assertEquals("jimo", b.getFirstName());
}

属性相关注解

@JsonIgnoreProperties

忽略某些注解, 用在类级别

@JsonIgnoreProperties({"id"})
public class Bean14 {
    public int id;
    public String name;

    public Bean14(int id, String name) {
        this.id = id;
        this.name = name;
    }
}

测试:

@Test
public void testBean14() throws JsonProcessingException {
    final Bean14 b = new Bean14(1, "jimo");

    final String s = new ObjectMapper().writeValueAsString(b);

    assertThat(s, containsString("jimo"));
    assertThat(s, not(containsString("id")));
}

JsonIgnore

也是忽略属性字段,用在字段级别

public class Bean15 {
    @JsonIgnore
    public int id;
    public String name;
}

JsonIgnoreType

忽略某种类型的值。

public class Bean16 {
    public int id;
    public Name name;

    public Bean16(int id, Name name) {
        this.id = id;
        this.name = name;
    }

    @JsonIgnoreType
    public static class Name {
        public String firstName;
        public String lastName;

        public Name(String firstName, String lastName) {
            this.firstName = firstName;
            this.lastName = lastName;
        }
    }
}

测试:

@Test
public void testBean16() throws JsonProcessingException {
    final Bean16.Name name = new Bean16.Name("jimo", "hehe");
    final Bean16 b = new Bean16(1, name);

    final String s = new ObjectMapper().writeValueAsString(b);
    System.out.println(s); // {"id":1}
}

JsonInclude

过滤某些字段,比如不为null的:

@JsonInclude(JsonInclude.Include.NON_NULL)
public class Bean17 {
    public int id;
    public String name;

    public Bean17(int id, String name) {
        this.id = id;
        this.name = name;
    }
}

测试:

@Test
public void testBean17() throws JsonProcessingException {
    final Bean17 b = new Bean17(1, null);

    final String s = new ObjectMapper().writeValueAsString(b);
    System.out.println(s); // {"id":1}
}

JsonAutoDetect

自动检测,也可以加一些规则

@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.PROTECTED_AND_PUBLIC)
public class Bean18 {
    public int id;
    protected String name;
    private int age;

    public Bean18(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
}

测试:

@Test
public void testBean18() throws JsonProcessingException {
    final Bean18 b = new Bean18(1, "jimo", 18);

    final String s = new ObjectMapper().writeValueAsString(b);
    System.out.println(s); // {"id":1,"name":"jimo"}
}

类型处理注解

  • @JsonTypeInfo: 声明序列化时要包含的类型信息
  • @JsonSubTypes: 声明注解的字类型
  • @JsonTypeName: 给被注解的类定义一个逻辑类型名称

看例子:

一个动物园里有动物,动物的子类有狗和猫:

public class ZooRaw {

    public Animal animal;

    public ZooRaw() {
    }

    public ZooRaw(Animal animal) {
        this.animal = animal;
    }

    public static class Animal {
        public String name;

        public Animal(String name) {
            this.name = name;
        }

        public Animal() {
        }
    }

    public static class Dog extends Animal {
        public double barkVolume;

        public Dog(String name) {
            super(name);
        }
    }

    public static class Cat extends Animal {
        boolean likeCream;
        public int lives;

        public Cat(String name, int lives) {
            super(name);
            this.lives = lives;
        }

        public Cat() {
        }
    }
}

现在我们序列化动物园:

@Test
public void testZooEmpty() throws JsonProcessingException {
    final ZooRaw.Dog dog = new ZooRaw.Dog("jimo");
    final ZooRaw zoo = new ZooRaw(dog);

    final String s = new ObjectMapper().writeValueAsString(zoo);
    System.out.println(s); // {"animal":{"name":"jimo","barkVolume":0.0}}
}

没问题,但是当我们反序列化时,得到的就不是dog的实例了:

@Test
public void testCatEmptyDeserialize() throws JsonProcessingException {
    String json = "{\"animal\":{\"name\":\"lily\"}}";

    final ZooRaw zoo = new ObjectMapper().readValue(json, ZooRaw.class);
    assertEquals("lily", zoo.animal.name);
    assertEquals(ZooRaw.Animal.class, zoo.animal.getClass());
}

如何才能得到呢?使用上面的注解:

public class Zoo {

    public Animal animal;

    public Zoo() {
    }

    public Zoo(Animal animal) {
        this.animal = animal;
    }

    @JsonTypeInfo(
            use = JsonTypeInfo.Id.NAME,
            include = JsonTypeInfo.As.PROPERTY,
            property = "type"
    )
    @JsonSubTypes({
            @JsonSubTypes.Type(value = Dog.class, name = "dog"),
            @JsonSubTypes.Type(value = Cat.class, name = "cat")
    })
    public static class Animal {
        public String name;

        public Animal(String name) {
            this.name = name;
        }

        public Animal() {
        }
    }

    @JsonTypeName("dog")
    public static class Dog extends Animal {
        public double barkVolume;

        public Dog(String name) {
            super(name);
        }
    }

    @JsonTypeName("cat")
    public static class Cat extends Animal {
        boolean likeCream;
        public int lives;

        public Cat(String name, int lives) {
            super(name);
            this.lives = lives;
        }

        public Cat() {
        }
    }
}

使用type字段区分类型,name区分同类型的不同类:

@Test
public void testZoo() throws JsonProcessingException {
    final Zoo.Dog dog = new Zoo.Dog("jimo");
    final Zoo zoo = new Zoo(dog);

    final String s = new ObjectMapper().writeValueAsString(zoo);
    System.out.println(s);
    // {"animal":{"type":"dog","name":"jimo","barkVolume":0.0}}
}

可以看到多了 type 字段。

反序列化也ok:

@Test
public void testCatDeserialize() throws JsonProcessingException {
    String json = "{\"animal\":{\"name\":\"lily\",\"type\":\"cat\"}}";

    final Zoo zoo = new ObjectMapper().readValue(json, Zoo.class);
    assertEquals("lily", zoo.animal.name);
    assertEquals(Zoo.Cat.class, zoo.animal.getClass());
}

通用注解

@JsonProperty

标注属性的

public class Bean19 {
    public int id;
    public String name;

    public Bean19() {
    }

    public Bean19(int id, String name) {
        this.id = id;
        this.name = name;
    }

    @JsonProperty("name")
    public void setTheName(String name) {
        this.name = name;
    }

    @JsonProperty("name")
    public String getTheName() {
        return name;
    }
}

测试:

@Test
public void testBean19() throws JsonProcessingException {
    final Bean19 b = new Bean19(1, "jimo");

    final String s = new ObjectMapper().writeValueAsString(b);
    System.out.println(s);
    // {"id":1,"name":"jimo"}

    final Bean19 bb = new ObjectMapper().readValue(s, Bean19.class);
    assertEquals("jimo", bb.getTheName());
}

@JsonFormat

public class Bean20 {
    public String name;

    @JsonFormat(
            shape = JsonFormat.Shape.STRING,
            pattern = "yyyy-MM-dd hh:mm:ss"
    )
    public Date date;

    public Bean20(String name, Date date) {
        this.name = name;
        this.date = date;
    }

    public Bean20() {
    }
}

测试:

@Test
public void testBean20() throws JsonProcessingException {
    final Bean20 b1 = new Bean20("jimo", new Date());

    final String s = new ObjectMapper().writeValueAsString(b1);
    System.out.println(s);
    // {"name":"jimo","date":"2020-09-08 01:16:55"}

    final Bean20 b2 = new ObjectMapper().readValue(s, Bean20.class);
    System.out.println(b2.date); // Tue Sep 08 09:16:55 CST 2020
}

@JsonUnwrapped

解构

public class Bean21 {
    public int id;

    @JsonUnwrapped
    public Name name;

    public Bean21(int id, Name name) {
        this.id = id;
        this.name = name;
    }

    public static class Name {
        public String firstName;
        public String lastName;

        public Name(String firstName, String lastName) {
            this.firstName = firstName;
            this.lastName = lastName;
        }

        public Name() {
        }
    }
}

测试:

@Test
public void testBean21() throws JsonProcessingException {
    final Bean21.Name name = new Bean21.Name("jimo", "hehe");
    final Bean21 b = new Bean21(1, name);

    final String s = new ObjectMapper().writeValueAsString(b);
    System.out.println(s);
    // {"id":1,"firstName":"jimo","lastName":"hehe"}
}

@JsonView

就像数据库的视图一样,序列化和反序列化时指定哪些被view标记的序列化:

public class Bean22 {
    @JsonView(Public.class)
    public int id;
    @JsonView(Public.class)
    public String firstName;
    @JsonView({Internal.class})
    public String lastName;

    public Bean22(int id, String firstName, String lastName) {
        this.id = id;
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public class Public {
    }

    public class Internal extends Public {
    }
}

测试:只序列化 Public 的:

@Test
public void testBean22() throws JsonProcessingException {
    final Bean22 b = new Bean22(1, "jimo", "hehe");

    final String s = new ObjectMapper()
            .writerWithView(Bean22.Public.class)
            .writeValueAsString(b);
    System.out.println(s); // {"id":1,"firstName":"jimo"}
}

@JsonManagedReference,@JsonBackReference

处理父子关系循环,特别是递归情况下的序列化。

有一个类A:引用了B

public class A {

    public int id;
    public B b;

    public A(int id, B b) {
        this.id = id;
        this.b = b;
    }
}

类B:引用了A

public class B {
    public int id;
    public String name;

    public List<A> items;

    public B(int id, String name) {
        this.id = id;
        this.name = name;
        items = new ArrayList<>();
    }
}

我们来测试:

@Test
public void testRef() throws JsonProcessingException {
    final B b = new B(2, "jimo");
    final A a = new A(1, b);
    b.items.add(a);

    final String s = new ObjectMapper().writeValueAsString(a);
    System.out.println(s);
}

得到一个栈溢出错误:

Caused by: java.lang.StackOverflowError

现在修改:

A.java

public class A {

    public int id;
    @JsonManagedReference
    public B b;

    public A(int id, B b) {
        this.id = id;
        this.b = b;
    }
}

B.java

public class B {
    public int id;
    public String name;

    @JsonBackReference
    public List<A> items;

    public B(int id, String name) {
        this.id = id;
        this.name = name;
        items = new ArrayList<>();
    }
}

结果为:

{"id":1,"b":{"id":2,"name":"jimo"}}

看起来结果里缺失了 items 字段,怎么解决看下面:

@JsonIdentityInfo

使用ID来标识对象。

A

@JsonIdentityInfo(
        generator = ObjectIdGenerators.PropertyGenerator.class,
        property = "id"
)
public class A {

    public int id;
    //    @JsonManagedReference
    public B b;

    public A(int id, B b) {
        this.id = id;
        this.b = b;
    }
}

B

@JsonIdentityInfo(
        generator = ObjectIdGenerators.PropertyGenerator.class,
        property = "id"
)
public class B {
    public int id;
    public String name;

    //    @JsonBackReference
    public List<A> items;

    public B(int id, String name) {
        this.id = id;
        this.name = name;
        items = new ArrayList<>();
    }
}

结果:

{"id":1,"b":{"id":2,"name":"jimo","items":[1]}}

@JsonFilter

在序列化时指定过滤器

@JsonFilter("myFilter")
public class Bean23 {
    public int id;
    public String name;

    public Bean23(int id, String name) {
        this.id = id;
        this.name = name;
    }
}

测试

@Test
public void testBean23() throws JsonProcessingException {
    final Bean23 b = new Bean23(1, "jimo");

    final SimpleFilterProvider filters = new SimpleFilterProvider().addFilter("myFilter",
            SimpleBeanPropertyFilter.filterOutAllExcept("name"));

    final String s = new ObjectMapper().writer(filters).writeValueAsString(b);
    assertEquals("{\"name\":\"jimo\"}", s);
}

自定义注解

@JacksonAnnotationInside

注解:排列列的顺序,忽略为null的

@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonPropertyOrder({"name", "id", "date"})
public @interface CustomOrderAnnotation {
}

定义bean

@CustomOrderAnnotation
public class Bean24 {
    public int id;
    public String name;
    public Date date;

    public Bean24(int id, String name, Date date) {
        this.id = id;
        this.name = name;
        this.date = date;
    }
}

测试:

@Test
public void testBean24() throws JsonProcessingException {
    final Bean24 b = new Bean24(1, "jimo", null);

    final String s = new ObjectMapper().writeValueAsString(b);
    assertEquals("{\"name\":\"jimo\",\"id\":1}", s);
}

混入

使用addMixIn(target,source) 方法进行混入:

public class Bean25 {
    public int id;
    public String name;
    public A a;

    public Bean25(int id, String name, A a) {
        this.id = id;
        this.name = name;
        this.a = a;
    }

    public class A {
    }

    @JsonIgnoreType
    public class B {
    }
}

测试:混入B类上的注解到A类后,A就被忽略了

@Test
public void testBean25() throws JsonProcessingException {
    final Bean25 b1 = new Bean25(1, "jimo", null);

    final String s = new ObjectMapper().writeValueAsString(b1);
    assertEquals("{\"id\":1,\"name\":\"jimo\",\"a\":null}", s);

    final String s1 = new ObjectMapper().addMixIn(Bean25.A.class, Bean25.B.class).writeValueAsString(b1);
    assertEquals("{\"id\":1,\"name\":\"jimo\"}", s1);
}

禁用注解

某些情况下想要禁止注解生效

@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonPropertyOrder({"name", "id"})
public class Bean26 {
    public int id;
    public String name;

    public Bean26(int id, String name) {
        this.id = id;
        this.name = name;
    }
}

测试:

@Test
public void testBean26() throws JsonProcessingException {
    final Bean26 b = new Bean26(1, null);

    final String s = new ObjectMapper().disable(MapperFeature.USE_ANNOTATIONS).writeValueAsString(b);
    assertEquals("{\"id\":1,\"name\":null}", s);
}
  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值