GraphQL Java從入門(mén)到實(shí)踐

源碼解析

GraphQL Java 從Schema文件到GraphQL實(shí)例
GraphQL Java 一次完整的執(zhí)行歷程
補(bǔ)充:GraphQL相關(guān)資料

一、GraphQL是什么

GraphQL 是一種協(xié)議和一種查詢(xún)語(yǔ)言另假。2012年像屋,GraphQL由Facebook內(nèi)部開(kāi)發(fā),2015年開(kāi)源边篮。

  • 應(yīng)用場(chǎng)景:
    • 針對(duì)統(tǒng)一需求己莺,后端需要適配多個(gè)端的數(shù)據(jù)需求,此時(shí)使用GraphQL可以提供大而全的接口戈轿,各個(gè)端根據(jù)自己的需求對(duì)數(shù)據(jù)進(jìn)行裁剪獲取
    • 遺留 REST API 數(shù)量暴增凌受,變得十分復(fù)雜,使用GrapQL可以提供統(tǒng)一的接口入口
  • 優(yōu)點(diǎn):
    • 按需請(qǐng)求所要的數(shù)據(jù)
    • 獲取多個(gè)資源只用一個(gè)請(qǐng)求
    • 提供統(tǒng)一的API入口
  • 缺點(diǎn):
    • N+1問(wèn)題
    • 引入了復(fù)雜性
    • 單點(diǎn)問(wèn)題凶杖、性能問(wèn)題胁艰、安全問(wèn)題

二、GraphQL Java入門(mén)

GraphQL的服務(wù)端在多個(gè)語(yǔ)言都有實(shí)現(xiàn)包括Haskell, JavaScript, Python, Ruby, Java, C#, Scala, Go, Elixir, Erlang, PHP, R,和 Clojure。
GraphQL Java是GraphQL規(guī)范的Java原生實(shí)現(xiàn)腾么,也是Java實(shí)現(xiàn)的基本核心奈梳,所以本文主要講解GraphQL Java的基本使用以及GraphQL的一些基本概念。如果需要在公司實(shí)施GraphQL的話(huà)解虱,建議使用針對(duì)GraphQL Java封裝后的GraphQL Java Tools來(lái)實(shí)現(xiàn)服務(wù)器端的接口改造攘须,因?yàn)樗仍鷮?shí)現(xiàn)更簡(jiǎn)單高效,更加符合面向?qū)ο缶幊趟季S習(xí)慣殴泰,當(dāng)然這是后話(huà)了于宙,只要把本文的基本概念弄清楚了,使用它也就是分分鐘的事情了悍汛。
萬(wàn)丈高樓平地起捞魁,接下來(lái)還是先從從GraphQL Java實(shí)現(xiàn)Hello World開(kāi)始吧。

  • 環(huán)境準(zhǔn)備离咐,引入GraphQL的Maven依賴(lài)
<dependency>
    <groupId>com.graphql-java</groupId>
    <artifactId>graphql-java</artifactId>
     <version>11.0</version>
</dependency>
<!--用于解析schema文件-->
<dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>27.0-jre</version>
</dependency>
  • 從最簡(jiǎn)單的hello world開(kāi)始谱俭,所有的核心邏輯也都在這個(gè)這里了
