zuul學(xué)習(xí)三:zuul路由詳解(二)

zuul 上傳文件

user-service中定義一個上傳接口:

@Controller
public class FileUploadController {

    @RequestMapping(value = "/upload", method = RequestMethod.POST)
    public @ResponseBody String handleFileUpload(@RequestParam(value = "file", required = true) MultipartFile file) throws IOException {
        byte[] bytes = file.getBytes();
        File fileToSave = new File(file.getOriginalFilename());
        FileCopyUtils.copy(bytes, fileToSave);
        return fileToSave.getAbsolutePath();
    }
}

配置文件配置如下:

spring:
  application:
    name: user-service
  http:
    multipart:
      max-file-size: 2000Mb # Max file size,默認(rèn)1M
      max-request-size: 2500Mb # Max request size玖喘,默認(rèn)10M
eureka:
  client:
    service-url:
     defaultZone: http://localhost:8761/eureka
  instance:
    instance-id:  ${spring.application.name}:${spring.cloud.client.ipAddress}:${spring.application.instance_id:${server.port}}
    prefer-ip-address: true
server:
  port: 8080

在user-service寫一個html簡單測試一下:

<form method="POST" enctype="multipart/form-data" action="/upload">
      File to upload:
      <input type="file" name="file">
      <input type="submit" value="Upload">
</form>

點擊上傳成功谎仲。

那么怎么通過zuul來代理呢?

