Dubbo服務(wù)如何優(yōu)雅的校驗參數(shù)

服務(wù)端在向外提供接口服務(wù)時辅鲸,不管是對前端提供HTTP接口菩彬,還是面向內(nèi)部其他服務(wù)端提供的RPC接口赁还,常常會面對這樣一個問題并齐,就是如何優(yōu)雅的解決各種接口參數(shù)校驗問題漏麦?

早期大家在做面向前端提供的HTTP接口時,對參數(shù)的校驗可能都會經(jīng)歷這幾個階段:每個接口每個參數(shù)都寫定制校驗代碼况褪、提煉公共校驗邏輯撕贞、自定義切面進(jìn)行校驗、通用標(biāo)準(zhǔn)的校驗邏輯测垛。

這邊提到的通用標(biāo)準(zhǔn)的校驗邏輯指的就是基于JSR303的Java Bean Validation捏膨,其中官方指定的具體實現(xiàn)就是?Hibernate Validator,在Web項目中結(jié)合Spring可以做到很優(yōu)雅的去進(jìn)行參數(shù)校驗食侮。

本文主要也是想給大家介紹下如何在使用Dubbo時做好優(yōu)雅的參數(shù)校驗号涯。


二、解決方案

Dubbo框架本身是支持參數(shù)校驗的锯七,同時也是基于JSR303去實現(xiàn)的链快,我們來看下具體是怎么實現(xiàn)的。

2.1 maven依賴

<!-- 定義在facade接口模塊的pom文件找那個 -->

<dependency>

? ? <groupId>javax.validation</groupId>

? ? <artifactId>validation-api</artifactId>

? ? <version>2.0.1.Final</version>

<!-- 如果不想facade包有多余的依賴起胰,此處scope設(shè)為provided久又,否則可以刪除 -->

? ? <scope>provided</scope>

</dependency>

<!-- 下面依賴通常加在Facade接口實現(xiàn)模塊的pom文件中 -->

<dependency>

? ? <groupId>org.hibernate.validator</groupId>

? ? <artifactId>hibernate-validator</artifactId>

? ? <version>6.2.0.Final</version>

</dependency>


2.2 接口定義

facade接口定義:

public interface UserFacade {

? ? FacadeResult<Boolean> updateUser(UpdateUserParam param);

}

參數(shù)定義

public class UpdateUserParam implements Serializable {

? ? private static final long serialVersionUID = 2476922055212727973L;

? ? @NotNull(message = "用戶標(biāo)識不能為空")

? ? private Long id;

? ? @NotBlank(message = "用戶名不能為空")

? ? private String name;

? ? @NotBlank(message = "用戶手機(jī)號不能為空")

? ? @Size(min = 8, max = 16, message="電話號碼長度介于8~16位")

? ? private String phone;

? ? // getter and setter ignored

}

公共返回定義

/**

* Facade接口統(tǒng)一返回結(jié)果

*/


2.3 Dubbo服務(wù)提供者端配置

Dubbo服務(wù)提供者端必須作這個validation="true"的配置,具體示例配置如下:

Dubbo接口服務(wù)端配置

<bean class="com.xxx.demo.UserFacadeImpl" id="userFacade"/>

<dubbo:service interface="com.xxx.demo.UserFacade" ref="userFacade" validation="true" />

Dubbo服務(wù)消費者端配置

這個根據(jù)業(yè)務(wù)方使用習(xí)慣不作強(qiáng)制要求效五,但建議配置上都加上validation="true"地消,示例配置如下:

<dubbo:reference id="userFacade" interface="com.xxx.demo.UserFacade" validation="true" />


2.5 驗證參數(shù)校驗

前面幾步完成以后,驗證這一步就比較簡單了畏妖,消費者調(diào)用該約定接口脉执,接口入?yún)魅險pdateUserParam對象,其中字段不用賦值戒劫,然后調(diào)用服務(wù)端接口就會得到如下的參數(shù)異常提示:

Dubbo接口服務(wù)端配置

