JPA ExampleMatcher 类&方法介绍及使用示例

前言:

本想在网上找JPA Example的教程文章,发现仅仅只有一些对零散的对模糊查询的一些示例

所以学习了下spring的Example文档,做了以下总结及说明和简单示例,一些方法的使用自己摸索出来的(都有过测试)

肯定有不足之处,望各位大神指出

spring官方文档

Interface ExampleMatcher

示例

嵌套类(内部类)

方法

  • getIgnoredPaths()
    • 返回所有设置忽略匹配 withIgonrePaths(String… ignorePaths) 的字段集合
    • 类型:Set
  • getMatchMode()
    • 返回匹配模式
      • ANY
      • ALL
    • 类型:ExampleMatcher.MatchMode
  • getNullHandler()
    • 获取一个空处理的ExampleMatcher
    • 类型:ExampleMatcher.NullHandler

以下返回boolean

  • isAllMatching()
    • 判断匹配模式受否为matchingAll()模式
  • isAnyMatching()
    • 判断匹配模式受否为matchingAny()模式
  • isIgnoreCaseEnabled()
    • 判断是否开启了忽略大小写模式
  • isIgnoredPath(String path)
    • 对一个字段判断是否是设置了忽略匹配模式;也就是设置了 withIgonrePaths(String… ignorePaths) 的字段

以下都是返回ExampleMatcher对象(链式调用)

  • getDefaultStringMatcher()

    • 返回默认的ExampleMatcher对象
  • matching()

    • 返回一个匹配所有字段的ExampleMatcher对象;源码调用的是matchingAll()方法

    • 源码

      static ExampleMatcher matching() {
          return matchingAll();
      }
      
    • 示例

      ExampleMatcher exampleMatcher = ExampleMatcher.matching()
          .withMatcher("username", ExampleMatcher.GenericPropertyMatcher::startsWith)
          .withIgnorePaths("password");
      Example<User> of = Example.of(user, exampleMatcher);
      List<User> all = userRepository.findAll(of);
      
  • matchingAll()

    • 如上:返回一个匹配所有字段的ExampleMatcher对象;
  • matchingAny()

    • 对任意一个字段进行匹配

    • 示例:使用起来和matching()并无区别

      ExampleMatcher exampleMatcher = ExampleMatcher.matchingAny()
          .withMatcher("username", ExampleMatcher.GenericPropertyMatcher::startsWith)
          .withIgnorePaths("password");
      Example<User> of = Example.of(user, exampleMatcher);
      List<User> all = userRepository.findAll(of);
      
  • withIgnoreCase(boolean defaultIgnoreCase)

    • 返回一个默认的ExampleMatcher

      • 默认是忽略大小写的
      ExampleMatcher.matching().withIgnoreCase(true);
      
    • 源码

      public ExampleMatcher withIgnoreCase(boolean defaultIgnoreCase) {
          return new TypedExampleMatcher(nullHandler, defaultStringMatcher, propertySpecifiers, ignoredPaths,
                                         defaultIgnoreCase, mode);
      }
      
  • withIgnoreCase(String… propertyPaths)

    • 对一个或多个字段设置忽略大小写

      ExampleMatcher.matching().withIgnoreCase("username");
      
  • withIgonrePaths(String… ignorePaths)

    • 对一个或多个字段设置,则此字段不受其它匹配影响,也就是说其他任何匹配模式不对这个字段生效

    • 如:不管设置了什么匹配模式,都不会对password字段生效

      ExampleMatcher exampleMatcher = ExampleMatcher.matching()
          .withMatcher("username", ExampleMatcher.GenericPropertyMatcher::startsWith)
          .withIgnorePaths("password");
      
  • withIgnoreNullValues

    • 返回一个对被忽略字段 Null空值处理的ExampleMatcher对象
  • withIncludeNullValues()

    • 返回一个对字段 Null空值处理的ExampleMatcher对象
  • withMatcher(String propertyPath, ExampleMatcher.GenericPropertyMatcher gennericPropertyMatcher)

    • propertyPath 字段名
    • gennericPropertyMatcher 匹配规则
  • withStringMatcher(ExampleMatcher.StringMatcher defaultStringMatcher)

    • 如:返回一个匹配开始的字符串的ExampleMatcher

    • ExampleMatcher stringMatcher = ExampleMatcher.matching().withStringMatcher(ExampleMatcher.StringMatcher.STARTING);
      
  • withTransformer(String propertyPath, ExampleMatcher.PropertyValueTransformer propertyValueTransformer)

    • propertyPath:字段名
    • propertyValueTransformer:转换规则
    ExampleMatcher username = ExampleMatcher.matching().withTransformer("username", o -> Optional.of("jpa-1"));
    

    与如下效果一致

    ExampleMatcher.GenericPropertyMatcher transform = new ExampleMatcher.GenericPropertyMatcher().transform(o -> Optional.of("jpa-1"));
    ExampleMatcher.matching().withMatcher("username",transform)
    

