MongoDB

1、MongoDB概念解析:
SQL和MongoDB概念区别
2、数据库:

"show dbs"命令可以显示所有数据的列表。
"db"命令可以显示当前数据库对象或集合。
"use"命令,可以连接到一个指定的数据库。

3、集合:
db.createCollection("mycoll", {capped:true, size:100000})  
4、元数据:
元数据
5、MongoDB数据类型:
数据类型
  • ObjectId


    ObjectId
6、创建数据库:
use DATABASE_NAME

如果数据库不存在,则创建数据库,否则切换到指定数据库。

  db.runoob.insert({"name":"菜鸟教程"})

集合只有在内容插入后才会创建
MongoDB 中默认的数据库为 test,如果你没有创建新的数据库,集合将存放在 test 数据库中。

7、删除数据库:
db.dropDatabase()#默认删除test

db.collection.drop()#删除集合
  • 例子

    >use runoob
    switched to db runoob
    >show tables
    site
    >db.site.drop()
    true
    >show tables
    >
    
8、创建集合:
db.createCollection(name, options)
  • 创建固定集合 mycol,整个集合空间大小 6142800 KB, 文档最大个数为 10000 个。

    >db.createCollection("mycol", {capped: true, autoIndexId: true, size: 6142800, max: 10000})
    
  • 在 MongoDB 中,你不需要创建集合。当你插入一些文档时,MongoDB 会自动创建集合。

    >db.mycol2.insert({"name":  "haha"})
    >show collections
    mycol2
    
9、删除集合:
db.collection.drop()

#例子
db.mycol2.drop()
10、插入文档:
db.COLLECTION_NAME.insert(document)

#例子
>db.mycol.insert({title: 'MongoDB教程', 
          description: 'MongoDB是一个Nosql数据库',
          by: '菜鸟教程',
          url: 'http://www.runoob.com',
          tags: ['MongoDB', 'database', 'NoSQL'],
          likes: 100
})
  • 查看已插入文档:

    db.mycol.find()
    
  • 将数据定义成一个变量

    > document=({title: 'MongoDB 教程', 
    description: 'MongoDB 是一个 Nosql 数据库',
    by: '菜鸟教程',
    url: 'http://www.runoob.com',
    tags: ['mongodb', 'database', 'NoSQL'],
    likes: 100
    });
    
    >db.mycol.insert(document)
    
    >db.mycol.save(document)
    
11、更新文档:
  • update()方法

    db.collection.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 :可选,抛出异常的级别。
    
  • 实例

    #将标题“MongoDB教程”改为“MongoDB”。
    >db.mycol.update({'title': 'MongoDB教程'}, {$set: {'title': 'MongoDB'}})
    WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })   # 输出信息
    >db.mycol.find().pretty()
    {
            "_id" : ObjectId("56064f89ade2f21f36b03136"),
            "title" : "MongoDB",
            "description" : "MongoDB 是一个 Nosql 数据库",
            "by" : "菜鸟教程",
            "url" : "http://www.runoob.com",
            "tags" : [
                    "mongodb",
                    "database",
                    "NoSQL"
              ],
            "likes" : 100
    }
    >
    >db.mycol.update({'title': 'MongoDB教程'}, {$set: {'title': 'MongoDB'}}, {multi: true})
    
  • save()方法

    db.collection.save(
       <document>,
       {
         writeConcern: <document>
       }
    )
      document : 文档数据。
      writeConcern :可选,抛出异常的级别。
    
  • 推荐使用

    db.collection.updateOne() #更新单个文档
    db.collection.updateMany() #更新多个文档
    
