Dropwizard官方教程(一) 入门

入门

本文将指导您完成一个简单的Dropwizard的Hello World项目。在此过程中,我们将解释各种底层库及其作用,以及Dropwizard中的重要概念,并建议一些组织技术来帮助您的项目能持续健康地增长。

概览

Dropwizard跨越了作为lib库和框架之间的界限。其目标是为生产就绪的Web应用程序所需的所有内容提供高性能,高可靠的实现。并且这些功能被封装到可重用的库中,因此您的应用程序仍然很精简和专注,从而缩短了产品上线时间和维护负担。

用于HTTP的Jetty

因为您的Web应用程序不能没有HTTP,Dropwizard使用Jetty ,一个令人难以置信的调优HTTP服务器直接嵌入到您的项目中。Dropwizard项目没有将应用程序交给复杂的应用程序服务器,而是有一个main方法运行HTTP服务器。将您的应用程序作为一个简单的过程运行,消除了生产中Java的许多令人讨厌的方面(没有PermGen问题,没有应用程序服务器配置和维护,没有神秘的部署工具,没有类加载器问题,没有隐藏的应用程序日志,没有尝试调整单个垃圾收集器与多个应用程序工作负载一起工作)并允许您使用所有现有的Unix进程管理工具。

用于REST的Jersey

为了构建RESTful Web应用程序,我们发现在功能或性能方面没有任何东西胜过JerseyJAX-RS参考实现)。它允许您编写干净,可测试的类,这些类可以优雅地将HTTP请求映射到简单的Java对象。它支持流输出,矩阵URI参数,条件GET请求等等。

用于JSON的Jackson

在数据格式方面,JSON已成为网络的通用语言,而Jackson则是JVM上的JSON之王。除了快速闪电之外,它还有一个复杂的对象映射器,允许您直接导出域模型。

用于监控的Metrics

指标有助于分析问题,为您提供无与伦比的洞悉您的生产环境中的代码的行为。

其他集成的工具

除了JettyJerseyJackson之外,Dropwizard还包括一些库,可以帮助您更快速高效地开发。

  • Guava除了高度优化的不可变数据结构外,还提供了越来越多的类来加速Java的开发。
  • Logbackslf4j用于高性能和灵活的日志记录。
  • Hibernate ValidatorJSR 349参考实现,它提供了一个简单的声明性框架,用于验证用户输入并生成有用且易于i18n的错误消息。
  • Apache的HttpClient的Jersey客户端库允许都与其他Web服务低收入和高层次的互动。
  • JDBI是使用Java的关系数据库最直接的方法。
  • Liquibase是在整个开发和发布周期中检查数据库模式的好方法,应用高级数据库重构而不是一次性DDL脚本。
  • FreemarkerMustache是简单的模板系统,适用于面向用户的更多应用程序。
  • Joda Time是一个非常完整,理智的库,用于处理日期和时间。

既然你已经了解到这了,那就让我们深入挖掘吧!

使用Maven进行设置

我们建议您将Maven用于新的Dropwizard应用程序。如果你是一个大型的Ant / IvyBuildrGradleSBTLeiningenGant粉丝,这很酷,但我们使用Maven,我们将在使用Maven时通过这个示例应用程序。如果您对Maven如何运作有任何疑问, Maven:The Complete Reference应该有你想要的东西。

你有三种选择:

  1. 使用dropwizard-archetype创建项目:

    mvn archetype:generate -DarchetypeGroupId=io.dropwizard.archetypes -DarchetypeArtifactId=java-simple -DarchetypeVersion=[REPLACE WITH A VALID DROPWIZARD VERSION]
    
  2. 看一下dropwizard-example

  3. 请按照以下教程了解如何将其包含在现有项目中

教程

首先,dropwizard.version使用当前版本的Dropwizard(1.3.5)向POM 添加属性:

<properties>
    <dropwizard.version>INSERT VERSION HERE</dropwizard.version>
</properties>

dropwizard-core库添加为依赖项:

<dependencies>
    <dependency>
        <groupId>io.dropwizard</groupId>
        <artifactId>dropwizard-core</artifactId>
        <version>${dropwizard.version}</version>
    </dependency>
</dependencies>

很好,这些足够了。我们现在已经建立了一个Maven项目,现在是时候开始编写真正的代码了。

创建配置类

每个Dropwizard应用程序都有自己的Configuration类的子类,它指定特定的环境参数。这些参数在YAML配置文件中指定,该文件被反序列化为应用程序配置类的实例。

我们将要构建的应用程序是一个高性能的Hello World服务。我们需要至少指定两件事:一个用于说出问候的模板和一个默认名称,以防用户未指定其名称。

这是我们的配置类的样子,这里有完整的示例

package com.example.helloworld;

import io.dropwizard.Configuration;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.hibernate.validator.constraints.NotEmpty;

public class HelloWorldConfiguration extends Configuration {
    @NotEmpty
    private String template;

    @NotEmpty
    private String defaultName = "Stranger";

    @JsonProperty
    public String getTemplate() {
        return template;
    }

    @JsonProperty
    public void setTemplate(String template) {
        this.template = template;
    }

    @JsonProperty
    public String getDefaultName() {
        return defaultName;
    }

    @JsonProperty
    public void setDefaultName(String name) {
        this.defaultName = name;
    }
}

这里有很多知识点,所以让我们解释一下吧。

当从YAML文件反序列化该类时,它将从YAML对象中提取两个根级别字段:template,我们的Hello World说明的模板,以及defaultName要使用的默认名称。templatedefaultName都标注了@NotEmpty,所以如果YAML配置文件有任何空值或缺少template完全的信息会抛出异常,并且应用程序将无法启动。

templatedefaultName的getter和setter都有注释 @JsonProperty,这允许Jackson既可以从YAML文件反序列化属性,也可以序列化它。

注意

从YAML到您的应用程序Configuration实例的映射由Jackson完成。这意味着您的Configuration类可以使用Jackson的所有对象映射注释。验证@NotEmpty由Hibernate Validator处理,它具有 广泛的内置约束供您使用。

我们的YAML文件将如下所示,完整的示例yml在这里

template: Hello, %s!
defaultName: Stranger

Dropwizard有许多比这更多的配置参数,但他们都有健全的默认值,这样可以保持你的配置文件小,重点突出。

因此,将YAML文件保存在您计划运行的jar的目录中(见下文)hello-world.yml,因为我们很快就会启动并运行,我们将需要它。接下来,我们正在创建我们的应用程序类!

创建应用程序类

结合项目的Configuration子类,Application子类构成了Dropwizard应用程序的核心。所述Application类启动各bundle和command命令,其提供基本功能在一起(稍后会详细介绍)。但是现在,我们 HelloWorldApplication看起来像这样:

package com.example.helloworld;

import io.dropwizard.Application;
import io.dropwizard.setup.Bootstrap;
import io.dropwizard.setup.Environment;
import com.example.helloworld.resources.HelloWorldResource;
import com.example.helloworld.health.TemplateHealthCheck;

public class HelloWorldApplication extends Application<HelloWorldConfiguration> {
    public static void main(String[] args) throws Exception {
        new HelloWorldApplication().run(args);
    }

    @Override
    public String getName() {
        return "hello-world";
    }

    @Override
    public void initialize(Bootstrap<HelloWorldConfiguration> bootstrap) {
        // nothing to do yet
    }

    @Override
    public void run(HelloWorldConfiguration configuration,
                    Environment environment) {
        // nothing to do yet
    }

}

如您所见,HelloWorldApplication使用应用程序的配置类型HelloWorldConfiguration进行参数化。initialize方法用于在运行应用程序之前配置所需应用程序的各个方面,如bundle,配置源提供程序等。此外,我们还添加了一个static main方法,它将成为我们应用程序的入口点。现在,我们没有实现任何功能,所以我们的run方法有点无聊。让我们解决这个问题!

