MongoDB


特点


特点

  • 面向集合存储,易存储对象类型的数据
  • 支持查询,以及动态查询
  • 支持 RUBY,PYTHON,JAVA,C++,C# 等多种语言
  • 文件存储格式为 BSON (一种JSON的扩展)
  • 文件复制和故障恢复和分片
  • 支持事务支持
  • 索引 聚合 关联查询

应用场景

在这里插入图片描述

安装


传统方式

1、下载MongoDB

- https://www.mongodb.com/try/download/community

在这里插入图片描述
2、将安装包上传到 linux 系统

tar -zxf mongodb-linux-aarch64-ubuntu2004-5.0.5.tgz

在这里插入图片描述
3、查看安装目录

ls
   bin 目录  用来存放启动 mongoDB 的服务以及客户端链接的脚本文件等 

4、启动 MongoDB 服务

./mongod --port=27017 --dbpath=../data --logpath=../logs/mongo.log

`--port`    指定服务监听端口号  默认是 27017
`--dbpath`  指定 mongodb 数据存放目录  启动要求目录必须存在
`--logpath` 指定 mongodb 日志文件存放位置

注意:由于指定了日志文件因此启动时日志输出到日志中终端不显示任何日志

5、客户端连接

./mongo --port=27017

Docker 方式

1、拉取 mongodb 镜像

docker pull mongo:5.0.5

2、运行 mongo 镜像

docker run -d --name mongo --p 27017:27017 mongo:5.0.5

3、进入 mongo 容器

docker exec -it bc6c bash

在这里插入图片描述

核心概念


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

基本操作


库 < database >

1、查看所有库

show databases; | show dbs;

在这里插入图片描述
注意:
在这里插入图片描述
2、创建数据库

use 库名

在这里插入图片描述
注意: use代表创建并使用,当库中没有数据时默认不显示这个库
在这里插入图片描述
3、删除数据库
默认删除当前选中的库

db.dropDatabase()

在这里插入图片描述
4、查看当前所在库

db;

在这里插入图片描述

集合 < Collection >

1、查看库中所有集合

show collections; | show tables;

在这里插入图片描述
2、创建集合

db.createCollection('集合名称',[options])

在这里插入图片描述
options可以是如下参数
在这里插入图片描述
3、删除集合

db.集合名称.drop();

在这里插入图片描述

文档 < document >

1、插入文档

  • 单条文档
db.集合名称.insert({"name":"编程","age":23,"bir":"2012-12-12"})
  • 多条文档
db.集合名称.insertMany(
   [<document 1>,<document 2>, ...],
   {
   	writeConcern:1,  //写入策略,默认为1,即要求确认写操作, 0 是不要求
   	ordered:true     //指定是否按顺序写入,默认为true,按顺序写入
   }
)

db.集合名称.insert([
   {"name":"编程","age":23,"bir":"2012-12-12"},
   {"name":"java","age":25,"bir":"2012-12-12"}
]);
  • 脚本方式
for(let i=0;i<100;i++){
	db.users.insert({"_id":i,"name":"java_"+i,"age":22});
}

注意:在 mongodb 中每个文档都会有一个_id作为唯一标识,_id默认会自动生成如果手动指定将使用手动指定的值作为_id的值。
2、查询所有

db.集合名称.find();

3、删除文档

db.集合名称.remove(
	<query>,
	{
		justOne:<boolean>,
		writeConcern:<document>
	}
)

db.users.remove({age:23})
db.users.remove({})    # 删除所有

参数说明:

  • query:可选 删除文档的条件
  • justone:可选 如果设为true或1,则只删除一个文档,如果不设置该参数,或使用默认值false,则删除所有匹配条件的文档。
  • writeConcern:可选 抛出异常的级别

4、更新文档

db.集合名称.update(
	<query>,
	<update>,
	{
		upsert:<boolean>,
		multi:<boolean>,
		writeConcern:<document>
	}
);

参数说明:

  • query:update的查询条件,类似sql update 查询内 where 后面的。
  • update:update的对象和一些更新的操作符(如 $ ,$inc…)等,也可以理解为sql update查询内 set 后面的。
  • upsert:可选,这个参数的意思是,如果不存在update的记录,是否插入 objNew,true为插入,默认是 false ,不插入。
  • multi:可选,mongodb默认是false,只更新找到的第一条记录,如果这个参数为 true ,就把按条件查出来的多条记录全部更新。
  • writeConcern:可选,抛出异常的级别。