10、删除文档:
  db.collection.remove(
     <query>,
     {
       justOne: <boolean>,
       writeConcern: <document>
     }
  )
    query :(可选)删除的文档的条件。
    justOne : (可选)如果设为 true 或 1,则只删除一个文档。
    writeConcern :(可选)抛出异常的级别。
  • 实例

    > db.col.find()
    { "_id" : ObjectId("56066169ade2f21f36b03137"), "title" : "MongoDB 教程", "description" : "MongoDB 是一个 Nosql 数据库", "by" : "菜鸟教程", "url" : "http://www.runoob.com", "tags" : [ "mongodb", "database", "NoSQL" ], "likes" : 100 }
    { "_id" : ObjectId("5606616dade2f21f36b03138"), "title" : "MongoDB 教程", "description" : "MongoDB 是一个 Nosql 数据库", "by" : "菜鸟教程", "url" : "http://www.runoob.com", "tags" : [ "mongodb", "database", "NoSQL" ], "likes" : 100 }
    #移除
    >db.mycol.remove({'title': 'MongoDB 教程'})
    WriteResult({ "nRemoved" : 2 })           # 删除了两条数据
    
    #删除所有数据
    >db.col.remove({})
    >db.col.find()
    >  
    
  • 推荐使用

    >db.inventory.deleteMany({})  #删除全部文档
    >db.inventory.deleteMany({status: 'A'}) #删除status=A的全部文档
    >db.inventory.deleteOne({status: 'D'}) #删除status=D的一个文档
    
11、查询文档:
  db.collection.find(query, projection)
    query :可选,使用查询操作符指定查询条件
    projection :可选,使用投影操作符指定返回的键。查询时返回文档中所有键值, 只需省略该参数即可(默认省略)。
  • 易读的方式

    >db.collection.find().pretty() #格式化显示所有文档
    
  • MongoDB与RDBMS where语句比较:


  • AND条件

    >db.collection.find({key1: value1, key2: value2}).pretty()
    >db.mycol.find({'by': '菜鸟教程', 'title': 'MongoDB 教程'}).pretty()
    
  • OR条件

    >db.collection.find({$or: [{'by': '菜鸟教程'}, {'title': 'MongoDB 教程'}]}).pretty()
    
  • AND和OR联合使用

    >db.collection.find({'likes': {$gt: 50}, $or: [{'by': '菜鸟教程'}, {'title': 'MongoDB 教程'}]}).pretty()
    
  • projection参数使用方法

    #若不指定 projection,则默认返回所有键,指定 projection 格式如下,有两种模式
    db.collection.find(query, {title: 1, by: 1}) // inclusion模式 指定返回的键,不返回其他键
    db.collection.find(query, {title: 0, by: 0}) // exclusion模式 指定不返回的键,返回其他键
    
12、条件操作符:
  • (>) 大于 - $gt   greater than

  • (<) 小于 - $lt    less than

  • (>=) 大于等于 - $gte

  • (<= ) 小于等于 - $lte

    db.collection.find({likes: {$gt: 100}})
    #Select * from collection where likes >=100;
    db.collection.find({likes: {$lte: 150}})
    
  • 模糊查询

    #查询包含“教”字的文档
    db.collection.find({title: /教/})
    #查询以“教”字开头的文档
    db.collection.find({title: /^教/})
    #查询以“教”字结尾的文档
    db.collection.find({title: /教$/})
    
13、$type操作符:
MongoDB中的类型
#获取集合中title为字符串的数据
>db.collection.find({'title': {$type: 2}})
>db.collection.find({'title': {$type: 'string'}})
15、Limit与Skip方法:
  • Limit()方法:指定数据记录的数量

    >db.COLLECTION_NAME.find().limit(NUMBER)
    
    
    举例
    #第一个 {} 放 where 条件,为空表示返回集合中所有文档。
    #第二个 {} 指定那些列显示和不显示 (0表示不显示 1表示显示)。
    >db.collection.find({}, {'title': 1, _id: 0}).limit(2) 
    { "title" : "PHP 教程" }
    { "title" : "Java 教程" }
    >
    
  • Skip()方法:跳过指定数量的数据

    >db.COLLECTION_NAME.find().limit(NUMBER).skip(NUMBER)
    
    
    举例
    >db.collection.find({}, {'title': 1, _id: 0}).limit(1).skip(1) #skip默认参数为0
    { "title" : "Java 教程" }
    >
    
  • Limit() 和Skip()联合举例

    #想要读取从 10 条记录后 100 条记录,相当于 sql 中limit (10,100)。
    >db.COLLECTION_NAME.find().skip(10).limit(100)
    
    #假设查询第100001条数据,这条数据的Amount值是:2399927,
    >db.test.sort({"amount":1}).skip(100000).limit(10)  //183ms
    >db.test.find({amount:{$gt:2399927}}).sort({"amount":1}).limit(10)  //53ms
    >
    