创建表示类

在我们进入Hello World应用程序的细节之前,我们需要停下来思考一下我们的API。幸运的是,我们的应用程序需要符合行业标准RFC 1149,它规定了Hello World的以下JSON表示:

{
  "id": 1,
  "content": "Hi!"
}

id字段是该问候的唯一标识符,并且content是该问候的文本表示。(值得庆幸的是,这是一个相当直接的行业标准。)

要对此表示进行建模,我们将创建一个表示类:

package com.example.helloworld.api;

import com.fasterxml.jackson.annotation.JsonProperty;
import org.hibernate.validator.constraints.Length;

public class Saying {
    private long id;

    @Length(max = 3)
    private String content;

    public Saying() {
        // Jackson deserialization
    }

    public Saying(long id, String content) {
        this.id = id;
        this.content = content;
    }

    @JsonProperty
    public long getId() {
        return id;
    }

    @JsonProperty
    public String getContent() {
        return content;
    }
}

这是一个非常简单的POJO,但有一些值得注意的事情。

首先,它是不可改变的。这使得Saying实例在多线程环境以及单线程环境中非常容易推理。其次,它使用JavaBeans标准idcontent属性。这允许Jackson将其序列化为我们需要的JSON。Jackson对象映射代码将id使用返回值填充JSON对象的字段#getId(),同样使用content#getContent()。最后,bean利用验证来确保内容大小不超过3。

注意

这里的JSON序列化由Jackson完成,它支持的不仅仅是像这样的简单JavaBean对象。除了复杂的注释集,您甚至可以编写自定义序列化程序和反序列化程序。

既然我们已经有了表示类,接下来学习下资源。

创建资源类

Jersey资源是Dropwizard应用程序的核心。每个资源类都与URI模板相关联。对于我们的应用程序,我们需要从URI /hello-world返回一个Saying 实例,因此我们的资源类如下所示:

package com.example.helloworld.resources;

import com.example.helloworld.api.Saying;
import com.codahale.metrics.annotation.Timed;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import java.util.concurrent.atomic.AtomicLong;
import java.util.Optional;

@Path("/hello-world")
@Produces(MediaType.APPLICATION_JSON)
public class HelloWorldResource {
    private final String template;
    private final String defaultName;
    private final AtomicLong counter;

    public HelloWorldResource(String template, String defaultName) {
        this.template = template;
        this.defaultName = defaultName;
        this.counter = new AtomicLong();
    }

    @GET
    @Timed
    public Saying sayHello(@QueryParam("name") Optional<String> name) {
        final String value = String.format(template, name.orElse(defaultName));
        return new Saying(counter.incrementAndGet(), value);
    }
}

HelloWorldResource有两个注释:@Path@Produces@Path("/hello-world") 告诉Jersey,这个资源可以在URI上访问/hello-world,并且 @Produces(MediaType.APPLICATION_JSON)让Jersey的内容协商代码知道这个资源产生的表示形式是application/json

HelloWorldResource采用两个参数进行构造:template它用于产生问候语,当用户拒绝告诉我们他们的名字时使用defaultNameAtomicLong为我们提供了一种廉价,线程安全的方法来生成唯一(ish)ID。

警告

资源类由多个线程同时使用。一般来说,我们建议资源是无状态/不可变的,但重要的是要记住上下文。

#sayHello(Optional<String>)是这个类的核心,这是一个相当简单的方法。@QueryParam("name")注解告诉Jersey从请求参数字符串中把name参数赋值给方法中的name参数。如果客户端发送请求 /hello-world?name=DougiesayHello将被调用Optional.of("Dougie"); 如果name查询字符串中没有参数,sayHello则将调用Optional.absent()。(支持Guava的Optional是Dropwizard为Jersey的现有功能添加的一些额外的功能)

注意

