spring cloud openFeign学习

1. 声明式REST客户端:Feign

Feign是一个声明式web服务客户端。它使得编写web服务客户端更加容易。使用Feign创建一个接口并对其进行注解标注。它包含可插入式注解支持,包括Feign注解和JAX-RS注解。Feign同样支持可插入式编码器和解码器。Spring Cloud为其增加了对Spring MVC注解和与Spring Web默认的HttpMessageConverters的支持。在使用Feign时Spring Cloud整合了Eureka、Spring Cloud CircuitBreak以及Spring Cloud LoadBalancer提供了一个http协议的负载均衡客户端。

1.1. 如何包含Feign

// pom文件引入
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
// 加入@EnableFeiginClients注解
@SpringBootApplication
@EnableFeignClients
public class Application {
  public static void main(String[] args) {
      SpringApplication.run(Application.class, args);
  }
}
// @FeignClient 修饰接口
@FeignClient("stores")
public interface StoreClient {
  @RequestMapping(method = RequestMethod.GET, value = "/stores")
  List<Store> getStores();
}

StoreClient.java(译者注:store没有特殊含义,并不是你必须使用store。这与你的具体项目有关,假如你正在开发一个天气的客户端,那你的名字就可能是WeatherClient.java)

@FeignClient("stores")
public interface StoreClient {
    @RequestMapping(method = RequestMethod.GET, value = "/stores")
    List<Store> getStores();

    @RequestMapping(method = RequestMethod.GET, value = "/stores")
    Page<Store> getStores(Pageable pageable);

    @RequestMapping(method = RequestMethod.POST, value = "/stores/{storeId}", consumes = "application/json")
    Store update(@PathVariable("storeId") Long storeId, Store store);
}

在@Feign注解中的字符串值(上面的是“store”)代表了任意的客户端名字,他将用来创建一个Spring Cloud 负载均衡客户端。你也可以使用url属性指定一个URL(绝对路径值或者只有一个主机名)。该接口对应的Bean在应用上下文中的名字为该接口的全限定名称。你可以使用@FeignClient注解的qualifier属性来定义你自己的别名。

上面的负载均衡客户端将试图发现“store”服务的物理地址。如果你的应用是一个Eureka客户端,它将在Eureka服务的注册中心获取到。如果你不想使用Eureka,你可以在你的外部配置中使用SimpleDiscoverClient简单地配置一个服务列表。

Spring Cloud OpenFeign支持Spring Cloud LoadBalancer阻塞模型的所有功能。你一在项目文档中读取更多他们的信息。

1.2. 覆盖Feign的默认配置

注解引入配置类方式配置openFeign

  • @EnableFeignClients 注释中defaultConfiguration属性配置 配置的是全局的
  • @FeignClient 注解中configuration属性配置,配置的是当前接口的配置

配置文件方式配置openFeign

  • 配置单个 (这种配置方式相当于一个@FeignClient,feignName相当于@FeignClient的name 或者指定的contextId属性 )
feign:
  client:
    config:
      feignName:
        connectTimeout: 5000
        readTimeout: 5000
        loggerLevel: full
        errorDecoder: com.example.SimpleErrorDecoder
        retryer: com.example.SimpleRetryer
       defaultQueryParameters:
          query: queryValue
        defaultRequestHeaders:
          header: headerValue
        requestInterceptors:
          - com.example.FooRequestInterceptor
          - com.example.BarRequestInterceptor
        decode404: false
        encoder: com.example.SimpleEncoder
        decoder: com.example.SimpleDecoder
        contract: com.example.SimpleContract
  • 全局配置(将feignName改成default就是全局配置)
feign:
  client:
    config:
      default:
        connectTimeout: 5000
        readTimeout: 5000
        loggerLevel: basic

Spring Cloud对Feign支持的一个核心盖面是命名客户端。每个Feign客户端都是集成组件的一部分,全体客户端合作工作按需连接远程客户端。应用程序开发者可以使用@FeignClient注解来给集成组件命名。Spring Cloud使用FeignClientsConfiguration按需为每个命名客户端创建一个ApplicationContext集成。其中包括一个feign.Decoder、一个feign.Encoder和一个feign.Contract。你可以使用@FeignClient注解的contextId属性覆盖集成组件的名字。

Spring Cloud允许你使用@FeignClient注解声明额外配置完全控制Feign客户端(在FeignClientsConfiguration)。
示例:

@FeignClient(name = "stores", configuration = FooConfiguration.class)
public interface StoreClient {
    //..
}

