Mongodb 基本操作

Mongodb的特点:

  • 模式自由:可以把不同结构的文档存储在同一个数据库里
  • 面向集合的储存:适合储存JSON风格文件的形式
  • 完整的索引支持:对任何属性可索引
  • 复制和高可用性:支持服务器之间的数据复制吗,支持主从模式和服务器间的相互复制,目的是提供冗余及自动故障转移
  • 自动分片:支持水平的数据库集群,可动态添加额外的机器
  • 丰富的查询:丰富的查询表达式,查询指令使用JSON形式的标记和查询文档中内嵌的对象和数组
  • 快速就地更新:查询优化器会分析查询表达式,并生成一个高效的查询计划
  • 高效的传统储存方式:支持二进制数据集大型对象(如:图片或照片)

基本操作

  • MongoDB将数据储存为一个文档,数据结构由键值对组成(key=>value)
  • MongoDB文档类似于JSON对象,字段值可以包含其他文档、数组、文档数组
  • 安装管理mongodb环境、完成数据库、集合的管理
  • 数据的增加、修改、删除、查询
SQL属于/概念 MongoDB术语/概念 解释/说明
database database 数据库
table collection 数据库表/集合
row document 数据记录行/文档
column field 数据字段/域
index index 索引
table joins 表连接,MongoDB不支持
primary key primary key 主键,MongoDB自动将_id字段设置为主键

三元素:数据库、集合、文档

  • 数据库是一个集合的物理容器
  • 集合是关系型数据库中的表
  • 文档对应着关系型数据库中的行
    1). 文档,就是一个对象,由键值对构成,是json的扩展Bson形式 {'name':'guojing','gender':'男’}
    2). 集合:类似于关系数据库中的表,储存多个文档,结构不固定,如可以存储如下文档在一个集合中 {'name':'guojing','gender':'男'} {'name':'huangrong','age':18} {'book':'shuihuzhuan','heros':'108’}
    3). 一个mongodb中可以建立多个数据库
    4). 数据库:是一个集合的物理容器,一个数据库中可以包含多个集合

1.不能是空字符串("")。
2.不得含有' '(空格)、.、[图片上传失败...(image-5a78e2-1549697942656)]

6). 文档:就是一个对象,由键值对构成,是json的扩展Bson(可以理解为在JSON的基础上添加了一些json中没有的数据类型)形式。 需要注意的是:

1.文档中的键/值对是有序的。
2.文档中的值不仅可以是在双引号里面的字符串,还可以是其他几种数据类型(甚至可以是整个嵌入的文档)。
3.MongoDB区分类型和大小写。
4.MongoDB的文档不能有重复的键。
5.文档的键是字符串。除了少数例外情况,键可以使用任意UTF-8字符。

文档键命名规范:

1.键不能含有\0 (空字符)。这个字符用来表示键的结尾。

  1. .和$有特别的意义,只有在特定环境下才能使用。

启动MongoDB

启动MongoDB服务

sudo service mongod start

  • 停止,停止后输入将不能启动shell

sudo service mongod stop

  • 重置服务

sudo service mongod restop

  • 启动mongodb客户端

mongo

  • 终端退出连接

exit 或 ctrl+c

MongoDB的基本操作

  • 查看当前数据库名称

db

  • 列出所有在物理上存在的数据库

show dbs

  • 切换数据库 如果数据库不存在,则指向数据库,但不创建,直到插入数据或创建集合时数据库才被创建

use 数据库名称

  • 查看当前数据库信息

db.stats()

db:当前数据库的名字。
collections:当前数据库的集合数。
objects:当前数据库所有集合总所包含的对象
(即文档)的数量。
avgObjSize:每个文档的平均大小(以字节
为单位)。
dataSize:此数据库中保存的未压缩数据的
总大小,不是指占有磁盘大小,单位是bytes。
storageSize:分配给此数据库的集合用于
存储文档的空间总量,也就是当前数据库占
有磁盘大小,单位是bytes。
numExtents:当前数据库所有集合包含的
扩展数量的统计。
indexes:数据库中包含的所有集合的索引
总数,也就是system.indexes表数据行数。
indexSize:此数据库上创建的所有索引的
总大小,单位是bytes。

  • 数据库删除:删除当前指向的数据库,如果数据库不存在,则什么也不做

db.dropDatabase()

集合的相关操作

  • 创建集合