如果客户端发送请求/hello-world?name=sayHello将被调用 Optional.of("")。这可能看起来很奇怪,但这遵循标准(应用程序可能具有不同的行为,具体取决于参数是否为空而不存在)。如果你希望/hello-world?name=返回“Hello,Stranger!”,可以用NonEmptyStringParam替换 Optional<String>参数。有关资源参数的更多信息,请参阅 文档

sayHello方法内部,我们递增计数器,使用格式化模板String.format(String,Object...) ,并返回一个Saying新实例。

因为sayHello带有注释@Timed,Dropwizard会自动将其调用的持续时间和速率记录为Metrics Timer。

一旦sayHello返回,Jersey就会获取Saying实例并查找可以将Saying实例写为application/json的转换类。Dropwizard内置了一个这样的转换类,允许使用Java对象生成JSON对象,JSON对象生成Java对象。该转换类生成JSON,客户端会收到200 OK 的内容类型为application/json的响应。

注册资源

不过,在这一切生效之前,我们需要回到HelloWorldApplication并注册这个新的资源类。在run方法中,我们可以从HelloWorldConfiguration实例中读取模板和默认名称 ,创建一个新HelloWorldResource实例,然后将其添加到应用程序的Jersey环境中:

@Override
public void run(HelloWorldConfiguration configuration,
                Environment environment) {
    final HelloWorldResource resource = new HelloWorldResource(
        configuration.getTemplate(),
        configuration.getDefaultName()
    );
    environment.jersey().register(resource);
}

当我们的应用程序启动时,我们使用配置文件中的参数创建一个新的资源类实例,并将其交给它Environment,它就像应用程序可以执行的所有操作的注册表。

注意

Dropwizard应用程序可以包含许多资源类,每个资源类都对应于自己的URI。只需添加另一个@Path注释资源类,并使用新类的实例调用register方法即可。

在我们走得太远之前,我们应该为我们的应用添加健康检查。

创建健康检查

运行状况检查为您提供了一种向应用程序添加小测试的方法,以便您验证应用程序在生产中是否正常运行。我们强烈建议您的所有应用程序至少具有一组最小的运行状况检查。

注意

事实上,我们强烈推荐这一点,如果你忽略了为你的项目添加健康检查,Dropwizard会唠叨你。

由于格式化字符串在应用程序运行时不太可能失败(与数据库连接池不同),因此我们必须在这里想一些创意。我们将添加一个运行状况检查,以确保我们可以格式化提供的模板:

package com.example.helloworld.health;

import com.codahale.metrics.health.HealthCheck;

public class TemplateHealthCheck extends HealthCheck {
    private final String template;

    public TemplateHealthCheck(String template) {
        this.template = template;
    }

    @Override
    protected Result check() throws Exception {
        final String saying = String.format(template, "TEST");
        if (!saying.contains("TEST")) {
            return Result.unhealthy("template doesn't include a name");
        }
        return Result.healthy();
    }
}

TemplateHealthCheck 检查两件事:提供的模板确实是格式良好的格式字符串,模板确实能生成具有给定名称的输出。

如果字符串不是格式良好的格式字符串(例如,有人意外地将Hello,%s%放入 配置文件中),那么String.format(String, Object...)将抛出一个IllegalFormatException ,并且运行状况检查将隐式失败。如果呈现的问候语不包括测试字符串,则运行状况检查将通过返回不健康的Result而显式失败。

添加健康检查

与Dropwizard中的大多数内容一样,我们使用适当的参数创建一个新实例并将其添加到Environment

@Override
public void run(HelloWorldConfiguration configuration,
                Environment environment) {
    final HelloWorldResource resource = new HelloWorldResource(
        configuration.getTemplate(),
        configuration.getDefaultName()
    );
    final TemplateHealthCheck healthCheck =
        new TemplateHealthCheck(configuration.getTemplate());
    environment.healthChecks().register("template", healthCheck);
    environment.jersey().register(resource);
}

现在我们快要准备好了!

建立工程的JAR包