在本示例中,客户端由存在于FeignClientsConfiguration和FooConfiguration中的组件组成(后面将会覆盖前面的FooConfiguration)。

FooConfiguration不需要使用@Configuration注解标注。然而,如果这样做了,一定要注意在任何可能扫描到该配置的@ComponentScan中排除它。否则包含了该配置将会将feign.Decoder、feign.Encoder、feign.Contract等作为默认源,如果定义了的话。这可以通过将配置放置到一个独立、不重复的非@ComponentScan和@SpringBootApplication包中避免,或者明确的在@ComponentScan中排除。

使用@FeignClient注解的contextId属性除了更改ApplicationContext集成的属性之外,还会覆盖客户端的别名,该别名将会创建客户端的配置bean的名字的一部分。

以前,使用url属性不要求必须使用name属性。现在name属相被强制要求使用。

name和url属相支持位置标识符。

@FeignClient(name = "${feign.name}", url = "${feign.url}")
public interface StoreClient {
    //..
}

Spring Cloud OpenFeign默认为feign提供如下bean(beanType beanName: ClassName):

  • Decoder feignDecoder: ResponseEntityDecoder(包装这一个Spring Decoder)
  • Encoder feignEncoder: SpringEncoder
  • Logger feignLogger: Self4jLogger
  • Contract feignContract: SpringMvcContract
  • Feign.Builder feignBuilder: FeignCircuitBreaker.Builder
  • Client feignClient: 如果Spring Cloud LoadBalancer在classpath中,FeignBlockingLoadBalancerClient被使用。如果没有东西在classpath中,默认的feign client被使用。

spring-cloud-stater-openfeign支持spring-cloud-stater-loadbalancer。然而,作为一个可选的依赖,你如果想要使用它就必须确定它已将被加入到你的项目中。

可以通过设置feign.okhttp.enabled或者feign.httpclient.enabled为true来使用OkHttpClient或者ApacheHttpClient,分别在classpath中添加他们的依赖。你可以通过提供一个org.apache.http.impl.client.CloseableHttpClient(在使用ApacheHttpClient时)或者okhttp3.OkHttpClient bean自定义被使用的客户端。

Spring Cloud OpenFeign 默认不为feign提供如下所示的bean,但是仍然从应用上下文中查找这些类型的bean来创建feign客户端:

  • Logger.Level
  • Retryer
  • ErroeDecoder
  • Request.Options
  • Collection< RequestInterceptor >
  • SetterFactory
  • QueryMapEncoder
    一个Retryer.NEVER_RETRY bean默认被创建为Retryer类型,这将会禁止重试操作。注意这个重试操作与Feign默认的不同,它将会自动重试IOExceptions,将他们作为暂时网络连接异常对待并且将任何RetryableException从一个ErrorDecoder抛出。

创建一个对应类型的bean并将它放到一个@FeignClient配置中使得你可以覆盖对应的bean。
示例:

@Configuration
public class FooConfiguration {
    @Bean
    public Contract feignContract() {
        return new feign.Contract.Default();
    }

    @Bean
    public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
        return new BasicAuthRequestInterceptor("user", "password");
    }
}

此例使用feign.Contract.Default代替SpringMvcContract并向RequestInterceptor集合中添加了一个RequestInterceptor。

@FeignClient也可以使用属性配置文件进行配置。

application.yml

feign:
  client:
    config:
      feignName:
        connectTimeout: 5000
        readTimeout: 5000
        loggerLevel: full
        errorDecoder: com.example.SimpleErrorDecoder
        retryer: com.example.SimpleRetryer
       defaultQueryParameters:
          query: queryValue
        defaultRequestHeaders:
          header: headerValue
        requestInterceptors:
          - com.example.FooRequestInterceptor
          - com.example.BarRequestInterceptor
        decode404: false
        encoder: com.example.SimpleEncoder
        decoder: com.example.SimpleDecoder
        contract: com.example.SimpleContract

在@EnableFeignClients中使用与上面相似的语法在defaultConfiguration属性中指定默认配置。不同之处在于此配置将会运用到所有的feign客户端中。

如果你更喜欢使用属性配置文件配置所有的@FeignClient,你可以将default作为feign名作为配置属性。

你可以使用feign.client.config.feignName.defaultQueryParameters和feign.client.config.feignName.defaultRequestHeaders来指定查询参数和头部,这些参数和头部将随名称为fileName的客户端的请求一起发送。

application.yml

feign:
  client:
    config:
      default:
        connectTimeout: 5000
        readTimeout: 5000
        loggerLevel: basic