db.createCollection(name, options)

  • name是要创建的集合的名称
  • options是一个文档,用于指定集合的配置 选项参数是可选的,所以只需要到指定的集合名称。以下是可以使用的选项列表:
字段 类型 描述
capped 布尔 (可选)如果为 true,则创建固定集合。固定集合是指有着固定大小的集合,当达到最大值时,它会自动覆盖最早的文档。 当该值为 true 时,必须指定 size 参数。
autoIndexId 布尔 (可选)如为 true,自动在 _id 字段创建索引。默认为 false。
size 数值 (可选)为固定集合指定一个最大值.当文档达到上限时,会将之前的数据覆盖,单位为字节。如果 capped 为 true,也需要指定该字段。
max 数值 (可选)指定固定集合中包含文档的最大数量。
  • 例1:不限制集合大小

db.createCollection("stu")

  • 例2:限制集合大小,后面学会插入语句后可以查看效果

参数capped:默认值为false表示不设置上限,值为
true表示设置上限
参数size:当capped值为true时,需要指定此参数
,表示上限大小,当文档达到上限时,会将之前的数
据覆盖,单位为字节

db.createCollection(
"sub",
{

capped : true,
size : 10
}
)

  • 例3:创建固定集合 sub,且文档最大个数为 100 个。

db.createCollection(
"sub",
{
capped : true,
size : 10 ,
max:100
}
)

  • 查看数据库集合

show collections:当前数据库的集合数。

  • 删除集合

db.集合名称.drop()
如果成功删除选定集合,则 drop() 方法返回 true,否则返回 false

文档

文档是一组键值对(即BSON)。MongoDB 的文档不需要设置相同的字段,并且相同的字段不需要相同的数据类型,这与关系型数据库有很大的区别,也是 MongoDB 非常突出的特点。
再次对比关系型数据库与非关系型数据库的相关术语: 下表列出了 RDBMS 与 MongoDB 对应的术语:

RDBMS MongoDB
数据库 数据库
表格 集合
文档
字段
表联合 嵌入文档
主键 主键 (MongoDB 提供了 key 为 _id )

MongoDB 数据类型

下表为MongoDB中常用的几种数据类型。

数据类型 描述
String 字符串。存储数据常用的数据类型。在 MongoDB 中,UTF-8 编码的字符串才是合法的。
Integer 整型数值。用于存储数值。根据你所采用的服务器,可分为 32 位或 64 位。
Boolean 布尔值。用于存储布尔值(真/假)。
Double 双精度浮点值。用于存储浮点值。
Array 用于将数组或列表或多个值存储为一个键。
Timestamp 时间戳。记录文档修改或添加的具体时间。
Object 用于嵌入式的文档,即一个值为一个文档
Null 用于创建空值。
Date 日期时间。用 UNIX 时间格式来存储当前日期或时间。你可以指定自己的日期时间:创建 Date 对象,传入年月日信息。
Object ID 对象 ID。用于创建文档的 ID。
Binary Data 二进制数据。用于存储二进制数据。

ObjectId

ObjectId 类似唯一主键,可以很快的去生成和排序

  • 每个文档都有一个属性,为_id,保证每个文档的唯一性
  • 可以自己去设置_id插入文档
  • 如果没有提供,那么MongoDB为每个文档提供了一个独特的_id,类型为objectID
  • objectID是一个12字节的十六进制数 前4个字节为当前时间戳 接下来3个字节的机器ID 接下来的2个字节中MongoDB的服务进程id 最后3个字节是简单的增量值

插入文档

  • 单条插入

db.集合名称.insert(document)

注意:插入文档时,如果不指定_id参数,MongoDB会为文档分配一个唯一的ObjectId*

  • 例:

db.stu.insert(
{name:'xxx',gender:1}
)

db.stu.insert(
{
_id:'20201226',
name:'xxxx',gender:1
}
)

  • 多条插入:

db.stu.insert(
[
{name:'王明',gender:1},
{name:'王玲玲',gender:0}
]
)

在3.2版本之后还提供了插入多条数据和插入单条数据的方法

db.集合名称.insertOne(document)
db.集合名称.insertMany(document)

查询全部文档

db.集合名称.find()

更新文档

MongoDB 使用 update() 和 save() 方法来更新集合中的文档。两个函数是有区别的。

  • update() 方法 update() 方法用于更新已存在的文档

db.collection.update(
<query>,
<update>,
{
upsert: <boolean>,
multi: <boolean>,
}
)

