elasticsearch中DSL基本用法

一、QueryString查询

即通过get形式查询,条件都在url中

Domain Specific Language,格式化查询语句

1、请求参数的查询(QueryString)

  • 请求(GET)
192.168.56.101:9200/shop/_search?q=desc:一纸家书网&q=age:20

GET /shop/_search?q=desc:一纸家书网
GET /shop/_search?q=nickname:家书&q=age:25

  • 注意:

text与keyword搜索对比测试(keyword不会被倒排索引,不会被分词)

二、DSL语法查询

QueryString用的很少,一旦参数复杂就难以构建,所以大多查询都会使用dsl来进行查询更好。

  • Domain Specific Language
  • 特定领域语言
  • 基于JSON格式的数据查询
  • 查询更灵活,有利于复杂查询

1、DSL格式语法

# 查询
POST  192.168.56.101:9200/shop/_search
{
    "query": {
        "match": {
            "desc": "一纸家书网"
        }
 
   }
}
# 判断某个字段是否存在
{
    "query": {
        "exists": {
            "field": "desc"
        }
    }
}

  • 语法格式为一个json object,内容都是key-value键值对,json可以嵌套。
  • key可以是一些es的关键字,也可以是某个field字段,后面会遇到

2、查询所有数据

  • 查询索引中的所有字段

    • QS查询方式
    GET    xxx.xxx.xx.xxx:9200/shop/_search
    
    • DSL查询方式
    POST   xxx.xxx.xx.xxx:9200/shop/_search
    
    {
        "query": {
            "match_all": {}
        }
    }
    
  • 查询部分字段信息

    • QS查询方式
    GET   xxx.xxx.xx.xxx:9200/shop/_search?_source=id,name
    
    • DSL查询方式
    POST   xxx.xxx.xx.xxx:9200/shop/_search
    
    {
        "query": {
            "match_all": {}
        },
        "_source": ["id", "name"]
    }
    

3、分页查询

"from": 0,
"size": 10

  • 请求(POST)

    POST   xxx.xxx.xx.xxx:9200/shop/_search
    
  • 请求参数

    • 查询所有字段信息
    {
        "query": {
            "match_all": {}
        },
        "from": 0,
        "size": 10
    }
    
    • 查询部分字段信息
    {
        "query": {
            "match_all": {}
        },
        "_source": [
            "id",
            "nickname",
            "age"
        ],
        "from": 5,
        "size": 5
    }
    

三、termmatch

  • term精确搜索

  • match分词搜索

1、用例

  • 请求方式(POST)
POST   xxx.xxx.xx.xxx:9200/shop/_search
  • json参数对比
# term形式,“一纸家书”会作为一个完整的词去匹配查询,不会对其进行分词
{
    "query": {
        "term": {
            "desc": "一纸家书"
        }
    }
}

# match形式:会对搜索条件“一纸家书”进行分词查询,比如:一、纸、家、书、家书等等
{
    "query": {
        "match": {
            "desc": "一纸家书"
        }
    }
}

2、terms多词查询

  • 请求方式(POST)
POST   xxx.xxx.xx.xxx:9200/shop/_search
  • json参数
{
    "query": {
        "terms": {
            "desc": ["一纸家书", "天子", "无敌"]
        }
    }
}

3、match_phrase短语匹配

match:分词后只要有匹配就返回
match_phrase:分词结果必须在text字段分词中都包含,而且顺序必须相同,而且必须都是连续的。(搜索比较严格,<font color='bisque'>这是当slop为0或者没有设置的时候,如果设置了slop的值大于0,顺序可以不同,也可以不连续</font>)

slop:match_phrase 短语匹配时,允许词语间跳过的数量

  • 测试用例:

    词条一: 我大学毕业后去上海工作,薪资还不错。

    词条二: 大学生活真好,毕业后就开始忙碌了

    词条三: 毕业已经一年了,大学同学都没有再见到过

    词条四: 毕业大学生都去大城市打拼了

    词条五: 大学生不断的走入社会,那些还没有毕业的也蠢蠢欲动

  • 请求方式(POST)

POST   xxx.xxx.xx.xxx:9200/shop/_search
  • json参数(无slop形式)
{
    "query": {
        "match_phrase": {
            "desc": {
                "query": "大学 毕业"
            }
        }
    }
}

此时只能查到“词条一”,

“词条一”中的“大学”和“毕业”顺序对,又连续

