【Redis二三事】一套超详细的Redis学习教程(步骤图片+实操)---第一集

⭐️写在前面


在这里插入图片描述

文章目录

👋1 NoSql简介

即Not-Only SQL(泛指非关系型的数据库),作为关系型数据库的补充

作用

  • 应对基于海量用户和海量数据前提下的数据处理问题

特征

  • 可扩容,可伸缩
  • 大数据量下高性能
  • 灵活的数据模型
  • 高可用

常见的Nosql数据库

  • Redis
  • memcache
  • HBase
  • MongoDB

👋2 Redis简介

Redis是Nosql的一种具体体现

概念:

  • Redis(REmote Dlctionary Server)是用C语言开发的一个开源的高性能键值对(key-value)数据库

特征

  • 数据间没有必然的关联关系
  • 内部采用单线程机制进行工作
  • 高性能。官方提供测试数据,50个并发执行100000个请求,读的速度是110000次/s,写的速度是81000次/s
  • 多数据类型支持
  - 字符串类型	string
  - 列表类型        list
  - 散列集合        hash
  - 集合类型        set
  - 有序集合类型 sorted_set
  • 持久化支持。可以进行数据灾难恢复

这里是引用

👋2.1 Redis的应用

这里是引用
在这里插入图片描述

🎉2.2 Redis的下载与安装

🎉2.2.1 Linux环境下安装Redis

有外网的情况下可执行如下命令

wget http://download.redis.io/releases/redis-4.0.0.tar.gz

List item

  • 执行对压缩包解压的命令

tar -xvf redis-4.0.0.tar.gz

  • 进入目录

cd redis-4.0.0/

  • 安装 redis

make install

显示下面这张图则代表安装成功
在这里插入图片描述

  • 进入src目录启动redis

cd src
redis-server

在这里插入图片描述

  • 复制一个会话,执行redis-cli即可操作redis

在这里插入图片描述

  • 切换端口启动redis服务,执行以下命令

redis-server --port <端口号>
在这里插入图片描述

  • 同样启动redis-cli也需要切换端口,执行命令 redis-cli -p <端口号>

在这里插入图片描述

2.2.1.1 切换启动方式

我们在开发可以按照不同方式启动redis,切换启动方式

查看redis的conf文件

使用cat命令过滤注释与空白并把文件内容转移到我们自己的redis-6379.conf中对其进行修改

cat redis.conf | grep -v "#" |grep -v "^$" > redis-6379.conf

进入redis-6379.conf修改文件

vim redis 6379.conf

在这里插入图片描述
修改为以下形式
在这里插入图片描述
下面我们使用配置文件启动
在这里插入图片描述
查看是否启动
在这里插入图片描述

> 将redis-6379.conf移动到我们自己创建的conf文件里
> 
> 将redis-6379.conf文件复制

在这里插入图片描述
修改端口及日志文件名称即可

在这里插入图片描述
启动两个服务
在这里插入图片描述
🌹启动成功

🌹2.2.2 windows环境下安装Redis

在学习的初级阶段我们先在windows上安装redis,后续再在linux上进行安装

安装链接:
redis安装
在这里插入图片描述
在这里插入图片描述

👋2.2.1 redis核心文件

- redis-server.exe 服务器启动命令
- redis-cli.exe 命令行客户端
- redis.windows.conf redis核心配置文件
- redis-benchmark.exe 性能测试工具
- redis-check-aof.exe AOF文件修复工具
- redis-check-dump.exe RDB文件检查工具(快照持久化文件)

启动redis:redis-server
启动命令行:redis-cli

在这里插入图片描述

👋3 Redis的五大数据类型

redis数据存储格式

  • redis自身是一个Map,其中所有的数据都是采用key:value的形式存储
  • 数据类型指的是存储的数据的类型,也就是value部分的类型,key部分永远都是字符串

我们可以和java中的对象类比着看待
在这里插入图片描述

👋3.1 String

在这里插入图片描述

👋3.1.1 String 类型数据的基本操作

  • 添加/修改数据
    • set key value
  • 获取数据
    • get key
  • 删除数据
    • del key