如果你即创建了@Configuration bean又创建了配置属性,配置属性将优先使用。配置属性将覆盖@Configuration的值。但是如果你想要改变@Configuration的优先级,你可以将feign.client.default-to-properties的值改为false。

如果你想使用相同的名称或url创建多个feign客户端,这些客户端可能会指向相同的服务,但是却使用不同的自定义配置。我们必须使用@FeignClient的contextId属性来避免这些配置bean的名字冲突。

@FeignClient(contextId = "fooClient", name = "stores", configuration = FooConfiguration.class)
public interface FooClient {
    //..
}

@FeignClient(contextId = "barClient", name = "stores", configuration = BarConfiguration.class)
public interface BarClient {
    //..
}

还可以将FeignClient配置为不从父上下文继承bean。可以通过重写FeignClientConfigurer bean中的inheritParentConfiguration()返回false来实现这一点:

@Configuration
public class CustomConfiguration{

@Bean
public FeignClientConfigurer feignClientConfigurer() {
            return new FeignClientConfigurer() {

                @Override
                public boolean inheritParentConfiguration() {
                    return false;
                }
            };

        }
}

默认情况下,外部客户机不编码斜杠/字符。你可以通过将feign.client.decodeSlash设置为false来改变这一行为。

1.3. 超时处理

我们既可以设置默认的超时配置,也可以针对某一个命名客户端做超时配置。OpenFeign通过两个超时参数进行工作:

  • contectTimeout 防止由于服务器的长处理时间而阻塞调用。
  • readTime 应用于连接建立时间,在返回响应花费时间过长时被触发。

一旦服务没有运行或者收到了一个连接失败的包。通讯将以错误消息或者回退结束。如果connectTimeout被设置的过晚,这些可能会在连接超时之前发生。执行查询和接收这样一个数据包花费的时间 导致了很大一部分时间。它可能会根据涉及DNS查找的远程主机进行更改。

1.4. 手动创建Feign客户端

在一些情况下,不使用上面的方法自定义你的Feign客户端是很有必要的。在这时候,你可以使用Feign Builder API来创建Feign客户端。下面是一个使用相同的接口但使用两个独立的请求拦截器创建两个Feign客户端的示例。

@Import(FeignClientsConfiguration.class)
class FooController {

    private FooClient fooClient;

    private FooClient adminClient;

        @Autowired
    public FooController(Decoder decoder, Encoder encoder, Client client, Contract contract) {
        this.fooClient = Feign.builder().client(client)
                .encoder(encoder)
                .decoder(decoder)
                .contract(contract)
                .requestInterceptor(new BasicAuthRequestInterceptor("user", "user"))
                .target(FooClient.class, "https://PROD-SVC");

        this.adminClient = Feign.builder().client(client)
                .encoder(encoder)
                .decoder(decoder)
                .contract(contract)
                .requestInterceptor(new BasicAuthRequestInterceptor("admin", "admin"))
                .target(FooClient.class, "https://PROD-SVC");
    }
}

在上面的示例中,FeignClientConfiguration.class是Spring Cloud OpenFeign提供的默认配置。

PROD-SVC是客户端将要发送请求的服务端名称。

Feign Contract对象定义了哪种注解和值在接口中是合法的。自动注入的Contract bean提供了对Spring MVC注解的支持,不支持Feign原生注解。

你也可以调用Builder的inheritParentContext(false)方法配置Feign客户端,使其不从父上下文中继承bean。

1.5. Feign支持Spring Cloud CircuitBreaker

如果Spring Cloud CricuitBreaker在classpath中,并且feign.cricuitbreaker.enabled=true,Feign将会使用一个断路器包裹所有方法。

通过使用@Scope(“prototype”)注解创建一个Feign.Builderber bean可以在所有的客户端中禁用Spring Cloud CricuitBreaker支持:

@Configuration
public class FooConfiguration {
        @Bean
    @Scope("prototype")
    public Feign.Builder feignBuilder() {
        return Feign.builder();
    }
}

断路器的命名遵循 <feignClientName>_<calledMethod>模式。当使用名字为foo的Feign客户端调用接口的bar方法时,断路器名字将会是foo_bar。

1.6 Feign 与Spring Cloud CricutBreaker回退

Spring CricuitBreaker支持回退的概念:当链路开路或者发生错误时一个默认的代码路径将会被执行。通过设置@FeignClient的fallback属性值为实现了回退的类的类名使Feign客户端支持回退。你仍然需要将该实现声明为一个Spring bean。