javax.validation.ValidationException: Failed to validate service: com.xxx.demo.UserFacade, method: updateUser, cause: [ConstraintViolationImpl{interpolatedMessage='用戶名不能為空', propertyPath=name, rootBeanClass=class com.xxx.demo.UpdateUserParam, messageTemplate='用戶名不能為空'}, ConstraintViolationImpl{interpolatedMessage='用戶手機(jī)號不能為空', propertyPath=phone, rootBeanClass=class com.xxx.demo.UpdateUserParam, messageTemplate='用戶手機(jī)號不能為空'}, ConstraintViolationImpl{interpolatedMessage='用戶標(biāo)識不能為空', propertyPath=id, rootBeanClass=class com.xxx.demo.UpdateUserParam, messageTemplate='用戶標(biāo)識不能為空'}]

javax.validation.ValidationException: Failed to validate service: com.xxx.demo.UserFacade, method: updateUser, cause: [ConstraintViolationImpl{interpolatedMessage='用戶名不能為空', propertyPath=name, rootBeanClass=class com.xxx.demo.UpdateUserParam, messageTemplate='用戶名不能為空'}, ConstraintViolationImpl{interpolatedMessage='用戶手機(jī)號不能為空', propertyPath=phone, rootBeanClass=class com.xxx.demo.UpdateUserParam, messageTemplate='用戶手機(jī)號不能為空'}, ConstraintViolationImpl{interpolatedMessage='用戶標(biāo)識不能為空', propertyPath=id, rootBeanClass=class com.xxx.demo.UpdateUserParam, messageTemplate='用戶標(biāo)識不能為空'}]

? ? at org.apache.dubbo.validation.filter.ValidationFilter.invoke(ValidationFilter.java:96)

? ? at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:83)

? ? ....

? ? at org.apache.dubbo.remoting.exchange.support.header.HeaderExchangeHandler.received(HeaderExchangeHandler.java:175)

? ? at org.apache.dubbo.remoting.transport.DecodeHandler.received(DecodeHandler.java:51)

? ? at org.apache.dubbo.remoting.transport.dispatcher.ChannelEventRunnable.run(ChannelEventRunnable.java:57)

? ? at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)

? ? at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)

? ? at java.lang.Thread.run(Thread.java:748)


三:定制Dubbo參數(shù)校驗異常返回

從前面內(nèi)容我們可以很輕松的驗證半夷,當(dāng)消費端調(diào)用Dubbo服務(wù)時婆廊,參數(shù)如果不合法就會拋出相關(guān)異常信息,消費端調(diào)用時也能識別出異常信息巫橄,似乎這樣就沒有問題了淘邻。

但從前面所定義的服務(wù)接口來看,一般業(yè)務(wù)開發(fā)會定義統(tǒng)一的返回對象格式(如前文示例中的FacadeResult)湘换,對于業(yè)務(wù)異常情況宾舅,會約定相關(guān)異常碼并結(jié)合相關(guān)性信息提示。因此對于參數(shù)校驗不合法的情況彩倚,服務(wù)調(diào)用方自然不希望服務(wù)端拋出一大段包含堆棧信息的異常信息筹我,而是希望還保持這種統(tǒng)一的返回形式,就如下面這種返回所示:

Dubbo接口服務(wù)端配置:

{

? "code": 1001,

? "msg": "用戶名不能為空",

? "data": null

}

3.1 ValidationFilter & JValidator

想要做到返回格式的統(tǒng)一帆离,我們先來看下前面所拋出的異常是如何來的蔬蕊?

從異常堆棧內(nèi)容我們可以看出這個異常信息返回是由ValidationFilter拋出的,從名字我們可以猜到這個是采用Dubbo的Filter擴(kuò)展機(jī)制的一個內(nèi)置實現(xiàn)哥谷,當(dāng)我們對Dubbo服務(wù)接口啟用參數(shù)校驗時(即前文Dubbo服務(wù)配置中的validation="true")岸夯,該Filter就會真正起作用,我們來看下其中的關(guān)鍵實現(xiàn)邏輯:

@Override

