mybatisPlus

MybatisPlus

快速入门

入门案例
1.引入MybatisPlus的起步依赖

MybatisPlus官方提供了starter,其中集成了Mybatis和MybatisPlus的所有功能,并且实现了自动装配的效果。因此我们可以用MybatisPlus的starter代替Mybatis的starter:

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.3.1</version>
        </dependency>
2.定义Mapper

自定义的Mapper继承MybatisPlus提供的BaseMapper接口:

public interface UserMapper extends BaseMapper<User> {

}
常见注解

MybatisPlus通过扫描实体类,并基于反射获取实体类信息作为数据库表信息。

  • 类名驼峰转下划线作为表名
  • 名为id的字段作为主键
  • 变量名驼峰下划线作为表的字段名

MybatisPlus中的几个比较常用的注解如下:

@TableName:用来指定表名
@TableId:用来指定表中的主键字段信息
@TableFieId:用来指定表中的普通字段信息

IdType枚举:

  • AUTO:数据库自增长

  • INPUT:通过set方法自行输入

  • ASSIGN_ID:分配 ID,接口IdentifierGenerator的方法nextId来生成id,默认实现类为DefaultIdentifierGenerator雪花算法

使用@TableField的常见场景:

  • 成员变量名与数据库字段名不一致

  • 成员变量名以is开头,且是布尔值

  • 成员变量名与数据库关键字冲突

  • 成员变量不是数据库字段

@TableName("tb_user")
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    //@TableField(exist = false)
    private String username;
    private String password;
    private String phone;
    private String info;
    private Integer status;
    private Integer balance;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
}
常见配置

MybatisPlus的配置继承了Mybatis原生配置和一些自己特有的配置。例如

mybatis-plus:
  type-aliases-package: com.itheima.mp.domain.po # 别名扫描包
  mapper-locations: "classpath*:/mapper/**/*.xml" # Mapper.xml文件地址,默认值
  configuration:
    map-underscore-to-camel-case: true # 是否开启下划线和驼峰的映
    cache-enabled: false # 是否开启二级缓存
  global-config:
    db-config:
      id-type: assign_id # id为雪花算法生
      update-strategy: not_null # 更新策略:只更新非空字段

具体可参考官方文档使用配置 | MyBatis-Plus (baomidou.com)

核心功能

条件构造器
基于QueryWrapper的查询

需求:

  • 查询出名字中带o的,存款大于等于1000元的人的id、username、info、balance字段
SELECT id,username,info,balance FROM user WHERE username LIKE ? AND balance >= ?
  • 更新用户名为jack的用户的余额为2000
UPDATE user     SET balance = 2000     WHERE (username = "jack")
基于UpdateWrapper的更新
  • 需求:更新id为1,2,4的用户的余额,扣200

    UPDATE user     SET balance = balance - 200     WHERE id in (1, 2, 4)
    
条件构造器的用法:
  • QueryWrapper和LambdaQueryWrapper通常用来构建select、delete、update的where条件部分

  • UpdateWrapper和LambdaUpdateWrapper通常只有在set语句比较特殊才使用

  • 尽量使用LambdaQueryWrapper和LambdaUpdateWrapper,避免硬编码

自定义sql

我们可以利用MyBatisPlus的Wrapper来构建复杂的Where条件,然后自己定义SQL语句中剩下的部分。

  • 需求:将id在指定范围的用户(例如1、2、4 )的余额扣减指定值

    1. 基于Wrapper构建where条件

      List<Long> ids = List.of(1L, 2L, 4L);
      int amount = 200;// 1.构建条件
      LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>().in(User::getId, ids);// 2.自定义SQL方法调用 
      userMapper.updateBalanceByIds(wrapper, amount);
      
    2. 在mapper方法参数中用Param注解声明wrapper变量名称,必须是ew

      void updateBalanceByIds(@Param("ew") LambdaQueryWrapper<User> wrapper, @Param("amount") int amount);
      
    3. 自定义SQL,并使用Wrapper条件

      <update id="updateBalanceByIds">    
      UPDATE tb_user SET balance = balance - #{amount} ${ew.customSqlSegment}
      </update>
      