db.集合名称.update({"name":"zhangsan"},{name:"11",bir:new date()})
这个更新是将符合条件的全部更新成后面的文档,相当于先删除再更新

db.集合名称.update({"name":"xiaohei"},{$set:{name:"mingming"}})
保留原来数据更新,但是只更新符合条件的第一条数据

db.集合名称.update({name:"小黑"},{$set:{name:"小明"}},{multi:true})
保留原来的数据更新,更新符合条件的所有数据

db.集合名称.update({name:"小黑"},{$set:{name:"小明"}},{multi:true,upsert:true})
保留原来的数据更新,更新符合条件的所有数据,没有条件符合时插入数据

文档查询

MongoDB查询文档使用 find() 方法。find() 方法以非结构化的方式来显示所有文档。

语法

db.集合名称.find(query,projection)
  • query:可选,使用查询操作符指定查询条件
  • projection:可选,使用投影操作符指定返回的键。查询时返回文档中所有键值,只需省略该参数即可(默认省略)

如果你需要以易读的方式来读取数据,可以使用 pretty() 方法,语法格式如下:

db.集合名称.find().pretty()
注意:pretty() 方法以格式化的方式来显示所有文档

对比语法
如果你熟悉常规的 SQL 数据,通过下表可以更好的理解 MongoDB 的条件查询语句
在这里插入图片描述
AND

db.集合名称.find({key1:value1,key2:value2,....}).pretty()
类似于 WHERE 语句:WHERE key1=value1 AND key2=value2

OR
MongoDB OR 条件语句使用了关键字 $or,语法格式如下:

db.集合名称.find({
	$or:[
		{key1:value1},
		{key2:value2}
	]
}).pretty()
类似于 WHERE 语句:WHERE key1=value1 or key2=value2

AND 和 OR 联合

- 类似 SQL 语句为:'where age > 50 and (name='编程' or name='MongoDB')'
- db.集合名称.find({"age":{$gt:50},$or:[{name:'编程'},{name:'MongoDB'}]}).pretty();

数组中查询

-- 测试数据
- db.集合名称.insert({"_id":11,"age":29,"likes":["看电视","读书","美女"],"name":"编程test"})
-- 执行数组查询
- db.users.find({likes:"看电视"})
-- $size 按照数组长度查询
- db.users.find(likes:{$size:3})

模糊查询