public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {

? ? if (validation != null && !invocation.getMethodName().startsWith("$")

? ? ? ? ? ? && ConfigUtils.isNotEmpty(invoker.getUrl().getMethodParameter(invocation.getMethodName(), VALIDATION_KEY))) {

? ? ? ? try {

? ? ? ? ? ? Validator validator = validation.getValidator(invoker.getUrl());

? ? ? ? ? ? if (validator != null) {

? ? ? ? ? ? ? ? // 注1

? ? ? ? ? ? ? ? validator.validate(invocation.getMethodName(), invocation.getParameterTypes(), invocation.getArguments());

? ? ? ? ? ? }

? ? ? ? } catch (RpcException e) {

? ? ? ? ? ? throw e;

? ? ? ? } catch (ValidationException e) {

? ? ? ? ? ? // 注2

? ? ? ? ? ? return AsyncRpcResult.newDefaultAsyncResult(new ValidationException(e.getMessage()), invocation);

? ? ? ? } catch (Throwable t) {

? ? ? ? ? ? return AsyncRpcResult.newDefaultAsyncResult(t, invocation);

? ? ? ? }

? ? }

? ? return invoker.invoke(invocation);

}

從前文的異常堆棧信息我們可以知道異常信息是由上述代碼「注2」處所產(chǎn)生呼巷,這邊是因為捕獲了ValidationException囱修,通過走讀代碼或者調(diào)試可以得知,該異常是由「注1」處valiator.validate方法所產(chǎn)生王悍。

而Validator接口在Dubbo框架中實現(xiàn)只有JValidator破镰,這個通過idea工具顯示Validator所有實現(xiàn)的UML類圖可以看出(如下圖所示),當(dāng)然調(diào)試代碼也可以很輕松定位到压储。


既然定位到JValidator了鲜漩,我們就繼續(xù)看下它里面validate方法的具體實現(xiàn),關(guān)鍵代碼如下所示:

@Override

public void validate(String methodName, Class<?>[] parameterTypes, Object[] arguments) throws Exception {

? ? List<Class<?>> groups = new ArrayList<>();

? ? Class<?> methodClass = methodClass(methodName);

? ? if (methodClass != null) {

? ? ? ? groups.add(methodClass);

? ? }

? ? Set<ConstraintViolation<?>> violations = new HashSet<>();

? ? Method method = clazz.getMethod(methodName, parameterTypes);

? ? Class<?>[] methodClasses;

? ? if (method.isAnnotationPresent(MethodValidated.class)){

? ? ? ? methodClasses = method.getAnnotation(MethodValidated.class).value();

? ? ? ? groups.addAll(Arrays.asList(methodClasses));

? ? }

? ? groups.add(0, Default.class);

? ? groups.add(1, clazz);

? ? Class<?>[] classgroups = groups.toArray(new Class[groups.size()]);

? ? Object parameterBean = getMethodParameterBean(clazz, method, arguments);

? ? if (parameterBean != null) {

? ? ? ? // 注1

? ? ? ? violations.addAll(validator.validate(parameterBean, classgroups ));

? ? }

? ? for (Object arg : arguments) {

? ? ? ? // 注2

? ? ? ? validate(violations, arg, classgroups);

? ? }

? ? if (!violations.isEmpty()) {

? ? ? ? // 注3

? ? ? ? logger.error("Failed to validate service: " + clazz.getName() + ", method: " + methodName + ", cause: " + violations);

? ? ? ? throw new ConstraintViolationException("Failed to validate service: " + clazz.getName() + ", method: " + methodName + ", cause: " + violations, violations);

? ? }

}

從上述代碼中可以看出當(dāng)「注1」和注「2」兩處代碼進(jìn)行參數(shù)校驗時所得到的「違反約束」的信息都被加入到violations集合中集惋,而在「注3」處檢查到「違反約束」不為空時孕似,就會拋出包含「違反約束」信息的ConstraintViolationException,該異常繼承自ValidationException刮刑,這樣也就會被ValidationFilter中方法所捕獲喉祭,進(jìn)而向調(diào)用方返回相關(guān)異常信息。

3.2 自定義參數(shù)校驗異常返回

從前一小節(jié)我們可以很清晰的了解到了為什么會拋出那樣的異常信息給調(diào)用方雷绢,如果想做到我們前面想要的訴求:統(tǒng)一返回格式泛烙,我們需要按照下面的步驟去實現(xiàn)。

3.2.1 自定義Filter

@Activate(group = {CONSUMER, PROVIDER}, value = "customValidationFilter", order = 10000)