Service接口
MP的Service接口使用流程是怎样的?
  • 自定义Service接口继承IService接口

    public interface IUserService extends IService<User> {
    }
    
  • 自定义Service实现类,实现自定义接口并继承ServiceImpl类

    @Service
    public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
    }
    
案例:基于Restful风格实现下列接口
@RequestMapping("/users")
@RestController
@RequiredArgsConstructor
public class UserController {

    //@Autowired   不推荐  springboot  推荐用构造函数注入   ,
    private final IUserService userService;
/*  用构造函数注入太繁琐了   加final  用@RequiredArgsConstructor 注解
    public UserController(IUserService iUserService) {
        this.iUserService = iUserService;
    }*/

    @PostMapping
    @ApiOperation("新增用户接口")
    public void saveUser(@RequestBody UserFormDTO userFormDTO){
        //1.把DDTO 拷贝到  PO
        User user = BeanUtil.copyProperties(userFormDTO,User.class);
        //2。新增
        userService.save(user);
    }

    @DeleteMapping("/{id}")
    @ApiOperation("删除用户接口")
    public void removeUser(@ApiParam("用户id")  @PathVariable("id") Long id){
        userService.removeById(id);
    }

    @GetMapping("/{id}")
    @ApiOperation("根据id查询用户接口")
    public UserVO queryUserById(@ApiParam("用户id")  @PathVariable("id") Long id){
        User user = userService.getById(id);

        return BeanUtil.copyProperties(user, UserVO.class);
    }

    @GetMapping
    @ApiOperation("根据id批量查询用户接口")
    public List<UserVO> queryUserByIds(@ApiParam("用户id集合")  @RequestParam("ids") List<Long> ids){
        List<User> users = userService.listByIds(ids);
        return BeanUtil.copyToList(users, UserVO.class);
    }
}
自定义service

controller

    @ApiOperation("扣减用户余额接口")
    @PutMapping("/{id}/deduction/{money}")
    public void deductMoneyById(@ApiParam("用户id")@PathVariable Long id, @ApiParam("扣减的余额")@PathVariable Integer money){
        userService.deductBalance(id,money);
    }

service

    void deductBalance(Long id, Integer money);

serviceImpl

    @Override
    public void deductBalance(Long id, Integer money) {
        //1.查询用户
        User user = getById(id);
        //2.校验用户状态
        if (user == null || user.getStatus() == 2){
            throw new RuntimeException("用户状态异常");
        }
        //3.校验余额是否充足
        if (user.getBalance() < money){
            throw new RuntimeException("用户余额不足");
        }
        //4.扣减余额 update tb-user set balance = balance - ?
        baseMapper.deductBalance(id,money);
    }

mapper

    @Update("update tb_user set balance = balance - #{money}  where id = #{id}")
    void deductBalance(@Param("id") Long id, @Param("money") Integer money);
案例:IService的Lambda查询

需求:实现一个根据复杂条件查询用户的接口,查询条件如下:

  • name:用户名关键字,可以为空

  • status:用户状态,可以为空

  • minBalance:最小余额,可以为空

  • maxBalance:最大余额,可以为空

mybatis 实现:

<select id="queryUsers" resultType="com.itheima.mp.domain.po.User">    
    SELECT *
    FROM tb_user    
    <where>        
        <if test="name != null">            
            AND username LIKE #{name}        
        </if>        
        <if test="status != null">            
            AND `status` = #{status}        
        </if>        
        <if test="minBalance != null and maxBalance != null">            
            AND balance BETWEEN #{minBalance} AND #{maxBalance}        
        </if>    
    </where>
</select>

mybatisPlus实现:

    @GetMapping("/list")
    @ApiOperation("根据复杂条件批量查询用户接口")
    public List<UserVO> queryUsers(UserQuery userQuery){
        //1.查询用户po
        List<User> users = userService.queryUsers(userQuery.getName(),userQuery.getStatus(),userQuery.getMinBalance(),userQuery.getMaxBalance());
        //2.po 拷贝到vo
        return BeanUtil.copyToList(users, UserVO.class);
    }