public static class ExampleMatcher.GenericPropertyMatcher

注:返回值都是ExampleMatcher.GenericPropertyMatcher

  • contains() 模糊包含匹配;
User user = new User();
user.setUsername("test");
ExampleMatcher exampleMatcher = ExampleMatcher.matching()
    .withMatcher("username", 
//对username字段进行包含模式匹配
ExampleMatcher.GenericPropertyMatcher::contains)	
    //不对password字段进行任何处理
    .withIgnorePaths("password");
Example<User> of = Example.of(user, exampleMatcher);
List<User> all = userRepository.findAll(of);
System.out.println(all);
  • endsWith() 后缀模糊匹配

    • 以上都是用::方法引用
    • 这次用new 对象的方式来创建ExampleMatcher
    //对字段结尾模糊批匹配
    ExampleMatcher.GenericPropertyMatcher endsWith = new ExampleMatcher.GenericPropertyMatcher().endsWith();
    ExampleMatcher exampleMatcher = ExampleMatcher.matchingAny()
        .withMatcher("username", endsWith)
        .withIgnoreCase("username")
        .withIgnorePaths("password");
    

以下用法和上面都一样,只是匹配模式不同而已

  • exact() 精确匹配

  • ignoreCase(boolean ignoreCase)

    • 设置忽略大小写为 true;也就是忽略大小写
  • caseSensitive()

    • 设置忽略大小写为 false;也就是不忽略大小写
  • startsWith()

    • 对开头的字符串模糊匹配
  • storeDefaultMatching()

    • 默认匹配模式
  • regex()

    • 将字符串视为正则表达式模式进行匹配
  • of(ExampleMatcher.StringMatcher stringMatcher)

    • ExampleMatcher.StringMatcher提供了如下枚举

      • CONTAINING

        • 匹配包含的字符串
      • DEFAULT

        • 默认匹配模式
      • ENDING

        • 匹配结尾的字符串
      • EXACT

        • 匹配精确的字符串
      • REGEX

        • 将字符串视为正则表达式进行匹配
      • STARTING

        • 匹配开始的字符串
    • 示例

      ExampleMatcher exampleMatcher = ExampleMatcher.matchingAny()
          //ExampleMatcher.GenericPropertyMatcher.of(ExampleMatcher.StringMatcher.STARTING))
          .withMatcher("username",ExampleMatcher.GenericPropertyMatcher.of(ExampleMatcher.StringMatcher.STARTING))
          .withIgnoreCase("username")
          .withIgnorePaths("password");
      
    ExampleMatcher.GenericPropertyMatcher.of(ExampleMatcher.StringMatcher.STARTING)
    
  • of(ExampleMatcher.StringMatcher stringMatcher, boolean ignoreCase)

    • 和上一个一样,只是多了一个boolean来指定忽略大小写

      ExampleMatcher.GenericPropertyMatcher.of(ExampleMatcher.StringMatcher.STARTING,false))
      
  • stringMatcher(ExampleMatcher.StringMatcher stringMatcher)

    • ExampleMatcher.StringMatcher提供了如下枚举

    • CONTAINING

      • 匹配包含的字符串
    • DEFAULT

      • 默认匹配模式
    • ENDING

      • 匹配结尾的字符串
    • EXACT

      • 匹配精确的字符串
    • REGEX

      • 将字符串视为正则表达式进行匹配
    • STARTING

      • 匹配开始的字符串
    • 示例

      //通过ExampleMatcher.StringMatcher.XXX定义匹配模式
      ExampleMatcher.StringMatcher defaultMode = ExampleMatcher.StringMatcher.DEFAULT;
      
      ExampleMatcher stringMatcher = ExampleMatcher.matching().withStringMatcher(ExampleMatcher.StringMatcher.STARTING);
      ExampleMatcher exampleMatcher = ExampleMatcher.matchingAny()
                  .withMatcher("username",ExampleMatcher.GenericPropertyMatcher::startsWith)   
          		//将defaultMode传入
          		.withMatcher("username",ExampleMatcher.GenericPropertyMatcher.of(defaultMode,true))
                  .withMatcher("username", endsWith)
                  .withIgnoreCase("username")
                  .withIgnorePaths("password");
      
  • transform(ExampleMatcher.PropertyValueTransformer propertyValueTransforme)

    • 在查询之前对属性值进行转换

    • 示例:将传过来的参数test替换为jpa-1

      ExampleMatcher.GenericPropertyMatcher().transform(o -> Optional.of("jpa-1"));
      
    User user = new User();
    user.setUsername("test");
    
    ExampleMatcher.GenericPropertyMatcher transform = new ExampleMatcher.GenericPropertyMatcher().transform(o -> Optional.of("jpa-1"));
    
    ExampleMatcher exampleMatcher = ExampleMatcher.matching()
        .withMatcher("username", ExampleMatcher.GenericPropertyMatcher::startsWith)
        .withMatcher("username", transform)
        .withIgnorePaths("password");
    
    Example<User> of = Example.of(user, exampleMatcher);
    List<User> all = userRepository.findAll(of);
    
    System.out.println(all);
    
    • 打印结果

      [User(id=7, username=jpa-1, password=123)]
      

      若没有使用替换

      结果为:

      [User(id=9, username=test, password=test)]
      
    • 注意:这里Optional.of(“jpa-1”)的jpa-1必须精确完整,且写了transform后 其它模糊匹配失效