“词条二”、“词条五”中的“大学”和“毕业”虽然顺序对,但是不连续

“词条三”中的“大学”和“毕业”顺序都不对所以无法查询

“词条四”中的“大学”和“毕业”顺序也不对,所以无法查询

  • json参数(添加slop形式)

    "slop": 2,表示 大学 和 毕业两个词中间最多可以间隔2个分词

{
    "query": {
        "match_phrase": {
            "desc": {
                "query": "大学 毕业",
                "slop": 3
            }
        }
    }
}
  • slop的值以及查询结果
* slop:0,结果为-->词条一
* slop:3,结果为-->词条一、词条二
* slop:4,结果为-->词条一、词条二、词条四
* slop:8,结果为-->词条一、词条二、词条四、词条三
* slop:10(大于等于10),结果为-->词条一、词条二、词条四、词条三、词条五

4、operator条件查询

  • operator

    • or:搜索内容分词后,只要存在一个词语匹配就展示结果,比如查询“一纸家书”,数据中有“一”就能被查出来

    • and:搜索内容分词后,都要满足词语匹配,比如查询“一纸家书”,数据中必须含有“一”、“纸”、“家书”才能查出来,顺序和是否连续到无所谓

  • 请求方式(POST)

POST   xxx.xxx.xx.xxx:9200/shop/_search
  • json参数
{
    "query": {
        "match": {
            "desc": "xbox游戏机"
        }
    }
}
# 等同于
{
    "query": {
        "match": {
            "desc": {
                "query": "xbox游戏机",
                "operator": "or"
            }
        }
    }
}
# 相当于 select * from shop where desc='xbox' or|and desc='游戏机'

5、minimum_should_match

  • 最低匹配精度

至少有[分词后的词语个数]x百分百,得出一个数据值取整。

举个例子:当前属性设置为70,若一个用户查询检索内容分词后有10个词语,那么匹配度按照 10x70%=7,则desc中至少需要有7个词语匹配,就展示;若分词后有8个,则8x70%=5.6,则desc中至少需要有5个词语匹配,就展示。

  • 也能设置具体的数字

表示个数

  • 请求方式(POST)
POST   xxx.xxx.xx.xxx:9200/shop/_search
  • json参数
{
    "query": {
        "match": {
            "desc": {
                "query": "女友生日送我好玩的xbox游戏机",
                "minimum_should_match": "60%"
            }
        }
    }
}

6、根据文档主键ids搜索

同QS查询: GET xxx.xxx.xx.xxx:9200/shop/_doc/1001

  • 请求方式(POST)
POST   xxx.xxx.xx.xxx:9200/shop/_search
  • json参数

查询文档id为:"1001", "1010", "1008"相关信息

{
   "query": {
       "ids": {
           "values": ["1001", "1010", "1008"]
       }
   }
}

7、multi_match

  • multi_match

满足使用match在多个字段中进行查询的需求,例如:我需要查名字username有“小天”以及别名'aliasname'有“小天”的用户,就需要用到该查询

  • boost

权重,为某个字段设置权重,权重越高,文档相关性得分就越高。通畅来说搜索商品名称要比商品简介的权重更高。如:"aliasname10",加数值

  • 请求方式(POST)

查名字username有“小天”以及别名'aliasname'有“小天”的用户

POST   xxx.xxx.xx.xxx:9200/shop/_search
  • json参数
{
    "query": {
        "multi_match": {
            "query": "小天",
            "fields": ["username", "aliasname"]
        }
    }
}
  • json参数(设置权重boost)
{
    "query": {
        "multi_match": {
            "query": "小天",
            "fields": ["username", "aliasname^10"]
        }
    }
}

aliasname^10 代表搜索提升10倍相关性,也就是说用户搜索的时候其实以这个aliasname为主,username为辅,aliasname的匹配相关度当然要提高权重比例了。

8、布尔查询bool

  • must:查询必须匹配搜索条件,类似于mysql中的 and
  • must_not:查询搜索条件一个都不满足的数据
  • should:查询匹配条件一个或者一个以上的数据,类似于mysql中的 or

8.1 must

查询username 或者 aliasname中包含“小天”,且性别sex为1(男),并且出生日期为:1996-01-14的用户信息(全都要满足)

POST  192.168.56.101:9200/shop/_search
{
    "query": {
        "bool": {
            "must": [
                {
                    "multi_match": {
                        "query": "小天",
                        "fields": ["username", "aliasname"]
                    }
                },
                {
                    "term": {
                        "sex": 1
                    }
                },
                {
                    "term": {
                        "birthday": "1996-01-14"
                    }
                }
            ]
        }
    }
}