参数说明

  • query : update的查询条件,类似sql update查询内where后面的。

  • update : update的对象和一些更新的操作符(如[图片上传失败...(image-208522-1549697942656)]

    inc...)等,也可以 理解为sql update查询内set后面的

  • upsert : 可选,这个参数的意思是,如果不存在update 的记录,是否插入objNew,true为插入,默认是false,不插 入。

  • multi : 可选,mongodb 默认是false,只更新找到的第 一条记录,如果这个参数为true,就把按条件查出来多条记录 全部更新。
    例1:全文档更新

db.stu.update(
{name:'xxxxx'},
{name:'张xxx'}
)

例2:指定属性更新,通过操作符$set

db.stu.insert(
{name:'李自成',gender:1}
)
db.stu.update(
{name:'李自成'},
{
$set:{name:'闯王李自成'}
}
)

例4:修改多条匹配到的数据(跟新所有name为‘李自成’的文档的dender字段)

db.stu.update(
{name:'李自成'},
{
$set:{gender:0}
},
{multi:true}
)

  • save() 方法 save() 方法通过传入的文档来替换已有文档,如果文档的_id已经存在则修改,如果文档的_id不存在则添加

db.集合名称.save(document)
db.stu.save(
{
_id:'20180820101010',
'name':'跟新'
}
)

pretty()

该函数,将数据以格式化的方式展示

db.集合名称.find().pretty()

  • 删除文档
    remove() 方法的基本语法格式如下所示:

db.collection.remove(
<query>,
{justOne: <boolean>,}
)

参数说明

  • query :(可选)删除的文档的条件。
  • justOne : (可选)如果设为 true 或 1,则只删除一个文档。

db.集合名称.remove(document)

  • 例1:只删除1条,1表示是否只删除一条为true的意思

db.集合名称.remove(
document,1
)
db.集合名称.remove(
document,
{justOne:true}
)

  • 例2:表示删除全部

db.集合名称.remove({})基本的数据查询
db.集合名称.find({条件文档})

  • findOne():查询,只返回第一个

db.集合名称.findOne({条件文档})

  • 例:查询出姓名等于李某某的学生

db.stu.find({name:'李某某'})
db.stu.findOne({name:'李某某'})比较运算符

  • 等于,默认是等于判断,没有运算符
  • 小于$lt
  • 小于或等于$lte
  • 大于$gt
  • 大于或等于$gte
  • 不等于$ne
    例:查询年龄大于或等于18的学生

db.stu.find({age:{$gte:18}})

逻辑运算符

查询时可以有多个条件,多个条件之间需要通过逻辑运算符连接
逻辑与:默认是逻辑与的关系
例:查询年龄大于或等于18,并且性别为1的学生

db.stu.find(
{
age:{$gte:18},
gender:1
}
)

逻辑或:使用$or

例4:查询年龄大于18,或性别为0的学生

db.stu.find(
{
[图片上传失败...(image-16b3f6-1549697942655)]

gt:18}},
{gender:1}
]
}
)

and和or一起使用

例5:查询年龄大于18或性别为0的学生,并且学生的姓名为gj

db.stu.find(
{
[图片上传失败...(image-22b084-1549697942655)]

gte:18}},
{gender:1}
],
name:'gj'
}
)


范围运算符

使用"[图片上传失败...(image-274450-1549697942659)]

nin" 判断是否在某个范围内 例:查询年龄为18、28的学生

db.stu.find(
{
age:{$in:[18,28]}
}
)

支持正则表达式

使用//或$regex编写正则表达式 例:查询姓李的学生

db.stu.find(
{name:/^李/}
)
db.stu.find(
{
name:{$regex:'^李'}
}
)

$type

想要获取某一中类型的数据 例如:如果想获取 "col" 集合中 title 为 String 的数据,你可以使用以下命令:

db.col.find(
{
"title" : {$type : 'string'}
}
)

Limit与Skip方法
  • limit() 方法 读取指定数量的数据记录

db.集合名称.find().limit(num)

  • Skip() 方法 使用skip()方法来跳过指定数量的数据,skip方法同样接受一个数字参数作为跳过的记录条数。

db.集合名称.find().skip(num)

limit() 方法、Skip() 方法 同时使用,不分先后顺序 表示跳过多少条,返回多少条
查询第5至8条数据

db.stu.find().limit(4).skip(5)
db.stu.find().skip(5).limit(4)

sort() 方法排序