static class ExampleMatcher.GenericPropertyMatchers

只定义了如下方法

方法
  • caseSensitive()
    • 不忽略大小写
  • ignoreCase()
    • 忽略大小写
  • contains()
    • 匹配包含字符串
  • endsWith()
    • 匹配结尾字符串
  • exact()
    • 精确匹配字符串
  • reges()
    • 将字符串作为正则表达式进行匹配
  • startsWith()
    • 匹配结尾字符串
  • storeDefaultMatching()
    • 默认匹配模式

使用和功能都和ExampleMatcher.GenericPropertyMatcher类的方法一样

static interface ExampleMatcher.MatcherConfigurer<T>

  • 第一步
    写一个类实现ExampleMatcher.MatcherConfigurer<User>这个接口

    package com.live.model;
    import org.springframework.data.domain.ExampleMatcher;
    
    public class MyMatching implements ExampleMatcher.MatcherConfigurer<User> {
        @Override
        public void configureMatcher(User matcher) {
            matcher.setUsername("jpa");
        }
    }
    

    泛型为要操作的的Entity

    即:

      package com.live.model;
      
      import lombok.Data;
      
      import javax.persistence.*;
      
      @Entity
      @Table(name = "jpa_user")
      @Data
      public class User {
      
          @Id
          @GeneratedValue(strategy = GenerationType.IDENTITY)
          private Integer id;
          @Column(name = "jpa_username",length = 40)
          private String username;
          @Column(name = "jap_password")
          private String password;
      
      }
    
  • 第二步: 调用MyMatching

    User user = new User();
    user.setUsername("test");
    
    ExampleMatcher exampleMatcher = ExampleMatcher.matchingAny()
        .withMatcher("username", ExampleMatcher.GenericPropertyMatcher::startsWith)
        .withIgnorePaths("password");
    
    //调用我们自己实现MyMatching类的congifureMatcher方法,将user传入
    new MyMatching().configureMatcher(user);
    
    Example<User> of = Example.of(user,exampleMatcher);
    List<User> all = userRepository.findAll(of);
    System.out.println(all);
    
  • 结果

    • new MyMatching().configureMatcher(user)
    • 将user的username=“test"
    • 设置为了”jpa
    • 所以结果为
    [
        User(id=8, username=jpa-2, password=234),
        User(id=7, username=jpa-1, password=123),
        User(id=6, username=jpa-dead, password=123123123123), 
        User(id=5, username=jpa-result, password=12345)
    ]
    

