Spring Cloud Alibaba(二、Nacos服务注册及 RestTemplate、FeignClient方式发现)

SpringCloud标准版的服务注册可以用 eureka、consul或zookeeper,配置中心用config或bus,现在Spring Cloud AlibabaNacos搞定这些,简言之就是注册中心加配置中心。Nacos可以代替eureka做服务注册中心。
Gitee上的Spring-Cloud-Alibaba/nacos-discovery-example

Nacos

Nacos是什么

Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。

Nacos 的关键特性包括:

  • 服务发现和服务健康监测

    Nacos 支持基于 DNS 和基于 RPC 的服务发现。服务提供者使用 原生SDKOpenAPI、或一个独立的Agent TODO注册 Service 后,服务消费者可以使用DNS TODOHTTP&API查找和发现服务。

    Nacos 提供对服务的实时的健康检查,阻止向不健康的主机或服务实例发送请求。Nacos 支持传输层 (PING 或 TCP)和应用层 (如 HTTP、MySQL、用户自定义)的健康检查。 对于复杂的云环境和网络拓扑环境中(如 VPC、边缘网络等)服务的健康检查,Nacos 提供了 agent 上报模式和服务端主动检测2种健康检查模式。Nacos 还提供了统一的健康检查仪表盘,帮助您根据健康状态管理服务的可用性及流量。

  • 动态配置服务

    动态配置服务可以让您以中心化、外部化和动态化的方式管理所有环境的应用配置和服务配置。

    动态配置消除了配置变更时重新部署应用和服务的需要,让配置管理变得更加高效和敏捷。

    配置中心化管理让实现无状态服务变得更简单,让服务按需弹性扩展变得更容易。

    Nacos 提供了一个简洁易用的UI (控制台样例 Demo) 帮助您管理所有的服务和应用的配置。Nacos 还提供包括配置版本跟踪、金丝雀发布、一键回滚配置以及客户端配置更新状态跟踪在内的一系列开箱即用的配置管理特性,帮助您更安全地在生产环境中管理配置变更和降低配置变更带来的风险。

  • 动态 DNS 服务

    动态 DNS 服务支持权重路由,让您更容易地实现中间层负载均衡、更灵活的路由策略、流量控制以及数据中心内网的简单DNS解析服务。动态DNS服务还能让您更容易地实现以 DNS 协议为基础的服务发现,以帮助您消除耦合到厂商私有服务发现 API 上的风险。

    Nacos 提供了一些简单的 DNS APIs (暂未实现) 帮助您管理服务的关联域名和可用的 IP:PORT 列表.

  • 服务及其元数据管理

    Nacos 能让您从微服务平台建设的视角管理数据中心的所有服务及元数据,包括管理服务的描述、生命周期、服务的静态依赖分析、服务的健康状态、服务的流量管理、路由及安全策略、服务的 SLA 以及最首要的 metrics 统计数据。

前提

您需要先下载 Nacos 并启动 Nacos server。操作步骤参见 Nacos 快速入门。我使用的是windows单机版,进入bin目录下执行以下命令启动nacos-startup.bat

启动Nacos

管理页面:访问ip:8848/nacos,看到如下画面就启动成功了,默认账号密码都是nacos。
Nacos控制台管理页面

服务注册与发现

本项目演示如何使用 Nacos Discovery Starter 完成 Spring Cloud 应用的服务注册与发现。

服务提供者
  1. 新建基于Nacos的服务提供者
    新建module,Spring boot的脚手架starter使用阿里云https://start.aliyun.com/
    Spring Initializr
  2. Dependiencies选择如下图所示:
    Nacos Service Discovery依赖

    1.我的IDEA 2020.1Spring Boot 2.2.0版本,如果与我不同,可以参考我的pom文件修改版本:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.pay.cloud.alibaba</groupId>
    <artifactId>nacos-discovery-provider</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>nacos-discovery-provider</name>
    <description>Spring cloud Alibaba nacos-discovery-provider project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud-alibaba.version>2.2.1.RELEASE</spring-cloud-alibaba.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- nacos -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring-cloud-alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

