graphql-java使用手冊:part3 執(zhí)行(Execution)

原文:http://blog.mygraphql.com/wordpress/?p=102

執(zhí)行(Execution)

查詢(Queries)

為了對 一個(gè)Schema 執(zhí)行查詢嗅辣。需要先構(gòu)造一個(gè) GraphQL
對象,并帶著一些參數(shù)去調(diào)用 execute() 方法.

查詢將返回一個(gè) ExecutionResult 對象祈坠,其中包含查詢的結(jié)果數(shù)據(jù)
(或出錯(cuò)時(shí)的錯(cuò)誤信息集合).

GraphQLSchema schema = GraphQLSchema.newSchema()
        .query(queryType)
        .build();

GraphQL graphQL = GraphQL.newGraphQL(schema)
        .build();

ExecutionInput executionInput = ExecutionInput.newExecutionInput().query("query { hero { name } }")
        .build();

ExecutionResult executionResult = graphQL.execute(executionInput);

Object data = executionResult.getData();
List<GraphQLError> errors = executionResult.getErrors();

更復(fù)雜的示例节沦,可以看 StarWars 查詢測試用例

Data Fetchers

每個(gè)graphql schema 中的field淳玩,都需要綁定相應(yīng)的
graphql.schema.DataFetcher 以獲取數(shù)據(jù). 其它GraphQL的實(shí)現(xiàn)把這叫
resolvers*.

很多時(shí)候,你可以用默認(rèn)的 graphql.schema.PropertyDataFetcher 去從 Java
POJO 中自動(dòng)提取數(shù)據(jù)到對應(yīng)的 field. 如果你未為 field 指定 data fetcher
那么就默認(rèn)使用它.

但你最少需要為頂層的領(lǐng)域?qū)ο?domain objects) 編寫 data fetchers.
其中可以會(huì)與database交互,或用HTTP與其它系統(tǒng)交互.

graphql-java 不關(guān)心你如何獲取你的業(yè)務(wù)數(shù)據(jù)旷档,這是你的自己.
它也不關(guān)心你如果授權(quán)你的業(yè)務(wù)數(shù)據(jù).
你應(yīng)該在自己的業(yè)務(wù)邏輯層跛璧,去實(shí)現(xiàn)這些邏輯.

簡單 Data fetcher 示例:

DataFetcher userDataFetcher = new DataFetcher() {
    @Override
    public Object get(DataFetchingEnvironment environment) {
        return fetchUserFromDatabase(environment.getArgument("userId"));
    }
};

框架在執(zhí)行查詢時(shí)严里。會(huì)調(diào)用上面的方法,其中的
graphql.schema.DataFetchingEnvironment 參數(shù)包括以下信息:被查詢的
field追城、查詢這個(gè)field時(shí)可能帶上的查詢參數(shù)刹碾、這個(gè)field的父數(shù)據(jù)對象(Source
Object)、 查詢的ROOT數(shù)據(jù)對象座柱、查詢執(zhí)行上下文環(huán)境對象(query context
object).

上面是同步獲取數(shù)據(jù)的例子迷帜,執(zhí)行引擎需要等待一個(gè) data fetcher
返回?cái)?shù)據(jù)才能繼續(xù)下一個(gè). 也可以通過編寫異步的 DataFetcher 叨吮,異步地返回
CompletionStage 對象,在下文中將會(huì)說明使用方法.

當(dāng)獲取數(shù)據(jù)出現(xiàn)異常時(shí)

如果異步是出現(xiàn)在調(diào)用 data fetcher 時(shí), 默認(rèn)的執(zhí)行策略(execution strategy)
將生成一個(gè) graphql.ExceptionWhileDataFetching
錯(cuò)誤瞬矩,并將其加入到查詢結(jié)果的錯(cuò)誤列表中. 請留意茶鉴,GraphQL
在發(fā)生異常時(shí),允許返回部分成功的數(shù)據(jù)景用,并將帶上異常信息.

下面是默認(rèn)的異常行為處理邏輯.

public class SimpleDataFetcherExceptionHandler implements DataFetcherExceptionHandler {
    private static final Logger log = LoggerFactory.getLogger(SimpleDataFetcherExceptionHandler.class);