16、排序Sort()方法:
# 通过使用1和-1来指定排序的方式
>db.COLLECTION_NAME.find().sort({KEY: 1})

#举例
>db.collection.find({}, {'title': 1,  _id: 0}).sort({'likes': -1})
{ "title" : "PHP 教程" }
{ "title" : "Java 教程" }
{ "title" : "MongoDB 教程" }
>
  • 注:skip(), limilt(), sort()三个放在一起执行的时候,执行的顺序是先 sort(), 然后是 skip(),最后是显示的 limit()
17、索引——createIndex()方法
>db.collection.createIndex(keys, options)
#key为需要创建索引字段,1 为指定按升序创建索引


举例
>db.collection.createIndex({'title': 1})
#使用多个字段创建索引(关系型数据库中称作复合索引)
>db.collection.createIndex({'title': 1, 'description': -1})
18、聚合——aggregate()方法
>db.COLLECTION_NAME.aggregate(AGGREGATE_OPERATION)
  • 集合

    {
       _id: ObjectId(7df78ad8902c)
       title: 'MongoDB Overview', 
       description: 'MongoDB is no sql database',
       by_user: 'runoob.com',
       url: 'http://www.runoob.com',
       tags: ['mongodb', 'database', 'NoSQL'],
       likes: 100
    },
    {
       _id: ObjectId(7df78ad8902d)
       title: 'NoSQL Overview', 
       description: 'No sql database is very fast',
       by_user: 'runoob.com',
       url: 'http://www.runoob.com',
       tags: ['mongodb', 'database', 'NoSQL'],
       likes: 10
    },
    {
       _id: ObjectId(7df78ad8902e)
       title: 'Neo4j Overview', 
       description: 'Neo4j is no sql database',
       by_user: 'Neo4j',
       url: 'http://www.neo4j.com',
       tags: ['neo4j', 'database', 'NoSQL'],
       likes: 750
    },
    
  • 举例

    #计算每个作者所写的文章数 
    >db.collection.aggregate([{$group: {_id: '$by_user', num_tutorial: {$sum: 1}}}])   
    {
       "result" : [
          {
             "_id" : "runoob.com",
             "num_tutorial" : 2
          },
          {
             "_id" : "Neo4j",
             "num_tutorial" : 1
          }
       ],
       "ok" : 1
    }
    >
    类似于
    select by_user, count(*) from collection group by by_user  
    
聚合表达式
  • 管道的概念


    聚合框架中常用的几个操作

1、$project实例

  >db.mycol.aggregate(
        {$project:{
              title: 1,
              author: 1,
        }});

2、$match实例

#$match用于获取分数大于70小于或等于90记录,然后将符合条件的记录送到下一阶段$group管道操作符进行处理。
>db.mycol.aggregate([
                {$match: {score: {$gt: 70, $lte: 90}}} ,
                {$group: {_id: null, count: {$sum: 1}}}           
                ]);

3、skip实例

  >db.mycol.aggregate(
              {$skip: 5});
19、MongoDB关系:

1:1
1:N
N:1
N:N

  • 嵌入式关系

    {"_id":ObjectId("52ffc33cd85242f436000001"),
     "contact": "987654321",
     "dob": "01-01-1991",
     "name": "Tom Benzamin",
     "address": [
        {
           "building": "22 A, Indiana Apt",
           "pincode": 123456,
           "city": "Los Angeles",
           "state": "California"
        },
        {
           "building": "170 A, Acropolis Apt",
           "pincode": 456789,
           "city": "Chicago",
           "state": "Illinois"
        }]
    }
    
    
    #查询用户地址
    db.users.findOne({"name": "Tom Benzamin"}, {"address": 1})
    
  • 引用式关系

    #通过引用文档的id字段来建立关系
    {
       "_id":ObjectId("52ffc33cd85242f436000001"),
       "contact": "987654321",
       "dob": "01-01-1991",
       "name": "Tom Benzamin",
       "address_ids": [
          ObjectId("52ffc4a5d85242602e000000"),
          ObjectId("52ffc4a5d85242602e000001")
       ]
    }
    
    
    #这种方法需要两次查询:
    #第一次查询用户地址的对象id;
    #第二次通过查询的id获取用户的详细地址信息。
    >var result = db.users.findOne({"name": "Tom Benzamin"}, {"address_ids": 1})
    >var addresses = db.address.find({"_id": {"$in": result["address_ids"]}})
    
