Mybatis-plus 快速入门

java 学习笔记指路
基础知识

Python转java补充知识
Java中常见的名词解释

前端

【黑马程序员pink老师前端】HTML
【黑马程序员pink老师前端】JavaScript基础大总结
【黑马程序员pink老师前端】JavaScript函数与作用域
【黑马程序员pink老师前端】JavaScript对象

数据库

【黑马程序员数据库】数据库基础大总结
【黑马程序员数据库】数据库引擎
【黑马程序员数据库】数据库索引

SQL编程

【牛客】SQL刷题篇入门篇
【牛客】SQL刷题篇进阶篇

Java 框架

spring框架学习
Mybatis-plus 快速入门
Maven基础
Zookeeper初认识
HTTP简介
Tomcat
Servlet
vue入门

一、Mybatis 简介

JAVA三层架构包括持久层、业务层、表现层。Mybatis是持久层框架,简单说就是操作数据库的。
Mybatis-plus是对 Mybatis 的优化。主要学习 Service 和 Mapper 两个接口。
本篇文章学习:
通用Mapper(BaseMapper)通用Service(IService)
通用接口方法无法满足需求时:自定义Mapper自定义Service
常用注释:@TableName@TableId@TableField@TableLogic
插件:分页插件乐观锁插件
提供更新和查找方法的Wrapper家族

0. IService和BaseMapper的区别

两个接口都可以对数据库增删改查(CRUD),两者的区别?

对比两个接口提供的方法可以看出,IService 是对 BaseMapper 的补充,光看数据库增删改查(CRUD)可以发现除了名字不同其他的都类似。BaseMapper不支持批量操作,IBaseService支持批量操作。
在这里插入图片描述

既然IService是对BaseMapper的补充,为什么还需要BaseMapper?

Service虽然加入了数据库的操作,但还是以业务功能为主,而更加复杂的SQL查询,还是要靠Mapper对应的XML文件里去编写SQL语句。

1. Mybatis-plus基本工程搭建

1.1 步骤总览

1、创建数据库
2、创建SpringBoot工程
3、引入依赖(pom文件中配置各个框架版本)
4、修改配置文件(数据库连接配置)
5、创建实体类(实体类是一种属性类,一般就是数据库的表)
6、创建 mapper 接口
7、启动类添加注解(在 Spring Boot 启动类中添加 @MapperScan 注解,扫描 Mapper 文件夹)
8、测试

1.2 步骤展开

看完步骤之后就可以尝试用一用了,增删改查的指令每次用来找就好

1、创建数据库

在这里插入图片描述

2、创建SpringBoot工程

在这里插入图片描述

3、引入依赖

springboot配置(中的版本会限制下面中包的版本,这就是为什么中好多包没有人工加版本)

<parent>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-parent</artifactId>
	<version>2.3.4.RELEASE</version>
	<relativePath/> <!-- lookup parent from repository -->
</parent>

其他包和版本设置

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

    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.4.1</version>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>

4、修改配置文件

在 application.properties 配置文件中添加 MySQL 数据库的相关配置:

// mysql数据库连接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=GMT%2B8&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=123456

设置与MySQL版本有关:
driver-class-name(驱动)

  • 8以上:com.mysql.cj.jdbc.Driver
  • 8以下:com.mysql.jdbc.Driver(没有cj)

url:

  • 8以上:?serverTimezone=GMT%2B8 需要添加东八区时区,jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=GMT%2B8&characterEncoding=utf-8
  • 8以下:jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&useSSL=true

5、创建实体类

实体类是一种属性类,一般就是数据库的表

package com.atguigu.mybatisplus.entity;

@Data
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

6、创建 IService / BaseMapper 接口

package com.atguigu.mybatisplus.mapper;
public interface UserMapper extends BaseMapper<User> {
}
package com.atguigu.mybatisplus.service;
public interface UserService extends IService<User> {
}

7、启动类添加注解

在 Spring Boot 启动类中添加 @MapperScan 注解,扫描 Mapper 文件夹

package com.atguigu.mybatisplus;

@SpringBootApplication
@MapperScan("com.atguigu.mybatisplus.mapper")
public class MybatisPlusApplication {
    ......
}

8、测试

package com.atguigu.mybatisplus;