    @Override
    public void accept(DataFetcherExceptionHandlerParameters handlerParameters) {
        Throwable exception = handlerParameters.getException();
        SourceLocation sourceLocation = handlerParameters.getField().getSourceLocation();
        ExecutionPath path = handlerParameters.getPath();

        ExceptionWhileDataFetching error = new ExceptionWhileDataFetching(path, exception, sourceLocation);
        handlerParameters.getExecutionContext().addError(error);
        log.warn(error.getMessage(), exception);
    }
}

如果你拋出的異常本身是 GraphqlError 類型涵叮,框架會(huì)把其中的消息 和
自定義擴(kuò)展屬性(custom extensions attributes)轉(zhuǎn)換到
ExceptionWhileDataFetching 對象中.
這可以方便你把自己的錯(cuò)誤信息,放到返回給調(diào)用者的 GraphQL 錯(cuò)誤列表中.

例如伞插,你在 DataFetcher 中拋出了這個(gè)異常. 那么 foo and fizz
屬性將會(huì)包含在返回給調(diào)用者的graphql查詢錯(cuò)誤中.

class CustomRuntimeException extends RuntimeException implements GraphQLError {
    @Override
    public Map<String, Object> getExtensions() {
        Map<String, Object> customAttributes = new LinkedHashMap<>();
        customAttributes.put("foo", "bar");
        customAttributes.put("fizz", "whizz");
        return customAttributes;
    }

    @Override
    public List<SourceLocation> getLocations() {
        return null;
    }

    @Override
    public ErrorType getErrorType() {
        return ErrorType.DataFetchingException;
    }
}

你可以編寫自己的 graphql.execution.DataFetcherExceptionHandler
來改變這些邏輯割粮。只需要在執(zhí)行策略(execution strategy)注冊一下.

例如,上面的代碼記錄了底層的異常和堆棧.
如果你不希望這些出現(xiàn)在輸出的錯(cuò)誤列表中媚污。你可以用以下的方法去實(shí)現(xiàn).

DataFetcherExceptionHandler handler = new DataFetcherExceptionHandler() {
    @Override
    public void accept(DataFetcherExceptionHandlerParameters handlerParameters) {
        //
        // do your custom handling here.  The parameters have all you need
    }
};
ExecutionStrategy executionStrategy = new AsyncExecutionStrategy(handler);

序列化成 JSON

通常舀瓢,用 HTTP 方法去調(diào)用 graphql ,用 JSON 格式作為返回結(jié)果.
返回耗美,需要把 graphql.ExecutionResult 對象轉(zhuǎn)換為 JSON 格式包.

一般用 Jackson or GSON 去做 JSON 序列化.
但他們對結(jié)果數(shù)據(jù)的轉(zhuǎn)換方法有一些不同點(diǎn). 例如 JSON 的`nulls` 在 graphql
結(jié)果中的是有用的京髓。所以必須在 json mappers 中設(shè)置需要它

為保證你返回的 JSON 結(jié)果 100% 合符 graphql 規(guī)范, 應(yīng)該調(diào)用result對象的
toSpecification 方法,然后以 JSON格式 發(fā)送響應(yīng).

這樣就可以確保返回?cái)?shù)據(jù)合符在
http://facebook.github.io/graphql/#sec-Response 中的規(guī)范

ExecutionResult executionResult = graphQL.execute(executionInput);

Map<String, Object> toSpecificationResult = executionResult.toSpecification();

sendAsJson(toSpecificationResult);

更新(Mutations)

如果你不了解什么叫更新(Mutations)商架,建議先閱讀規(guī)范
http://graphql.org/learn/queries/#mutations.

首先堰怨,你需要定義一個(gè)支持輸入?yún)?shù)的 GraphQLObjectType .
在更新數(shù)據(jù)時(shí),框架會(huì)帶上這些參數(shù)去調(diào)用 data fetcher.

下面是蛇摸,GraphQL 更新語句的例子 :

mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
  createReview(episode: $ep, review: $review) {
    stars
    commentary
  }
}

修改操作是需要帶輸入?yún)?shù)的备图,上例中對應(yīng)變量 $ep and $review

對應(yīng)地,Schema 應(yīng)該這么寫【譯注:以下是 Java 寫法赶袄,你也可以用SDL寫法】 :

GraphQLInputObjectType episodeType = GraphQLInputObjectType.newInputObject()
        .name("Episode")
        .field(newInputObjectField()
                .name("episodeNumber")
                .type(Scalars.GraphQLInt))
        .build();