List<User> queryUsers(String name, Integer status, Integer minBalance, Integer maxBalance);
    @Override
    public List<User> queryUsers(String name, Integer status, Integer minBalance, Integer maxBalance) {
        return lambdaQuery() // 复杂条件查询
                .like(name!= null,User::getUsername,name)
                .eq(status!= null,User::getStatus,status)
                .ge(minBalance!= null,User::getBalance,minBalance)
                .le(maxBalance!= null,User::getBalance,maxBalance)
                .list();
    }
案例:IService的Lambda更新

需求:改造根据id修改用户余额的接口,要求如下

  • 完成对用户状态校验

  • 完成对用户余额校验

  • 如果扣减后余额为0,则将用户status修改为冻结状态(2)

        int remainBalance = user.getBalance() - money;
//        baseMapper.deductBalance(id,money);
        lambdaUpdate() // 复杂条件跟新
                .set(User::getBalance, remainBalance)
                .set(remainBalance == 0, User::getStatus, 2)
                .eq(User::getId, id)  //构建
                .eq(User::getBalance ,user.getBalance())// 乐观锁
                .update(); // 执行
案例:IService批量新增
  • 普通for循环插入

        @Test
        void testSaveOneByOne() {
            Long b = System.currentTimeMillis();
            for (int i = 0; i < 100000; i++) {
                iUserService.save(buildUser(i));
            }
            Long e = System.currentTimeMillis();
            System.out.println("耗时:" + (e - b));
        }
    

    耗时11万毫秒

  • IService的批量插入

        @Test
        void testSaveBatch() {
            //我们准备每次批量插入1000条,插入100次 即100000条数据
            //1.准备一个容量为1000的集合
            List<User> list = new ArrayList<>(1000);
            Long b = System.currentTimeMillis();
            for (int i = 0; i < 100000; i++) {
                //添加一个user
                list.add(buildUser(i));
                if (i % 1000 == 0) {
                    //每1000条插入一次
                    iUserService.saveBatch(list);
                    //清空
                    list.clear();
                }
            }
            Long e = System.currentTimeMillis();
            System.out.println("耗时:" + (e - b));
        }
    

    耗时5万毫秒

  • 开启rewriteBatchedStatements=true参数

    耗时5000毫秒

    批处理方案:

    普通for循环逐条插入速度极差,不推荐

    • MP的批量新增,基于预编译的批处理,性能不错

    • 配置jdbc参数,开rewriteBatchedStatements,性能最好

扩展功能

代码生成
  1. 通过MybatisPlus 插件

  2. 配置插件数据库连接 ,注意加上表名( 报错:url加上 ?serverTimezone=UTC)

  3. 选择生成配置

静态工具
案例:静态工具查询
  • 改造根据id查询用户的接口,查询用户的同时,查询出用户对应的所有地址

  • 实现根据用户id查询收货地址功能,需要验证用户状态,冻结用户抛出异常(练习)

    @Override
    public UserVO queryUserAndAdressById(Long id) {
        //1,查询用户
        User user = getById(id);
        if(user == null || user.getStatus() == 2){
            throw new RuntimeException("用户状态异常");
        }
        //2.查询地址
        List<Address> addresses = Db.lambdaQuery(Address.class)
                .eq(Address::getUserId, id)
                .list();
        //3.封装Vo
        UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);
        if (CollUtil.isNotEmpty(addresses)){
            userVO.setAddresses(BeanUtil.copyToList(addresses, AddressVO.class));
        }
        return userVO;
    }