@SpringBootTest
class MybatisPlusApplicationTests {

	//@Autowired //默认按类型装配。是spring的注解
	@Resource //默认按名称装配,找不到与名称匹配的bean,则按照类型装配。是J2EE的注解
	private UserMapper userMapper;

	@Test
	void testSelectList() {
		//selectList()方法的参数:封装了查询条件
		//null:无任何查询条件
		List<User> users = userMapper.selectList(null);
		users.forEach(System.out::println);
	}
}

补充、查看sql输出日志

mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

2. BaseMapper CRUD 接口

2.1 增C(Insert)

@Test
public void testInsert(){

    User user = new User();
    user.setName("Helen");
    user.setAge(18);
    //不设置email属性,则生成的动态sql中不包括email字段

    int result = userMapper.insert(user);
    System.out.println("影响的行数:" + result); //影响的行数
    System.out.println("id:" + user.getId()); //id自动回填
}

2.2 查R(Select)

@Test
public void testSelect(){

    //按id查询
    User user = userMapper.selectById(1);
    System.out.println(user);

    //按id列表查询
    List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
    users.forEach(System.out::println);

    //按条件查询
    Map<String, Object> map = new HashMap<>();
    map.put("name", "Helen"); //注意此处是表中的列名,不是类中的属性名
    map.put("age", 18);
    List<User> users1 = userMapper.selectByMap(map);
    users1.forEach(System.out::println);
}

2.3 改U(Update)

@Test
public void testUpdate(){

    User user = new User();
    user.setId(1L);
    user.setAge(28);

    //注意:update时生成的sql自动是动态sql
    int result = userMapper.updateById(user);
    System.out.println("影响的行数:" + result);
}

2.4 删D(Delete)

@Test
public void testDelete(){

    int result = userMapper.deleteById(5);
    System.out.println("影响的行数:" + result);
}

2.5 配置分页拦截器

//Spring boot方式
@EnableTransactionManagement
@Configuration
@MapperScan("com.xxgg.blog.mapper")
public class MybatisPlusConfig {