@FeignClient(name = "test", url = "http://localhost:${server.port}/", fallback = Fallback.class)
protected interface TestClient {

    @RequestMapping(method = RequestMethod.GET, value = "/hello")
    Hello getHello();

    @RequestMapping(method = RequestMethod.GET, value = "/hellonotfound")
    String getException();

}

@Component
static class Fallback implements TestClient {

    @Override
    public Hello getHello() {
        throw new NoFallbackAvailableException("Boom!", new RuntimeException());
    }

    @Override
    public String getException() {
        return "Fixed response";
    }

}

如果你想获取触发回退的原因,你可以使用@FeignClient注解的fallbackFactory属性。

@FeignClient(name = "testClientWithFactory", url = "http://localhost:${server.port}/",
        fallbackFactory = TestFallbackFactory.class)
protected interface TestClientWithFactory {

    @RequestMapping(method = RequestMethod.GET, value = "/hello")
    Hello getHello();

    @RequestMapping(method = RequestMethod.GET, value = "/hellonotfound")
    String getException();

}

@Component
static class TestFallbackFactory implements FallbackFactory<FallbackWithFactory> {

    @Override
    public FallbackWithFactory create(Throwable cause) {
        return new FallbackWithFactory();
    }

}

static class FallbackWithFactory implements TestClientWithFactory {

    @Override
    public Hello getHello() {
        throw new NoFallbackAvailableException("Boom!", new RuntimeException());
    }

    @Override
    public String getException() {
        return "Fixed response";
    }

}

1.7. Feign 和 @Parimary

当Feign使用Spring Cloud CricuitBreaker回退时,在一个ApplicationContext中会有多个相同类型的bean。由于没有一个确定的bean或没有被标记为primary,@Wutowired注解将不能使用。为了能够正常工作,Spring Cloud OpenFeign将所有头的Feign标记为@Primary,因此,spring将会知道那个bean将被用来注入。在一些情况下,这可能不是那么称心如意。可以通过将@FeignClient注解的primary属性设置为false来关闭这一行为。

@FeignClient(name = "hello", primary = false)
public interface HelloClient {
    // methods here
}

1.8. Feign的继承支持

Feign通过单继承支持样板api。这允许将常见操作分组到方便的基本接口中。

UserService.java

public interface UserService {

    @RequestMapping(method = RequestMethod.GET, value ="/users/{id}")
    User getUser(@PathVariable("id") long id);
}

UserResource.java

@RestController
public class UserResource implements UserService {

}

UserClient.java

package project.user;

@FeignClient("users")
public interface UserClient extends UserService {

}

通常不建议在服务器和客户机之间共享接口。它引入了紧耦合,并且实际上不适用于springmvc的当前形式(方法参数映射不是继承的)。

1.9. Feign 请求/响应 压缩

您可以考虑为您的外部请求启用请求或响应GZIP压缩。您可以通过启用以下属性之一来执行此操作:

feign.compression.request.enabled=true
feign.compression.response.enabled=true

Feign 请求/响应 压缩提供的设置与您可能为web服务器设置的设置类似:

feign.compression.request.enabled=true
feign.compression.request.mime-types=text/xml,application/xml,application/json
feign.compression.request.min-request-size=2048

这些属性允许您选择压缩媒体类型和最小请求阈值长度。

对于除OkHttpClient之外的http客户端,可以启用默认的gzip解码器来解码UTF-8编码的gzip响应:

feign.compression.response.enabled=true
feign.compression.response.useGzipDecoder=true

1.10. Feign日志

为每个创建的外部客户机创建一个记录器。默认情况下,记录器的名称是用于创建外部客户机的接口的完整类名。外部日志记录只响应DEBUG级别。
application.yml

logging.level.project.user.UserClient: DEBUG

你可以为每个客户端配置Logger.level对象,告诉Feign要记录多少。选择包括:

  • NONE,不记录(默认)
  • BASIC,只记录请求方法和URL以及响应状态代码和执行时间。
  • HEADERS,记录基本信息以及请求和响应头。
  • FULL,记录请求和响应的头、正文和元数据。

例如,下面将设置Logger至FULL:

@Configuration
public class FooConfiguration {
    @Bean
    Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }
}

1.11. openFeign @SpringQueryMap注解

@GetMapping和@PostMapping:
在Rest服务的RPC调用中,一般我们会使用@GetMapping和@PostMapping两种方式
同时对应的接收参数则会使用@RequestParam和@RequestBody来获取。

@RequestBody:
@RequestBody只能用在Post请求,并且一个Post请求只能有一个@RequestBody。 @RequestBody的参数可以包括复杂类型。