8.2 should

查询username 或者 aliasname中包含“小天”,或者性别为0(女)的,或者出生日期为:2000-01-14的用户信息(满足一个就行)

POST  192.168.56.101:9200/shop/_search
{
    "query": {
        "bool": {
            "should": [
                {
                    "multi_match": {
                        "query": "小天",
                        "fields": ["username", "aliasname"]
                    }
                },
                {
                    "term": {
                        "sex": 0
                    }
                },
                {
                    "term": {
                        "birthday": "2000-01-14"
                    }
                }
            ]
        }
    }
}

8.3 must_not

查询username 或者 aliasname中不包含包含“小天”,且性别不是0(女),且出生日期不是:2000-01-14的用户信息(全都不能满足)

POST  192.168.56.101:9200/shop/_search
{
    "query": {
        "bool": {
            "must_not": [
                {
                    "multi_match": {
                        "query": "小天",
                        "fields": ["username", "aliasname"]
                    }
                },
                {
                    "term": {
                        "sex": 0
                    }
                },
                {
                    "term": {
                        "birthday": "2000-01-14"
                    }
                }
            ]
        }
    }
}

8.4 混合使用

查询 username 和 aliasname 中必须包含“小天”,并且年龄不为20,升高为180或者175的用户

POST  192.168.56.101:9200/shop/_search
{
    "query": {
        "bool": {
            "must": [
                {
                    "match": {
                        "username": "小天"
                    }
                },
                {
                    "match": {
                        "aliasname": "小天"
                    }
                }
            ],
            "must_not": [
                {
                    "term": {
                        "age": 20
                    }
                }
            ],
            "should": [
                {
                    "term": {
                        "height": 180
                    }
                },
                {
                    "term": {
                        "height": 175
                    }
                }
            ]
        }
    }
}

8.5 加权boost

查询 username 包含“小天”,或者年龄为20岁的用户,优先年龄为20岁的(权重更大,获取的数据更靠前)

POST  192.168.56.101:9200/shop/_search
{
    "query": {
        "bool": {
            "should": [
                {
                    "match": {
                        "username": {
                            "query": "小天",
                            "boost": 2
                        }
                    }   
                },
                {
                    "match": {
                        "age": {
                            "query": 20,
                            "boost": 10
                        }
                    }   
                }
            ]
        }
    }
}

9、过滤器post_filter

<font color='bisque'>对搜索出来的结果进行数据过滤。不会到es库里去搜,不会去计算文档的相关度分数</font>,所以过滤的性能会比较高,过滤器可以和全文搜索结合在一起使用。

<font color='bisque'>post_filter元素是一个顶层元素,只会对搜索结果进行过滤。不会计算数据的匹配度相关性分数,不会根据分数去排序,query则相反,会计算分数,也会按照分数去排序。</font>

9.1 使用场景

  • query:根据用户搜索条件检索匹配记录
  • post_filter:用于查询后,对结果数据的筛选

9.2 post_filter关键字

  • gte:大于等于
  • lte:小于等于
  • gt:大于
  • lt:小于
  • 除此以外还能做其他的match等操作也行

9.3 实例

查询专业包含“软件工程”,年龄在10到20岁的用户

POST  192.168.56.101:9200/shop/_search
{
    "query": {
        "match": {
            "major": "软件工程"
        }
    },
    "post_filter": {
        "range": {
            "age": {
                "gt": 10,
                "lt": 20
            }
        }
    }
}

10、排序sort

  • desc
  • asc
  • 组合排序

10.1 用例

查询专业包含“软件工程”,年龄在10到20岁的用户,并按年龄倒序,性别正序排序

POST  192.168.56.101:9200/shop/_search
{
    "query": {
        "match": {
            "major": "软件工程"
        }
    },
    "post_filter": {
        "range": {
            "age": {
                "gt": 10,
                "lt": 20
            }
        }
    },
    "sort": [
        {
            "age": "desc"
        },
        {
            "sex": "asc"
        }
    ]
}

10.2 对文本进行排序

当文本被分词,所以去做排序会报错,通常我们可以为这个字段增加额外的一个附属属性,类型为keyword,用于做排序。就相当于对分词后的分词数据进行排序,一般不推荐使用

POST  192.168.56.101:9200/user2/_mapping
  • 添加keyword的