我们建议把Dropwizard应用程序构建为“单” JAR文件-一个.jar包含所有的运行应用程序所需的.class文件。这允许您构建单个可部署工件,您可以将其从开发环境升级到测试环境再到生产环境,而无需担心已安装库中的差异。要开始构建我们的Hello World应用程序作为单JAR,我们需要配置一个名为maven-shade的Maven插件。在文件的<build><plugins>部分中pom.xml,添加以下内容:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-shade-plugin</artifactId>
    <version>2.3</version>
    <configuration>
        <createDependencyReducedPom>true</createDependencyReducedPom>
        <filters>
            <filter>
                <artifact>*:*</artifact>
                <excludes>
                    <exclude>META-INF/*.SF</exclude>
                    <exclude>META-INF/*.DSA</exclude>
                    <exclude>META-INF/*.RSA</exclude>
                </excludes>
            </filter>
        </filters>
    </configuration>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>shade</goal>
            </goals>
            <configuration>
                <transformers>
                    <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
                    <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                        <mainClass>com.example.helloworld.HelloWorldApplication</mainClass>
                    </transformer>
                </transformers>
            </configuration>
        </execution>
    </executions>
</plugin>

这将配置Maven在其package阶段执行以下操作:

  • 生成一个pom.xml文件,该文件不包含在单JAR中的库的依赖项的内容。
  • 从已签名的JAR中排除所有数字签名。如果不这样做,则Java认为签名无效,并且不会加载或运行您的JAR文件。
  • 整理JAR中META-INF/services的各种条目而不是覆盖它们。(如果没有那些,那么Dropwizard和Jersey都不会工作)
  • com.example.helloworld.HelloWorldApplication设为JAR MainClass。这将允许您使用java -jar运行JAR 。

警告

如果您的应用程序具有必须签名的依赖项(例如,JCA / JCE提供程序或其他可信库),则必须在 该库的maven-shade-plugin配置中添加排除项,并将该JAR包含在类路径中。

警告

由于Dropwizard使用Java ServiceLoader功能来注册和加载扩展,因此maven-shade-plugin的minimizeJar选项将导致应用程序JAR不能正常工作。

版本化您的JAR

Dropwizard也可以使用项目版本,如果它嵌入在JAR的清单中作为 Implementation-Version。要使用Maven嵌入此信息,请将以下内容添加到文件的 <build><plugins>部分pom.xml

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-jar-plugin</artifactId>
    <version>2.4</version>
    <configuration>
        <archive>
            <manifest>
                <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
            </manifest>
        </archive>
    </configuration>
</plugin>

在尝试确定您在计算机上部署的应用程序版本时,这非常方便。

完成配置后,进入项目目录并运行mvn package(或从IDE 运行package 目标)。你应该看到这样的东西:

[INFO] Including org.eclipse.jetty:jetty-util:jar:7.6.0.RC0 in the shaded jar.
[INFO] Including com.google.guava:guava:jar:10.0.1 in the shaded jar.
[INFO] Including com.google.code.findbugs:jsr305:jar:1.3.9 in the shaded jar.
[INFO] Including org.hibernate:hibernate-validator:jar:4.2.0.Final in the shaded jar.
[INFO] Including javax.validation:validation-api:jar:1.0.0.GA in the shaded jar.
[INFO] Including org.yaml:snakeyaml:jar:1.9 in the shaded jar.
[INFO] Replacing original artifact with shaded artifact.
[INFO] Replacing /Users/yourname/Projects/hello-world/target/hello-world-0.0.1-SNAPSHOT.jar with /Users/yourname/Projects/hello-world/target/hello-world-0.0.1-SNAPSHOT-shaded.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 8.415s
[INFO] Finished at: Fri Dec 02 16:26:42 PST 2011
[INFO] Final Memory: 11M/81M
[INFO] ------------------------------------------------------------------------

恭喜!你已经建立了你的第一个Dropwizard项目!现在是时候运行了!

运行您的应用程序

现在您已经构建了一个JAR文件,现在是时候运行它了。

在项目目录中,运行以下命令:

java -jar target/hello-world-0.0.1-SNAPSHOT.jar

您应该看到如下内容:

usage: java -jar hello-world-0.0.1-SNAPSHOT.jar
       [-h] [-v] {server} ...

positional arguments:
  {server}               available commands

optional arguments:
  -h, --help             show this help message and exit
  -v, --version          show the service version and exit

Dropwizard接受第一个命令行参数并将其分派给匹配的命令。在这种情况下,唯一可用的命令是server,将您的应用程序作为HTTP服务器运行。该 server命令需要一个配置文件,所以让我们继续为它提供 我们之前保存的YAML文件

java -jar target/hello-world-0.0.1-SNAPSHOT.jar server hello-world.yml

您应该看到如下内容:

INFO  [2011-12-03 00:38:32,927] io.dropwizard.cli.ServerCommand: Starting hello-world
INFO  [2011-12-03 00:38:32,931] org.eclipse.jetty.server.Server: jetty-7.x.y-SNAPSHOT
INFO  [2011-12-03 00:38:32,936] org.eclipse.jetty.server.handler.ContextHandler: started o.e.j.s.ServletContextHandler{/,null}
INFO  [2011-12-03 00:38:32,999] com.sun.jersey.server.impl.application.WebApplicationImpl: Initiating Jersey application, version 'Jersey: 1.10 11/02/2011 03:53 PM'
INFO  [2011-12-03 00:38:33,041] io.dropwizard.setup.Environment:

    GET     /hello-world (com.example.helloworld.resources.HelloWorldResource)

INFO  [2011-12-03 00:38:33,215] org.eclipse.jetty.server.handler.ContextHandler: started o.e.j.s.ServletContextHandler{/,null}
INFO  [2011-12-03 00:38:33,235] org.eclipse.jetty.server.AbstractConnector: Started BlockingChannelConnector@0.0.0.0:8080 STARTING
INFO  [2011-12-03 00:38:33,238] org.eclipse.jetty.server.AbstractConnector: Started SocketConnector@0.0.0.0:8081 STARTING

您的Dropwizard应用程序现在正在侦听8080应用程序请求和8081 管理请求的端口。如果按^C,应用程序将正常关闭,首先关闭服务器套接字,然后等待处理正在进行的请求,然后关闭进程本身。

大功告成,试一下! http://localhost:8080/hello-world

我们正在产生问候语,真棒。当然你的应用程序能做到的远远不止这些。使用Dropwizard的主要原因之一是它提供的开箱即用的操作工具,所有这些工具都可以在管理端口上 http://localhost:8081/找到。

如果访问指标资源 http://localhost:8081/metrics,则可以看到所有应用程序的指标都表示为JSON对象。

访问线程的资源 http://localhost:8081/threads可以让您快速获得在这个过程中运行的所有线程的线程转储。

提示

当Jetty工作线程处理传入的HTTP请求时,线程名称将设置为请求的方法和URI。在调试性能不佳的请求时,这非常有用。

健康检查资源运行 健康检查类。你应该看到这样的东西:

* deadlocks: OK
* template: OK

template这是你的TemplateHealthCheck的结果,毫不奇怪地通过了。 deadlocks是一个内置的运行状况检查,它查找死锁的JVM线程并打印出列表(如果找到)。

下一步

嗯,恭喜。您已经准备好用于生产的Hello World应用程序(除了缺少测试),它能够每秒执行30,000-50,000个请求。希望您已经了解了Dropwizard如何将Jetty,Jersey,Jackson和其他稳定,成熟的库结合起来,为开发RESTful Web应用程序提供了一个非凡的平台。

Dropwizard还有很多内容(命令commands,绑定bundles,servlet,高级配置,验证validation,HTTP客户端,数据库客户端,视图等),所有这些都在用户手册中介绍。

官网 https://www.dropwizard.io/1.3.5/docs/getting-started.html

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

推荐阅读更多精彩内容