public static void main(String[] args) {
// 1\. 定義Schema, 一般會(huì)定義在一個(gè)schema文件中
String schema = "type Query{hello: String}";
// 2\. 解析Schema
SchemaParser schemaParser = new SchemaParser();
TypeDefinitionRegistry typeDefinitionRegistry = schemaParser.parse(schema);
// 為Schema 中hello方法綁定獲取數(shù)據(jù)的方法
RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring()
        // 這里綁定的是最簡(jiǎn)單的靜態(tài)數(shù)據(jù)數(shù)據(jù)獲取器, 正常使用時(shí),獲取數(shù)據(jù)的方法返回一個(gè)DataFetcher實(shí)現(xiàn)即可
        .type("Query", builder -> builder.dataFetcher("hello", new StaticDataFetcher("world")))
        .build();
// 將TypeDefinitionRegistry與RuntimeWiring結(jié)合起來(lái)生成GraphQLSchema
SchemaGenerator schemaGenerator = new SchemaGenerator();
GraphQLSchema graphQLSchema = schemaGenerator.makeExecutableSchema(typeDefinitionRegistry, runtimeWiring);
// 實(shí)例化GraphQL, GraphQL為執(zhí)行GraphQL語(yǔ)言的入口
GraphQL graphQL = GraphQL.newGraphQL(graphQLSchema).build();
// 執(zhí)行查詢(xún)
ExecutionResult executionResult = graphQL.execute("{hello}");
// 打印執(zhí)行結(jié)果
System.out.println(executionResult.getData().toString());
}

三、GraphQL Java服務(wù)端改造

1. 定義schema

  • schema是什么
    • 通俗點(diǎn)說(shuō)宵蛀,schema就是協(xié)議昆著,規(guī)范,或者可以當(dāng)他是接口文檔术陶。就跟我們平時(shí)生成的swagger文檔一樣凑懂,定義接口是什么,參數(shù)是什么梧宫,返回值有哪些接谨,類(lèi)型是什么,哪些值不能為空等等塘匣。
    • GraphQL規(guī)定疤坝,每一個(gè)schema有一個(gè)根(root)query和根(root)mutation,還有一種subscription類(lèi)型馆铁,我們暫時(shí)用不上。
      • query即定義查詢(xún)接口锅睛,當(dāng)然這只是一種語(yǔ)義規(guī)范埠巨,在接口里寫(xiě)更改操作也是可以的,但是不推薦现拒。
      • mutation即定義更改接口辣垒,同上也是一種語(yǔ)義規(guī)范。
      • 在目前的GraphQL實(shí)現(xiàn)中印蔬,只能定義一個(gè)schema文件勋桶,一個(gè)文件中只能定義一個(gè)querymutation,如果要定義多個(gè)會(huì)報(bào)錯(cuò)。
  • 數(shù)據(jù)類(lèi)型
    • GraphQL定義了ID【相當(dāng)于String類(lèi)型例驹,GraphQL用來(lái)自己實(shí)現(xiàn)緩存】捐韩,Int(整型), Float(浮點(diǎn)型)【Java中實(shí)現(xiàn)為Double類(lèi)型】, String(字符串), Boolean(布爾型)和ID(唯一標(biāo)識(shí)符類(lèi)型)五個(gè)基本類(lèi)型,在GraphQL中他們統(tǒng)稱(chēng)叫標(biāo)量類(lèi)型(Scalar Type)鹃锈,java實(shí)現(xiàn)中實(shí)現(xiàn)了更多的類(lèi)型都定義在graphql.Scalars類(lèi)中荤胁,比如BigInteger、BigDecimal等屎债。GraphQL允許我們自定義標(biāo)量類(lèi)型仅政,比如Data類(lèi)型,只需實(shí)現(xiàn)相關(guān)的序列化盆驹,反序列化和驗(yàn)證的功能即可圆丹。已有實(shí)現(xiàn)參看這里:https://github.com/graphql-java/graphql-java-extended-scalars
    • !用來(lái)表示這個(gè)參數(shù)是非空的。[]表示查詢(xún)這個(gè)字段返回的是數(shù)組(List)躯喇,[]里面是數(shù)組的類(lèi)型辫封。
  • 對(duì)象類(lèi)型
    • type來(lái)定義對(duì)象類(lèi)型,就跟Java用class來(lái)定義一個(gè)類(lèi)一樣玖瘸。
    • input來(lái)定義接口輸入類(lèi)型秸讹,即接口中的輸入對(duì)象。
  • 基本概念差不多就這么多雅倒,下面我們?cè)陧?xiàng)目的resources目錄中定義一個(gè)名schema.graphqls為Schema文件