20、数据库引用:
  • 使用DBRefs

    {$ref:  , $id:  , $db:  }
    

ref:集合名称id:引用的id
$db:数据库名称,可选参数

  { "_id":ObjectId("53402597d852426020000002"),
     "address": {
           "$ref": "address_home",
           "$id": ObjectId("534009e4d852427820000002"),
           "$db": "runoob"
            },
     "contact": "987654321",
     "dob": "01-01-1991",
     "name": "Tom Benzamin"
  }


  #通过指定$ref参数(address_home集合)来查找集合中指定id的用户地址信息
  >var user = db.users.findOne({"name": "Tom Benzamin"})
  >var dbRef = user.address
  >db[dbRef.$ref].findOne({"_id": (dbRef.$id)})
  
  #返回结果
  {
     "_id" : ObjectId("534009e4d852427820000002"),
     "building" : "22 A, Indiana Apt",
     "pincode" : 123456,
     "city" : "Los Angeles",
     "state" : "California"
  }
21、查询分析:
  • 使用explain()

    #在集合中创建索引
    >db.users.ensureIndex({gender: 1, user_name: 1})
    >db.users.find({gender: "M"}, {user_name: 1, _id: 0}).explain()
    
    
    #查询结果
    {
       "cursor" : "BtreeCursor gender_1_user_name_1",
       "isMultiKey" : false,
       "n" : 1,
       "nscannedObjects" : 0,
       "nscanned" : 1,
       "nscannedObjectsAllPlans" : 0,
       "nscannedAllPlans" : 1,
       "scanAndOrder" : false,
       "indexOnly" : true,
       "nYields" : 0,
       "nChunkSkips" : 0,
       "millis" : 0,
       "indexBounds" : {
          "gender" : [
             [
                "M",
                "M"
             ]
          ],
          "user_name" : [
             [
                {
                   "$minElement" : 1
                },
                {
                   "$maxElement" : 1
                }
             ]
          ]
       }
    }
    

indexOnly: 字段为 true ,表示我们使用了索引。

cursor:因为这个查询使用了索引,MongoDB 中索引存储在B树结构中,所以这是也使用了 BtreeCursor 类型的游标。如果没有使用索引,游标的类型是 BasicCursor。这个键还会给出你所使用的索引的名称,你通过这个名称可以查看当前数据库下的system.indexes集合(系统自动创建,由于存储索引信息,这个稍微会提到)来得到索引的详细信息。

n:当前查询返回的文档数量。

nscanned/nscannedObjects:表明当前这次查询一共扫描了集合中多少个文档,我们的目的是,让这个数值和返回文档的数量越接近越好。

millis:当前查询所需时间,毫秒数。

indexBounds:当前查询具体使用的索引。

  • 使用hint()
    使用hint来强制MongoDB使用一个指定的索引。

    #如下查询实例指定了使用 gender 和 user_name 索引字段来查询:
    >db.users.find({gender: "M"}, {user_name: 1, _id: 0}).hint({gender: 1, user_name: 1})
    #可以使用 explain() 函数来分析以上查询:
    >db.users.find({gender:"M"},{user_name:1,_id:0}).hint({gender:1,user_name:1}).explain()
    
22、原子操作:

原子操作常用命令:

  • $set:用来指定一个键并更新键值,若键不存在则创建

    { $set : { field : value } }
    
  • $unset:用来删除一个键。

    { $unset : { field : 1} }
    
  • $inc:可以对文档的某个值为数字型(只能为满足要求的数字)的键进行增减的操作。

    { $inc : { field : value } }
    
  • $push:把value追加到field里面去,field一定要是数组类型才行,如果field不存在,会新增一个数组类型加进去。

    { $push : { field : value } }
    
  • $pushAll:一次可以追加多个值到一个数组字段内。

    { $pushAll : { field : value_array } }
    
  • $pull:从数组field内删除一个等于value值。

    { $pull : { field : _value } }
    
  • $addToSet:增加一个值到数组内,而且只有当这个值不在数组内才增加。

  • $pop:删除数组的第一个或最后一个元素

    { $pop : { field : 1 } }
    
  • $rename:修改字段名称

    { $rename : { old_field_name : new_field_name } }
    
  • $bit:位操作,integer类型

    {$bit : { field : {and : 5}}}
    