避免了循环依赖

  • 改造根据id批量查询用户的接口,查询用户的同时,查询出用户对应的所有地址
    @Override
    public List<UserVO> queryUserAndAddressByIds(List<Long> ids) {
        // 1.查询用户
        List<User> users = listByIds(ids);
        if (CollUtil.isEmpty(users)) {
            return Collections.emptyList();
        }
        // 2.查询地址
        // 2.1.获取用户id集合
        List<Long> userIds = users.stream().map(User::getId).collect(Collectors.toList());
        // 2.2.根据用户id查询地址
        List<Address> addresses = Db.lambdaQuery(Address.class).in(Address::getUserId, userIds).list();
        // 2.3.转换地址VO
        List<AddressVO> addressVOList = BeanUtil.copyToList(addresses, AddressVO.class);
        // 2.4.用户地址集合分组处理,相同用户的放入一个集合(组)中
        Map<Long, List<AddressVO>> addressMap = new HashMap<>(0);
        if (CollUtil.isNotEmpty(addressVOList)) {
            addressMap = addressVOList.stream().collect(Collectors.groupingBy(AddressVO::getUserId));
        }

        // 3.转换VO返回
        List<UserVO> list = new ArrayList<>(users.size());
        for (User user : users) {
            // 3.1.转换User的PO为VO
            UserVO vo = BeanUtil.copyProperties(user, UserVO.class);
            list.add(vo);
            // 3.2.转换地址VO
            vo.setAddresses(addressMap.get(user.getId()));
        }
        return list;
    }

强大的steam流

逻辑删除

逻辑删除就是基于代码逻辑模拟删除效果,但并不会真正删除数据。思路如下:

  • 在表中添加一个字段标记数据是否被删除

  • 当删除数据时把标记置为1

  • 查询时只查询标记为0的数据

例如逻辑删除字段为deleted:

  • 删除操作:
UPDATE user SET deleted = 1 WHERE id = 1 AND deleted = 0
  • 查询操作:

    SELECT * FROM user WHERE deleted = 0
    

MybatisPlus提供了逻辑删除功能,无需改变方法调用的方式,而是在底层帮我们自动修改CRUD的语句。我们要做的就是在application.yaml文件中配置逻辑删除的字段名称和值即可:

mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: deleted # 全局逻辑删除的实体字段名,字段类型可以是boolean、integer
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

逻辑删除本身也有自己的问题,比如:

  • 会导致数据库表垃圾数据越来越多,影响查询效率

  • SQL中全都需要对逻辑删除字段做判断,影响查询效率

因此,不太推荐采用逻辑删除功能,如果数据不能删除,可以采用把数据迁移到其它表的办法。

枚举处理器
  • 配置统一的枚举类型处理器,实现类型转换
mybatis-plus:
  configuration:
    default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler

使用枚举类代替代码中的 1,2 ,3,4

@Getter
public enum UserStatus {
    NORMAL(1,"正常"),
    FROZEN(2,"冻结"),
    ;
    @EnumValue  //通过这个注解表明存入数据库的值  实现枚举类型的变量与数据库字段的转换
    @JsonValue  // 通过这个注解表明返回前端的字段
    private final int value;
    private final String desc;

    UserStatus(int value, String desc) {
        this.value = value;
        this.desc = desc;
    }
}
JSON处理器

当数据库中有一个json类型的字段(—> 转对象)

  • 先定义一个对象实体

    @Data
    @NoArgsConstructor
    @AllArgsConstructor(staticName = "of")
    public class UserInfo {
        private Integer age;
        private String intro;
        private String gender;
    }
    
    @Data
    @TableName(value = "tb_user",autoResultMap = true) // 开启自动结果映射
    public class User {
        @TableField(typeHandler = JacksonTypeHandler.class) // 指定类型处理器
        private UserInfo info;
    }
    

插件功能

分页插件

首先,要在配置类中注册MyBatisPlus的核心插件,同时添加分页插件:

@Configuration
public class MybatisConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 1.创建分页插件
        PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
        paginationInnerInterceptor.setMaxLimit(1000L);
        // 2.添加分页插件
        interceptor.addInnerInterceptor(paginationInnerInterceptor);
        return interceptor;
    }
}