# 定義查詢(xún)接口, 一個(gè)schema文件中只能定義一個(gè)Query對(duì)象
type Query {
    # 無(wú)參, 返回字符串
    hello: String
    # 字段參數(shù)且不能為空, 返回普通對(duì)象
    bookById(id: ID!): Book
    # 對(duì)象參數(shù), 返回列表
    books(book: BookInput): [Book]
    listOrgTrucks(orgTruck: OrgTruckInput):[OrgTruck]
}
# 定義修改接口
type Mutation {
    hello: String
}
# 定義入?yún)?duì)象
input BookInput {
  id: ID
  name: String
}
#定義普通對(duì)象
type Book {
  id: ID
  name: String
  pageCount: Int
  author: Author
}
type Author {
  id: ID
  firstName: String
  lastName: String
}

2. 定義DataFetcher

DataFetcher在GraphQL Java服務(wù)器中是一個(gè)非常重要的概念璃诀,在執(zhí)行查詢(xún)時(shí),通過(guò)Datafetcher獲取一個(gè)字段的數(shù)據(jù)蔑匣。也就是說(shuō)我們需要為querymutation中定義的方法劣欢,以及對(duì)定義的對(duì)象中的字段綁定一個(gè)DataFetcher實(shí)現(xiàn),這樣在GraphQL執(zhí)行語(yǔ)法后才能通過(guò)綁定的DataFetcher執(zhí)行相應(yīng)的邏輯裁良。也因此GraphQL是一個(gè)執(zhí)行引擎凿将,解析語(yǔ)法后具體要執(zhí)行什么邏輯,GraphQL并不關(guān)心价脾,你只需要在DataFetcher接口并綁定到字段上即可牧抵。
當(dāng)GraphQL Java執(zhí)行查詢(xún)時(shí),它為查詢(xún)中遇到的每個(gè)字段調(diào)用適當(dāng)?shù)腄atafetcher侨把。DataFetcher是一個(gè)只有一個(gè)方法的接口犀变,帶有一個(gè)類(lèi)型的參數(shù)DataFetcherEnvironment:

public interface DataFetcher<T> {
    T get(DataFetchingEnvironment dataFetchingEnvironment) throws Exception;
}
  • 重要提示:模式中的每個(gè)字段都有一個(gè)與之關(guān)聯(lián)的DataFetcher。如果沒(méi)有為特定字段指定任何DataFetcher秋柄,則使用默認(rèn)的PropertyDataFetcher获枝。
  • 現(xiàn)在創(chuàng)建一個(gè)新的類(lèi)GraphQLDataFetchers,其中包含圖書(shū)和作者的示例列表骇笔,此處在靜態(tài)數(shù)據(jù)中獲取數(shù)據(jù)省店,但GraphQL并不需要指定數(shù)據(jù)來(lái)自何處嚣崭,數(shù)據(jù)可以來(lái)自任何你定義的地方,數(shù)據(jù)庫(kù)懦傍,RPC都可雹舀,這也是GraphQL強(qiáng)大的地方。