在这里插入图片描述

  • 添加/修改多个数据
    • mset key1 value1 key2 value2 …
  • 获取多个数据
    • mget key1 key2…
  • 获取数据字符个数
    • strlen key
  • 追加信息到原始信息后部(如果原始信息存在就追加,否则新建)
    • append key value

在这里插入图片描述
单数据操作与多数据操作的选择之惑

首先我们需要明白一条指令的执行过程
在这里插入图片描述

  • set指令发送需要时间
  • redis执行需要时间
  • 处理完毕返回需要消耗时间
单指令执行3条指令消耗的时间:3x发送+3x处理+3x返回

多指令执行3条指令的执行过程:1x发送+3x处理+1x返回

在数据量较少时,单指令与多指令并没有多少差别
但是一旦数据量上涨,多指令消耗的时间远远少于单指令

👋3.1.2 String类型数据的扩展操作

业务场景:

大型企业级应用中,分表操作是基本操作,使用多张表存储同类型数据,但是对应id必须保证统一性,不能重复。Oracle数据库具有sequence设定,可以解决该问题,但是MYSQL数据库并不具有类似的机制那么如何解决?

在这里插入图片描述

解决方案

  • 设置数值数据增加指定范围的值
    • incr key
    • incrby key increment
    • incrbyfloat key increment
  • 设置数值数据减少指定范围的值
    • decr key
    • decrby key increment

在这里插入图片描述

👋string作为数值操作

  • string在redis内部存储默认就是一个字符串,当遇到增减类操作incr,decr时会转成数值型进行计算。
  • redis所有操作都是**原子性**的,采用单线程处理所有业务,命令是一个一个执行的,因此无需考虑并发带来的数据影响
  • 注意:胺树脂进行操作的数据,如果原始数据不能转成数值,或超越了redis数值上限范围,将报错。9223372036854775807(java中long型数据最大值,long.MAX_VALUE)

业务场景:

在这里插入图片描述
我们为投票的人设置news_id,并为他设置投票倒数时长,时长一到,id擦掉

解决方案:

  • 设置数据具有指定的生命周期
  • setex key seconds value
  • psetex key milliseconds value
    在这里插入图片描述
  • 如果在时间之内又重新设置数据的生命周期,则之前的设置作废,按照新的生命周期来

我们得到结论 redis控制数据的生命周期,通过数据是否失效控制业务行为,适用于所有具有时效性限定控制的操作

👋3.1.3 string类型数据操作的注意事项

- 数据操作不成功的反馈与数据正常操作之间的差异
  - 表示运行结果是否成功
    - (integer)0->false 失败
    - (integer)1->true 成功
  - 表示运行结果值
    - (integer)3->3      3- (integer)1->1      1- 数据未获取到
  - (nil)等同于null
- 数据最大存储量
  - 512MB
- 数值计算最大范围(java中的long的最大值)
  - 9223372036854775807

👋3.1.4 综合案例

主页高频访问信息显示控制,例如新浪微博大V主页显示粉丝数与微博数量

在这里插入图片描述

解决方案:

  • 在redis中为大V用户设定用户信息,以用户主键和属性值作为key,后台设定定时刷新策略即可
    在这里插入图片描述

表名+主键名+主键值+属性名来作为它的key

  • 在redis中以json格式存储大V用户信息,定时刷新(也可以使用hash类型)

在这里插入图片描述
在这里插入图片描述

  • redis应用于各种结构型和非结构型高热度数据访问加速
    在这里插入图片描述
    在这里插入图片描述

👋3.2 hash

在这里插入图片描述

👋3.2.1 hash类型简介

  • 新的存储需求:对一系列存储的数据进行编组,方便管理,典型应用存储对象信息
  • 需要的存储结构:一个存储空间保存多个键值对数据
  • hash类型:底层使用哈希表结构实现数据存储

在这里插入图片描述