接着,就可以使用分页的API了:

    @Test
    void testPageQuery(){
        //1.准备分页条件
        int pageNo = 1;
        int pageSize = 2;
        //1.1分页条件
        Page<User> page = Page.of(pageNo,pageSize);
        //1.2排序条件
        page.addOrder(new OrderItem("balance",true)); // 先按照金额排序  金额相同再按照id排序
        page.addOrder(new OrderItem("id",true));
        //2.分页查询
        Page<User> p = iUserService.page(page);
        System.out.println(p.getTotal());
        System.out.println(p.getPages());
        List<User> users = p.getRecords();
        users.forEach(System.out::println);
    }
通用分页实体

封装统一查询条件实体

@Data
@ApiModel(description = "分页查询实体")
public class PageQuery {
    @ApiModelProperty("页码")
    private Integer pageNo = 1;
    @ApiModelProperty("页码")
    private Integer pageSize = 5;
    @ApiModelProperty("排序字段")
    private String sortBy;
    @ApiModelProperty("是否升序")
    private Boolean isAsc = true;
}

userPageQuery 继承

@Data
@ApiModel(description = "用户查询条件实体")
public class UserQuery extends PageQuery {
    @ApiModelProperty("用户名关键字")
    private String name;
    @ApiModelProperty("用户状态:1-正常,2-冻结")
    private Integer status;
    @ApiModelProperty("余额最小值")
    private Integer minBalance;
    @ApiModelProperty("余额最大值")
    private Integer maxBalance;
}

封装统一的返回结果实体

@Data
@ApiModel(description = "分页结果")
public class PageDTO<T> {
    @ApiModelProperty("总条数")
    private Long total;
    @ApiModelProperty("总页数")
    private Long pages;
    @ApiModelProperty("集合")
    private List<T> list;
}
    @Override
    public PageDTO<UserVO> queryUsersPage(UserQuery query) {
        String name = query.getName();
        Integer status = query.getStatus();
        // 1.构建分页条件
        Page<User> page = Page.of(query.getPageNo(), query.getPageSize());

        //1.2 排序条件
        if (StrUtil.isNotEmpty(query.getSortBy())) {
            //不为空
            page.addOrder(new OrderItem(query.getSortBy(), query.getIsAsc()));
        } else {
            //为空
            page.addOrder(new OrderItem("update_time", false));
        }
        // 2.分页查询
        Page<User> p = lambdaQuery()
                .like(name != null, User::getUsername, name)
                .eq(status != null, User::getStatus, status)
                .page(page);

        // 3.封装VO结果
        PageDTO<UserVO> dto = new PageDTO<>();
        dto.setTotal(p.getTotal());
        dto.setPages(p.getPages());
        List<User> records = p.getRecords();
        if (CollUtil.isEmpty(records)) {
            dto.setList(Collections.emptyList());
            return dto;
        }
        //拷贝user 的vo
        List<UserVO> vos = BeanUtil.copyToList(records, UserVO.class);
        dto.setList(vos);
        //返回结果
        return dto;
    }
在PageQuery中定义方法,将PageQuery对象转为MyBatisPlus中的Page对象
@Data
@ApiModel(description = "分页查询实体")
public class PageQuery {
    @ApiModelProperty("页码")
    private Integer pageNo = 1;
    @ApiModelProperty("页码")
    private Integer pageSize = 5;
    @ApiModelProperty("排序字段")
    private String sortBy;
    @ApiModelProperty("是否升序")
    private Boolean isAsc = true;

    public <T> Page<T> toMpPage(OrderItem... items){
        // 1.分页条件
        Page<T> page = Page.of(pageNo, pageSize);
        // 2.排序条件
        if(StrUtil.isNotBlank(sortBy)){
            // 不为空
            page.addOrder(new OrderItem(sortBy, isAsc));
        }else if(items != null){
            // 为空,默认排序
            page.addOrder(items);
        }
        return page;
    }
    public <T> Page<T> toMpPage(String defaultSortBy, Boolean defaultAsc){
        return toMpPage(new OrderItem(defaultSortBy, defaultAsc));
    }
    public <T> Page<T> toMpPageDefaultSortByCreateTime(){
        return toMpPage(new OrderItem("create_time", false));
    }
    public <T> Page<T> toMpPageDefaultSortByUpdateTime(){
        return toMpPage(new OrderItem("update_time", false));
    }
}
在PageDTO中定义方法,将MyBatisPlus中的Page结果转为PageDTO结果
@Data
@ApiModel(description = "分页结果")
public class PageDTO<T> {
    @ApiModelProperty("总条数")
    private Long total;
    @ApiModelProperty("总页数")
    private Long pages;
    @ApiModelProperty("集合")
    private List<T> list;