"keyword": {
    "type": "keyword"
}
{
    "properties": {
        "id": {
            "type": "long"
        },
        "nickname": {
            "type": "text",
            "analyzer": "ik_max_word",
            "fields": {
                "keyword": {
                    "type": "keyword"
                }
            }
        }
    }
}
  • 添加数据
  • 对文本进行排序

使用 nickname.keyword 的方式

{
    "sort": [
        {
            "nickname.keyword": "desc"
        }
    ]
}

10.3 深度分页

从9990页开始查询问题

{
    "query": {
        "match_all": {}
    },
    "from": 9990,
    "size": 10
}
  • 查询过程分析

我们在获取第9999条到10009条数据的时候,其实每个分片都会拿到10009条数据,然后集合在一起,总共是10009*3=30027条数据,针对30027数据再次做排序处理,最终会获取最后10条数据

如此一来,搜索得太深,就会造成性能问题,会耗费内存和占用cpu。而且es为了性能,他不支持超过一万条数据以上的分页查询。那么如何解决深度分页带来的性能呢?其实我们应该避免深度分页操作(限制分页页数),比如最多只能提供100页的展示,从第101页开始就没了,毕竟用户也不会搜的那么深,我们平时搜索淘宝或者百度,一般也就看个10来页就顶多了。

  • 提升搜索量

通过设置index.max_result_window来突破10000数据,然后就可以使用上面的查询去获取数据

GET  192.168.56.101:9200/shop/_settings

PUT  192.168.56.101:9200/shop/_settings
{ 
    "index.max_result_window": "20000"
}

11、查询关键词高亮highlight

会为查询结果中的关键词添加标签,默认为<em>标签,可以自行配置

11.1 用例一

使用默认标签

POST  192.168.56.101:9200/user2/_search
  • 查询条件
{
    "query": {
        "match": {
            "desc": "薪资"
        } 
    },
    "highlight": {
        "fields": {
            "desc": {}
        }
    }
}
  • 查询结果
{
    "took": 27,
    "timed_out": false,
    "_shards": {
        "total": 3,
        "successful": 3,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": {
            "value": 1,
            "relation": "eq"
        },
        "max_score": 1.0925692,
        "hits": [
            {
                "_index": "test",
                "_id": "1001",
                "_score": 1.0925692,
                "_source": {
                    "id": 1001,
                    "desc": "我大学毕业后去上海工作,薪资还不错。"
                },
                "highlight": {
                    "desc": [
                        "我大学毕业后去上海工作,<em>薪资</em>还不错。"
                    ]
                }
            }
        ]
    }
}

11.2 用例二

自定义标签

POST  192.168.56.101:9200/user2/_search
  • 查询条件
{
    "query": {
        "match": {
            "desc": "薪资"
        } 
    },
    "highlight": {
        "pre_tags": ["<span>"],
        "post_tags": ["</span>"],
        "fields": {
            "desc": {}
        }
    }
}
  • 查询结果
{
    "took": 9,
    "timed_out": false,
    "_shards": {
        "total": 3,
        "successful": 3,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": {
            "value": 1,
            "relation": "eq"
        },
        "max_score": 1.0925692,
        "hits": [
            {
                "_index": "test",
                "_id": "1001",
                "_score": 1.0925692,
                "_source": {
                    "id": 1001,
                    "desc": "我大学毕业后去上海工作,薪资还不错。"
                },
                "highlight": {
                    "desc": [
                        "我大学毕业后去上海工作,<span>薪资</span>还不错。"
                    ]
                }
            }
        ]
    }
}

12 前缀查询prefix

根据前缀去查询

POST  192.168.56.101:9200/user2/_search
{
    "query": {
        "prefix": {
            "desc": "baidu"
        }
    }
}
  • head 可视化(<font color='bisque'>待完善</font>)

13、模糊搜索fuzzy

模糊搜索,并不是指的sql的模糊搜索,而是用户在进行搜索的时候的打字错误现象,搜索引擎会自动纠正,然后尝试匹配索引库中的数据。

  • 基本查询

把baidu打成了beidu

POST  192.168.56.101:9200/user2/_search
{
    "query": {
        "fuzzy": {
            "desc": "beidu"
        }
    }
}
  • 多字段搜索
{
  "query": {
    "multi_match": {
      "fields": [ "usernem", "alaisname"],
      "query": "小天 小民",
      "fuzziness": "AUTO"
    }
  }
}
{
  "query": {
    "multi_match": {
      "fields": [ "desc", "nickname"],
      "query": "演说",
      "fuzziness": "1"
    }
  }
}

