一、 Cypher

模式(pattern)

  • 节点用圆括号表示 ()
  • 我们使用冒号来表示标签,例如: (:Person)
  • 节点间的关系用两个破折号表示,例如:(:Person)--(:Movie)
  • 关系的方向使用大于或小于符号表示,例如:(:Person)-->(:Movie)
  • 关系的类型用两个破折号之间的方括号表示,例如: [:ACTED_IN]
  • 在语音气泡中绘制的属性是用类似JSON的语法指定的。Neo4j中的属性是键/值对。例如{name:'Tom Hanks'}

示例:

(m:Movie {title: 'Cloud Atals'})<-[ACTED_IN]-(p:Person)

该模式中的两个节点类型是Movie和Person。Person节点与Movie节点有指向的ACTED_IN关系。该模式中的特定Movie节点由值为Cloud Atlas的title属性过滤。这个图形代表了所有在电影《Cloud Atlas》中扮演过角色的人。

How Cypher works

使用 MATCH关键字从图中检索数据。你可以认为 MATCH子句类似于SQL语句中的 FROM
例如,如果我们想在图中查找Person,我们将匹配单个节点的模式,其标签为 :Person

MATCH (:Person)

假设我们想从图中检索所有Person节点。我们可以在冒号前放置一个值来赋值一个变量。
使用变量p表示从图中检索到的所有Person节点,使用RETURN子句返回它们。

MATCH (p:Person)
RETURN P

假如我们想查询name为Tom Hanks的节点。我们所有的Person节点有name属性。我们可以使用大括号指定nameTom Hanks的键值对作为过滤器。 由于Tom Hanks是一个字符串,我们需要将它放在单引号或双引号中。

MATCH (p:Person {name: 'Tom Hanks'})
RETURN p

该查询返回一个表示Tom Hanks的节点。在Neo4j Browser的图形视图中,节点被可视化为一个气泡。
在Cypher语句中,可以使用点表示法访问属性。例如,使用name属性键p.name返回name属性值。

MATCH (p:Person {name: 'Tom Hanks'})
RETURN  p.born

这个查询返回Tom Hanks节点的born属性的值。

在Cypher中,标签、属性键和变量是区分大小写的。Cypher关键字不区分大小写。最佳实践:

  • 使用驼峰命名标签。
  • 使用驼峰命名属性和变量
  • 关键字大写

筛选查询的另一种方式是使用WHERE子句,而不是使用大括号指定属性值。

MATCH (p:Person)
WHERE p.name = 'Tom Hanks'
RETURN p.name

可以向WHERE子句添加根复杂的逻辑

MATCH (p:Person)
WHERE p.name = 'Tom Hanks' OR p.name = 'Rita Wilson'
RETURN p.name, p.born

遍历

MATCH (p:Person {name: 'Tom Hanks'})-[:ACTED_IN]->(m:Movie)
RETURN m.title

过滤查询

1.按节点标签过滤

MATCH (p:Person)-[:ACTED_IN]->(m:Movie)
WHERE m.title='The Matrix'
RETURN p.name
MATCH (p)-[:ACTED_IN]->(m)
WHERE p:Person AND m:Movie AND m.title='The Matrix'
RETURN p.name

这两个查询执行的方式相同

2.范围过滤

MATCH (p:Person)-[:ACTED_IN]->(m:Movie)
WHERE 2000 <= m.released <= 2003
RETURN p.name, m.title, m.released

3.根据属性的存在进行过滤

MATCH (p:Person)-[:ACTED_IN]->(m:Movie)
WHERE p.name='Jack Nicholson' AND m.tagline IS NOT NULL
RETURN m.title, m.tagline

4.通过部分字符串进行过滤

Cypher有一组与字符串相关的关键字,您可以在WHERE子句中使用它们来测试字符串属性值。你可以指定STARTS WITHEND WITHCONTAINS

MATCH (p:Person)-[:ACTED_IN]->()
WHERE p.name STARTS WITH 'Michael'
RETURN p.name

字符串测试区分大小写,因此您可能需要使用toLower()toUpper()函数来确保测试产生正确的结果。例如:

MATCH (p:Person)-[:ACTED_IN]->()
WHERE toLower(p.name) STARTS WITH 'michael'
RETURN p.name

5.根据图中的模式进行过滤

MATCH (p:Person)-[:WROTE]->(m:Movie)
WHERE NOT exists( (p)-[:DIRECTED]->(m) )
RETURN p.name, m.title

6.列表过滤