GraphQLInputObjectType reviewInputType = GraphQLInputObjectType.newInputObject()
        .name("ReviewInput")
        .field(newInputObjectField()
                .name("stars")
                .type(Scalars.GraphQLString))
        .field(newInputObjectField()
                .name("commentary")
                .type(Scalars.GraphQLString))
        .build();

GraphQLObjectType reviewType = newObject()
        .name("Review")
        .field(newFieldDefinition()
                .name("stars")
                .type(GraphQLString))
        .field(newFieldDefinition()
                .name("commentary")
                .type(GraphQLString))
        .build();

GraphQLObjectType createReviewForEpisodeMutation = newObject()
        .name("CreateReviewForEpisodeMutation")
        .field(newFieldDefinition()
                .name("createReview")
                .type(reviewType)
                .argument(newArgument()
                        .name("episode")
                        .type(episodeType)
                )
                .argument(newArgument()
                        .name("review")
                        .type(reviewInputType)
                )
                .dataFetcher(mutationDataFetcher())
        )
        .build();

GraphQLSchema schema = GraphQLSchema.newSchema()
        .query(queryType)
        .mutation(createReviewForEpisodeMutation)
        .build();

注意揽涮,輸入?yún)?shù)應(yīng)該是 GraphQLInputObjectType 類型. 請留意.
對于修改操作,輸入?yún)?shù)只能用這個(gè)類型(type)饿肺,而不能用如
>><<GraphQLObjectType之類的輸出類型(type). Scalars 類型(type)
可以用于輸入和輸出.

對于更新操作蒋困,DataFetcher的職責(zé)是執(zhí)行數(shù)據(jù)更新行返回執(zhí)行結(jié)果.

private DataFetcher mutationDataFetcher() {
    return new DataFetcher() {
        @Override
        public Review get(DataFetchingEnvironment environment) {
            //
            // The graphql specification dictates that input object arguments MUST
            // be maps.  You can convert them to POJOs inside the data fetcher if that
            // suits your code better
            //
            // See http://facebook.github.io/graphql/October2016/#sec-Input-Objects
            //
            Map<String, Object> episodeInputMap = environment.getArgument("episode");
            Map<String, Object> reviewInputMap = environment.getArgument("review");

            //
            // in this case we have type safe Java objects to call our backing code with
            //
            EpisodeInput episodeInput = EpisodeInput.fromMap(episodeInputMap);
            ReviewInput reviewInput = ReviewInput.fromMap(reviewInputMap);

            // make a call to your store to mutate your database
            Review updatedReview = reviewStore().update(episodeInput, reviewInput);

            // this returns a new view of the data
            return updatedReview;
        }
    };
}

上面代碼,先更新業(yè)務(wù)數(shù)據(jù)唬格,然后返回 Review 對象給調(diào)用方.

異步執(zhí)行(Asynchronous Execution)

graphql-java 是個(gè)全異步的執(zhí)行引擎. 如下家破,調(diào)用 executeAsync() 后,返回
CompleteableFuture

GraphQL graphQL = buildSchema();

ExecutionInput executionInput = ExecutionInput.newExecutionInput().query("query { hero { name } }")
        .build();

CompletableFuture<ExecutionResult> promise = graphQL.executeAsync(executionInput);

promise.thenAccept(executionResult -> {
    // here you might send back the results as JSON over HTTP
    encodeResultToJsonAndSendResponse(executionResult);
});

promise.join();

使用 CompletableFuture
對象购岗,你可以指定汰聋,在查詢完成后,組合其它操作(action)或函數(shù)你的函數(shù).
需要你需要同步等待執(zhí)行結(jié)果 喊积,可以調(diào)用 .join() 方法.

graphql-java引擎內(nèi)部是異步執(zhí)行的烹困,但你可以通過調(diào)用 join
方法變?yōu)橥降却? 下面是等效的代碼:

ExecutionResult executionResult = graphQL.execute(executionInput);

// the above is equivalent to the following code (in long hand)

CompletableFuture<ExecutionResult> promise = graphQL.executeAsync(executionInput);
ExecutionResult executionResult2 = promise.join();

如果你編寫的 graphql.schema.DataFetcher 返回 CompletableFuture<T>
對象,那么它會(huì)被糅合到整個(gè)異步查詢中.
這樣乾吻,你可以同時(shí)發(fā)起我個(gè)數(shù)據(jù)獲取操作髓梅,讓它們并行運(yùn)行.
而由DataFetcher控制具體的線程并發(fā)策略.