核心是引入如下依赖:

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

踩坑:
由于Spring Boot 2.3.0版本太新,使用<spring-cloud-alibaba.version>2.2.1.RELEASE</spring-cloud-alibaba.version>,会报错Endpoint ID 'nacos-discovery' contains invalid characters, please migrate to a valid format.。查看Spring-Cloud-Alibaba开源代码pom.xml,发现他使用的spring-cloud版本是<spring-cloud-commons.version>2.2.2.RELEASE</spring-cloud-commons.version>,二者的版本对应关系见上篇文章末尾部分

  1. application.properties
server.port=18081
spring.application.name=service-provider
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
#spring.cloud.nacos.discovery.instance-enabled=true

spring.cloud.nacos.username=nacos
spring.cloud.nacos.password=nacos

management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always

配置文件中配置 Nacos Server 地址:spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
Nacos 服务的名称会使用:spring.application.name=service-provider配置值。

  1. 启动类
package com.pay.cloud.alibaba.nacosdiscoveryprovider;


import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * @ClassName: NacosDiscoveryProviderApplication
 * @Description: nacos服务提供者
 * @author: 郭秀志 jbcode@126.com
 * @date: 2020/6/11 16:51
 * @Copyright:
 */
@EnableDiscoveryClient
@SpringBootApplication
public class NacosDiscoveryProviderApplication {

    public static void main(String[] args) {
        SpringApplication.run(NacosDiscoveryProviderApplication.class, args);
    }

    @RestController
    class EchoController {

        @GetMapping("/")
        public ResponseEntity index() {
            return new ResponseEntity("index error", HttpStatus.INTERNAL_SERVER_ERROR);
        }

        @GetMapping("/test")
        public ResponseEntity test() {
            return new ResponseEntity("error", HttpStatus.INTERNAL_SERVER_ERROR);
        }

        @GetMapping("/sleep")
        public String sleep() {
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "ok";
        }

        @GetMapping("/echo/{string}")
        public String echo(@PathVariable String string) {
            return "Guoxiuzhi's Nacos Discovery output:" + string;
        }

        @GetMapping("/divide")
        public String divide(@RequestParam Integer a, @RequestParam Integer b) {
            return String.valueOf(a / b);
        }

    }
}

使用 @EnableDiscoveryClient注解开启服务注册与发现功能。

  1. 启动 Nacos Server
  • 首先需要获取 Nacos Server,支持直接下载和源码构建两种方式。

    1. 直接下载:Nacos Server 下载页
    2. 源码构建:进入 Nacos Github 项目页面,将代码 git clone 到本地自行编译打包,参考此文档推荐使用源码构建方式以获取最新版本
  • 启动 Server,进入解压后文件夹或编译打包好的文件夹,找到如下相对文件夹 nacos/bin,并对照操作系统实际情况之下如下命令。

    1. Linux/Unix/Mac 操作系统,执行命令 sh startup.sh -m standalone
    2. Windows 操作系统,执行命令 cmd startup.cmd
  1. 启动应用
    支持 IDE 直接启动和编译打包后启动。
    然后看看nacos控制台,可以看到服务名称为service-provider已经注册到了Nacos:
    服务注册截图
服务消费者

分别使用 RestTemplate 和 FeignClient来消费服务。

  1. pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.pay.alibaba.nacos</groupId>
    <artifactId>nacos-discovery-consumer</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>nacos-discovery-consumer</name>
    <description>nacos-discovery-consumer project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud-alibaba.version>2.2.1.RELEASE</spring-cloud-alibaba.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
            <version>2.2.2.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
            <version>2.1.2.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>

    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring-cloud-alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
  1. 代码
    启动类:
package com.pay.alibaba.nacos.nacosdiscovery.consumer;


import com.alibaba.cloud.sentinel.annotation.SentinelRestTemplate;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.UrlCleaner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

/**
 * @ClassName: ConsumerApplication
 * @Description: 启动类
 * @author: 郭秀志 jbcode@126.com
 * @date: 2020/6/11 20:15
 * @Copyright:
 */