可以在WHERE子句中定义列表。在查询期间,图形引擎将把每个属性与列表中的值进行比较。可以在列表中放置数值值或字符串值,但通常情况下,列表的元素是相同类型的数据。如果您正在使用字符串类型的属性进行测试,那么列表的所有元素都将是字符串。

MATCH (p:Person)
WHERE p.born IN [1965, 1970, 1975]
RETURN p.name, p.born
MATCH (p:Person)-[r:ACTED_IN]->(m:Movie)
WHERE  'Neo' IN r.roles AND m.title='The Matrix'
RETURN p.name, r.roles

7.节点或关系具有哪些属性?

具有给定标签的节点的属性不必相同。发现节点属性的一种方法是使用keys()函数。这个函数返回一个节点的所有属性键的列表。
通过运行以下代码来发现图中Person节点的键:

MATCH (p:Person)
RETURN p.name, keys(p)

8.图中有什么属性?

CALL db.propertyKeys()

一旦定义了属性键,即使目前没有使用该属性键的节点或关系,属性键仍然保留在图中。

创建节点

MERGE

MERGE关键字在数据库中创建模式
在MERGE关键字之后,指定要创建的模式。通常这将是单个节点或两个节点之间的关系。
假设我们想要创建一个节点来表示Michael Cain。运行Cypher代码创建节点。

MERGE (p:Person {name: 'Michael Cain'})

在使用MERGE创建节点时,必须执行至少一个属性作为节点的唯一主键。

Executing multiple Cypher clauses

我们还可以在单个Cypher代码块中链接多个MERGE子句。

MERGE (p:Person {name: 'Katie Holmes'})
MERGE (m:Movie {title: 'The Dark Knight'})
RETURN p, m

CREATE

CREATE子句也可以创建节点。使用CREATE的好处是它在添加节点之前不查找主键。如果您确定数据是干净的,并且希望在导入过程中提高速度,则可以使用CREATE。使用MERGE,因为它消除了节点的重复。

创建关系

就像可以使用MERGE在图中创建节点一样,也可以使用MERGE创建两个节点之间的关系。首先,您必须有对将要为其创建关系的两个节点的引用。当你在两个节点之间创建关系时,它必须有:

  • Type(类型)
  • Direction(方向)

例如,如果PersonMovie节点都已经存在,我们可以在创建它们之间的关系之前使用MATCH子句找到它们。

MATCH (p:Person {name: 'Michael Cain'})
MATCH (m:Movie {title: 'The Dark Knight'})
MERGE (p)-[:ACTED_IN]->(m)

在这里,我们找到了想要创建关系的两个节点。然后使用对找到的节点的引用来创建ACTED_IN关系。
我们可以确认这种关系存在如下:

MATCH (p:Person {name: 'Michael Cain'})-[:ACTED_IN]-(m:Movie {title: 'The Dark Knight'})
RETURN p, m

默认情况下,在Neo4j Browser中,可视化连接节点之间有关系的节点。

不需要在MATCH模式中指定方向,因为查询引擎将查找所有连接的节点,而不管关系的方向如何。
例如,如果我们指定了这个关系模式:

MATCH (p:Person {name: 'Michael Cain'})<-[:ACTED_IN]-(m:Movie {title: 'The Dark Knight'})
RETURN p, m

该查询不返回节点,因为在图中没有与Person节点具有ACTED_IN关系的节点。

使用多个子句创建节点和关系

我们还可以在单个Cypher代码块中链接多个MERGE子句。

MERGE (p:Person {name: 'Chadwick Boseman'})
MERGE (m:Movie {title: 'Black Panther'})
MERGE (p)-[:ACTED_IN]-(m)

注意,在我们创建关系的MERGE子句中,我们没有指定关系的方向。默认情况下,如果在创建关系时不指定方向,则总是假设从左到右。
我们可以确认这种关系存在如下:

MATCH (p:Person {name: 'Chadwick Boseman'})-[:ACTED_IN]-(m:Movie {title: 'Black Panther'})
RETURN p, m

使用MERGE在单个子句中创建节点和关系

MERGE所做的是在图中不存在节点或关系时创建节点或关系。

这段代码成功创建了节点和关系:

MERGE (p:Person {name: 'Emily Blunt'})-[:ACTED_IN]->(m:Movie {title: 'A Quiet Place'})
RETURN p, m

您可以多次执行这个Cypher代码,它不会创建任何新的节点或关系。

属性设置

为节点或关系添加属性

有两张方式设置节点或关系的属性

1. 内联作为MERGE子句的一部分

您已经了解了如何为节点创建主键属性。您还可以为关系内联设置如下属性:

MERGE (p:Person {name: 'Michael Cain'})
MERGE (m:Movie {title: 'Batman Begins'})
MERGE (p)-[:ACTED_IN {roles: ['Alfred Penny']}]->(m)
RETURN p,m

2. 对节点或关系的引用使用set关键字

MATCH (p:Person)-[r:ACTED_IN]->(m:Movie)
WHERE p.name = 'Michael Cain' AND m.title = 'The Dark Knight'
SET r.roles = ['Alfred Penny']
RETURN p, r, m

设置多个属性

如果需要设置多个属性,可以用逗号分隔它们。

MATCH (p:Person)-[r:ACTED_IN]->(m:Movie)
WHERE p.name = 'Michael Cain' AND m.title = 'The Dark Knight'
SET r.roles = ['Alfred Penny'], r.year = 2008
RETURN p, r, m

更新属性

如果对节点或关系有引用,也可以使用SET修改属性

MATCH (p:Person)-[r:ACTED_IN]->(m:Movie)
WHERE p.name = 'Michael Cain' AND m.title = 'The Dark Knight'
SET r.roles = ['Mr. Alfred Penny']
RETURN p, r, m

移除属性

通过使用remove关键字或将属性设置为null,可以从节点或关系中删除或删除属性。

MATCH (p:Person)-[r:ACTED_IN]->(m:Movie)
WHERE p.name = 'Michael Cain' AND m.title = 'The Dark Knight'
REMOVE r.roles
RETURN p, r, m
MATCH (p:Person)
WHERE p.name = 'Gene Hackman'
SET p.born = null
RETURN p

永远不要删除用作节点主键的属性

合并处理

您已经了解了可以使用MERGE在图中创建节点和关系。MERGE操作首先试图在图中找到一个模式。如果找到了模式,则数据已经存在,并且没有创建。如果没有找到模式,则可以创建数据。

定制化MERGE的行为

你可以在运行时指定行为,使你能够在创建节点或找到节点时设置属性。
我们可以使用ON CREATE SETON MATCH SET条件,或者SET关键字来设置任何附加属性。
在本例中,如果McKenna Grace的Person节点不存在,则创建该节点并设置createdAt属性。如果找到节点,则设置updatedAt属性。在这两种情况下,都设置了born属性。

// Find or create a person with this name
MERGE (p:Person {name: 'McKenna Grace'})

// Only set the `createdAt` property if the node is created during this query
ON CREATE SET p.createdAt = datetime()

// Only set the `updatedAt` property if the node was created previously
ON MATCH SET p.updatedAt = datetime()

// Set the `born` property regardless
SET p.born = 2006

RETURN p