下面示例使用 java.util.concurrent.ForkJoinPool.commonPool()
并行執(zhí)行器拟蜻,用其它線程完成數(shù)據(jù)獲取.

DataFetcher userDataFetcher = new DataFetcher() {
    @Override
    public Object get(DataFetchingEnvironment environment) {
        CompletableFuture<User> userPromise = CompletableFuture.supplyAsync(() -> {
            return fetchUserViaHttp(environment.getArgument("userId"));
        });
        return userPromise;
    }
};

上面是舊的寫法,也可以用Java 8 lambdas 的寫法:

DataFetcher userDataFetcher = environment -> CompletableFuture.supplyAsync(
        () -> fetchUserViaHttp(environment.getArgument("userId")));

graphql-java 保證所有 CompletableFuture 對象組合枯饿,最后生成合符 graphql
規(guī)范的執(zhí)行結(jié)果.

還有一個(gè)方法可以簡化異步 data fetchers 的編寫. 使用
graphql.schema.AsyncDataFetcher.async(DataFetcher<T>)
去包裝DataFetcher. 這樣可以使用 static imports 來提高代碼可讀性.

DataFetcher userDataFetcher = async(environment -> fetchUserViaHttp(environment.getArgument("userId")));

關(guān)于執(zhí)行策略(Execution Strategies)

在執(zhí)行查詢或更新數(shù)據(jù)時(shí)酝锅,引擎會(huì)使用實(shí)現(xiàn)了
>><<graphql.execution.ExecutionStrategy接口 的對象,來決定執(zhí)行策略.
graphql-java 中已經(jīng)有幾個(gè)現(xiàn)成的策略奢方,但如果你需要搔扁,你可以寫自己的。.

你可以這樣給 GraphQL 對象綁定執(zhí)行策略蟋字。

GraphQL.newGraphQL(schema)
        .queryExecutionStrategy(new AsyncExecutionStrategy())
        .mutationExecutionStrategy(new AsyncSerialExecutionStrategy())
        .build();

實(shí)際上稿蹲,上面就是引擎默認(rèn)的策略了。大部分情況下用它就夠了鹊奖。

異步執(zhí)行策略(AsyncExecutionStrategy)

默認(rèn)的查詢 執(zhí)行策略是 graphql.execution.AsyncExecutionStrategy
苛聘,它會(huì)把每個(gè) field 返回視為 CompleteableFuture 。它并不會(huì)控制 filed
的獲取順序. 這個(gè)策略可以優(yōu)化查詢執(zhí)行的性能.