static class ExampleMatcher.MatchMode

方法
  • static ExampleMatcher.MatchMode valueOf(String name)

    • 根据name返回匹配模式

    • 如:

      ExampleMatcher.MatchMode.valueOf("ANY")
      
  • static ExampleMatcher.MatchMode[] values()

    • 返回所有匹配模式

    • 如:

      ExampleMatcher.MatchMode[] values = ExampleMatcher.MatchMode.values();
      
    • 打印结果

      [ANY,ALL]
      

static ExampleMatcher.NoOpPropertyValueTransofrmer

方法
  • static ExampleMatcher.NoOpPropertyValueTransformer valueOf(String name)

    • 返回具有指定名称的此类型的枚举常量

    • 如:

      ExampleMatcher.NoOpPropertyValueTransformer instance = ExampleMatcher.NoOpPropertyValueTransformer.valueOf("INSTANCE");
      
  • static ExampleMatcher.NoOpPropertyValueTrasformer[] values()

    • 返回一个数组,该数组包含这个枚举类型的常量,按声明的顺序排列

      ExampleMatcher.NoOpPropertyValueTransformer[] values = ExampleMatcher.NoOpPropertyValueTransformer.values();
      
    • 打印结果:只有一个

      [INSTANCE]
      
  • Optional apply(Optional source)

    • 定义Function

      public class MyFunction implements Function<Optional<User>, User> {
          @Override
          public User apply(Optional<User> user) {
              User user1 = new User();
              user1.setUsername("apply-username");
              user1.setPassword("apply-password");
              user1.setId(200);
              return user1;
          }
      }
      
    • 通过ExampleMatcher.NoOpPropertyValueTransformer.valueOf(“INSTANCE”);获取 ExampleMatcher.NoOpPropertyValueTransformer

    • 再调用apply();进行消费

      instance.apply(Optional.ofNullable(new MyFunction().apply(Optional.of(user))));
      
      User user = new User();
      user.setUsername("test");
      
      ExampleMatcher.NoOpPropertyValueTransformer instance = ExampleMatcher.NoOpPropertyValueTransformer.valueOf("INSTANCE");
      
      Optional<Object> instance = instance.apply(Optional.ofNullable(new MyFunction().apply(Optional.of(user))));
      //通过get()将instance强转为User
      User o = (User) instance.get();
      
      System.out.println(o);
      
    • 打印结果

      User(id=200, username=apply-username, password=apply-password)
      

ExampleMatcher.NullHandler

方法
  • static ExampleMatcher.NullHandler valueOf(String name)
    • 根据参数name,返回一个NullHandler
    • name可写的有:
      • INCLUDE
      • IGNORE
  • static ExampleMatcher.NullHandler values()
    • 结果
      • [INCLUDE,IGNORE]

static class ExampleMatcher.StringMatcher

方法
  • static ExampleMatcher.StringMatcher valueOf(String name)
    • 根据参数name,返回对应的ExampleMatcher.StringMatcher
  • static ExampleMatcher.StringMatcher values()
    • 返回ExampleMatcher.StringMatcher[]数组
枚举
  • CONTAINING
    • 匹配包含的字符串
  • DEFAULT
    • 默认匹配模式
  • ENGING
    • 匹配结尾的字符串
  • EXACT
    • 精确匹配字符串
  • REGEX
    • 将字符串作为正则表达式匹配
  • STARTING
    • 匹配开头的字符串