👋3.2.2 hash类型数据的基本操作

  • 添加/修改数据
    • hset key field value
  • 获取数据
    • hget key field
    • hgetall key
  • 删除数据
    • hdel key field1 [field2]
      在这里插入图片描述
  • 添加/修改多个数据
    • hmset key field1 value1 field2 value2
  • 获取多个数据
    • hmget key field1 field2
  • 获取哈希表中字段的数量
    • hlen key
  • 获取哈希表中是否存在指定的字段
    • hexists key field

在这里插入图片描述

👋3.2.3 hash类型数据扩展操作

  • 获取哈希表中所有字段名或字段值
    • hkeys key
    • hvals key
  • 设置指定字段的数值数据增加指定范围的值
    • hincrby key field increment
    • hincrbyfloat key field increment
      在这里插入图片描述

👋3.2.4 hash类型数据操作的注意事项

- 1.hash类型下的value只能存储字符串,不允许存储其他数据类型,不存在嵌套现象。如果数据未获取到,对应的
  值为(nil)
- 2.每个hash可以存储2^23-1个键值对
- 3.hash类型十分贴近对象的数据存储形式,并且可以灵活添加对象属性。但hash设计初衷不是为了存储大量对象
  而设计的,切记不可滥用,更不可以将hash作为对象列表使用
- 4.hgetall操作可以获取全部属性,如果内部field过多,遍历整体数据效率就会很低,有可能成为数据访问瓶颈

hsetnx key field value

如果当前value存在则不会修改,当前value不存在则进行修改

👋3.3 list

类比java中的linkedlist

list类型
在这里插入图片描述

👋3.3.1 list类型数据基本操作

  • 添加/修改数据
    • lpush key value1 [value2]… (从左边进)
    • rpush key value1 [value2]…(从右边进)
  • 获取数据
    • lrange key start stop(不知道多少个数据时stop设置为-1,负数代表倒数第一个元素)
    • lindex key index
    • llen key
  • 获取并移除数据
    • lpop key
    • rpop key
  • 移除指定数据
    • lrem key count value

在这里插入图片描述

👋3.3.2 list类型数据扩展操作

在规定时间内获取并移除数据(现在列表里没有不代表将来没有)

  • blpop key1 [key2] timeout
  • brpop key1[key2] timeout

业务场景1

微信朋友圈点赞,要求按照点赞顺序显示点赞好友信息
在这里插入图片描述
在这里插入图片描述

其中count代表删除几个列表中的指定元素,例如删除3个a
在这里插入图片描述

由此可以看出

  • redis应用于具有操作先后顺序的数据控制

👋3.3.3 list类型数据操作注意事项

  • list中保存的数据都是string类型的,数据总容量是有限的,最多2^32-1个元素
  • list具有索引的概念,但是操作数据时通常以队列的形式进行入队出队操作,或以栈的形式进行入栈出栈操作
  • 获取全部数据操作结束索引设置为-1
  • list可以对数据进行分页操作,通常第1页的信息来自于list,第2页及更多的信息通过数据库的形式加载

业务场景1
在这里插入图片描述
解决方案
在这里插入图片描述

每台服务器上的信息都由redis进行统计,保证了信息时间按顺序进行展示

在这里插入图片描述

由此我们可见redis应用于最新消息的展示
在这里插入图片描述

👋3.4 set

类比java中的set

  • 新的存储要求:存储大量的数据,在查询方面提供更高的效率
  • 需要的存储结构:能够保存大量的数据,高效的内部存储机制,便于查询
  • set类型:与hash存储结构完全相同,仅存储键,不存储值(nil),并且值是不允许重复的

为什么不用list的原因: list内部的数据结构是链表,查询慢,增删快,虽然能存取大量数据,但是读取效率低下

set在存储空间中的分布
在这里插入图片描述

👋3.4.1 set类型数据的基本操作

  • 添加数据
    • sadd key member1 [member2]
  • 获取全部数据
    • smembers key
  • 删除数据
    • srem key member1 [member2]
  • 获取集合数据总量
    • scard key
  • 判断集合中是否包含指定数据
    • sismember key member
      在这里插入图片描述
      在这里插入图片描述

👋3.4.2 set类型数据的扩展操作

业务场景1