    /**
     * 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题(该属性会在旧插件移除后一同移除)
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }

    @Bean
    public ConfigurationCustomizer configurationCustomizer() {
        return configuration -> configuration.setUseDeprecatedExecutor(false);
    }
}

3. 通用Service(IService)

参考文章

3.1 save(增)

// 插入一条记录(选择字段,策略插入)
boolean save(T entity);
// 插入(批量)
boolean saveBatch(Collection<T> entityList);
// 插入(批量)
boolean saveBatch(Collection<T> entityList, int batchSize);

3.2 SaveOrUpdate(增或改)

// TableId 注解存在更新记录,否插入一条记录
boolean saveOrUpdate(T entity);
// 根据updateWrapper尝试更新,否继续执行saveOrUpdate(T)方法
boolean saveOrUpdate(T entity, Wrapper<T> updateWrapper);
// 批量修改插入
boolean saveOrUpdateBatch(Collection<T> entityList);
// 批量修改插入
boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize);

3.3 Remove(删)

// 根据 entity 条件,删除记录
boolean remove(Wrapper<T> queryWrapper);
// 根据 ID 删除
boolean removeById(Serializable id);
// 根据 columnMap 条件,删除记录
boolean removeByMap(Map<String, Object> columnMap);
// 删除(根据ID 批量删除)
boolean removeByIds(Collection<? extends Serializable> idList);

3.4 Update(改)

// 根据 UpdateWrapper 条件,更新记录 需要设置sqlset
boolean update(Wrapper<T> updateWrapper);
// 根据 whereEntity 条件,更新记录
boolean update(T entity, Wrapper<T> updateWrapper);
// 根据 ID 选择修改
boolean updateById(T entity);
// 根据ID 批量更新
boolean updateBatchById(Collection<T> entityList);
// 根据ID 批量更新
boolean updateBatchById(Collection<T> entityList, int batchSize);

3.5 Get(按条件查)

// 根据 ID 查询
T getById(Serializable id);
// 根据 Wrapper,查询一条记录。结果集,如果是多个会抛出异常,随机取一条加上限制条件 wrapper.last("LIMIT 1")
T getOne(Wrapper<T> queryWrapper);
// 根据 Wrapper,查询一条记录
T getOne(Wrapper<T> queryWrapper, boolean throwEx);
// 根据 Wrapper,查询一条记录
Map<String, Object> getMap(Wrapper<T> queryWrapper);
// 根据 Wrapper,查询一条记录
<V> V getObj(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);

3.6 List(查)

// 查询所有
List<T> list();
// 查询列表
List<T> list(Wrapper<T> queryWrapper);
// 查询(根据ID 批量查询)
Collection<T> listByIds(Collection<? extends Serializable> idList);
// 查询(根据 columnMap 条件)
Collection<T> listByMap(Map<String, Object> columnMap);
// 查询所有列表
List<Map<String, Object>> listMaps();
// 查询列表
List<Map<String, Object>> listMaps(Wrapper<T> queryWrapper);
// 查询全部记录
List<Object> listObjs();
// 查询全部记录
<V> List<V> listObjs(Function<? super Object, V> mapper);
// 根据 Wrapper 条件,查询全部记录
List<Object> listObjs(Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录
<V> List<V> listObjs(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);

3.7 Page(分页查)

// 无条件分页查询
IPage<T> page(IPage<T> page);
// 条件分页查询
IPage<T> page(IPage<T> page, Wrapper<T> queryWrapper);
// 无条件分页查询
IPage<Map<String, Object>> pageMaps(IPage<T> page);
// 条件分页查询
IPage<Map<String, Object>> pageMaps(IPage<T> page, Wrapper<T> queryWrapper);

3.8 Count(查记录数)

// 查询总记录数
int count();
// 根据 Wrapper 条件,查询总记录数
int count(Wrapper<T> queryWrapper);

3.9 Chain(链式)

query(链式查询)

// 链式查询 普通
QueryChainWrapper<T> query();
// 链式查询 lambda 式。注意:不支持 Kotlin
LambdaQueryChainWrapper<T> lambdaQuery(); 

// 示例:
query().eq("column", value).one();
lambdaQuery().eq(Entity::getId, value).list();

update(链式更改)

// 链式更改 普通
UpdateChainWrapper<T> update();
// 链式更改 lambda 式。注意:不支持 Kotlin 
LambdaUpdateChainWrapper<T> lambdaUpdate();

// 示例:
update().eq("column", value).remove();
lambdaUpdate().eq(Entity::getId, value).update(entity);

4. 自定义Mapper

4.1 步骤

1.定义接口
2.配置xml文件,并在xml文件中配置SQL语句
其他的都和通用Mapper一样

4.2 详细步骤

接口方法定义

List<User> selectAllByName(String name);

创建xml文件

在resources目录中创建mapper目录,创建UserMapper.xml。mapper目录是持久层映射文件的默认目录,如果是其他目录,需要配置mapper-locations,例如:mybatis-plus.mapper-locations=classpath:xml/*.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.atguigu.mybatisplus.mapper.UserMapper">

   <sql id="Base_Column_List">
       id, name, age, email
   </sql>

    <select id="selectAllByName" resultType="com.atguigu.mybatisplus.entity.User">
        select
        <include refid="Base_Column_List"/>
        from user
        where
        name = #{name}
    </select>
</mapper>

测试

在MapperTests中创建如下测试用例

@Test
public void testSelectAllByName(){
    List<User> users = userMapper.selectAllByName("Helen");
    users.forEach(System.out::println);
}

5. 自定义Service

5.1 步骤

1.定义接口
2.实现接口方法
其他的都和通用Service一样

5.2 详细步骤

接口方法定义

List<User> listAllByName(String name);

实现接口方法

@Override
public List<User> listAllByName(String name) {
    // baseMapper对象指向当前业务的mapper对象
    return baseMapper.selectAllByName("Helen");
}

测试

ServiceTests中添加测试方法

@Test
public void testListAllByName(){
    List<User> users = userService.listAllByName("Helen");
    users.forEach(System.out::println);
}

6. @Table注释

6.1 @TableName

指定连接的表名 t_user

@TableName(value = "t_user")
public class User {
}

6.2 @TableId

默认情况下数据库的 id 列使用的是基于雪花算法的策略生成.
为什么需要使用雪花算法
所以的数据基本都是根据 id 来查询的,数据量很大的时候不会存在同一个表中。mysql数据库是根据聚簇索引进行查找的,而这种查找方法在 id 有顺序的时候查找速度更快。所以 id 的设置需要满足有序、方便扩展到其他分表、各个分表使用频率差不多(均衡)、id 不能重复
在这里插入图片描述
雪花算法是由Twitter公布的分布式主键生成算法,它能够保证不同表的主键的不重复性,以及相同表的主键的有序性。

  • 长度共 64 bit(一个long型)。
  • 首先是一个符号位,正数是 0,负数是 1。
  • 41bit 时间截(毫秒级),存储的是时间截的差值(当前时间截 - 开始时间截),结果约等于 69.73年。
  • 10bit 作为机器的 ID,5个 bit 是数据中心(是哪个地区的数据中心),5个 bit 的机器ID(每个数据中心中的机器)。
  • 12bit 作为毫秒内的流水号。

6.2.1 value 属性

表中的 id 只有设置为 id 的时候mybatis才会知道这个数据是id而采用雪花算法,如果数据库中 id 没有命名为 id 就需要 value 属性
比如 id 在数据库中叫 uid。

@TableId(value = "uid")
private String id;

6.2.2 type属性

IdType.ASSIGN_ID:使用基于雪花算法的策略生成数据id
IdType.AUTO:使用数据库的自增策略

@TableId(type = IdType.ASSIGN_ID)
private Long id;

全局配置:要想影响所有实体的配置,可以设置全局主键配置

// 全局设置主键生成策略
mybatis-plus.global-config.db-config.id-type = auto

6.3 @TableField

6.3.1 自动填充

照阿里的编写要求,每个数据库都要有 create_time 和 update_time ,也就是表创建的时间和表更新的时间。
在这种很多表都需要添加的时候才用自动填充,只改几个表的时候直接在实体类中标注就可以。
step1:添加fill属性

@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;

@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;

step2:实现元对象处理器接口 -> 创建handler包,创建MyMetaObjectHandler类
添加 @Component 注解

package com.atguigu.mybatisplus.handler;

@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("start insert fill ....");
        this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
        this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("start update fill ....");
        this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
    }
}

6.3.2 判断字段是否存在

@Override
public void insertFill(MetaObject metaObject) {
    
    //其他代码
    
    //判断是否具备author属性
    boolean hasAuthor = metaObject.hasSetter("author");
    if(hasAuthor){
        log.info("start insert fill author....");
        this.strictInsertFill(metaObject, "author", String.class, "Helen"); 
    }
}

6.3.3 判断字段是否赋值

@TableField(fill = FieldFill.INSERT)
private Integer age;
 @Override
public void insertFill(MetaObject metaObject) {

    //其他代码
    
    //判断age是否赋值
    Object age = this.getFieldValByName("age", metaObject);
    if(age == null){
        log.info("start insert fill age....");
        this.strictInsertFill(metaObject, "age", String.class, "18");
    }

}

6.4 @TableLogic

逻辑删除(和物理删除不同,类似于放入回收站,是可以找回的)
step1:数据库中创建逻辑删除状态列
数据库中添加 is_deleted 列(是否删除,根据阿里编程准则,是否用is_命名),0是未逻辑删除,1是逻辑删除。
step2:实体类中添加逻辑删除属性

@TableLogic
@TableField(value = "is_deleted")
private Integer deleted;

step3:测试
测试删除:删除功能被转变为更新功能

-- 实际执行的SQL
update user set is_deleted=1 where id = 1 and is_deleted=0

测试查询:被逻辑删除的数据默认不会被查询

-- 实际执行的SQL
select id,name,is_deleted from user where is_deleted=0

7. 插件

7.1 分页插件

7.1.1 为什么需要分页?

同一业务的单表数据也会达到单台数据库服务器的处理瓶颈

7.1.2 单表数据拆分有两种方式

垂直分表和水平分表
在这里插入图片描述

7.2 乐观锁插件

8. Wrapper家族

3.1 QueryWrapper

Mybatis-plus 提供的动态条件查询(条件构造器)
常见用法:用来查询id等
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.2 获取和赋值

get 参数名 获取
set 参数名 赋值

3.3 AjaxResult

SpringBoot通用返回类AjaxResult用来返回状态码信息以及提示信息(AjaxResult),以便于浏览器端进行异步数据处理。其中,code表示请求的返回状态码,message表示请求返回的消息提示,data则表示请求返回的数据。
AjaxResult的使用方法非常简单,只需要在Controller中将需要返回的数据,封装到AjaxResult对象中即可。下面是一段例子代码:

@RequestMapping("/demo")
@ResponseBody
public AjaxResult demo() {
    User user = new User();
    user.setName("Test");
    user.setAge(18);
    return AjaxResult.success(user);
}

3.4 ServiceImpl

ServiceImpl 类是我们进行SQL操作中非常重要的一个类,通过MybatisPlus生成的各个实体类的 XXXImpl 都会继承 ServiceImpl 类那里继承全部的方法。

3.5 ****接口

两种保存方式:save,batchsave

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
### 回答1: MyBatis-Plus是一个基于MyBatis的增强工具,提供了一种简化开发的方式。相比于MyBatis,MyBatis-Plus在持久层代码的编写中提供了更多的便利功能,使得开发人员能够更加高效地完成数据库操作。 要下载MyBatis-Plus快速入门文档,有以下几种方式: 1. 官方网站:MyBatis-Plus的官方网站(https://baomidou.com/)上提供了详细的文档和教程资源,包括快速入门文档。在官网上,你可以找到快速入门文档的下载链接,并根据自己的需要进行下载。 2. GitHub:MyBatis-Plus的项目在GitHub上有一个仓库,地址是(https://github.com/baomidou/mybatis-plus)。在该仓库中,你可以找到各种文档和示例代码。在仓库的README文件中,会提供快速入门文档的链接,你可以直接点击链接下载文档。 3. 知识分享平台:如CSDN、简书、博客园等,有很多博主或开发者会分享自己的经验和学习心得,包括MyBatis-Plus快速入门文档。你可以在这些平台上进行搜索,找到适合自己的教程并下载学习。 不论你选择哪一种方式下载MyBatis-Plus快速入门文档,建议你在学习前先了解一些基础的MyBatis知识,这样能更好地理解和应用MyBatis-Plus框架的功能。祝你学习顺利! ### 回答2: 要下载MyBatis-Plus快速入门文档,可以按照以下步骤进行: 1. 打开浏览器,进入MyBatis-Plus官方网站(https://mp.baomidou.com/)。 2. 在网页顶部的菜单栏中,找到并点击“文档”选项。 3. 进入文档页面后,可以看到各个版本的文档列表。根据自己使用的MyBatis-Plus版本,选择相应的文档版本。 4. 点击选择的文档版本,进入该版本的文档页面。 5. 在文档页面中,可以看到各个章节的内容。浏览页面找到“快速入门”章节。 6. 点击“快速入门”章节,即可查看该章节的详细内容。 7. 如果需要将文档保存到本地,我们可以进行以下操作:选中文档内容,右键点击“复制”或者使用快捷键Ctrl+C,将内容复制到剪贴板中。 8. 打开文本编辑器(例如记事本、Word等),粘贴剪贴板中的内容(右键点击“粘贴”或使用快捷键Ctrl+V)。 9. 保存文件,选择保存路径并命名文档文件。 通过以上步骤,您就可以成功下载MyBatis-Plus快速入门文档并保存到本地了。希望这个回答对您有帮助。 ### 回答3: 要下载MyBatis-Plus快速入门文档,可以按照以下步骤进行操作: 1. 打开浏览器,进入MyBatis-Plus官方网站(https://mp.baomidou.com/)。 2. 在页面上方的菜单栏中,可以看到一个名为“文档”的选项,鼠标悬停在上面。 3. 在弹出的下拉菜单中,选择“快速入门”。 4. 进入快速入门页面后,可以看到文档的内容。 5. 如果需要下载文档,可以找到页面上方的一个名为“下载PDF”的按钮,点击它。 6. 浏览器会开始下载一个PDF文件,即MyBatis-Plus快速入门文档。 7. 下载完成后,可以在浏览器的下载目录中找到该文件。 8. 双击打开文件,即可查看和学习MyBatis-Plus快速入门内容。 通过以上步骤,你可以成功下载并使用MyBatis-Plus快速入门文档,帮助你更好地理解和使用这个框架。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

浪里摸鱼

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

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

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

打赏作者

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

抵扣说明:

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

余额充值