@PostMapping(path = "/update")
Boolean updateServiceInfo(@RequestBody updateXXXXXParams params)

@RequestParam:
一般用在Get请求中,但是@RequestParam 的参数只能是基本类型

@GetMapping(path = "/detail")
XXXXX getXXXXXXDetail(@RequestParam String id);

4:@SpringQueryMap
如果我们是Get请求,但是请求的参数又很多, 比如我在查询并分页的时候,我们的入参只有一个查询的keyword, 但是又有分页的一些信息pageNum, pageSize, totalSize, 这时候我们一般会把这些数据封装有一个pojo, 但是feign的get方式又不支持,这里我们可以使用openfeign提供的@SpringQueryMap来解决这个问题,传递对象参数,让框架自动解析
step1: 引用POM依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-openfeign-core</artifactId>
    <version>2.2.1.RELEASE</version>
</dependency>

step2:添加SpringQueryMap注解

@GetMapping(path = "/xxxx /list")
List<XXXXX> getServiceActionsList(@SpringQueryMap XXXXPageParams params);

作者:蓉漂里的小白
链接:https://www.jianshu.com/p/9d1d770e22b0
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

如果你想在生成的查询参数之上获取更多控制,你可以实现自定义的QueryMapEncoder bean。

1.12. HATEOAS 支持

Spring 提供了一些创建符合HATEOAS原则、Spring Hateoas和Spring Data REST的REST表示的API。

如果你的项目使用了org.springframework.boot:spring-boot-starter-hateoas或者org.framework.boot:spring-boot-starter-data-rest启动器,Feign HATEOAS将被默认启动。

当HATEOAS支持被启用后,Feign客户端被允许序列化和反序列化HATEOAS表述模型:EntityModel、CollectionModel和PagedModel。

@FeignClient("demo")
public interface DemoTemplate {

    @GetMapping(path = "/stores")
    CollectionModel<Store> getStores();
}

1.13. Spring @MatriVariable 支持

Spring Cloud OpenFeign提供对Spring@MatriVariable注解的支持。

如果一个方法参数被以map的形式传递,@MatriVariable路径段是通过加入map中以等号连接的键值对被创建的。

如果传递了另一个对象,则@MatrixVariable注释中提供的名称(如果已定义)或带注释的变量名称将使用=,与提供的方法参数联接。

重点
尽管在服务端,Spring不要求用户使用与matrix变量名同名的路径段占位符,因为这可能使得在客户端比较模糊,但Spring Cloud OpenFeign要求你在路径段占位符命名与@MatrixVariable的name属性值(如果定义了的话)或者声明的变量名相匹配。

例如:

@GetMapping("/objects/links/{matrixVars}")
Map<String, List<String>> getObjects(@MatrixVariable Map<String, List<String>> matrixVars);

注意变量名和路径段占位符都叫matrixVars。

@FeignClient("demo")
public interface DemoTemplate {

    @GetMapping(path = "/stores")
    CollectionModel<Store> getStores();
}

1.14 Feign CollectionFormat支持

我们通过@CollectionFormat注解提供对feign.CollectionFormat的支持。你可以通过向他传递一个要求的feign.CollectionFormat作为注解的值使用它注解一个Feign客户端方法。

在下面的例子中,CSV格式被用来代替默认的EXPLODED来处理方法。

@FeignClient(name = "demo")
    protected interface PageableFeignClient {

        @CollectionFormat(feign.CollectionFormat.CSV)
        @GetMapping(path = "/page")
        ResponseEntity performRequest(Pageable page);

    }

在发送Pageable作为查询参数时设置CSV格式,以便正确编码。

1.15. Reactive支持

由于OpenFeign项目目前不支持被动客户机,比如springwebclient,springcloud也不支持对外开放。我们一旦它在核心项目中可用,就会在这里添加对它的支持。

在此之前,我们建议使用feign reactive来支持springwebclient。

1.15.1. 早期初始化错误

根据您使用feign户机的方式,启动应用程序时可能会看到初始化错误。要解决此问题,可以在自动连接客户端时使用ObjectProvider。

@Autowired
ObjectProvider<TestFeginClient> testFeginClient;

1.16. Spring Data 支持

您可以考虑启用Jackson模块来提供支持org.springframework.data.domain.Page以及org.springframework.data.domain.Sort解码。

feign.autoconfiguration.jackson.enabled=true

2. 配置属性

要查看所有与Sleuth相关的配置属性的列表,请查看附录页

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

推荐阅读更多精彩内容