每位用户首次使用今日头条时会设置3项爱好的内容,但是后期为了增加用户的活跃度,兴趣点。必须让用户对其他信息类别产生兴趣,增加客户留存度,如何实现?

业务分析

  • 系统分析出各个分类的最新或最热点信息条目并组织成set集合
  • 随机挑选其中部分信息
  • 配合用户关注信息分类中的热点信息组织成展示的全信息集合

解决方案

  • 随机获取集合中指定数量的数据
    • srandmember key [count]
  • 随机获取集合中的某个数据并将该数据移出集合
    • spop key

在这里插入图片描述
由此可以得出

  • redis应用于随机推荐类信息检索,例如热点歌单推荐,热点新闻推荐,热卖旅游线路,应用app,大v推荐等

业务场景2

在这里插入图片描述
推荐共同关注的好友,提高用户留存率

解决方案

交、并、差集图示

在这里插入图片描述
在这里插入图片描述

求两个集合的交、并、差集

  • sinter key1 [key2]
    在这里插入图片描述
  • sunion key1 [key2]

在这里插入图片描述

  • sdiff key1 [key2]

在这里插入图片描述
求两个集合的交、并、差集并存储到指定集合中

  • sinterstore destination key1 [key2]
    在这里插入图片描述
  • sunionstore destination key1 [key2]
  • sdiffstore destination key1 [key2]

将指定数据从原始集合中移动到目标集合中

  • smove source destination member

在这里插入图片描述
在这里插入图片描述

由此我们可以得到

  • redis应用于同类信息的关联搜索、二度关联搜索、深度关联搜索

👋3.4.3 set类型数据操作的注意事项

  • set类型不允许数据重复,如果添加的数据在set中已经存在,将只保留一份
  • set虽然和hash的存储结构相同,但是无法启用hash中存储值的空间

业务场景1

集团公司共具有12000名员工,内部OA系统中具有700多个角色,3000多个业务操作,23000多种数据,每位员工具有一个或多个角色,如何快速进行业务操作的权限检验?

在这里插入图片描述
这里不考虑其他的,只是用redis作为校验,证明它可以做到这件事,真实开发会有其他框架完成

解决方案

在这里插入图片描述
校验思想1:

我们设定一个用户有两个角色,每个角色有2-3个不同权限,将用户权限通过sunionstore存储到 uid:007中,判断权限可以直接判断uid:007中是否存在,存在则有权限,不存在则无权限

在这里插入图片描述
校验思想2:

通过sismember 命令判断是否具有该权限

在这里插入图片描述

第一种思想是数据存储方提供数据,然后将校验的业务操作放到程序的业务层去进行

第二种思想是直接把要校验的数据放在数据这一端校验,直接把结果给你

第一种形式耦合度较低,分工明确,数据和业务分散开来,而第二种把业务校验的工作糅合到数据中去了
,对于redis,还是提供基础数据而非提供校验结果比较好

由此我们可以得出

redis应用于同类型不重复数据的合并操作
在这里插入图片描述

👋3.5 sorted_set

  • 新的存储需求:数据排序有利于数据的有效展示,需要提供一种可以根据自身特征进行排序的方式
  • 需要的存储结构:新的存储模型,可以保存可排序的数据
  • sorted_set类型:在set的存储结构基础上添加可排序字段

在这里插入图片描述
score便是数据的排序特征,我们根据他对数据进行排序

👋3.5.1 sorted_set类型数据的基本操作

  • 添加数据
    • zadd key score1 member1 [score2 member2]
  • 获取全部数据
    • zrange key start stop [WITHSCORES] (按从小到大的顺序展示)
    • zrevrange key start stop [WITHSCORES] (按从大到小的顺序展示)
  • 删除数据
    • zrem key member [member …]

对上面操作的演示
在这里插入图片描述
在这里插入图片描述

  • 按条件获取数据
    • zrangebyscore key min max [WITHSCORES] [LIMIT]
    • zrevrangebyscore key max min [WITHSCORES]
  • 按条件删除数据
    • zremrangebyrank key start stop (按索引删除)
    • zremrangebyscore key min max (按范围删除)

查询成绩在50-90之间的数据
在这里插入图片描述