- 类似 SQL 中为 'where name like '%name%''
- db.users.find({likes://});
- 注意:在 MongoDB 中使用正则表达式可以是实现近似模糊查询功能

排序

- db.集合名称.find().sort({name:1,age:1})
- 1  升序    -1 降序
- 类似 SQL 语句为:'order by name,age'

分页

- db.集合名称.find().sort({条件}).skip(start).limit(rows);
- 类似于 SQL 语句为 'limit start,rows'

总条数

- db.集合名称.count()
- db.集合名称.find({"name":"编程"}).count();
- 类似于 SQL 语句为 'select count(id) from ....'

去重

- db.集合名称.distinct('字段')
- 类似于 SQL 语句为 'select distinct name from ....'

指定返回字段

- db.集合名称.find({条件},{name:1,age:1})
- 参数: 1 返回   0 不返回    '注意:0 和 1 不能同时使用'

- db.users.find({},{name:1});
- db.users.find({},{_id:0})

$type

说明
$type 操作符是基于BSON类型来检索集合中匹配的数据类型,并返回结果。
MongoDB 中可以使用的类型如下表所示:
在这里插入图片描述
使用

db.col.insert({
	title:'PHP 教程',
	description:'PHP 是一种创建动态交互性站点的强有力的服务器端',
	by:'小红',
	url:'http://www.baidu.com',
	tags:['php'],
	likes:200
});

db.col.insert({
	title:'Java 教程',
	description:'Java 是一种创建动态交互性站点的强有力的服务器端',
	by:'小红',
	url:'http://www.baidu.com',
	tags:['java'],
	likes:550
});

db.col.insert({
	title:'MongoDB  教程',
	description:'MongoDB 是一个 Nosql 数据库',
	by:'小红',
	url:'http://www.baidu.com',
	tags:['mongodb'],
	likes:100
});

db.col.insert({
	title:23333,
	description:'23333 是一个B站',
	by:'小红',
	url:'http://www.baidu.com',
	tags:['23333'],
	likes:100
});

如果想获取 “col” 集合中 title为 String 的数据,可以使用如下命令:

db.col.find({"title":{$type:2}}).pretty();
或
db.col.find({"title":{$type:'string'}}).pretty();

如果想获取 “col” 集合中 tags 为 Array 的数据,可以使用如下命令:

db.col.find({"tags":{$type:'array'}}).pretty();

索引

说明
索引通常能够极大的提高查询的效率,如果没有索引,MongoDB在读取数据时必须扫描集合中的每个文件并选取那些符合查询条件的记录。这种扫描全集合的查询效率是非常低的。特别在处理大量的数据时,查询可以花费几十秒甚至几分钟,这对网站的性能是非常致命的。索引是特殊的数据结构,索引存储在一个易于遍历读取的数据集合中,索引是对数据库表中一列或多列的值进行排序的一种结构。

原理
在这里插入图片描述
从根本上说,MongoDB中的索引与其他数据库系统中的索引类似。MongoDB在集合层面上定义了索引,并支持对MongoDB集合中的任何字段或文档的子字段进行索引。

操作
·1、创建索引

db.集合名称.createIndex(keys,options)
db.集合名称.createIndex({"title:1","description:-1"})

db.users.createIndex({name:1},{name:'name_index',expireAfterSeconds:15});

-- 创建复合索引
db.users.createIndex({name:1,age:-1},{name:'name_age_index'});

-说明:语法中 Key 值为你要创建的索引字段,1 为指定按升序创建索引,如果你想按降序来创建索引指定为 -1 即可。

createIndex()接收可选参数,可选参数列表如下
在这里插入图片描述
2、查看集合索引

db.集合名称.getIndexes()

3、查看集合索引大小

db.集合名称.totalIndexSize()

4、删除集合所有索引

db.集合名称.dropIndexes()

5、删除集合指定索引

db.集合名称.dropIndex("索引名称")

复合索引
说明:一个索引的值是由多个 key 进行维护的索引的称之为 复合索引

db.集合名称.createIndex({"title":1,"description":-1})

注意:mongoDB中复合索引和传统关系型数据库一致都是左前缀原则
在这里插入图片描述
在这里插入图片描述

整合应用


说明:这里主要以 springboot 应用为基础应用进行整合开发
Spring Data:Spring 数据框架 JPA、Redis、Elasticsearch、AMQP、MongoDB
JdbcTemplate
RedisTemplate
ElasticTemplate
AmqpTemplate
MongoTemplate
SpringBoot Spring Data MongoDB

环境搭建

引入依赖

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

编写配置

# mongodb 没有开启任何安全协议
# mongodb(协议)://121.5.166.13(主机):27017(端口)/baidu(库名)
spring.data.mongodb.uri=mongodb://121.5.166.13.27017/baidu

# mongodb 存在密码
spring.data.mongodb.host=tx.ceshi.cn
spring.data.mongodb.port=27017
spring.data.mongodb.database=baidu
spring.data.mongodb.username=root
spring.data.mongodb.password=root

集合操作

创建集合

@Test
public void testCreateCollection(){
	mongoTemplate.createCollection("users");  //参数:创建集合名称
}

注意:创建集合不能存在,存在报错

删除集合

public void testDeleteCollection(){
	mongoTemplate.dropCollection("users");  //参数:创建集合名称
}

相关注解

  • @Document
    修饰范围:用在类上
    作用:用来映射这个类的一个对象为 mongo 中一条文档数据
    属性:(value、collection)用来指定操作的集合名称
  • @Id
    修饰范围:用在成员变量、方法上
    作用:用来将成员变量的值映射为文档的 _id 的值
  • @Field
    修饰范围:用在成员变量、方法上
    作用:用来将成员变量以及值映射为文档中的一个 key、value对
    属性:(name,value)用来指定在文档中 key 的名称,默认为成员变量名
  • @Transient
    修饰范围:用在成员变量、方法上
    作用:用来指定该成员变量,不参与文档的序列化

文档操作

添加

public void testSaveOrUpdate(){
	User user = new User();
	user.setId("1");
	user.setAge(23);
	user.setName("编程");
	user.setBir(new Date());
	User userDB = mongoTemplate.insert(user); //返回保存的对象  insert or save
	System.out.println(userDB);
}

insert:插入重复数据时,insert 报 DuplicateKeyException 提示主键重复;save 对已存在的数据进行更新。
save:批处理操作时,insert 可以一次性插入整个数据,效率较高;save 需遍历整个数据,一次插入或更新,效率较低。

查询
在这里插入图片描述
常见查询

@Test
public void testQuery(){
	// 基于 id 查询
	template.findById("1",User.class);
	
	//查询所有
	template.findAll(User.class);
	template.find(new Query(),User.class)

	//等值查询
	template.find(Query.query(Criteria.where("name").is("编程")),User.class)

	// > gt   < lt   >= gte   <=  lte
	template.find(Query.query(Criteria.where("age").lt(25)),User.class);

	template.find(Query.query(Criteria.where("age").gt(25)),User.class);

	template.find(Query.query(Criteria.where("age").lte(25)),User.class);
	
	template.find(Query.query(Criteria.where("age").gte(25)),User.class);
	
	//and 查询
	template.find(Query.query(Criteria.where("name").is("编程").and("age").is(23)),User.class);

	//or 查询
	Criteria criteria = new Criteria().
	orOperator(Criteria.where("name").is("编程_1"),Criteria.where("name").is("编程_2"));
	template.find(Query.query(criteria),User.class);

	//and or 查询
	Criteria criteria_1 = new Criteria().and("age").is(23)
	.orOperator(
	Criteria.where("name").is("编程_1"),
	Criteria.where("name").is("编程_2")
	);
	template.find(Query.query(criteria_1),User.class);

	// sort 排序
	Query query = new Query();
	query.with(Sort.by(Sort.Order.desc("age")));  //desc 降序   asc 升序
	template.find(query,User.class);
	
	//skip limit 分页
	Query queryPage = new Query();
	queryPage.with(Sort.by(Sort.Order.desc("age")))  //desc 降序  asc 升序
	.skip(0)  //起始条数
	.limit(4);  //每页显示记录数
	template.find(queryPage,User.class);

	// count 总条数
	template.count(new Query(),User.class);
    template.count(Query.query(Criteria.where("name").is("编程")),User.class)
	//distinct 去重
	//参数 1:查询条件   参数 2:去重字段    参数 3:操作集合   参数 4:返回类型
	template.findDistinct(new Query(),"name",User.class,String.class);

	// 使用 json 字符串方式查询
	Query query = new BasicQuery("{$or:[{name:'编程_1'},{name:'编程_2'}]}","{name:0}");
    template.find(query, User.class);
}

更新

@Test
public void testUpdate(){
	Update update = new Update();
	update.set("salary",4000.1);
	// 更新符合条件的第一条数据
	mongoTemplate.updateFirst(Query.query(Criteria.where("salary").is(2800.1)),update,User.class);
	// 多条更新
	mongoTemplate.updateMulti(Query.query(Criteria.where("salary").is(2800.1)),update,User.class);
	// 没有符合条件的插入数据
	update.setOnInsert("id",10); //设置不使用默认的_id
	UpdateResult updateResult = mongoTemplate.upsert(Query.query(Criteria.where("salary").is(2800.1)),update,User.class);
	
	System.out.println(updateResult.getModifiedCount());
	System.out.println(updateResult.getMatchedCount());
	System.out.println(updateResult.getUpsertedId());
}

删除

@Test
public void testDelete(){
	// 删除所有
	mongoTemplate.remove(new Query(),User.class);
	// 条件删除
	mongoTemplate.remove(Query.query(Criteria.where("name").is("编程")),User.class);
}

副本集 < Replica Set >


说明

MongoDB 副本集 (Replica Set)是有自动故障恢复功能的主从集群,有一个 Primary 节点和一个或多个 Secondary 节点组成。副本集没有固定的主节点,当主节点发生故障时整个集群会选举一个主节点为系统提供服务以保证系统的高可用。
在这里插入图片描述

Automatic Failover

自动故障转移机制:当主节点未与集合的其他成员通信超过配置的选举超时时间(默认为10秒)时,合格的辅助节点将调用选举以将自己提名为新的主节点。集群尝试完成新主节点的选举并恢复正常操作。
在这里插入图片描述

搭建副本集

  • 创建数据目录
- 在安装目录中创建
mkdir -p ../repl/data1
mkdir -p ../repl/data2
mkdir -p ../repl/data3
  • 搭建副本集
./mongod --port 27017 --dbpath ../repl/data1 --bind_ip 0.0.0.0 --replSet myreplace/[121.5.167.13:27018,121.5.167.13:27019]

./mongod --port 27018 --dbpath ../repl/data2 --bind_ip 0.0.0.0 --replSet myreplace/[121.5.167.13:27019,121.5.167.13:27017]

./mongod --port 27019 --dbpath ../repl/data3 --bind_ip 0.0.0.0 --replSet myreplace/[121.5.167.13:27017,121.5.167.13:27018]

注意:--replSet 副本集 myreplace 副本集名称/集群中其他节点的主机和端口

  • 配置副本集,连接任意节点
    use admin
    初始化副本集
var confing = {
	_id:"myreplace",
	members:[
	{_id:0,host:"121.5.167.13:27017"},
	{_id:1,host:"121.5.167.13:27018"},
	{_id:2,host:"121.5.167.13:27019"}	
	]
}

rs.initiate(config);  # 初始化配置

设置客户端临时可以访问

rs.slaveOK();       # 旧版本
re.secondaryOk();   # 新版本

客户端操作副本集

navicat (连接主节点就可以) 客户端软件的问题
在这里插入图片描述
应用程序

# mongo replica set
spring.data.mongodb.uri=mongodb://tx.chenyn.cn:27017,tx.chenyn.cn:27018,tx.chenyn:27019/baizhi?replicaSet=myreplace

分片集群 < Sharding Cluster >


副本集:自动故障转移 主从复制 集群 解决问题:1、数据冗余备份 2、架构高可用
不能解决:单节点压力问题(硬件限制 并发访问压力)

说明

分片(sharding)是指将数据拆分,将其分散在不同机器的过程,有时也用分区(partitioning)来表示这个概念,将数据分散在不同的机器上,不需要功能强大的大型计算机就能存储更多的数据,处理更大的负载。

分片的目的是通过分片能够增加更多机器来应对不断的增加负载和数据,还不应用运行。

MongoDB支持自动分片,可以摆脱手动分片的管理困扰,集群自动切分数据做负载均衡。MongoDB分片的基本思想就是将集合拆分成多个块,这些块分散在若干个片里,每个片只负责总数据的一部分,应用程序不必知道哪些片对应哪些数据,甚至不需要知道数据拆分了,所以在分片之前会运行一个路由进程,mongos进程,这个路由器知道所有的数据存放位置,应用只需要直接与monogs交互即可。mongos自动将请求转到相应的片上获取数据,从应用角度看分不分片没有什么区别。

架构

在这里插入图片描述

  • shard:用于存储实际的数据块,实际生产环境中一个 shard server角色可由几台机器组个一个 replica set 承担,防止主机单点故障。
  • Config Server:mongod 实例,存储了整个 ClusterMetadata。
  • Query Routers:前端路由,客户端由此接入,且让整个集群看上去像单一数据库,前端应用可以透明使用。
  • Shard Key:片键,设置分片时需要在集合中选一个键,用该键的值作为拆分数据的依据,这个片键称之为(shard key),片键的选取很重要,片键的选取决定了数据散列是否均匀。

搭建

1、集群规划

Shard Server 1:27017
Shard Repl   1:27018

Shard Server 2:27019
Shard Repl   2:27020

Shard Server 3:27021
Shard Repl   3:27022

Config Server:27023
Config Server:27024
Config Server:27025

Route Process:27026

2、进入安装的 bin 目录创建数据目录

mkdir -p ../cluster/shard/s0
mkdir -p ../cluster/shard/s0-repl

mkdir -p ../cluster/shard/s1
mkdir -p ../cluster/shard/s1-repl

mkdir -p ../cluster/shard/s2
mkdir -p ../cluster/shard/s2-repl

mkdir -p ../cluster/shard/config1
mkdir -p ../cluster/shard/config2
mkdir -p ../cluster/shard/config3

3、启动4个 shard 服务

# 启动 s0、r0
./mongod --port 27017 --dbpath ../cluster/shard/s0 --bind_ip 0.0.0.0 --shardsvr --replSet r0/121.5.167.13:27018

./mongod --prot 27018 --dbpath ../cluster/shard/s0-repl --bind_ip 0.0.0.0 --shardsvr --replSet r0/121.5.167.13:27017

- 1、登录任意节点
- 2、use admin
- 3、执行
     config = {
		_id:"r0",
		members:[
			{_id:0,host:"121.5.167.13:27017"},
			{_id:1,host:"121.5.167.13:27018"}
		]
	}
	rs.initiate(config);  # 初始化
# 启动 s1、r1
./mongod --port 27019 --dbpath ../cluster/shard/s1 --bind_ip 0.0.0.0 --shardsvr --replSet r1/121.5.167.13:27020

./mongod --port 27020 --dbpath ../cluster/shard/s1-repl --bind_ip 0.0.0.0 --shardsvr --replSet r1/121.5.167.13:27019

- 1、登录任意节点
- 2、use admin
- 3、执行
     config = {
		_id:"r1",
		members:[
			{_id:0,host:"121.5.167.13:27019"},
			{_id:1,host:"121.5.167.13:27020"}
		]
	}
	rs.initiate(config);  # 初始化
# 启动  s2、r2
./mongod --port 27021 --dbpath ../cluster/shard/s2 --bind_ip 0.0.0.0 --shardsvr --replSet r2/121.5.167.13:27022

./mongod --port 27022 --dbpath ../cluster/shard/s2-repl --bind_ip 0.0.0.0 --shardsvr --replSet r2/121.5.167.13:27021

- 1、登录任意节点
- 2、use admin
- 3、执行
     config = {
		_id:"r2",
		members:[
			{_id:0,host:"121.5.167.13:27021"},
			{_id:1,host:"121.5.167.13:27022"}
		]
	}
	rs.initiate(config);  # 初始化

4、启动3个 config 服务

./mongod --port 27023 --dbpath ../cluster/shard/config1 --bind_ip 0.0.0.0 --replSet config/[121.5.167.13:27024,121.5.167.13:27025] --configsvr

./mongod --port 27024 --dbpath ../cluster/shard/config2 --bind_ip 0.0.0.0 --replSet config/[121.5.167.13:27023,121.5.167.13:27025] --configsvr

./mongod --port 27025 --dbpath ../cluster/shard/config3 --bind_ip 0.0.0.0 --replSet config/[121.5.167.13:27023,121.5.167.13:27024] --configsvr

5、初始化 config server 副本集

- 1、登录任意节点 config server
- 2、use admin
- 3、执行
     config = {
		_id:"config",
		configsvr:true,
		members:[
			{_id:0,host:"121.5.167.13:27023"},
			{_id:1,host:"121.5.167.13:27024"},
			{_id:2,host:"121.5.167.13:27025"}
		]
	}
	rs.initiate(config);  # 初始化副本集配置 

6、启动 mongos 路由服务

./mongos --port 27026 --configdb config/121.5.167.13:27023,121.5.167.13:27024,121.5.167.13:27025 --bind_ip 0.0.0.0

7、登录mongos服务

1、登录 mongo --port 27026
2、use admin
3、添加分片信息
   db.runCommand({addshard:"r0/121.5.167.13:27017,121.5.167.13:27018","allowLocal":true});
   db.runCommand({addshard:"r1/121.5.167.13:27019,121.5.167.13:27020","allowLocal":true});
   db.runCommand({addshard:"r2/121.5.167.13:27021,121.5.167.13:27021","allowLocal":true});
4、指定分片的数据库
   db.runCommand({enablesharding:"baizhi"});
5、设置库的片键信息   
   db.runCommand({shardcollection:"baizhi.users",key:{_id:1}})
   db.runCommand({shardcollection:"baizhi.users",key:{_id:"hashed"}})
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值