使用
ExampleMatcher exampleMatcher = ExampleMatcher.matching()
    //这个通过ExampleMatcher.GenericPropertyMatcher方法引用startWith来模糊匹配开头的字符串
    .withMatcher("username", ExampleMatcher.GenericPropertyMatcher::startsWith)
    
    //这个通过ExampleMatcher.GenericPropertyMatcher.of()方法传入ExampleMatcher.StringMatcher.STARTING来模糊匹配开头的字符串
    .withMatcher("username", ExampleMatcher.GenericPropertyMatcher.of(ExampleMatcher.StringMatcher.STARTING))
    
    .withIgnorePaths("password");

Example<User> example = Example.of(user);

List<User> all = userRepository.findAll(example);

示例

Entity

package com.live.model;

import lombok.Data;

import javax.persistence.*;

@Entity
@Table(name = "jpa_user")
@Data
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    @Column(name = "jpa_username",length = 40)
    private String username;
    @Column(name = "jap_password")
    private String password;

}

数据库
在这里插入图片描述

根据前缀模糊查询
  • /findAllStartsWith/jpa
@GetMapping("/findAllStartsWith/{username}")
public List<User> findAllByUsername(@PathVariable(value = "username") String username) {
    User user = new User();
    user.setUsername(username);
    
    ExampleMatcher matching = ExampleMatcher.matching()
         //这个通过ExampleMatcher.GenericPropertyMatcher方法引用startWith来模糊匹配开头的字符串
    	.withMatcher("username", ExampleMatcher.GenericPropertyMatcher::startsWith)
   		 //这个通过ExampleMatcher.GenericPropertyMatcher.of()方法传入ExampleMatcher.StringMatcher.STARTING来模糊匹配开头的字符串
    	//.withMatcher("username", ExampleMatcher.GenericPropertyMatcher.of(ExampleMatcher.StringMatcher.STARTING))
        //不对password字段处理
        .withIgnorePaths("password");
    
    Example<User> example = Example.of(user, matching);
    
    return userRepository.findAll(example);
}
  • 结果

    [
        User(id=8, username=jpa-2, password=234),
        User(id=7, username=jpa-1, password=123),
        User(id=6, username=jpa-dead, password=123123123123), 
        User(id=5, username=jpa-result, password=12345)
    ]
    
根据后缀模糊查询
  • /findAllEndswith/dead

    @GetMapping("/findAllEndswith/{username}")
    public List<User> findAllByUsername(@PathVariable(value = "username") String username) {
        User user = new User();
        user.setUsername(username);
        
         //也可以将匹配模式单独写出来
        ExampleMatcher.GenericPropertyMatcher endsWith = new ExampleMatcher.GenericPropertyMatcher().endsWith();
        
        ExampleMatcher matching = ExampleMatcher.matching()
        	.withMatcher("username", endsWith)
            //不对password字段处理
            .withIgnorePaths("password");
        
        Example<User> example = Example.of(user, matching);
        
        return userRepository.findAll(example);
    }
    
  • 结果

    [
        User(id=6, username=jpa-dead, password=123123123123)
    ]
    
根据包含模式查询
  • /findAllContains/e

    @GetMapping("/findAllContains/{username}")
    public List<User> findAllByUsername(@PathVariable(value = "username") String username) {
        User user = new User();
        user.setUsername(username);
        
        ExampleMatcher matching = ExampleMatcher.matching()
             //这个通过ExampleMatcher.GenericPropertyMatcher方法引用contains来匹配包含的字符串
        	.withMatcher("username", ExampleMatcher.GenericPropertyMatcher::contains)
       		 //这个通过ExampleMatcher.GenericPropertyMatcher.of()方法传入ExampleMatcher.StringMatcher.CONTAINING来匹配保安的字符串
        	//.withMatcher("username", ExampleMatcher.GenericPropertyMatcher.of(ExampleMatcher.StringMatcher.CONTAINING))
            //不对password字段处理
            .withIgnorePaths("password");
        
        Example<User> example = Example.of(user, matching);
        
        return userRepository.findAll(example);
    }
    
  • 结果

    [
    	User(id=6, username=jpa-dead, password=123123123123),
        User(id=5, username=jpa-result, password=12345), 
        User(id=9, username=test, password=test)
    ]
    