14、占位符查询wildcard

  • ?:1个字符

  • *:1个或多个字符

POST  192.168.56.101:9200/user2/_search
{
  "query": {
    "wildcard": {
      "desc": "*oo?"
    }
  }
}
{
  "query": {
    "wildcard": {
      "desc": "演*"
    }
  }
}

14、游标查询scroll

一次性查询1万+数据,往往会造成性能影响,因为数据量太多了。这个时候可以使用滚动搜索,也就是 scroll。
滚动搜索可以先查询出一些数据,然后再紧接着依次往下查询。在第一次查询的时候会有一个滚动id,相当于一个锚标记,随后再次滚动搜索会需要上一次搜索的锚标记,根据这个进行下一次的搜索请求。每次搜索都是基于一个历史的数据快照,查询数据的期间,如果有数据变更,那么和搜索是没有关系的,搜索的内容还是快照中的数据。

  • 获取scroll_id

scroll=1m, Elasticsearch 将 “search context” 保存多久,即生成的scroll_id多久过期,1m表示搜索保持的上下文时间为1分钟。

POST  192.168.56.101:9200/shop/_search?scroll=1m
{
    "query": {
        "match_all": {}
    },
    "sort": ["_doc"],
    "size": 5
}
  • 后续查询

获取结果中的_scroll_id,以便进行下一轮查询(必须携带)

POST  192.168.56.101:9200/_search/scroll
{
    "scroll_id": "FGluY2x1ZGVfY29udGV4dF91dWlkDnF1ZXJ5VGhlbkZldGNoAxZmeThnNlFkbVI2LWdMTEF4em1lZXdnAAAAAAAAAFMWYkJ6eFg5alpUTFdpaXdLZ1l4emtYZxZmeThnNlFkbVI2LWdMTEF4em1lZXdnAAAAAAAAAFUWYkJ6eFg5alpUTFdpaXdLZ1l4emtYZxZmeThnNlFkbVI2LWdMTEF4em1lZXdnAAAAAAAAAFQWYkJ6eFg5alpUTFdpaXdLZ1l4emtYZw==",
    "scroll": "1m"
}

15、批量操作bulk

批量新增、修改、删除等

15.1 批量操作基本语法

bulk操作和以往的普通请求格式有区别。<font color='bisque'>不要格式化json</font>,不然就不在同一行了,这个需要注意。

  • 参数结构如下

\n,表示换行,所以必须回车,否则会报错

{ action: { metadata }}\n
{ request body        }\n
{ action: { metadata }}\n
{ request body        }\n
  • { action: { metadata }}代表批量操作的类型,可以是新增、删除或修改
  • \n是每行结尾必须填写的一个规范,每一行包括最后一行都要写,用于es的解析
  • { request body }是请求body,增加和修改操作需要,删除操作则不需要

15.2 批量操作的类型

action 必须是以下选项之一

  • create:如果文档不存在,那么就创建它。存在会报错。发生异常报错不会影响其他操作。
  • index:创建一个新文档或者替换一个现有的文档。
  • update:部分更新一个文档。
  • delete:删除一个文档。

metadata 中需要指定要操作的文档的_index 、 _type 和 _id,_index 、 _type也可以在url中指定

15.2 批量操作实例

<font color='red'>TODO</font>

四、用例数据

1、创建index

PUT  192.168.56.101:9200/test
{
   "settings": {
       "index": {
           "number_of_shards": "3",
           "number_of_replicas": "0"
       }
   }
}

2、为索引test创建mappings

POST  192.168.56.101:9200/test/_mapping
{
   "properties": {
       "id": {
           "type": "long"
       },
       "desc": {
           "type": "text",
           "analyzer": "ik_max_word"
       }
   }
}

3、为索引test添加数据

POST  192.168.56.101:9200/test/_doc/1001(对应的id)

一条条的添加数据,记得对应id,方便识别

{
   "id": 1001,
   "desc": "我大学毕业后去上海工作,薪资还不错。"
}

{
   "id": 1002,
   "desc": "大学生活真好,毕业后就开始忙碌了"
}

{
   "id": 1003,
   "desc": "毕业已经一年了,大学同学都没有再见到过"
}

{
   "id": 1004,
   "desc": "毕业大学生都去大城市打拼了"
}

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

推荐阅读更多精彩内容