@Component
public class GraphQLDataFetchers {
    private static List<Map<String, String>> books = Arrays.asList(
            ImmutableMap.of("id", "book-1",
                    "name", "Harry Potter and the Philosopher's Stone",
                    "pageCount", "223",
                    "authorId", "author-1"),
            ImmutableMap.of("id", "book-2",
                    "name", "Moby Dick",
                    "pageCount", "635",
                    "authorId", "author-2"),
            ImmutableMap.of("id", "book-3",
                    "name", "Interview with the vampire",
                    "pageCount", "371",
                    "authorId", "author-3")
    );
    private static List<Map<String, String>> authors = Arrays.asList(
            ImmutableMap.of("id", "author-1",
                    "firstName", "Joanne",
                    "lastName", "Rowling"),
            ImmutableMap.of("id", "author-2",
                    "firstName", "Herman",
                    "lastName", "Melville"),
            ImmutableMap.of("id", "author-3",
                    "firstName", "Anne",
                    "lastName", "Rice")
    );
   public DataFetcher getAllBooks() {
        return environment -> {
            Map<String, Object> arguments = environment.getArgument("book");
            Book book = JSON.parseObject(JSON.toJSONString(arguments), Book.class);
            return books;
        };
    }
    public DataFetcher getBookByIdDataFetcher() {
       // dataFetchingEnvironment 封裝了查詢(xún)中帶有的參數(shù)
        return dataFetchingEnvironment -> {
            String bookId = dataFetchingEnvironment.getArgument("id");
            return books
                    .stream()
                    .filter(book -> book.get("id").equals(bookId))
                    .findFirst()
                    .orElse(null);
        };
    }
    public DataFetcher getAuthorDataFetcher() {
    // 這里因?yàn)槭峭ㄟ^(guò)Book查詢(xún)Author數(shù)據(jù)的子查詢(xún)谎脯,所以dataFetchingEnvironment.getSource()中封裝了Book對(duì)象的全部信息
   //即GraphQL中每個(gè)字段的Datafetcher都是以自頂向下的方式調(diào)用的葱跋,父字段的結(jié)果是子Datafetcherenvironment的source屬性。
        return dataFetchingEnvironment -> {
            Map<String,String> book = dataFetchingEnvironment.getSource();
            String authorId = book.get("authorId");
            return authors
                    .stream()
                    .filter(author -> author.get("id").equals(authorId))
                    .findFirst()
                    .orElse(null);
        };
    }
}
  • 上面實(shí)現(xiàn)了兩個(gè)Datafetcher源梭,他們會(huì)綁定到Schema文件中bookById方法娱俺,和Book對(duì)象的author字段上,這樣在執(zhí)行bookById方法和獲取author字段信息時(shí)废麻,就會(huì)條用對(duì)應(yīng)的DataFetcher方法荠卷。此外,沒(méi)有綁定的指定DataFetcher的字段烛愧,會(huì)使用默認(rèn)的PropertyDataFetcher油宜,即DataFetcher中返回的對(duì)象屬性如果跟Schema中定義的屬性名相同的話(huà),會(huì)自動(dòng)賦值給對(duì)應(yīng)的屬性怜姿,否則定義的字段值為null慎冤。

3.解析Schema并綁定DataFetcher

  • 定義一個(gè)GraphQLProvider類(lèi),來(lái)初始化GraphQL類(lèi)
@Component
public class GraphQLProvider {
    @Autowired
    GraphQLDataFetchers graphQLDataFetchers;
    private GraphQL graphQL;
    @PostConstruct
    public void init() throws IOException {
        URL url = Resources.getResource("schema.graphqls");
        String sdl = Resources.toString(url, Charsets.UTF_8);
        GraphQLSchema graphQLSchema = buildSchema(sdl);
        this.graphQL = GraphQL.newGraphQL(graphQLSchema).build();
    }
    private GraphQLSchema buildSchema(String sdl) {
        TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(sdl);
        RuntimeWiring runtimeWiring = buildWiring();
        SchemaGenerator schemaGenerator = new SchemaGenerator();
        return schemaGenerator.makeExecutableSchema(typeRegistry, runtimeWiring);
    }
    private RuntimeWiring buildWiring() {
        return RuntimeWiring.newRuntimeWiring()
                // 僅僅是體驗(yàn)Mutation這個(gè)功能,返回一個(gè)字符串
                .type("Mutation", builder -> builder.dataFetcher("hello", new StaticDataFetcher("Mutation hello world")))
                // 返回字符串
                .type("Query", builder -> builder.dataFetcher("hello", new StaticDataFetcher("Query hello world")))
                // 通過(guò)id查詢(xún)book
                .type(newTypeWiring("Query").dataFetcher("bookById", graphQLDataFetchers.getBookByIdDataFetcher()))
                // 查詢(xún)所有的book
                .type(newTypeWiring("Query").dataFetcher("books", graphQLDataFetchers.getAllBooks()))
                // 查詢(xún)book中的author信息
                .type(newTypeWiring("Book").dataFetcher("author", graphQLDataFetchers.getAuthorDataFetcher()))
                .build();
    }