举例

> t.find() 
{ "_id" : ObjectId("4b97e62bf1d8c7152c9ccb74"), "title" : "ABC", "comments" : [ { "by" : "joe", "votes" : 3 }, { "by" : "jane", "votes" : 7 } ] }

> t.update( {'comments.by':'joe'}, {$inc:{'comments.$.votes':1}}, false, true )

> t.find() 
{ "_id" : ObjectId("4b97e62bf1d8c7152c9ccb74"), "title" : "ABC", "comments" : [ { "by" : "joe", "votes" : 4 }, { "by" : "jane", "votes" : 7 } ] }
23、高级索引:
{
   "address": {
      "city": "Los Angeles",
      "state": "California",
      "pincode": "123"
   },
   "tags": [
      "music",
      "cricket",
      "blogs"
   ],
   "name": "Tom Benzamin"
}
  • 索引数组字段

    >db.users.ensureIndex({"tags": 1})
    #创建索引后,检索集合的tags字段
    >db.users.find({tags: "music"})
    >db.users.find({tags: "music"}).explain()
    
  • 索引子文档字段

    >db.users.ensureIndex({"address.city": 1, "address.state": 1, "address.pincode": 1})
    >db.users.find({"address.city": "Los Angeles"})
    >db.users.find({"address.state":"California","address.city":"Los Angeles"})
    >
    
24、ObjectId:
  • ObjectId格式:
    前4个字节表示时间戳
    接下来的3个字节是机器标识码
    紧接的两个字节由进程id组成(PID)
    最后三个字节是随机数

    #创建新的ObjectId
    >newObjectId = ObjectId()
    ObjectId("5349b4ddd2781d08c09890f3")
    
    
    #创建文档的时间戳
    >ObjectId("5349b4ddd2781d08c09890f4").getTimestamp()
    #返回文档创建时间
    ISODate("2014-04-12T21:49:17Z")
    
    
    #ObjectId转换为字符串
    >new ObjectId().str
    #返回Guid格式的字符串
    5349b4ddd2781d08c09890f3
    
25、Map Reduce:
  • MapReduce基本语法:

    >db.collection.mapReduce(
          function()  {emit(key, value);},   //map函数
          function(key, values) {return reduceFunction},   //reduce函数
          {
              out: collection,
              query: document,
              sort: document,
              limit: number
          }
    )
    

注:使用 MapReduce 要实现两个函数 Map 函数和 Reduce 函数,Map 函数调用 emit(key, value),遍历 collection 中所有的记录,将 key 与 value 传递给 Reduce 函数进行处理。

  • 参数说明
    **map **:映射函数 (生成键值对序列,作为 reduce 函数参数)。
    reduce:统计函数,reduce函数的任务就是将key-values变成key-value,也就是把values数组变成一个单一的值value。。
    out:统计结果存放集合 (不指定则使用临时集合,在客户端断开后自动删除)。
    query:一个筛选条件,只有满足条件的文档才会调用map函数。(query,limit,sort可以随意组合)
    sort:和limit结合的sort排序参数(也是在发往map函数前给文档排序),可以优化分组机制
    limit:发往map函数的文档数量的上限(要是没有limit,单独使用sort的用处不大)

  • 举例
    在集合 orders 中查找 status:"A" 的数据,并根据 cust_id 来分组,并计算 amount 的总和。


    >db.posts.mapReduce( 
       function() { emit(this.user_name,1); }, 
       function(key, values) {return Array.sum(values)}, 
          {  
             query:{status:"active"},  
             out:"post_total" 
          }
    )
    
    #输出结果
    {
            "result" : "post_total",
            "timeMillis" : 23,
            "counts" : {
                    "input" : 5,
                    "emit" : 5,
                    "reduce" : 1,
                    "output" : 2
            },
            "ok" : 1
    }
    

未完待续...

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

推荐阅读更多精彩内容