public class CustomValidationFilter implements Filter {

? ? private Validation validation;

? ? public void setValidation(Validation validation) { this.validation = validation; }

? ? public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {

? ? ? ? if (validation != null && !invocation.getMethodName().startsWith("$")

? ? ? ? ? ? ? ? && ConfigUtils.isNotEmpty(invoker.getUrl().getMethodParameter(invocation.getMethodName(), VALIDATION_KEY))) {

? ? ? ? ? ? try {

? ? ? ? ? ? ? ? Validator validator = validation.getValidator(invoker.getUrl());

? ? ? ? ? ? ? ? if (validator != null) {

? ? ? ? ? ? ? ? ? ? validator.validate(invocation.getMethodName(), invocation.getParameterTypes(), invocation.getArguments());

? ? ? ? ? ? ? ? }

? ? ? ? ? ? } catch (RpcException e) {

? ? ? ? ? ? ? ? throw e;

? ? ? ? ? ? } catch (ConstraintViolationException e) {// 這邊細(xì)化了異常類型

? ? ? ? ? ? ? ? // 注1

? ? ? ? ? ? ? ? Set<ConstraintViolation<?>> violations = e.getConstraintViolations();

? ? ? ? ? ? ? ? if (CollectionUtils.isNotEmpty(violations)) {

? ? ? ? ? ? ? ? ? ? ConstraintViolation<?> violation = violations.iterator().next();// 取第一個進(jìn)行提示就行了

? ? ? ? ? ? ? ? ? ? FacadeResult facadeResult = FacadeResult.fail(ErrorCode.INVALID_PARAM.getCode(), violation.getMessage());

? ? ? ? ? ? ? ? ? ? return AsyncRpcResult.newDefaultAsyncResult(facadeResult, invocation);

? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? return AsyncRpcResult.newDefaultAsyncResult(new ValidationException(e.getMessage()), invocation);

? ? ? ? ? ? } catch (Throwable t) {

? ? ? ? ? ? ? ? return AsyncRpcResult.newDefaultAsyncResult(t, invocation);

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? return invoker.invoke(invocation);

? ? }

}

該自定義filter與內(nèi)置的ValidationFilter唯一不同的地方就在于「注1」處所新增的針對特定異常ConstraintViolationException的處理翘紊,從異常對象中獲取包含的「違反約束」信息蔽氨,并取其中第一個來構(gòu)造業(yè)務(wù)上所定義的通用數(shù)據(jù)格式FacadeResult對象,作為Dubbo服務(wù)接口調(diào)用返回的信息。


3.2.2 自定義Filter的配置

開發(fā)過Dubbo自定義filter的同學(xué)都知道鹉究,要讓它生效需要作一個符合SPI規(guī)范的配置宇立,如下所示:


a. 新建兩級目錄分別是META-INF和dubbo,這個需要特別注意自赔,不能直接新建一個目錄名為「META-INFO.dubbo」妈嘹,否則在初始化啟動的時候會失敗。

b. 新建一個文件名為com.alibaba.dubbo.rpc.Filter匿级,當(dāng)然也可以是org.apache.dubbo.rpc.Filter蟋滴,Dubbo開源到Apache社區(qū)后,默認(rèn)支持這兩個名字痘绎。

c. 文件中配置內(nèi)容為:customValidationFilter=com.xxx.demo.dubbo.filter.CustomValidationFilter。

3.3.3 Dubbo服務(wù)配置

有了自定義參數(shù)校驗的Filter配置后肖粮,如果只做到這的話孤页,其實還有一個問題,應(yīng)用啟動后會有兩個參數(shù)校驗Filter生效涩馆。當(dāng)然可以通過指定Filter的order來實現(xiàn)自定義Filter先執(zhí)行行施,但很顯然這種方式不穩(wěn)妥,而且兩個Filter的功能是重復(fù)的魂那,因此只需要一個生效就可以了蛾号,Dubbo提供了一種機(jī)制可以禁用指定的Filter,只需在Dubbo配置文件中作如下配置即可:

<!-- 需要禁用的filter以"-"開頭并加上filter名稱 -->

<!-- 查看源碼涯雅,可看到需要禁用的ValidationFilter名為validation-->

<dubbo:provider filter="-validation"/>

但經(jīng)過上述配置后鲜结,發(fā)現(xiàn)customValidationFilter并沒有生效,經(jīng)過調(diào)試以及對dubbo相關(guān)文檔的學(xué)習(xí)活逆,對Filter生效機(jī)制有了一定的了解精刷。

a. dubbo啟動后,默認(rèn)會生效框架自帶的一系列Filter蔗候;

可以在dubbo框架的資源文件org.apache.dubbo.rpc.Filter中看到具體有哪些怒允,不同版本的內(nèi)容可能會有些許差別。

cache=org.apache.dubbo.cache.filter.CacheFilter

validation=org.apache.dubbo.validation.filter.ValidationFilter? // 注1

echo=org.apache.dubbo.rpc.filter.EchoFilter

generic=org.apache.dubbo.rpc.filter.GenericFilter

genericimpl=org.apache.dubbo.rpc.filter.GenericImplFilter

token=org.apache.dubbo.rpc.filter.TokenFilter

accesslog=org.apache.dubbo.rpc.filter.AccessLogFilter

activelimit=org.apache.dubbo.rpc.filter.ActiveLimitFilter

classloader=org.apache.dubbo.rpc.filter.ClassLoaderFilter

context=org.apache.dubbo.rpc.filter.ContextFilter

consumercontext=org.apache.dubbo.rpc.filter.ConsumerContextFilter

exception=org.apache.dubbo.rpc.filter.ExceptionFilter

executelimit=org.apache.dubbo.rpc.filter.ExecuteLimitFilter

deprecated=org.apache.dubbo.rpc.filter.DeprecatedFilter

compatible=org.apache.dubbo.rpc.filter.CompatibleFilter

timeout=org.apache.dubbo.rpc.filter.TimeoutFilter

tps=org.apache.dubbo.rpc.filter.TpsLimitFilter

trace=org.apache.dubbo.rpc.protocol.dubbo.filter.TraceFilter

future=org.apache.dubbo.rpc.protocol.dubbo.filter.FutureFilter

monitor=org.apache.dubbo.monitor.support.MonitorFilter

metrics=org.apache.dubbo.monitor.dubbo.MetricsFilter

如上「注1」中的Filter就是我們上一步配置中想要禁用的Filter锈遥,因為這些filter都是Dubbo內(nèi)置的纫事,所以這些filter集合有一個統(tǒng)一的名字,default所灸,因此如果想全部禁用丽惶,除了一個一個禁用外,也可以直接用'-default'達(dá)到目的庆寺,這些默認(rèn)內(nèi)置的filter只要沒有全部或單獨禁用蚊夫,那就會生效。

b. 想要開發(fā)的自定義Filter能生效懦尝,不并一定要在<dubbo:provider filter="xxxFitler" >中體現(xiàn)知纷;如果我們沒有在Dubbo相關(guān)的配置文件中去配置Filter相關(guān)信息壤圃,只要寫好自定義filter代碼,并在資源文件/META-INF/dubbo/com.alibaba.dubbo.rpc.Filter中按照spi規(guī)范定義好即可琅轧,這樣所有被加載的Filter都會生效伍绳。

c. 如果在Dubbo配置文件中配置了Filter信息,那自定義Filter只有顯式配置才會生效乍桂。

d. Filter配置也可以加在dubbo service配置中(<dubbo:service interface="..." ref="..." validation="true" filter="xFilter,yFilter"/>)冲杀。

當(dāng)dubbo配置文件中provider 和service部分都配置了Filter信息,針對service具體生效的Filter取兩者配置的并集睹酌。

因此想要自定義的校驗Filter在所有服務(wù)中都生效权谁,需要作如下配置:

<dubbo:provider filter="-validation, customValidationFilter"/>


四、如何擴(kuò)展校驗注解

前面示例中都是利用參數(shù)校驗的內(nèi)置注解去完成憋沿,在實際開發(fā)中有時候會遇到默認(rèn)內(nèi)置的注解無法滿足校驗需求旺芽,這時就需要自定義一些校驗注解去滿足需求,方便開發(fā)辐啄。

假設(shè)有這樣一個場景采章,某參數(shù)值需要校驗只能在指定的幾個數(shù)值范圍內(nèi),類似于白名單一樣壶辜,下面就以這個場景來演示下如何擴(kuò)展校驗注解悯舟。

4.1 定義校驗注解

@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })@Retention(RUNTIME)@Documented@Constraint(validatedBy = { })// 注1// @Constraint(validatedBy = {AllowedValueValidator.class}) 注2public@interfaceAllowedValue {? ? Stringmessage()default"參數(shù)值不在合法范圍內(nèi)";? ? Class[] groups()default{ };? ? Class[] payload()default{ };long[] value()default{}; }

publicclassAllowedValueValidatorimplementsConstraintValidator {privatelong[] allowedValues;@Overridepublicvoidinitialize(AllowedValue constraintAnnotation){this.allowedValues = constraintAnnotation.value();? ? }@OverridepublicbooleanisValid(Long value, ConstraintValidatorContext context){if(allowedValues.length ==0) {returntrue;? ? ? ? }returnArrays.stream(allowedValues).anyMatch(o -> Objects.equals(o, value));? ? }}

「注1」中的校驗器(Validator)并沒有指定,當(dāng)然是可以像「注2」中那樣直接指定校驗器砸民,但考慮到自定義注解有可能是直接暴露在facade包中抵怎,而具體的校驗器的實現(xiàn)有時候會包含一些業(yè)務(wù)依賴,所以不建議直接在此處指定阱洪,而是通過Hibernate Validator提供的Validator發(fā)現(xiàn)機(jī)制去完成關(guān)聯(lián)便贵。

4.2 配置定制Validator發(fā)現(xiàn)


a. 在resources目錄下新建META-INF/services/javax.validation.ConstraintValidator文件。

b. 文件中只需填入相應(yīng)Validator的全路徑:com.xxx.demo.validator.AllowedValueValidator冗荸,如果有多個的話承璃,每行一個。

五蚌本、總結(jié)

本文主要介紹了使用Dubbo框架時如何使用優(yōu)雅點方式完成參數(shù)的校驗盔粹,首先演示了如何利用Dubbo框架默認(rèn)支持的校驗實現(xiàn),然后接著演示了如何配合實際業(yè)務(wù)開發(fā)返回統(tǒng)一的數(shù)據(jù)格式程癌,最后介紹了下如何進(jìn)行自定義校驗注解的實現(xiàn)舷嗡,方便進(jìn)行后續(xù)自行擴(kuò)展實現(xiàn),希望能在實際工作中有一定的幫助嵌莉。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末进萄,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌中鼠,老刑警劉巖可婶,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異援雇,居然都是意外死亡矛渴,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門惫搏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來具温,“玉大人,你說我怎么就攤上這事筐赔∠承桑” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵川陆,是天一觀的道長剂习。 經(jīng)常有香客問我,道長较沪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任失仁,我火速辦了婚禮尸曼,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘萄焦。我一直安慰自己控轿,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布拂封。 她就那樣靜靜地躺著茬射,像睡著了一般。 火紅的嫁衣襯著肌膚如雪冒签。 梳的紋絲不亂的頭發(fā)上在抛,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天,我揣著相機(jī)與錄音萧恕,去河邊找鬼刚梭。 笑死,一個胖子當(dāng)著我的面吹牛票唆,可吹牛的內(nèi)容都是我干的朴读。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼走趋,長吁一口氣:“原來是場噩夢啊……” “哼衅金!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤氮唯,失蹤者是張志新(化名)和其女友劉穎鉴吹,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體您觉,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡拙寡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了琳水。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片肆糕。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖在孝,靈堂內(nèi)的尸體忽然破棺而出诚啃,到底是詐尸還是另有隱情,我是刑警寧澤私沮,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布始赎,位于F島的核電站,受9級特大地震影響仔燕,放射性物質(zhì)發(fā)生泄漏造垛。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一晰搀、第九天 我趴在偏房一處隱蔽的房頂上張望五辽。 院中可真熱鬧,春花似錦外恕、人聲如沸杆逗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽罪郊。三九已至,卻和暖如春尚洽,著一層夾襖步出監(jiān)牢的瞬間悔橄,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工翎朱, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留橄维,地道東北人。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓拴曲,卻偏偏與公主長得像争舞,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子澈灼,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,901評論 2 345

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