  // 執(zhí)行GraphQL語(yǔ)言的入口
    @Bean
    public GraphQL graphQL() {
        return graphQL;
    }

4. 定義controller通過(guò)GraphQL查詢(xún)數(shù)據(jù)

  • 在Spring Boot中不需要定義這個(gè)沧卢,默認(rèn)會(huì)定義一個(gè)host/graphql的Servlet
@RestController
public class GraphQLController {
    @Autowired
    private GraphQL graphQL;
    @RequestMapping(value = "/graphql")
  // 這里定義的一個(gè)字符串接口所有的參數(shù)蚁堤,定義對(duì)象也是可以的
    public Map<String, Object> graphql(@RequestBody String request) {
        JSONObject req = JSON.parseObject(request);
        ExecutionInput executionInput = ExecutionInput.newExecutionInput()
               // 需要執(zhí)行的查詢(xún)語(yǔ)言
                .query(req.getString("query"))
              // 執(zhí)行操作的名稱(chēng),默認(rèn)為null
                .operationName(req.getString("operationName"))
              // 獲取query語(yǔ)句中定義的變量的值
                .variables(req.getJSONObject("variables"))
                .build();
       // 執(zhí)行并返回結(jié)果
        return this.graphQL.execute(executionInput).toSpecification();
    }
}

5. 演示上訴代碼并說(shuō)明一些概念

本文使用GraphQLPlayground演示但狭,下載地址:https://github.com/prisma/graphql-playground/releases
當(dāng)然用官方的graphiql:https://github.com/graphql/graphiql披诗, 或者postman也都是可以的。

  • 查詢(xún)所有的book


    1555855026893.jpg
  • 通過(guò)id查詢(xún)book


    2222.jpg
  • 分別解釋一下上圖中的概念
    • 1.query立磁、mutation對(duì)應(yīng)上面說(shuō)的查詢(xún)和修改規(guī)范呈队,也是schema中定義的類(lèi)型,默認(rèn)類(lèi)型為query如第一個(gè)圖唱歧。
    • 2.bookByIds就是上面定義Controller中獲取的operationName名稱(chēng)宪摧,這個(gè)由查詢(xún)方自行定義,對(duì)后端沒(méi)有特別的意義颅崩。
    • 3.查詢(xún)變量的定義绍刮,相當(dāng)于query查詢(xún)接口的入?yún)ⅲ梢栽趒uery里面的接口中引用,7初就是定義查詢(xún)的實(shí)參。
    • 4.定義bookById接口的別名说莫,即可以對(duì)接口定義別名姑蓝,在同一個(gè)查詢(xún)中多次請(qǐng)求同一個(gè)接口時(shí)膝蜈,必須為接口定義不同的別名,否則會(huì)報(bào)錯(cuò)熔掺,無(wú)法請(qǐng)求饱搏。看返回?cái)?shù)據(jù)中別名為key置逻,接口返回的數(shù)據(jù)為value推沸。
    • 5.對(duì)應(yīng)bookById的另一個(gè)別名,這里相當(dāng)于對(duì)bookById用不同的參數(shù)進(jìn)行了第二次查詢(xún)券坞,這也是GraphQL重要特性之一的合并不同查詢(xún)?yōu)橐淮尾樵?xún)節(jié)約傳輸成本鬓催。
    • 6.對(duì)Schema中query中定義的hello查詢(xún),返回一個(gè)字符串恨锚,是為了區(qū)別6下面mutation 中hello的定義宇驾,即在GraphQL中通過(guò)查詢(xún)或變更類(lèi)型+里面定義的接口確定一個(gè)唯一執(zhí)行入口。
    • 7.定義3中參數(shù)的實(shí)際入?yún)⒑锪妫稍贑ontroller接口中的variables參數(shù)接收课舍。
    • 8.為查詢(xún)的所有接口的返回值,默認(rèn)為接口別名(或接口名)為key他挎,接口返回的數(shù)據(jù)為value的Json數(shù)據(jù)筝尾。
    • 9.點(diǎn)擊9可以查看服務(wù)端所有定義的接口信息,也是在GraphQL存在的問(wèn)題之一办桨,會(huì)想客戶(hù)端暴露所有的接口信息筹淫。
    • 10.點(diǎn)擊10可以查看服務(wù)端定義的Schema信息,對(duì)服務(wù)端定義的Schema信息一覽無(wú)余崔挖。