在查询前更改属性值
  • /findOneTransform/jpa

    • 注意:Optional.of(“jpa-1”)的”jpa-1“必须精确完整,且是更改属性后,模糊查询失效
    @GetMapping("/findOneTransform/{username}")
    public List<User> findAllByUsername(@PathVariable(value = "username") String username) {
        User user = new User();
        user.setUsername(username);
        
        ExampleMatcher.GenericPropertyMatcher transform = new ExampleMatcher.GenericPropertyMatcher().transform(o -> Optional.of("jpa-1"));
        
        ExampleMatcher stringMatcher = ExampleMatcher.matching().withStringMatcher(ExampleMatcher.StringMatcher.STARTING);
        
        ExampleMatcher exampleMatcher = ExampleMatcher.matchingAny()
            .withMatcher("username", stringMatcher)
            .withMatcher("username", transform)
            //对username字段忽略大小写
            .withIgnoreCase("username")
             //不对password字段处理
            .withIgnorePaths("password");
        new MyMatching().configureMatcher(user);
    
        Example<User> of = Example.of(user, exampleMatcher);
        List<User> all = userRepository.findAll(of);
        System.out.println(all);
    }
    
  • 结果

    [
        User(id=7, username=jpa-1, password=123)
    ]
    
精确查询
  • /findOneExact/jpa-dead

    @GetMapping("/findOneExact/{username}")
    public List<User> findAllByUsername(@PathVariable(value = "username") String username) {
        User user = new User();
        user.setUsername(username);
        
        ExampleMatcher exampleMatcher = ExampleMatcher.matchingAny()
            .withMatcher("username", ExampleMatcher.GenericPropertyMatcher::exact)
            .withIgnorePaths("password");
        
        Example<User> of = Example.of(user,exampleMatcher);
        
        Optional<User> user = userRepository.findOne(of);
        System.out.println(user);
    }
    
  • 结果

    Optional[
        User(
            id=6, 
            username=jpa-dead, 
            password=123123123123
        )
    ]
    
  • 16
    点赞
  • 54
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
使用Spring BootJPA时,可以通过集成Spring Data JPASpring Cache来实现缓存功能。下面是一个简单的示例代码,展示了如何在Spring Boot项目中使用缓存: 首先,确保在pom.xml文件中添加必要的依赖项: ```xml <!-- Spring Boot Starter Data JPA --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <!-- Spring Boot Starter Cache --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <!-- Spring Boot Starter Web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> ``` 接下来,在Spring Boot的配置中启用缓存和JPA: ```java @Configuration @EnableCaching @EnableJpaRepositories("com.example.repository") // 指定JPA Repository的包路径 public class AppConfig { // 配置数据源、实体管理器等其他必要的配置 @Bean public CacheManager cacheManager() { return new ConcurrentMapCacheManager(); // 使用ConcurrentMap作为缓存管理器 } } ``` 然后,在需要进行缓存的方法上添加相应的注解,例如使用`@Cacheable`注解来启用缓存,并指定缓存名称: ```java @Service public class UserService { @Autowired private UserRepository userRepository; @Cacheable(value = "usersCache") // 指定缓存名称为"usersCache" public User getUserById(Long id) { // 从数据库中查询用户 Optional<User> userOptional = userRepository.findById(id); return userOptional.orElse(null); } } ``` 在上述示例中,`getUserById`方法使用了`@Cacheable`注解,并指定了缓存名称为"usersCache"。当方法被调用时,如果缓存中存在对应的结果,则直接返回缓存中的数据,否则从数据库中查询并将结果保存到缓存中。 需要注意的是,为了使缓存正常工作,确保在实体中适当地使用`@Cacheable`注解,以标识实体是否可被缓存。 这只是一个简单的示例,你可以根据自己的需求和业务逻辑进行更复杂的缓存配置。希望对你有所帮助!如果还有其他问题,请随时提问。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DeathAndLife

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值