查询成绩在0-99之间的前两个数据
在这里插入图片描述

按范围删除
在这里插入图片描述

按索引删除
在这里插入图片描述

注意

  • minmax用于限定搜索查询的条件
  • startstop用于限定查询范围,作用于索引,表示开始和结束索引
  • offsetcount用于限定查询范围,作用于查询结果,表示开始位置和数据总量

其他操作

  • 获取集合数据总量
    • zcard key
    • zcount key min max
  • 集合交、并操作
    • zinterstore destination numkeys key [key …]
    • zunionstore destination numkeys key [key …]

操作演示:

在这里插入图片描述

sorted_set集合交集操作演示
在这里插入图片描述
在这里插入图片描述

👋3.5.2 sorted_set 业务场景

业务场景1

票选广东十大杰出青年,各类综艺选秀海选投票

各类资源网站TOP10(电影,歌曲,文档,电商,游戏等)

聊天室活跃度统计

业务分析

  • 为所有参与排名的资源建立排序依据

解决方案

  • 获取数据对应的索引(排名)
    • zrank key member
    • zrevrank key member
  • score值获取与修改
    • zscore key member
    • zincrby key increment member

我们设置电影aa,bb,cc并对其票数进行排名,并对aa票数进行+1操作

在这里插入图片描述

👋3.5.3 sorted_set类型数据操作的注意事项

  • score保存的数据存储空间是64位,如果是整数范围是-9007199254740992~9007199254740992
  • score保存的数据也可以是一个双精度的double值,基于双精度浮点数的特征,可能会丢失精度,使用时候要慎重
  • sorted_set底层存储还是基于set结构的,因此数据不能重复,如果重复添加相同的数据,score值将被反复覆盖,保留最后一次修改的结果

由此可知

  • redis应用于基于时间顺序的数据操作,而不关注具体时间
    在这里插入图片描述

👋4 Redis通用命令

👋4.1 key通用指令

key是一个字符串,通过key获取redis中保存的数据

  • 删除指定key

    • del key
  • 获取key是否存在

    • exists key
  • 获取key的类型

    • type key
  • 为指定key设置有效期

    • expire key seconds

    • pexpire key milliseconds (和第1个区别在于可以设置毫秒级)

    • expireat key timestamp

    • pexpireat key milliseconds-timestamp

  • 获取key的有效时间

    • ttl key (如果一个key不存在,它的返回值为-2,如果key存在但是过了有效期,它的返回值为-1)
    • pttl key
  • 切换key从时效性转换为永久性

    • persist key
  • 查询key

    • keys pattern
    • 查询模式pattern规则

在这里插入图片描述

  • 为key改名

    • rename key newkey
    • renamenx key newkey (如果新名称已存在则不修改)
  • 对所有key排序

    • sort (排list、set、sorted_set中的元素)
  • 其他key通用操作

    • help @generic

👋4.2 数据库通用指令

key的重复问题

  • key是由程序员定义的
  • redis在使用过程中,伴随着操作数据量的增加,会出现大量的数据以及对应的key
  • 数据不区分种类,类别混杂在一起,极易出现重复或冲突

解决方案

  • redis为每个服务提供有16个数据库,编号从0到15
  • 每个数据库之间的数据相互独立

在这里插入图片描述

👋4.2.1 数据库通用操作

  • 切换数据库(不切换则默认使用0号数据库)
    • select index
  • 其他操作
    • quit (退出当前数据库)
    • ping (测试服务器是否连通)
    • echo message (输出信息)
  • 数据移动 (将当前数据库的指定数据移动到目标数据库)
    • move key db
  • 数据清除
    • dbsize (查看当前数据库有多少个key)
    • flushdb (清除当前数据)
    • flushall (清除所有数据)
      在这里插入图片描述

👋5 Jedis

👋5.1 Jedis简介

Jedis是java语言与redis进行交互的工具,但是Jedis不是java语言连接redis的唯一渠道,其他渠道还有

  • SpringData Redis
  • Lettuce

👋5.2 Jedis书写Hello World

第一步:创建Maven工程