If you @EnableZuulProxy you can use the proxy paths to upload files and it should just work as long as the files are small. For large files there is an alternative path which bypasses the Spring DispatcherServlet (to avoid multipart processing) in "/zuul/*". I.e. if zuul.routes.customers=/customers/** then you can POST large files to "/zuul/customers/*". The servlet path is externalized via zuul.servletPath. Extremely large files will also require elevated timeout settings if the proxy route takes you through a Ribbon load balancer, e.g.

如果是你使用@EnableZuulProxy你可以使用代理的路徑來上傳文件,并且必須是文件比較小的時候。對于大文件一個可選擇的方案就是繞開Spring的DispatcherServlet(避免多部分處理)通過/zuul/*路徑。如果zuul.routes.customers=/customers/**你可以上傳大文件通過/zuul/customers/*路徑峡眶。 如果使用Ribbon進行負(fù)載均衡,超大文件也將需要設(shè)置的超時時間植锉。

小文件傳輸不需要修改zuul的配置:
zuul的配置:

spring:
  application:
    name: zuul-service
eureka:
  client:
    service-url:
     defaultZone: http://localhost:8761/eureka
  instance:
    instance-id:  ${spring.application.name}:${spring.cloud.client.ipAddress}:${spring.application.instance_id:${server.port}}
    prefer-ip-address: true
server:
  port: 6069

測試成功:

curl -v -H "Transfer-Encoding: chunked" -F "file=@Netty_in_Action最新版.pdf" localhost:6069/zuul/user-service/upload

如果是大文件傳輸辫樱,需要在zuul服務(wù)修改配置:

spring:
  application:
    name: zuul-service
eureka:
  client:
    service-url:
     defaultZone: http://localhost:8761/eureka
  instance:
    instance-id:  ${spring.application.name}:${spring.cloud.client.ipAddress}:${spring.application.instance_id:${server.port}}
    prefer-ip-address: true
server:
  port: 6069

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 60000

ribbon:
  ConnectTimeout: 3000
  ReadTimeout: 60000

參考資料
Uploading Files through Zuul

重定向問題

解決了Cookie問題之后,我們已經(jīng)能夠通過網(wǎng)關(guān)來訪問并登錄到我們的web應(yīng)用了俊庇。但是這個時候又發(fā)現(xiàn)另外一個問題:雖然可以通過網(wǎng)關(guān)訪問登錄頁面并發(fā)起登錄請求狮暑,但是登錄成功之后,我們跳轉(zhuǎn)的url卻是具體web應(yīng)用實例的地址辉饱,而不是通過網(wǎng)關(guān)的路由地址搬男。這個問題特別嚴(yán)重,因為使用api網(wǎng)關(guān)的一個重要原因就是將網(wǎng)關(guān)作為統(tǒng)一入口彭沼,從而不暴露所有內(nèi)部服務(wù)細(xì)節(jié)缔逛。那么時什么原因?qū)е铝诉@個問題呢?

demo
比如我們在user服務(wù)定義了一個controller姓惑,重定向到hello.html

@Controller
public class UserController2 {

    private Logger logger = LoggerFactory.getLogger(getClass());

    @RequestMapping("/testRedirect")
    public String testRedirect(){
        logger.info("user2 testRedirect");
        return "redirect:hello.html";
    }
}

直接訪問user服務(wù)跳轉(zhuǎn)

http://192.168.1.57:8080/testRedirect

通過zuul代理褐奴,因為此url是跳轉(zhuǎn)資源,直接跳轉(zhuǎn)到web真實實例的url

http://192.168.1.57:6069/user-service/testRedirect

通過瀏覽器開發(fā)工具查看登錄以及登錄之后的請求詳情于毙,可以發(fā)現(xiàn)敦冬,引起問題的大致原因時由于spring secutityshiro在登錄完成之后,通過重定向的方式跳轉(zhuǎn)到登錄后的頁面唯沮,此時登錄后的請求結(jié)果狀態(tài)嗎為302脖旱,請求響應(yīng)頭信息中的Location指向了具體的服務(wù)實例地址,而請求頭信息中的Host也指向了具體的服務(wù)實例ip地址和端口介蛉。所以萌庆,該問題的根本原因在于spring cloud zuul在路由請求時,并沒有將最初的host信息設(shè)置正確币旧,如何解決践险?

配置zuul.add-host-header=true即可。

我配置了然后訪問:

http://192.168.1.57:6069/user-service/testRedirect

跳轉(zhuǎn)的地址是

http://192.168.1.57:6069/hello.html

很明顯跳轉(zhuǎn)到http://192.168.1.57:6069/user-service/hello.html才對,沒找到解決的方案捏境。

網(wǎng)上根據(jù)這個問題有人在spring cloud上提issue于游,自己在zuul上寫個過濾器毁葱,我覺得這個可以不解決垫言,因為現(xiàn)在都是前后端分離架構(gòu),不多數(shù)都不在后端進行跳轉(zhuǎn)倾剿,zuul.add-host-header=true只是為了不暴露真實的ip信息筷频,如果要重定向到具體的前端頁面可以自己可以配置zuul.routes到指定的服務(wù)上。

參考資料
Spring Cloud實戰(zhàn)小貼士:Zuul處理Cookie和重定向

Hystrix的路由回退

When a circuit for a given route in Zuul is tripped you can provide a fallback response by creating a bean of type ZuulFallbackProvider. Within this bean you need to specify the route ID the fallback is for and provide a ClientHttpResponse to return as a fallback. Here is a very simple ZuulFallbackProvider implementation.
當(dāng)Zuul中給定路由的電路跳閘時前痘,您可以通過創(chuàng)建ZuulFallbackProvider類型的bean來提供回退響應(yīng)凛捏。 在這個bean中,您需要指定回退所對應(yīng)的路由ID芹缔,并提供一個ClientHttpResponse作為后備返回坯癣。 這是一個非常簡單的ZuulFallbackProvider實現(xiàn)。

demo
zuul-service中去定義MyFallbackProvider繼承ZuulFallbackProvider最欠,定義了路由id為user-service服務(wù)的回退示罗。

@Component
public class MyFallbackProvider implements ZuulFallbackProvider {

    //getRoute返回的必須要和zuul.routes.***一致,才能針對某個服務(wù)降級
    @Override
    public String getRoute() {
        return "user-service";
    }

    @Override
    public ClientHttpResponse fallbackResponse() {
        return new ClientHttpResponse() {
            @Override
            public HttpStatus getStatusCode() throws IOException {
                return HttpStatus.OK;
            }

            @Override
            public int getRawStatusCode() throws IOException {
                return 200;
            }

            @Override
            public String getStatusText() throws IOException {
                return "OK";
            }

            @Override
            public void close() {

            }

            @Override
            public InputStream getBody() throws IOException {
                return new ByteArrayInputStream(("fallback"+MyFallbackProvider.this.getRoute()).getBytes());
            }

            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_JSON);
                return headers;
            }
        };
    }
}

當(dāng)訪問user-service超時的時候頁面上顯示的是fallbackuser-service芝硬。

參考資料
官網(wǎng)Providing Hystrix Fallbacks For Routes

異構(gòu)語言支持Sidecar

Do you have non-jvm languages you want to take advantage of Eureka, Ribbon and Config Server? The Spring Cloud Netflix Sidecar was inspired by Netflix Prana. It includes a simple http api to get all of the instances (ie host and port) for a given service. You can also proxy service calls through an embedded Zuul proxy which gets its route entries from Eureka. The Spring Cloud Config Server can be accessed directly via host lookup or through the Zuul Proxy. The non-jvm app should implement a health check so the Sidecar can report to eureka if the app is up or down.
你有沒有非jvm語言你想利用Eureka蚜点,Ribbon和配置服務(wù)器? Spring Cloud Netflix Sidecar的靈感來自Netflix Prana拌阴。 它包含一個簡單的http api來獲取給定服務(wù)的所有實例(即主機和端口)绍绘。 您還可以通過嵌入式Zuul代理來代理服務(wù)調(diào)用,該代理從Eureka獲取其路由條目迟赃。 可以通過主機查找或通過Zuul Proxy直接訪問Spring Cloud Config Server陪拘。 非jvm應(yīng)用程序應(yīng)該執(zhí)行健康檢查,以便Sidecar可以向應(yīng)用程序啟動或關(guān)閉時向eureka報告纤壁。

To include Sidecar in your project use the dependency with group org.springframework.cloud and artifact id spring-cloud-netflix-sidecar.
要在項目中包含Sidecar藻丢,請使用組org.springframework.cloud和artifact id 為spring-cloud-netflix-sidecar的依賴關(guān)系。

To enable the Sidecar, create a Spring Boot application with @EnableSidecar. This annotation includes @EnableCircuitBreaker, @EnableDiscoveryClient, and @EnableZuulProxy. Run the resulting application on the same host as the non-jvm application.
要啟用Sidecar摄乒,請使用@EnableSidecar創(chuàng)建一個Spring Boot應(yīng)用程序悠反。 此注釋包括@EnableCircuitBreaker@EnableDiscoveryClient@EnableZuulProxy馍佑。 在與非jvm應(yīng)用程序相同的主機上運行生成的應(yīng)用程序斋否。

To configure the side car add sidecar.port and sidecar.health-uri to application.yml. The sidecar.port property is the port the non-jvm app is listening on. This is so the Sidecar can properly register the app with Eureka. The sidecar.health-uri is a uri accessible on the non-jvm app that mimicks a Spring Boot health indicator. It should return a json document like the following:
要將side car配置為sidecar.portsidecar.health-uriapplication.ymlsidecar.port屬性是非jvm應(yīng)用程序正在偵聽的端口拭荤。 這是因為Sidecar可以正確地注冊該應(yīng)用程序與eureka茵臭。 sidecar.health-uri是一個可以在非jvm應(yīng)用程序上訪問的uri,它可以模仿Spring Boot健康指示器舅世。 它應(yīng)該返回一個json文檔旦委,如下所示:

{
  "status":"UP"
}

demo
寫一個node.js的服務(wù)奇徒,端口是8060,訪問localhost:8060返回"歡迎來到首頁"缨硝,訪問http://localhost:8060/health.json,將會返回{"status":"UP"}

var http = require('http');
var url = require('url');
var path = require('path');

//創(chuàng)建server
var server = http.createServer(function (req,res) {

    //獲得請求的路徑
    var pathname = url.parse(req.url).pathname;
     res.writeHead(200,{'Content-Type':'application/json;charset=utf-8'});
     //訪問http://locaLhost:8060/,將會返回首頁
     if(pathname === '/'){
         res.end(JSON.stringify({"index":"歡迎來到首頁"}));
     }

     //訪問http://localhost:8060/health,將會返回{"status":"UP"}
    else if(pathname ==="/health.json"){
         res.end(JSON.stringify({"status":"UP"}));
     }

     //其他情況返回404
    else {
         res.end("404");
     }
});

//創(chuàng)建監(jiān)聽摩钙,并打印日志
server.listen(8060,function () {
    console.log('listening on localhost:8060');
})

啟動服務(wù):

node node-service.js 

分別訪問首頁和健康檢查頁面。

然后新建一個zuul-sidecar服務(wù)查辩,依賴如下:

 <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zuul</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-netflix-sidecar</artifactId>
        </dependency>
    </dependencies>

啟動類SidecarApplication胖笛,除了@SpringBootApplication還標(biāo)記有@EnableSidecar注解:

@SpringBootApplication
@EnableSidecar
public class SidecarApplication {
    public static void main(String[] args) {
        SpringApplication.run(SidecarApplication.class, args);
    }
}

配置文件application.yml:

server:
  port: 8070
spring:
  application:
    name: zuul-sidecar
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
  instance:
    instance-id:  ${spring.application.name}:${spring.cloud.client.ipAddress}:${spring.application.instance_id:${server.port}}
    prefer-ip-address: true
sidecar:
  port: 8060                                      # Node.js微服務(wù)的端口
  health-uri: http://localhost:8060/health.json   # Node.js微服務(wù)的健康檢查URL

啟動服務(wù),zuul-sidecar注冊到eureka上了宜岛,控制面板如下长踊,

通過zuul訪問zuul-sidecar間接訪問node的服務(wù),訪問首頁和健康頁面

http://192.168.1.57:6069/zuul-sidecar/
http://192.168.1.57:6069/zuul-sidecar/health.json

訪問sidecar的服務(wù):

http://localhost:8070/

user服務(wù)訪問node服務(wù)也可以通過sidecar來訪問通過注冊到zuul的服務(wù)名來訪問。

在user-service中定義:

@GetMapping("/sidecar")
    public String sidecar(){
        String response = restTemplate.getForObject("http://zuul-sidecar/",String.class);
        return response;
}
http://192.168.1.57:8080/user/sidecar

成功訪問萍倡。

The api for the DiscoveryClient.getInstances() method is /hosts/{serviceId}. Here is an example response for /hosts/customer that returns two instances on different hosts. This api is accessible to the non-jvm app (if the sidecar is on port 5678) at http://localhost:5678/hosts/{serviceId}
.
DiscoveryClient.getInstances()方法的api是/hosts/{serviceId}身弊。 以下是/ hosts/customers的一個示例響應(yīng),它會在不同的主機上返回兩個實例列敲。 這個api可以訪問http://localhost:5678/hosts/{serviceId}的非jvm應(yīng)用程序(如果sidecar在端口5678上)阱佛。比如我上面的列子就可以根據(jù)http://localhost:8070/hosts/user-service來查看user-service的服務(wù)信息。具體原因下面解釋酿炸。

The Zuul proxy automatically adds routes for each service known in eureka to /<serviceId>, so the customers service is available at /customers. The Non-jvm app can access the customer service via http://localhost:5678/customers (assuming the sidecar is listening on port 5678).
Zuul代理自動將eureka中已知的每個服務(wù)的路由添加到/<serviceId>瘫絮,以便客戶可以在/客戶端使用客戶服務(wù)。 非jvm應(yīng)用程序可以通過http://localhost:5678/customers訪問客戶服務(wù)(假設(shè)邊界正在偵聽端口5678)填硕。

If the Config Server is registered with Eureka, non-jvm application can access it via the Zuul proxy. If the serviceId of the ConfigServer is configserver and the Sidecar is on port 5678, then it can be accessed at http://localhost:5678/configserver

如果配置服務(wù)器在Eureka中注冊麦萤,則非jvm應(yīng)用程序可以通過Zuul代理訪問它。 如果ConfigServer的serviceId是configserver扁眯,而Sidecar在端口5678上壮莹,則可以訪問http://localhost:5678/configserver

使用sidecar也是可以訪問注冊到eureka上的服務(wù),也就是使用zuul的代理姻檀,而不需要另外的起一個zuul服務(wù)器命满。比如下面的可以通過zuul-sidecar訪問user服務(wù)。

http://localhost:8070/user-service/user/index

參考資料
官網(wǎng)Polyglot support with Sidecar

Hystrix和ribbon支持

spring-cloud-starter-zuul依賴包括spring-cloud-starter-hystrixspring-cloud-starter-ribbon模塊的依賴绣版,所以zuul天生就擁有線程隔離和斷路器的自我保護功能胶台,以及對服務(wù)調(diào)用的客戶端負(fù)載均衡功能。
需要注意的事杂抽,當(dāng)使用path與url的映射關(guān)系來配置路由規(guī)則的時候诈唬,對于路由轉(zhuǎn)發(fā)的請求不會采用hystrixCommand來包裝,所以這類請求沒有線程隔離和斷路器的保護缩麸,并且也不會有負(fù)載均衡的能力铸磅。因此,我們在使用zuul的時候盡量使用path和serviceId的組合來進行配置,這樣不僅可以保證api網(wǎng)關(guān)的健壯和穩(wěn)定阅仔,也能用到ribbon的客戶端負(fù)載均衡功能吹散,

我們在使用zuul搭建api網(wǎng)關(guān)的時候,可以通過hystrix和ribbon的參數(shù)來調(diào)整路由請求的各種超時時間等配置八酒,比如下面這些參數(shù)的設(shè)置空民。

  • hystrix.command.default.execution.isolation.thread.timeoutInMillseconds:該參數(shù)可以用來設(shè)置api網(wǎng)關(guān)中路由轉(zhuǎn)發(fā)請求hystrixCommand執(zhí)行超時時間,單位為毫秒丘跌。當(dāng)路由轉(zhuǎn)發(fā)請求的命令執(zhí)行時間超過該配置值之后袭景,hystrix會將該執(zhí)行命令標(biāo)記為timeout并拋出異常唁桩,zuul會對該異常進行處理并返回如下的json信息給外部調(diào)用方闭树。
{
    "timestamp":14454545234324,
    "status":500,
    "error":"Internal Server Error",
    "exception":"com.netflix.zuul.exception.ZuulException",
    "message":"TIMEOUT"
}
  • ribbon.ConnectTimeout:該參數(shù)用來設(shè)置路由轉(zhuǎn)發(fā)請求的時候,創(chuàng)建請求連接的超時時間荒澡。當(dāng)ribbon.ConnectTimeout的配置值小于hystrix.command.default.execttion.isolation.thread.timeoutInMilliseconds配置值的時候报辱,若出現(xiàn)路由請求連接超時時,會自動進行重試路由請求单山,如果請求依然失敗碍现,zuul會返回如下json信息給外部調(diào)用方。
{
    "timestamp":14454545234324,
    "status":500,
    "error":"Internal Server Error",
    "exception":"com.netflix.zuul.exception.ZuulException",
    "message":"NUMBEROF_RETRIES_NEXTSERVER_EXCEEDED"
}

ribbon.ConnectTimeout的配置值大于hystrix.command.default.execution.isolation.thread.timeoutInMillseconds配置值的時候米奸,當(dāng)出現(xiàn)路由請求連接超時時昼接,由于此時對于路由轉(zhuǎn)發(fā)的請求命令已經(jīng)超時,所以不會進行重試路由請求悴晰,而是直接按請求命令超時處理慢睡,返回TIMEOUT的錯誤信息。

  • ribbon.ReadTimeout:該參數(shù)用來設(shè)置路由轉(zhuǎn)發(fā)請求的超時時間铡溪,它的處理與ribbon.ConnectTimeout類似漂辐,只是它的超時是對請求連接建立之后的處理時間。當(dāng)ribbon.ReadTimeout的配置值小于hystrix.command.default.execttion.isolation.thread.timeoutInMilliseconds配置值的時候棕硫,若路由請求的處理時間超過該配置值并且依賴服務(wù)還沒有響應(yīng)的時候髓涯,會自動進行重試路由請求。如果重試后依然沒有獲得請求響應(yīng)哈扮,zuul會返回NUMBEROF_RETRIES_NEXTSERVER_EXCEEDED錯誤纬纪。如果ribbon.ReadTimeout的配置值大于hystrix.command.default.execttion.isolation.thread.timeoutInMilliseconds配置值,若路由請求的處理時間超過該配置值并且依賴服務(wù)還沒有響應(yīng)時滑肉,不會進行重試路由請求包各,而是直接按請求命令超時處理,返回timeout的錯誤信息赦邻。

根據(jù)上面的介紹我們知道髓棋,在使用zuul的服務(wù)路由時,如果路由轉(zhuǎn)發(fā)請求發(fā)生超時(連接超時或處理超時),只要超時時間的設(shè)置小于hystrix的命令超時時間按声,那么它就會自動發(fā)起重試膳犹。有些背景下,我們需要關(guān)閉重試機制签则,那么可以通過下面的二個參數(shù)進行設(shè)置须床。

zuul.retryable=false
zuul.routes.<route>.retryable=false

其中,zuul.retryable用來關(guān)閉全局的重試機制渐裂,而zuul.routes.<route>.retryable=false指定路由關(guān)閉重試機制豺旬。

本博客代碼
代碼地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市柒凉,隨后出現(xiàn)的幾起案子族阅,更是在濱河造成了極大的恐慌,老刑警劉巖膝捞,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件坦刀,死亡現(xiàn)場離奇詭異,居然都是意外死亡蔬咬,警方通過查閱死者的電腦和手機鲤遥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來林艘,“玉大人盖奈,你說我怎么就攤上這事『” “怎么了钢坦?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長咕村。 經(jīng)常有香客問我场钉,道長,這世上最難降的妖魔是什么懈涛? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任逛万,我火速辦了婚禮,結(jié)果婚禮上批钠,老公的妹妹穿的比我還像新娘宇植。我一直安慰自己,他們只是感情好埋心,可當(dāng)我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布指郁。 她就那樣靜靜地躺著,像睡著了一般拷呆。 火紅的嫁衣襯著肌膚如雪闲坎。 梳的紋絲不亂的頭發(fā)上疫粥,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天,我揣著相機與錄音腰懂,去河邊找鬼梗逮。 笑死,一個胖子當(dāng)著我的面吹牛绣溜,可吹牛的內(nèi)容都是我干的慷彤。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼怖喻,長吁一口氣:“原來是場噩夢啊……” “哼底哗!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起锚沸,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤跋选,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后咒吐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體野建,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡属划,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年恬叹,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片同眯。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡绽昼,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出须蜗,到底是詐尸還是另有隱情硅确,我是刑警寧澤,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布明肮,位于F島的核電站菱农,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏柿估。R本人自食惡果不足惜循未,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望秫舌。 院中可真熱鬧的妖,春花似錦、人聲如沸足陨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽墨缘。三九已至星虹,卻和暖如春零抬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背宽涌。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工媚值, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人护糖。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓琢感,卻偏偏與公主長得像,于是被迫代替她去往敵國和親虾啦。 傳聞我的和親對象是個殘疾皇子本谜,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,700評論 2 354

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