Data fetchers 返回 CompletionStage`
對象忠聚,就可以全異步執(zhí)行整個(gè)查詢了设哗。

例如以下的查詢:

query {
  hero {
    enemies {
      name
    }
    friends {
      name
    }
  }
}

The AsyncExecutionStrategy is free to dispatch the enemies field at
the same time as the friends field. It does not have to do enemies
first followed by friends, which would be less efficient.

這個(gè)策略不會(huì)按順序來集成結(jié)果數(shù)據(jù)。但查詢結(jié)果會(huì)按GraphQL規(guī)范順序來返回咒林。只是數(shù)據(jù)獲取的順序不確定熬拒。

對于查詢爷光,這個(gè)策略是 graphql 規(guī)范
http://facebook.github.io/graphql/#sec-Query 允許和推薦的垫竞。

詳細(xì)見 規(guī)范 .

異步順序執(zhí)行策略(AsyncSerialExecutionStrategy)

Graphql 規(guī)范指出,修改操作(mutations)“必須”按照 field 的順序來執(zhí)行蛀序。

所以欢瞪,為了確保一個(gè) field 一個(gè) field
順序地執(zhí)行更新,更新操作(mutations)默認(rèn)使用
graphql.execution.AsyncSerialExecutionStrategy 策略徐裸。你的 mutation
Data Fetcher 仍然可以返回 CompletionStage 對象, 但它和其它 field
的是串行執(zhí)行的遣鼓。

基于執(zhí)行器的執(zhí)行策略:ExecutorServiceExecutionStrategy

The graphql.execution.ExecutorServiceExecutionStrategy execution
strategy will always dispatch each field fetch in an asynchronous
manner, using the executor you give it. It differs from
AsyncExecutionStrategy in that it does not rely on the data fetchers
to be asynchronous but rather makes the field fetch invocation
asynchronous by submitting each field to the provided
java.util.concurrent.ExecutorService.

因?yàn)檫@樣,所以它不能用于更新(mutation)操作重贺。

ExecutorService  executorService = new ThreadPoolExecutor(
        2, /* core pool size 2 thread */
        2, /* max pool size 2 thread */
        30, TimeUnit.SECONDS,
        new LinkedBlockingQueue<Runnable>(),
        new ThreadPoolExecutor.CallerRunsPolicy());

GraphQL graphQL = GraphQL.newGraphQL(StarWarsSchema.starWarsSchema)
        .queryExecutionStrategy(new ExecutorServiceExecutionStrategy(executorService))
        .mutationExecutionStrategy(new AsyncSerialExecutionStrategy())
        .build();

訂閱執(zhí)行策略(SubscriptionExecutionStrategy)

Graphql 訂閱(subscriptions) 使你可以對GraphQL
數(shù)據(jù)進(jìn)行為狀態(tài)的訂閱骑祟。你可以使用 SubscriptionExecutionStrategy
執(zhí)行策略,它支持 reactive-streams APIs气笙。

閱讀 http://www.reactive-streams.org/ 可以得到關(guān)于 Publisher
Subscriber 接口的更多信息次企。

也可以閱讀subscriptions的文檔,以了解如何編寫基于支持訂閱的 graphql
服務(wù)潜圃。

批量化執(zhí)行器(BatchedExecutionStrategy)

對于有數(shù)組(list)field 的 schemas缸棵, 我們提供了
graphql.execution.batched.BatchedExecutionStrategy
策略。它可以批量化地調(diào)用標(biāo)注了@Batched 的 DataFetchers 的 get() 方法谭期。

關(guān)于 BatchedExecutionStrategy
是如何工作的堵第。它是如此的特別吧凉,讓我不知道如何解釋【譯注:原文:Its a
pretty special case that I don’t know how to explain properly】

控制字段的可見性

所有 GraphqlSchema
的字段(field)默認(rèn)都是可以訪問的。但有時(shí)候踏志,你可能想不同用戶看到不同部分的字段阀捅。

你可以在schema 上綁定一個(gè)
graphql.schema.visibility.GraphqlFieldVisibility 對象。.

框架提供了一個(gè)可以指定字段(field)名的實(shí)現(xiàn)针余,叫
graphql.schema.visibility.BlockedFields..

GraphqlFieldVisibility blockedFields = BlockedFields.newBlock()
        .addPattern("Character.id")
        .addPattern("Droid.appearsIn")
        .addPattern(".*\\.hero") // it uses regular expressions
        .build();

GraphQLSchema schema = GraphQLSchema.newSchema()
        .query(StarWarsSchema.queryType)
        .fieldVisibility(blockedFields)
        .build();

如果你需要也搓,還有一個(gè)實(shí)現(xiàn)可以防止 instrumentation 攔截你的 schema。

請注意涵紊,這會(huì)使您的服務(wù)器違反graphql規(guī)范和大多數(shù)客戶端的預(yù)期傍妒,因此請謹(jǐn)慎使用.

GraphQLSchema schema = GraphQLSchema.newSchema()
        .query(StarWarsSchema.queryType)
        .fieldVisibility(NoIntrospectionGraphqlFieldVisibility.NO_INTROSPECTION_FIELD_VISIBILITY)
        .build();

你可以編寫自己的 GraphqlFieldVisibility 來控制字段的可見性。

class CustomFieldVisibility implements GraphqlFieldVisibility {

    final YourUserAccessService userAccessService;

    CustomFieldVisibility(YourUserAccessService userAccessService) {
        this.userAccessService = userAccessService;
    }

    @Override
    public List<GraphQLFieldDefinition> getFieldDefinitions(GraphQLFieldsContainer fieldsContainer) {
        if ("AdminType".equals(fieldsContainer.getName())) {
            if (!userAccessService.isAdminUser()) {
                return Collections.emptyList();
            }
        }
        return fieldsContainer.getFieldDefinitions();
    }

    @Override
    public GraphQLFieldDefinition getFieldDefinition(GraphQLFieldsContainer fieldsContainer, String fieldName) {
        if ("AdminType".equals(fieldsContainer.getName())) {
            if (!userAccessService.isAdminUser()) {
                return null;
            }
        }
        return fieldsContainer.getFieldDefinition(fieldName);
    }
}

查詢緩存(Query Caching)

Before the graphql-java engine executes a query it must be parsed and
validated, and this process can be somewhat time consuming.

為了避免重復(fù)的解釋和校驗(yàn)摸柄。 GraphQL.Builder
可以使用PreparsedDocumentProvider去重用 Document 實(shí)例颤练。

它不是緩存 查詢結(jié)果,只是緩存解釋過的文檔( Document )驱负。

Cache<String, PreparsedDocumentEntry> cache = Caffeine.newBuilder().maximumSize(10_000).build(); (1)
GraphQL graphQL = GraphQL.newGraphQL(StarWarsSchema.starWarsSchema)
        .preparsedDocumentProvider(cache::get) (2)
        .build();
  1. 創(chuàng)建你需要的緩存實(shí)例嗦玖,本例子是使用的是 Caffeine
    。它是個(gè)高質(zhì)量的緩存解決方案跃脊。緩存實(shí)例應(yīng)該是線程安全和可以線程間共享的宇挫。
  2. PreparsedDocumentProvider 是一個(gè)函式接口( functional
    interface),方法名是get酪术。.

為提高緩存命中率器瘪,GraphQL 語句中的 field 參數(shù)(arguments)建議使用變量(
variables)來表達(dá),而不是直接把值寫在語句中绘雁。

下面的查詢 :

query HelloTo {
     sayHello(to: "Me") {
        greeting
     }
}

應(yīng)該寫成:

query HelloTo($to: String!) {
     sayHello(to: $to) {
        greeting
     }
}

帶上參數(shù)( variables):

{
   "to": "Me"
}

這樣橡疼,這不管查詢的變量(variable)如何變化 ,查詢解釋也就可以重用庐舟。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末欣除,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子挪略,更是在濱河造成了極大的恐慌历帚,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件杠娱,死亡現(xiàn)場離奇詭異挽牢,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)墨辛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進(jìn)店門卓研,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事奏赘×壬粒” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵磨淌,是天一觀的道長疲憋。 經(jīng)常有香客問我,道長梁只,這世上最難降的妖魔是什么缚柳? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮搪锣,結(jié)果婚禮上秋忙,老公的妹妹穿的比我還像新娘。我一直安慰自己构舟,他們只是感情好灰追,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著狗超,像睡著了一般弹澎。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上努咐,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天苦蒿,我揣著相機(jī)與錄音,去河邊找鬼渗稍。 笑死佩迟,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的免胃。 我是一名探鬼主播音五,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼羔沙!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起厨钻,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤扼雏,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后夯膀,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體诗充,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年诱建,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蝴蜓。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,096評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖茎匠,靈堂內(nèi)的尸體忽然破棺而出格仲,到底是詐尸還是另有隱情,我是刑警寧澤诵冒,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布凯肋,位于F島的核電站,受9級特大地震影響汽馋,放射性物質(zhì)發(fā)生泄漏侮东。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一豹芯、第九天 我趴在偏房一處隱蔽的房頂上張望悄雅。 院中可真熱鬧,春花似錦铁蹈、人聲如沸煤伟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽便锨。三九已至,卻和暖如春我碟,著一層夾襖步出監(jiān)牢的瞬間放案,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工矫俺, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留吱殉,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓厘托,卻偏偏與公主長得像友雳,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子铅匹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評論 2 355

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理押赊,服務(wù)發(fā)現(xiàn),斷路器包斑,智...
    卡卡羅2017閱讀 134,659評論 18 139
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法流礁,類相關(guān)的語法,內(nèi)部類的語法罗丰,繼承相關(guān)的語法神帅,異常的語法,線程的語...
    子非魚_t_閱讀 31,639評論 18 399
  • 作者: 一字馬胡 轉(zhuǎn)載標(biāo)志 【2017-11-13】 更新日志 導(dǎo)入 作為一種強(qiáng)大的DSQL萌抵,學(xué)習(xí)GraphQL...
    一字馬胡閱讀 11,010評論 0 13
  • 原文:http://blog.mygraphql.com/wordpress/?p=100 創(chuàng)建Schema Sc...
    MarkZhu閱讀 2,255評論 1 1
  • 寫作應(yīng)該是一件隨時(shí)隨地的事 有一種病大概就叫強(qiáng)迫癥找御。寫東西總是想要找特定的時(shí)間元镀,在特定的空間,有特定的字?jǐn)?shù)和自...
    安倩倩穎閱讀 317評論 0 0