在这里插入图片描述
第二步:引入依赖

    <dependencies>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.9.0</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>

第三步:输出helloworld

    @Test
    public void testJedis(){
        //连接redis
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        //操作redis
        jedis.set("name","helloworld");
        String name = jedis.get("name");
        System.out.println(name);
        //关闭连接
        jedis.close();
    }

在这里插入图片描述

👋5.3 Jedis常规操作演示

1.测试list

    @Test
    public void testList(){
        //连接redis
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        //操作redis
        jedis.lpush("list1","a","b","c");
        jedis.rpush("list1","x");
        List<String> list1 = jedis.lrange("list1", 0, -1);
        for (String s : list1) {
            System.out.println(s);
        }
        //关闭连接
        jedis.close();
    }

在这里插入图片描述
2.测试hash

    @Test
    public void testHash(){
        //连接redis
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        //操作redis
        jedis.hset("hash1","a1","a1");
        jedis.hset("hash1","a2","a2");
        Map<String, String> hash1 =
                jedis.hgetAll("hash1");
        System.out.println(hash1);
        //关闭连接
        jedis.close();
    }

在这里插入图片描述
在这里插入图片描述

案例

在这里插入图片描述

案例:需求分析

在这里插入图片描述

代码实现

package com.wql;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.exceptions.JedisDataException;

/**
 * Description
 * User:
 * Date:
 * Time:
 */
public class service {
    private String id;
    private int num;
    public service(String id,int num){
        this.id = id;
        this.num = num;
    }

    //控制单元(控制business的调用)
    public void ss(){
        //连接redis
        Jedis jedis = new Jedis("127.0.0.1", 6379);

        //业务操作
        String value = jedis.get("compid:"+id);
        try {
            if (value==null){
            jedis.setex("compid:"+id,5,Long.MAX_VALUE-num+"");
            }else {
                Long val = jedis.incr("compid:"+id);
                business(id,num-(Long.MAX_VALUE-val));
            }
        }catch (JedisDataException e){
            System.out.println("使用已经达到次数上限,请升级会员级别");
            return;
        }finally {
            //关闭jedis
            jedis.close();
        }

    }

    //业务操作
    public void business(String id,Long val){
        System.out.println("用户:"+id+"业务操作执行第"+val+"次");
    }
}

class MyThread extends Thread{
    service sc;
    public MyThread(String id,int num){
        sc = new service(id,num);
    }
    @Override
    public void run() {
        while (true){
            sc.ss();
            try {
                Thread.sleep(300L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

class Main{
    public static void main(String[] args) {
        MyThread mt1 = new MyThread("1",10);
        MyThread mt2 = new MyThread("2",30);
        mt1.start();
        mt2.start();
    }
}

在这里插入图片描述

6.4 Jedis简易工具类

Jdeis本身已经提供了连接池JedisPool

  • JedisPool:Jedis提供的连接池技术
    • poolConfig:连接池配置对象
    • host:redis服务地址
    • port:redis服务端口号
package com.wql.util;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import java.util.ResourceBundle;

/**
 * Description
 * User:
 * Date:
 * Time:
 */
public class JedisUtils {
    private static JedisPool jp = null;
    private static String host;
    private static int port;
    private static int maxTotal;
    private static int maxIdle;
    static {

        ResourceBundle rb = ResourceBundle.getBundle("redis");
        host = rb.getString("redis.host");
        port = Integer.parseInt(rb.getString("redis.port"));
        maxTotal = Integer.parseInt(rb.getString("redis.maxTotal"));
        maxIdle = Integer.parseInt(rb.getString("redis.maxIdle"));

        JedisPoolConfig jpc = new JedisPoolConfig();
        jpc.setMaxTotal(maxTotal);
        jpc.setMaxIdle(maxIdle);
        jp = new JedisPool(jpc,host,port);
    }
    public static Jedis getJedis(){

        return jp.getResource();
    }

    public static void main(String[] args) {
        System.out.println(getJedis());
    }
}

在这里插入图片描述

评论 53
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

温文艾尔

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

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

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

打赏作者

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

抵扣说明:

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

余额充值