**sort() **方法对数据进行排序,sort() 方法可以通过参数指定排序的字段,并使用 1 和 -1 来指定排序的方式,其中 1 为升序排列,而 -1 是用于降序排列。

  • 升序

db.集合名称.find().sort({排序字段:1})

  • 降序

db.集合名称.find().sort({排序字段:-1})

根据多个字段排序: 例:先根据年龄做降序,再根据性别做升序

db.集合名称.find().sort({age:-1,gender:1})

注意: skip(), limilt(), sort()三个放在一起执行的时候,执行的顺序是先 sort(), 然后是 skip(),最后是显示的 limit()。

distinct() 去重

db.集合名称.distinct('去重字段',{条件})

例:查找年龄大于20的姓名(去重)

db.集合名称.distinct('name',{age:{$gt:20}})
project投影(可以指定想要返回的字段)
在查询到的返回结果中,只选择必要的字段,而不是选择一个文档的整个字段
参数为字段与值,值为1表示显示,值为0不显示
db.集合名称.find({},{字段名称:0,...})

count() 统计个数

db.集合名称.count({条件})

  • 表示返回集合中的文档数量

db.集合名称.find().count()
db.集合名称.find({条件}).count()

  • 在count()函数中添加条件
    例:统计年龄大于20的男生人数

db.集合名称.count(
{
age:{$gt:20},
gender:1})

MongoDB的聚合

aggregate() 方法

MongoDB中聚合的方法使用aggregate() 语法
aggregate() 方法的基本语法格式如下所示:

db.COLLECTION_NAME.aggregate(AGGREGATE_OPERATION)

管道的概念

  • 管道在Unix和Linux中一般用于将当前命令的输出结果作为下一个命令的参数。
  • MongoDB的聚合管道将MongoDB文档在一个管道处理完毕后将结果传递给下一个管道处理。管道操作是可以重复的。
  • 表达式:处理输入文档并输出。表达式是无状态的,只能用于计算当前聚合管道的文档,不能处理其它的文档。
    聚合框架中常用的几个操作:

$group:将集合中的文档分组,可用于统计结果。
$project:修改输入文档的结构。可以用来重命名、增加或
删除域,也可以用于创建计算结果以及嵌套文档。
$match:用于过滤数据,只输出符合条件的文档,$match使用MongoDB的标准查询操作。

$limit:用来限制MongoDB聚合管道返回的文档数。
$skip:在聚合管道中跳过指定数量的文档,并返回余下的文
档。
$unwind:将文档中的某一个数组类型字段拆分成多条,每条
包含数组中的一个值。
$sort:将输入文档排序后输出。
$sum: 计算总和
$avg:计算平均值
$min:获取最小值
$max:获取最大值
$count:计算总数
$push:插入一个值到元组
$first:排序获取第一个文档数据
$last:排序获取最后一个文档数据

管理员权限

  • root:只在admin数据库中可用,超级账号,超级权限
  • Read:允许用户读取指定的数据库
  • readWrite:允许用户读写指定数据库

创建超级管理用户

use admindb.createUser({   user:'admin',pwd:'123',roles:[{role:'root',db:'admin'}]})

启用安全认证

新版本 注意:keys and values之间一定要加空格, 否则解析会报错

security:
  authorization: enabled

低版本

或者修改配置文件将auth=true前面的注释拿掉然后保存并退出

修改普通用户权限或密码
  • 修改用户:可以修改pwd、roles属性
  • 注意这里只有超级管理员才有权限修改普通用户的密码和管理权限
  • 修改用户密码(切换到有权限操作的数据库下)