@SpringBootApplication
@EnableDiscoveryClient(autoRegister = true)
@EnableFeignClients
public class ConsumerApplication {

    @LoadBalanced
    @Bean
    @SentinelRestTemplate(urlCleanerClass = UrlCleaner.class, urlCleaner = "clean")
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    @LoadBalanced
    @Bean
    @SentinelRestTemplate
    public RestTemplate restTemplate1() {
        return new RestTemplate();
    }

    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class, args);
    }

}

说明:

添加 @LoadBlanced 注解,使得 RestTemplate 接入 Ribbon

 @Bean
 @LoadBalanced
 public RestTemplate restTemplate() {
     return new RestTemplate();
 }

EchoService

package com.pay.alibaba.nacos.nacosdiscovery.consumer.service;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
/**   
 * @ClassName: EchoService
 * @Description: 
 * @author: 郭秀志 jbcode@126.com
 * @date:   2020/6/11 21:11    
 * @Copyright:  
 */
@FeignClient(name = "service-provider", fallback = EchoServiceFallback.class,
        configuration = FeignConfiguration.class)
public interface EchoService {

    @GetMapping("/echo/{str}")
    String echo(@PathVariable("str") String str);

    @GetMapping("/divide")
    String divide(@RequestParam("a") Integer a, @RequestParam("b") Integer b);

    default String divide(Integer a) {
        return divide(a, 0);
    }

    @GetMapping("/notFound")
    String notFound();

}

class FeignConfiguration {

    @Bean
    public EchoServiceFallback echoServiceFallback() {
        return new EchoServiceFallback();
    }

}

class EchoServiceFallback implements EchoService {

    @Override
    public String echo(@PathVariable("str") String str) {
        return "echo fallback";
    }

    @Override
    public String divide(@RequestParam Integer a, @RequestParam Integer b) {
        return "divide fallback";
    }

    @Override
    public String notFound() {
        return "notFound fallback";
    }

}

说明:
FeignClient 已经默认集成了 Ribbon ,此处演示如何配置一个 FeignClient。使用 @FeignClient 注解将 EchoService 这个接口包装成一个 FeignClient,属性 name 对应服务名 service-provider。

 @FeignClient(name = "service-provider")
 public interface EchoService {
     @GetMapping(value = "/echo/{str}")
     String echo(@PathVariable("str") String str);
 }

UrlCleaner

package com.pay.alibaba.nacos.nacosdiscovery.consumer.uitil;
/**   
 * @ClassName: UrlCleaner
 * @Description: 
 * @author: 郭秀志 jbcode@126.com
 * @date:   2020/6/11 20:18    
 * @Copyright:  
 */
public class UrlCleaner {

    public static String clean(String url) {
        System.out.println("enter urlCleaner");
        if (url.matches(".*/echo/.*")) {
            System.out.println("change url");
            url = url.replaceAll("/echo/.*", "/echo/{str}");
        }
        return url;
    }

}

TestController

package com.pay.alibaba.nacos.nacosdiscovery.consumer.controller;


import com.pay.alibaba.nacos.nacosdiscovery.consumer.service.EchoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

/**   
 * @ClassName:  TestController
 * @Description: 
 * @author: 郭秀志 jbcode@126.com
 * @date:   2020/6/11 21:00
 * @Copyright:  
 */
@RestController
public class TestController {

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private RestTemplate restTemplate1;

    @Autowired
    private EchoService echoService;

    @Autowired
    private DiscoveryClient discoveryClient;

    // @PostConstruct
    // public void init() {
    // restTemplate1.setErrorHandler(new ResponseErrorHandler() {
    // @Override
    // public boolean hasError(ClientHttpResponse response) throws IOException {
    // return false;
    // }
    //
    // @Override
    // public void handleError(ClientHttpResponse response) throws IOException {
    // System.err.println("handle error");
    // }
    // });
    // }

    @GetMapping("/echo-rest/{str}")
    public String rest(@PathVariable String str) {
        return restTemplate.getForObject("http://service-provider/echo/" + str,
                String.class);
    }