如果要为ON CREATE set或ON MATCH set子句设置多个属性,可以用逗号分隔它们。例如:
ON CREATE SET m.released = 2020, m.tagline = `A great ride!'

关系的MERGE

// Find or create a person with this name
MERGE (p:Person {name: 'Michael Cain'})

// Find or create a movie with this title
MERGE (m:Movie {title: 'The Cider House Rules'})

// Find or create a relationship between the two nodes
MERGE (p)-[:ACTED_IN]->(m)

另一种创建这些节点和关系的方法如下:

MERGE (p:Person {name: 'Michael Cain'})-[:ACTED_IN]->(m:Movie {title: 'The Cider House Rules'})
RETURN p, m

删除数据

Neo4j中,可以删除:

  • 节点
  • 关系
  • 属性
  • 标签
    为了删除数据,你必须先检索到它,然后才能删除。

删除节点

MATCH (p:Person)
WHERE p.name = 'Jane Doe'
DELETE p

删除关系

MATCH (p:Person {name: 'Jane Doe'})-[r:ACTED_IN]->(m:Movie {title: 'The Matrix'})
DELETE r
RETURN p, m

如果我们试图删除Jane Doe节点,我们将收到一个错误,因为它在图中有关系。您应该会收到一个错误。Neo4j防止了图中的孤立关系。

删除节点和关系

Neo4j提供了一个功能,如果节点具有传入或传出关系,则不能删除该节点。这可以防止图表中出现孤立关系。

MATCH (p:Person {name: 'Jane Doe'})
DETACH DELETE p

这段代码删除了关系和Person节点。
您还可以使用此代码删除数据库中的所有节点和关系。

MATCH (n)
DETACH DELETE n

删除标签

最佳实践是一个节点至少有一个标签,但不要超过四个。

MATCH (p:Person {name: 'Jane Doe'})
REMOVE p:Developer
RETURN p

图中有什么标签?

这段代码返回图中定义的所有节点标签。

CALL db.labels()

图遍历

  1. 查询的锚通常基于MATCH子句。锚点通常由存储在图中的元数据或内联或WHERE子句中提供的筛选器确定。
  2. 在检索了锚节点之后,如果查询指定了一个路径,那么下一步就是遵循该路径。作为锚集一部分的加载的初始节点具有指向关系的指针,这些关系指向关系的另一端的节点。

Avoiding labels for better performance
用于锚节点检索的Person标签在这里很好,但是用于模式另一端的标签是不必要的。在非锚节点上设置标签将强制进行标签检查,这实际上是不必要的。

PROFILE MATCH (p:Person)-[:ACTED_IN]->(m:Movie)
WHERE p.name = 'Tom Hanks'
RETURN m.title AS movie

更好的写法:

PROFILE MATCH (p:Person)-[:ACTED_IN]->(m)
WHERE p.name = 'Tom Hanks'
RETURN m.title AS movie

Returning paths
在Neo4j Browser中,当您返回节点时,默认情况下关系是可视化的。例如,这个查询与它所遍历的相关路径是可视化的:

MATCH (person:Person)-[]->(movie)
WHERE person.name = 'Walt Disney'
RETURN person, movie

可变长度遍历

neo4j使用深度优先遍历

Neo4j始终坚持关系的唯一性。也就是说,两个节点之间永远不会有两个相同类型和方向的关系。这使Neo4j能够避免图遍历中的循环或无限循环。

最短路径

MATCH p = shortestPath((p1:Person)-[*]-(p2:Person))
WHERE p1.name = "Eminem"
AND p2.name = "Charlton Heston"
RETURN  p

可变长度遍历

MATCH (p:Person {name: 'Eminem'})-[:ACTED_IN*2]-(others:Person)
RETURN  others.name

遍历长度限制

MATCH (p:Person {name: 'Eminem'})-[:ACTED_IN*1..4]-(others:Person)
RETURN  others.name

全局变量

可以用with子句定义和初始化要在查询中使用的变量。

WITH 'Tom Hanks' AS actorName
MATCH (p:Person)-[:ACTED_IN]->(m:Movie)
WHERE p.name = actorName
RETURN m.title AS movies

使用WITH定义表达式。在定义表达式(例如toLower(m.c etitle))时,必须指定用AS关键字定义的别名。

结果限制

WITH  'Tom Hanks' AS theActor
MATCH (p:Person)-[:ACTED_IN]->(m:Movie)
WHERE p.name = theActor
RETURN m.title AS movies LIMIT 2
WITH  'Tom Hanks' AS theActor
MATCH (p:Person)-[:ACTED_IN]->(m:Movie)
WHERE p.name = theActor
WITH m  LIMIT 2
// possibly do more with the two m nodes
RETURN m.title AS movies

结果排序

WITH  'Tom Hanks' AS theActor
MATCH (p:Person)-[:ACTED_IN]->(m:Movie)
WHERE p.name = theActor
WITH m ORDER BY m.year LIMIT 5
// possibly do more with the five m nodes in a particular order
RETURN m.title AS movies, m.year AS yearReleased

在WITH子句中使用映射投影

MATCH (n:Movie)
WHERE n.imdbRating IS NOT NULL
AND n.poster IS NOT NULL
WITH n {
  .title,
  .year,
  .languages,
  .plot,
  .poster,
  .imdbRating,
  directors: [ (n)<-[:DIRECTED]-(d) | d { tmdbId:d.imdbId, .name } ]
}
ORDER BY n.imdbRating DESC LIMIT 4
RETURN collect(n)

尽管这对客户端处理很好,但它占用服务器上更多的内存,因为记录不能流到客户端,而是被收集到服务器上的列表结构中。

流水线似的查询

Using WITH for aggregation

MATCH (:Movie {title: 'Toy Story'})-[:IN_GENRE]->(g:Genre)<-[:IN_GENRE]-(m)
WHERE m.imdbRating IS NOT NULL
WITH g.name AS genre,
count(m) AS moviesInCommon,
sum(m.imdbRating) AS total
RETURN genre, moviesInCommon,
total/moviesInCommon AS score
ORDER By score DESC

Using WITH for collecting

MATCH (m:Movie)--(a:Actor)
WHERE m.title CONTAINS 'New York'
WITH m, collect (a.name) AS actors,
count(*) AS numActors
RETURN m.title AS movieTitle, actors
ORDER BY numActors DESC
MATCH (m:Movie)<-[:ACTED_IN]-(a:Actor)
WHERE m.title CONTAINS 'New York'
WITH m, collect (a.name) AS actors,
count(*) AS numActors
ORDER BY numActors DESC
RETURN collect(m { .title, actors, numActors }) AS movies

Using LIMIT early

PROFILE MATCH (p:Actor)
WHERE p.born.year = 1980
WITH p  LIMIT 3
MATCH (p)-[:ACTED_IN]->(m:Movie)
WITH p, collect(m.title) AS movies
RETURN p.name AS actor,  movies

Use DISTINCT when necessary

MATCH (p:Actor)
WHERE p.born.year = 1980
WITH p  LIMIT 3
MATCH (p)-[:ACTED_IN]->(m:Movie)-[:IN_GENRE]->(g:Genre)
WITH p, collect(DISTINCT g.name) AS genres
RETURN p.name AS actor, genres

Unwinding Lists

UNWIND为列表的每个元素返回一行。

MATCH (m:Movie)-[:ACTED_IN]-(a:Actor)
WHERE a.name = 'Tom Hanks'
UNWIND m.languages AS lang
RETURN m.title AS movie,
m.languages AS languages,
lang AS language
MATCH (m:Movie)
UNWIND m.languages AS lang
WITH m, trim(lang) AS language
// this automatically, makes the language distinct because it's a grouping key
WITH language, collect(m.title) AS movies
RETURN language, movies[0..10]

子查询

CALL {
   MATCH (m:Movie) WHERE m.year = 2000
   RETURN m ORDER BY m.imdbRating DESC LIMIT 10
}
MATCH  (:User)-[r:RATED]->(m)
RETURN m.title, avg(r.rating)
MATCH (m:Movie)
CALL {
    WITH m
    MATCH (m)<-[r:RATED]-(u:User)
     WHERE r.rating = 5
    RETURN count(u) AS numReviews
}
RETURN m.title, numReviews
ORDER BY numReviews DESC

Combining query results with UNION

MATCH (m:Movie) WHERE m.year = 2000
RETURN {type:"movies", theMovies: collect(m.title)} AS data
UNION ALL
MATCH (a:Actor) WHERE a.born.year > 2000
RETURN { type:"actors", theActors: collect(DISTINCT a.name)} AS data

UNION ALL返回所有结果,这在内存上效率更高,但可能导致重复。UNION返回不同的结果。

Using UNION with subqueries

UNION的结果不能直接后处理。但是如果在子查询中封装UNION,则可以进一步处理结果。

MATCH (p:Person)
WITH p LIMIT 100
CALL {
  WITH p
  OPTIONAL MATCH (p)-[:ACTED_IN]->(m:Movie)
  RETURN m.title + ": " + "Actor" AS work
UNION
  WITH p
  OPTIONAL MATCH (p)-[:DIRECTED]->(m:Movie)
  RETURN m.title+ ": " +  "Director" AS work
}
RETURN p.name, collect(work)

Cypher参数

在测试Cypher语句时,您将使用各种文字值来确保Cypher查询是正确的。但您不希望每次测试时都更改Cypher语句。事实上,任何对Cypher语句的更改都需要重新编译Cypher代码,这是非常昂贵的。您创建了不会更改的Cypher语句,除非在查询中替换占位符(参数)。最佳实践是在Cypher语句中参数化值。

:param actorName: 'Tom Hanks'

设置参数之后,执行查询

MATCH (p:Person)-[:ACTED_IN]->(m:Movie)
WHERE p.name = $actorName
RETURN m.released AS releaseDate,
m.title AS title
ORDER BY m.released DESC

在Neo4j Browser会话中设置整数值时应该特别注意。由于JavaScript中的整数与Neo4j类型系统中的整数存在差异,所以在设置参数时,任何整数都将转换为浮点值。这是为了避免大量数据的任何数据丢失。

要强制该数字为整数,可以使用=>操作符。

:param number=> 10

Setting multiple parameters

您还可以使用json样式的语法来设置Neo4j Browser会话中的所有参数。可以在此对象中指定的值有数字、字符串和布尔值。在这个例子中,我们为会话设置了两个参数:

:params {actorName: 'Tom Cruise', movieName: 'Top Gun'}

如果设置了多个参数,可以使用前面看到的=>操作符向参数集添加更多的参数

:param number=> 10

Using multiple parameters

MATCH (p:Person)-[:ACTED_IN]->(m:Movie)
WHERE p.name = $actorName
AND m.title = $movieName
RETURN p, m

查看所有参数

:params

删除参数

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

推荐阅读更多精彩内容