6. 此GraphQL Java(原生實(shí)現(xiàn))服務(wù)器端搭建存在的問(wèn)題

  • 在服務(wù)端只能定義一個(gè)Schema文件贸街,隨著接口越來(lái)越多這個(gè)文件會(huì)超級(jí)龐大。
  • Schema中定義的接口需要手動(dòng)跟對(duì)應(yīng)的DataFetcher綁定狸相,無(wú)法根據(jù)Schema定義自動(dòng)綁定對(duì)應(yīng)的解析方法薛匪。
  • 解決方案:
    • 自行擴(kuò)展GraphQL Java項(xiàng)目(成本太大)
    • 使用GraphQL Java Tool,很好的封裝了GraphQL Java脓鹃,實(shí)現(xiàn)了面向?qū)ο蟮拈_(kāi)發(fā)開(kāi)發(fā)模式逸尖,具體參看官方介紹
    • 使用Spring Boot搭建,GraphQL針對(duì)Spring Boot添加了起步依賴(lài)瘸右,同時(shí)使用GraphQL Java Tool娇跟,使得開(kāi)發(fā)GraphQL更加的簡(jiǎn)單高效
    • Spring Boot GraphQL Demo

參考

擴(kuò)展閱讀

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市太颤,隨后出現(xiàn)的幾起案子苞俘,更是在濱河造成了極大的恐慌,老刑警劉巖龄章,帶你破解...
    沈念sama閱讀 211,817評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吃谣,死亡現(xiàn)場(chǎng)離奇詭異乞封,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)岗憋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)肃晚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人仔戈,你說(shuō)我怎么就攤上這事关串。” “怎么了监徘?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,354評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵晋修,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我耐量,道長(zhǎng)飞蚓,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,498評(píng)論 1 284
  • 正文 為了忘掉前任廊蜒,我火速辦了婚禮趴拧,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘山叮。我一直安慰自己著榴,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,600評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布屁倔。 她就那樣靜靜地躺著脑又,像睡著了一般。 火紅的嫁衣襯著肌膚如雪锐借。 梳的紋絲不亂的頭發(fā)上问麸,一...
    開(kāi)封第一講書(shū)人閱讀 49,829評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音钞翔,去河邊找鬼严卖。 笑死,一個(gè)胖子當(dāng)著我的面吹牛布轿,可吹牛的內(nèi)容都是我干的哮笆。 我是一名探鬼主播,決...
    沈念sama閱讀 38,979評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼汰扭,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼稠肘!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起萝毛,我...
    開(kāi)封第一講書(shū)人閱讀 37,722評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤项阴,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后笆包,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體环揽,經(jīng)...
    沈念sama閱讀 44,189評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡拷沸,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,519評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了薯演。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,654評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡秧了,死狀恐怖跨扮,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情验毡,我是刑警寧澤衡创,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站晶通,受9級(jí)特大地震影響璃氢,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜狮辽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,940評(píng)論 3 313
  • 文/蒙蒙 一一也、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧喉脖,春花似錦椰苟、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,762評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至题诵,卻和暖如春洁仗,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背性锭。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,993評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工赠潦, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人篷店。 一個(gè)月前我還...
    沈念sama閱讀 46,382評(píng)論 2 360
  • 正文 我出身青樓祭椰,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親疲陕。 傳聞我的和親對(duì)象是個(gè)殘疾皇子方淤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,543評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容