    @GetMapping("/index")
    public String index() {
        return restTemplate1.getForObject("http://service-provider", String.class);
    }

    @GetMapping("/test")
    public String test() {
        return restTemplate1.getForObject("http://service-provider/test", String.class);
    }

    @GetMapping("/sleep")
    public String sleep() {
        return restTemplate1.getForObject("http://service-provider/sleep", String.class);
    }

    @GetMapping("/notFound-feign")
    public String notFound() {
        return echoService.notFound();
    }

    @GetMapping("/divide-feign")
    public String divide(@RequestParam Integer a, @RequestParam Integer b) {
        return echoService.divide(a, b);
    }

    @GetMapping("/divide-feign2")
    public String divide(@RequestParam Integer a) {
        return echoService.divide(a);
    }

    @GetMapping("/echo-feign/{str}")
    public String feign(@PathVariable String str) {
        return echoService.echo(str);
    }

    @GetMapping("/services/{service}")
    public Object client(@PathVariable String service) {
        return discoveryClient.getInstances(service);
    }

    @GetMapping("/services")
    public Object services() {
        return discoveryClient.getServices();
    }

}

echo 方法上的 @RequestMapping 注解将 echo 方法与 URL "/echo/{str}" 相对应,@PathVariable 注解将 URL 路径中的 {str} 对应成 echo 方法的参数 str。

完成以上配置后,将两者自动注入到 TestController 中。

@RestController
public class TestController {

    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private EchoService echoService;

    @GetMapping(value = "/echo-rest/{str}")
    public String rest(@PathVariable String str) {
        return restTemplate.getForObject("http://service-provider/echo/" + str, String.class);
    }
    @GetMapping(value = "/echo-feign/{str}")
    public String feign(@PathVariable String str) {
        return echoService.echo(str);
    }
}

配置必要的配置,在 nacos-discovery-consumer 项目的 /src/main/resources/application.properties 中添加基本配置信息

#################################### common config : ####################################
spring.application.name=nacos-discovery-consumer
# 应用服务web访问端口
server.port=18080
# ActuatorWeb访问端口
management.server.port=18081
management.endpoints.jmx.exposure.include=*
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always
# spring cloud access&secret config
# 可以访问如下地址查看: https://usercenter.console.aliyun.com/#/manage/ak
alibaba.cloud.access-key=****
alibaba.cloud.secret-key=****
#################################### nacosdiscovery config : ####################################
# 微服务引擎控制台: https://mse.console.aliyun.com
# Nacos帮助文档: https://nacos.io/zh-cn/docs/concepts.html
# Nacos认证信息
spring.cloud.nacos.discovery.username=nacos
spring.cloud.nacos.discovery.password=nacos
# Nacos 服务发现与注册配置,其中子属性 server-addr 指定 Nacos 服务器主机和端口
spring.cloud.nacos.discovery.server-addr=localhost:8848

测试运行

启动应用,支持 IDE 直接启动和编译打包后启动。

  1. 启动provider项目
    采用打包方式启动2个服务,来验证consumer请求provider的负载均衡。
    java -jar nacos-discovery-provider-0.0.1-SNAPSHOT.jar --server.port=8080 --management.server.port=8081
    java -jar nacos-discovery-provider-0.0.1-SNAPSHOT.jar --server.port=8090 --management.server.port=8091
    查看Nacos控制台,可以看到2个provider实例。
    服务列表
  2. 启动consumer项目
    采用IDEA直接启动方式,启动后如上图同样看到consumer被注册进服务列表。
    restTemplate方式访问:http://localhost:18080/echo-rest/郭秀志,可以看到应用的第一个控制台输出:

restTemplate方式访问:http://localhost:18080/echo-rest/loadbalance,可以看到应用的第二个控制台输出:


FeignClient方式浏览器访问:http://localhost:18080/echo-feign/loadbalance,可以看到返回字符串:
FeignClient

绕口令式总结:可见独立的客户端消费者应用通过负载均衡根据Nacos注册的服务名称(而不是ip+端口号)调用服务生效。

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