db.updateUser(‘ljh',{pwd:'456'})

  • 添加用户权限(切换到有权限操作的数据库下)

db.grantRolesToUser('username',[{role:'',db:''}])

  • 移除用户权限(切换到有权限操作的数据库下)

db.revokeRolesFromUser('username',[{role:'',db:''}])

  • 删除用户(方式一)(切换到有权限操作的数据库下)

db.dropUser('username')

  • 删除用户 (方式二)

use admin db.system.users.remove({user:'username'})

MongoDB 备份(mongodump)

  • 在Mongodb中我们使用mongodump命令来备份MongoDB数据。该命令可以导出所有数据到指定目录中。

mongodump -h dbhost -d dbname -o dbdirectory

  • -h: MongDB所在服务器地址,例如:127.0.0.1,当然也可以指定端口号:127.0.0.1:27017
  • -d: 需要备份的数据库实例,例如:test
  • -o: 备份的数据存放位置,例如:c:\data\dump,当然该目录需要提前建立,在备份完成后,系统自动在dump目录下建立一个test目录,这个目录里面存放该数据库实例的备份数据。
如果没有开启权限

mongodump -h 127.0.0.1:27017 -d 数据库名称 -o ~/Desktop/数据库备份的路径
mongodump -h 127.0.0.1:27017 -o ~/Desktop/数据库备份的路径

如果开启了权限设置(使用超级管理员权限)

我们也可以使用账号密码的方式备份指定用户的数据库
设置了超级管理员也可以使用如下方法备份

mongodump -u username -p password --authenticationDatabase 'admin' -d dbname -o dbpath

恢复备份

mongodb使用 mongorestore 命令来恢复备份的数据。

mongorestore -h hostname:port -d dbname --dir path

  • -h (host): MongoDB所在服务器地址,默认为: localhost:27017
  • -d (db): 需要恢复的数据库实例,例如:test,当然这个名称也可以和备份时候的不一样,比如test2
  • --dir: 指定备份的目录
如果没有开启权限

mongorestore -h 127.0.0.1:27017 -d 数据库名称 --dir 数据库备份文件路径
mongorestore -h 127.0.0.1 -dir 数据库备份文件路径

下面是我添加了管理员权限之后的备份命令如果开启了权限设置(使用超级管理员权限)

mongorestore -u username -p password --authenticationDatabase 'admin' -d 数据库名称 --dir 数据库备份文件路径

MongoDB数据导入与导出导出工具:

mongoexport

mongoexport -d dbname -c collectionname -o file --type json/csv -f field

参数说明:

  • d :数据库名
  • c :collection名
  • o :输出的文件名
  • -type : 输出的格式,默认为json
  • f :输出的字段,如果-type为csv,则需要加上-f "字段名"
    示例:

导出json

mongoexport -d class1804 -c books -o ~/桌面/dump/books.json --type json

导出csv

mongoexport -d class1804 -c books -o ~/桌面/dump/books.csv --type csv -f 'by_user,likes'数据导入:

mongoimport

mongoimport -d dbname -c collectionname --file filename --headerline --type json/csv

参数说明:

  • d:数据库名
  • c:collection名
  • -type:导入的格式默认json
  • f:导入的字段名
  • -headerline:如果导入的格式是csv,则可以使用第一行的标题作为导入的字段
  • -file:要导入的文件
    示例:
    导入json

mongoimport -d class1712B -c books --file ~/桌面/dump/books --type json

导入csv

mongoimport -d class1712B -c info --file ~/桌面/dump/books --headerline --type csv

什么是复制

  • 复制提供了数据的冗余备份,并在多个服务器上存储数据副本,提高了数据的可用性,并可以保证数据的安全性
  • 复制还允许从硬件故障和服务中断中恢复数据 为什么要复制
  • 数据备份
  • 数据灾难恢复
  • 读写分离
  • 高(24* 7)数据可用性
  • 无宕机维护
  • 副本集对应用程序是透明

复制的工作原理

  • 复制至少需要两个节点A、B...
  • A是主节点,负责处理客户端请求
  • 其余的都是从节点,负责复制主节点上的数据
  • 节点常见的搭配方式为:一主一从、一主多从
  • 主节点记录在其上的所有操作,从节点定期轮询主节点获取这些操作,然后对自己的数据副本执行这些操作,从而保证从节点的数据与主节点一致
  • 主节点与从节点进行数据交互保障数据的一致性
  • 读写分离,基本的原理是让主数据库处理事务性增、改、删操作(INSERT、UPDATE、DELETE),而从数据库处理SELECT查询操作。数据库复制被用来把事务性操作导致的变更同步到集群中的从数据库,读写分离的目的是为了实现高并发场景下的请求分流,避免对数据库的访问过于集中,导致性能下降甚至是宕机。

直接使用主从复制局限

(1)主从复制在master宕机后,没有自动选举master机制,导致主节点服务一挂,便不能对外提供增删改操作。
(2)所有增删改操作都是针对主节点进行操作,可能导致主节点性能下降。
(3)从节点对主节点的数据都是全量拷贝,对主从节点的压力都是不小的。

复制的特点

  • N 个节点的集群
  • 任何节点可作为主节点
  • 所有写入操作都在主节点上
  • 自动故障转移
  • 自动恢复
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容