    public static <PO, VO> PageDTO<VO> of(Page<PO> p, Class<VO> clazz) {
        PageDTO<VO> dto = new PageDTO<>();
        // 1.总条数
        dto.setTotal(p.getTotal());
        // 2.总页数
        dto.setPages(p.getPages());
        // 3.当前页数据
        List<PO> records = p.getRecords();
        if (CollUtil.isEmpty(records)) {
            dto.setList(Collections.emptyList());
            return dto;
        }
        // 4.拷贝user的VO
        dto.setList(BeanUtil.copyToList(records, clazz));
        // 5.返回
        return dto;
    }

    public static <PO, VO> PageDTO<VO> of(Page<PO> p, Function<PO, VO> convertor) {
        PageDTO<VO> dto = new PageDTO<>();
        // 1.总条数
        dto.setTotal(p.getTotal());
        // 2.总页数
        dto.setPages(p.getPages());
        // 3.当前页数据
        List<PO> records = p.getRecords();
        if (CollUtil.isEmpty(records)) {
            dto.setList(Collections.emptyList());
            return dto;
        }
        // 4.拷贝user的VO
        dto.setList(records.stream().map(convertor).collect(Collectors.toList()));
        // 5.返回
        return dto;
    }
}

简化

    @Override
    public PageDTO<UserVO> queryUsersPage(UserQuery query) {
        String name = query.getName();
        Integer status = query.getStatus();
        // 1.构建分页条件
        Page<User> page = query.toMpPageDefaultSortByUpdateTime();

        // 2.分页查询
        Page<User> p = lambdaQuery()
                .like(name != null, User::getUsername, name)
                .eq(status != null, User::getStatus, status)
                .page(page);

        // 3.封装VO结果
        return PageDTO.of(p, user -> {
            // 1.拷贝基础属性
            UserVO vo = BeanUtil.copyProperties(user, UserVO.class);
            // 2.处理特殊逻辑
            vo.setUsername(vo.getUsername().substring(0, vo.getUsername().length() - 2) + "**");
            return vo;
        });
    }

或者

    @Override
    public PageDTO<UserVO> queryUsersPage(UserQuery query) {
        String name = query.getName();
        Integer status = query.getStatus();
        // 1.构建分页条件
        Page<User> page = query.toMpPageDefaultSortByUpdateTime();

        // 2.分页查询
        Page<User> p = lambdaQuery()
                .like(name != null, User::getUsername, name)
                .eq(status != null, User::getStatus, status)
                .page(page);

        // 3.封装VO结果
        return PageDTO.of(p, UserVO.class);
    }

3.封装VO结果
return PageDTO.of(p, user -> {
// 1.拷贝基础属性
UserVO vo = BeanUtil.copyProperties(user, UserVO.class);
// 2.处理特殊逻辑
vo.setUsername(vo.getUsername().substring(0, vo.getUsername().length() - 2) + “**”);
return vo;
});
}


或者

```java
    @Override
    public PageDTO<UserVO> queryUsersPage(UserQuery query) {
        String name = query.getName();
        Integer status = query.getStatus();
        // 1.构建分页条件
        Page<User> page = query.toMpPageDefaultSortByUpdateTime();

        // 2.分页查询
        Page<User> p = lambdaQuery()
                .like(name != null, User::getUsername, name)
                .eq(status != null, User::getStatus, status)
                .page(page);

        // 3.封装VO结果
        return PageDTO.of(p, UserVO.class);
    }

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值