Bean Validation完結篇:你必須關注的邊邊角角(約束級聯(lián)砰识、自定義約束能扒、自定義校驗器、國際化失敗消息...)

每篇一句

沒有任何技術方案會是一種銀彈仍翰,任何東西都是有利弊的

相關閱讀

【小家Java】深入了解數(shù)據(jù)校驗:Java Bean Validation 2.0(JSR303赫粥、JSR349、JSR380)Hibernate-Validation 6.x使用案例
【小家Spring】Spring方法級別數(shù)據(jù)校驗:@Validated + MethodValidationPostProcessor優(yōu)雅的完成數(shù)據(jù)校驗動作
【小家Java】深入了解數(shù)據(jù)校驗(Bean Validation):從深處去掌握@Valid的作用(級聯(lián)校驗)以及常用約束注解的解釋說明


<center>對Spring感興趣可掃碼加入wx群:Java高工予借、架構師3群(文末有二維碼)</center>


前言

一般來說越平,對于web項目我們都有必要對請求參數(shù)進行校驗,有的前端使用JavaScript校驗灵迫,但是為了安全起見后端的校驗都是必須的秦叛。因此數(shù)據(jù)校驗不僅僅是在web下,在方方面面都是一個重要的點瀑粥。前端校驗有它的JS校驗框架(比如我之前用的jQuery Validation Plugin)挣跋,后端自然也少不了。

前面洋洋灑灑已經(jīng)把數(shù)據(jù)校驗Bean Validation講了很多了狞换,如果你已經(jīng)運用在你的項目中避咆,勢必將大大提高生產(chǎn)力吧,本文作為完結篇(不是總結篇)就不用再系統(tǒng)性的介紹Bean Validation他了修噪,而是旨在介紹你在使用過程中不得不關心的周邊查库、細節(jié)~

如果說前面是用機,那么本文就有點玩機的意思~

BV(Bean Validation)的使用范圍

本次再次強調了這一點(設計思想是我認為特別重要的存在):使用范圍黄琼。
Bean Validation并不局限于應用程序的某一層或者哪種編程模型, 它可以被用在任何一層, 除了web程序樊销,也可以是像Swing這樣的富客戶端程序中(GUI編程)。

我抄了一副業(yè)界著名的圖給大家:


Bean Validation的目標是簡化Bean校驗脏款,將以往重復的校驗邏輯進行抽象和標準化围苫,形成統(tǒng)一API規(guī)范;

說到抽象統(tǒng)一API撤师,它可不是亂來的剂府,只有當你能最大程度的得到公有,這個動作才有意義丈氓,至少它一般都是與業(yè)務無關的周循。抽象能力是對程序員分級的最重要標準之一

約束繼承

如果子類繼承自他的父類,除了校驗子類万俗,同時還會校驗父類湾笛,這就是約束繼承(同樣適用于接口)。

// child和person上標注的約束都會被執(zhí)行
public class Child extends Person {
    ...
}

注意:如果子類覆蓋了父類的方法闰歪,那么子類和父類的約束都會被校驗嚎研。

約束級聯(lián)(級聯(lián)校驗)

如果要驗證屬性關聯(lián)的對象,那么需要在屬性上添加@Valid注解,如果一個對象被校驗临扮,那么它的所有的標注了@Valid的關聯(lián)對象都會被校驗论矾,這些對象也可以是數(shù)組、集合杆勇、Map等贪壳,這時會驗證他們持有的所有元素

Demo

@Getter
@Setter
@ToString
public class Person {

    @NotNull
    private String name;
    @NotNull
    @Positive
    private Integer age;

    @Valid
    @NotNull
    private InnerChild child;
    @Valid // 讓它校驗List里面所有的屬性
    private List<InnerChild> childList;

    @Getter
    @Setter
    @ToString
    public static class InnerChild {
        @NotNull
        private String name;
        @NotNull
        @Positive
        private Integer age;
    }

}

校驗程序:

    public static void main(String[] args) {
        Person person = new Person();
        person.setName("fsx");
        Person.InnerChild child = new Person.InnerChild();
        child.setName("fsx-age");
        child.setAge(-1);
        person.setChild(child);

        // 設置childList
        person.setChildList(new ArrayList<Person.InnerChild>(){{
            Person.InnerChild innerChild = new Person.InnerChild();
            innerChild.setName("innerChild1");
            innerChild.setAge(-11);
            add(innerChild);

            innerChild = new Person.InnerChild();
            innerChild.setName("innerChild2");
            innerChild.setAge(-12);
            add(innerChild);
        }});

        Validator validator = Validation.byProvider(HibernateValidator.class).configure().failFast(false)
                .buildValidatorFactory().getValidator();
        Set<ConstraintViolation<Person>> result = validator.validate(person);

        // 輸出錯誤消息
        result.stream().map(v -> v.getPropertyPath() + " " + v.getMessage() + ": " + v.getInvalidValue())
                .forEach(System.out::println);
    }

打印校驗失敗的消息:

age 不能為null: null
childList[0].age 必須是正數(shù): -11
child.age 必須是正數(shù): -1
childList[1].age 必須是正數(shù): -12

約束失敗消息message自定義

每個約束定義中都包含有一個用于提示驗證結果的消息模版message蚜退,并且在聲明一個約束條件的時候,你可以通過這個約束注解中的message屬性來重寫默認的消息模版(這是自定義message最簡單的一種方式)闰靴。

如果在校驗的時候,這個約束條件沒有通過,那么你配置的MessageInterpolator插值器會被用來當成解析器來解析這個約束中定義的消息模版, 從而得到最終的驗證失敗提示信息。
默認使用的插值器是org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator钻注,它借助org.hibernate.validator.spi.resourceloading.ResourceBundleLocator來獲取到國際化資源屬性文件從而填充模版內(nèi)容~

資源解析器默認使用的實現(xiàn)是PlatformResourceBundleLocator蚂且,在配置Configuration初始化的時候默認被賦值:

    private ConfigurationImpl() {
        this.validationBootstrapParameters = new ValidationBootstrapParameters();

        // 默認的國際化資源文件加載器USER_VALIDATION_MESSAGES值為:ValidationMessages
        // 這個值就是資源文件的文件名~~~~
        this.defaultResourceBundleLocator = new PlatformResourceBundleLocator(
                ResourceBundleMessageInterpolator.USER_VALIDATION_MESSAGES
        );
        this.defaultTraversableResolver = TraversableResolvers.getDefault();
        this.defaultConstraintValidatorFactory = new ConstraintValidatorFactoryImpl();
        this.defaultParameterNameProvider = new DefaultParameterNameProvider();
        this.defaultClockProvider = DefaultClockProvider.INSTANCE;
    }

這個解析器會嘗試解析模版中的占位符( 大括號括起來的字符串,形如這樣{xxx})幅恋。
它解析message的核心代碼如下(比如此處message模版是{javax.validation.constraints.NotNull.message}為例):

public abstract class AbstractMessageInterpolator implements MessageInterpolator {
    ...
    private String interpolateMessage(String message, Context context, Locale locale) throws MessageDescriptorFormatException {
        // 如果message消息木有占位符杏死,那就直接返回  不再處理了~
        // 這里自定義的優(yōu)先級是最高的~~~
        if ( message.indexOf( '{' ) < 0 ) {
            return replaceEscapedLiterals( message );
        }

        // 調用resolveMessage方法處理message中的占位符和el表達式
        if ( cachingEnabled ) {
            resolvedMessage = resolvedMessages.computeIfAbsent( new LocalizedMessage( message, locale ), lm -> resolveMessage( message, locale ) );
        } else {
            resolvedMessage = resolveMessage( message, locale );
        }   
        ...
    }

    private String resolveMessage(String message, Locale locale) {
        String resolvedMessage = message;

        // 獲取資源ResourceBundle三部曲
        ResourceBundle userResourceBundle = userResourceBundleLocator.getResourceBundle( locale );
        ResourceBundle constraintContributorResourceBundle = contributorResourceBundleLocator.getResourceBundle( locale );
        ResourceBundle defaultResourceBundle = defaultResourceBundleLocator.getResourceBundle( locale );
        ...
    }
}

對如上message的處理步驟大致總結如下:

  1. 若沒占位符符號{需要處理,直接返回(比如我們自定義message屬性值全是文字捆交,就直接返回了)~
  2. 占位符或者EL淑翼,交給resolveMessage()方法從資源文件里拿內(nèi)容來處理~
  3. 拿取資源文件,按照如下三個步驟尋找:
    1. userResourceBundleLocator:去用戶自己的classpath里面去找資源文件(默認名字是ValidationMessages.properties品追,當然你也可以使用國際化名)
    2. contributorResourceBundleLocator:加載貢獻的資源包
    3. defaultResourceBundle:默認的策略窒舟。去這里于/org/hibernate/validator加載ValidationMessages.properties
  4. 需要注意的是,如上是加載資源的順序诵盼。無論怎么樣,這三處的資源文件都會加載進內(nèi)存的(并無短路邏輯)银还。進行占位符匹配的時候风宁,依舊遵守這規(guī)律
    1. 最先用自己當前項目classpath下的資源去匹配資源占位符,若沒匹配上再用下一級別的資源~~~
    2. 規(guī)律同上蛹疯,依次類推戒财,遞歸的匹配所有的占位符(若占位符沒匹配上,原樣輸出捺弦,并不是輸出null哦~)

需要注意的是饮寞,因為{在此處是特殊字符,若你就想輸出{列吼,請轉義:\{

了解了這些之后幽崩,想自定義失敗消息message,就簡直不要太簡單了好不好寞钥,例子如下:

    @Min(value = 10, message = "{com.fsx.my.min.message}")
    private Integer age;

寫一個資源屬性文件慌申,命名為ValidationMessages.properties放在類路徑下,文件內(nèi)容如下:

// 此處可以使用占位符{value}讀取注解對應屬性上的值
com.fsx.my.min.message=[自定義消息]最小值必須是{value}

運行測試用例理郑,打印輸出如下失敗消息:

age [自定義消息]最小值必須是10: -1

完美(自定義的生效了)

說明:因為我的平臺是中文的蹄溉,因此文件命名為ValidationMessages_zh_CN.properties的效果也是一樣的咨油,因為Hibernate Validation提供了Locale國際化的支持


Spring環(huán)境下自定義國際化消息

上面使用的是Hibernate Validation內(nèi)置的對國際化的支持,由于大部分情況下我們都是在Spring環(huán)境下使用數(shù)據(jù)校驗柒爵,因此有必要講講Spring加持情況下的國家化做法役电。我們知道Spring MVC是有專門做國際化的模塊的,因此國際化這個動作當然也是可以交給Spring自己來做的棉胀,此處我也給一個Demo吧:

說明:即使在Spring環(huán)境下法瑟,你照常使用Hibernate Validation的國際化方案,依舊是沒有問題的~

1膏蚓、向容器內(nèi)配置驗證器(含有自己的國際化資源文件):

@Configuration
public class RootConfig {

    @Bean
    public LocalValidatorFactoryBean localValidatorFactoryBean() {
        LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean();

        // 使用Spring加載國際化資源文件
        //ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        //messageSource.setBasename("MyValidationMsg");

        ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
        messageSource.setBasename("MyValidationMsg"); // 注意此處名字就隨意啦瓢谢,畢竟交給spring了`.properties`就不需要了哦
        messageSource.setCacheSeconds(120); // 緩存時長
        // messageSource.setFileEncodings(); // 設置編碼 UTF-8

        localValidatorFactoryBean.setValidationMessageSource(messageSource);
        return localValidatorFactoryBean;
    }
}

運行單測:

@Slf4j
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {RootConfig.class})
public class TestSpringBean {

    @Autowired
    private LocalValidatorFactoryBean localValidatorFactoryBean;

    @Test
    public void test1() {
        Person person = new Person();
        person.setAge(-5);

        Validator validator = localValidatorFactoryBean.getValidator();
        Set<ConstraintViolation<Person>> result = validator.validate(person);

        // 輸出錯誤消息
        result.stream().map(v -> v.getPropertyPath() + " " + v.getMessage() + ": " + v.getInvalidValue())
                .forEach(System.out::println);
    }

}

打印校驗失敗消息如下(完美生效):

age [自定義消息]最小值必須是10: -5

說明:若是Spring應用,如果你還需要考慮國際化的話驮瞧,我個人建議使用Spring來處理國際化氓扛,而不是Hibernate~(有種Spring的腦殘粉感覺有木有,當然這不是強制的)


Spring MVC中如何自定義全局校驗器Validator

Spring MVC默認配置的(使用的)校驗器的執(zhí)行代碼如下:

public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {\
    ...
    @Bean
    public Validator mvcValidator() {
        Validator validator = getValidator();
        if (validator == null) {
            if (ClassUtils.isPresent("javax.validation.Validator", getClass().getClassLoader())) {
                Class<?> clazz;
                try {
                    String className = "org.springframework.validation.beanvalidation.OptionalValidatorFactoryBean";
                    clazz = ClassUtils.forName(className, WebMvcConfigurationSupport.class.getClassLoader());
                } catch (ClassNotFoundException | LinkageError ex) {
                    throw new BeanInitializationException("Failed to resolve default validator class", ex);
                }
                validator = (Validator) BeanUtils.instantiateClass(clazz);
            } else {
                validator = new NoOpValidator();
            }
        }
        return validator;
    }
    ...
}

代碼很簡答论笔,就不逐行解釋了采郎。我歸納如下:

  1. Spring MVC中校驗要想自動生效,必須導入了javax.validation.Validator才行狂魔,否則是new NoOpValidator()它木有校驗行為
  2. Spring MVC最終默認使用的校驗器是OptionalValidatorFactoryBeanLocalValidatorFactoryBean的子類)~
  3. 顯然蒜埋,要想校驗生效@EnableWebMvc也是必須的(SpringBoot環(huán)境另說)

那如何自定義一個全局的校驗器呢?最佳做法如下:

@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {
    ...
    @Override
    public Validator getValidator() {
        // return "global" validator
        return new LocalValidatorFactoryBean();
    }
    ...
}

當然最楷,你還可以使用@InitBinder來設置整份,甚至可以細粒度設置到只與當前Controller綁定的校驗器都是可行的(比如你可以使用自定校驗器實現(xiàn)各種私有的、比較復雜的邏輯判斷)

==自定義約束==

JSRHibernate支持的約束條件已經(jīng)足夠強大籽孙,應該是能滿足我們絕大部分情況下的基礎驗證的烈评。如果還是不能滿足業(yè)務需求,我們還可以自定義約束犯建,也很簡單一事讲冠。

JSRHibernate提供的約束注解解釋說明:【小家Java】深入了解數(shù)據(jù)校驗(Bean Validation):從深處去掌握@Valid的作用(級聯(lián)校驗)以及常用約束注解的解釋說明

自定義一個約束分如下三步(說是2步也成):

  1. 自定義一個約束注解
  2. 實現(xiàn)一個校驗器(實現(xiàn)接口:ConstraintValidator)
  3. 定義默認的校驗錯誤信息

給個Demo:此處以自定義一個約束注解來校驗集合的長度范圍@CollectionRange

1、自定義注解(此處使用得比較高級)

@Documented
@Constraint(validatedBy = {})
@SupportedValidationTarget(ValidationTarget.ANNOTATED_ELEMENT)
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Repeatable(value = CollectionRange.List.class)
@Size // 校驗動作委托給Size去完成  所以它自己并不需要校驗器~~~
@ReportAsSingleViolation // 組合組件一般建議標注上
public @interface CollectionRange {

    // 三個必備的基本屬性
    String message() default "{com.fsx.my.collection.message}";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};

    // 自定義屬性  @OverridesAttribute這里有點方法覆蓋的意思~~~~~~ 子類屬性覆蓋父類的默認值嘛
    @OverridesAttribute(constraint = Size.class, name = "min")
    int min() default 0;
    @OverridesAttribute(constraint = Size.class, name = "max")
    int max() default Integer.MAX_VALUE;

    // 重復注解
    @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
    @Retention(RUNTIME)
    @Documented
    public @interface List {
        CollectionRange[] value();
    }
}

2适瓦、實現(xiàn)一個校驗器
此例用不著(下面會有)
3竿开、自定義錯誤消息
當然,你可以寫死在message屬性上玻熙,但是本處使用配置的方式來~

com.fsx.my.collection.message=[自定義消息]你的集合的長度必須介于{min}和{max}之間(包含邊界值)

運行案例:

@Getter
@Setter
@ToString
public class Person {
    @CollectionRange(min = 5, max = 10)
    private List<Integer> numbers;
}

    // 測試用例
    public static void main(String[] args) {
        Person person = new Person();
        person.setNumbers(Arrays.asList(1, 2, 3));

        Validator validator = Validation.byProvider(HibernateValidator.class).configure().failFast(false)
                .buildValidatorFactory().getValidator();
        Set<ConstraintViolation<Person>> result = validator.validate(person);

        // 輸出錯誤消息
        result.stream().map(v -> v.getPropertyPath() + " " + v.getMessage() + ": " + v.getInvalidValue())
                .forEach(System.out::println);
    }

輸出校驗信息如下(校驗成功):

numbers [自定義消息]你的集合的長度必須介于5和10之間(包含邊界值): [1, 2, 3]

組合約束

這塊比較簡單殴玛,很多情況下一個字段是需要有多個約束(不為空且大于0)的蚊逢。這個時候我們有兩種做法:

  1. 就在該屬性上標注多個注解即可(推薦
  2. 自定義一個注解,把這些注解封裝起來,形成一個新的約束注解(使用場景相對較少)

自定義message消息==可使用的變量==

我們知道約束的失敗消息message里是可以使用{}占位符來動態(tài)取值的恋谭,默認情況下能夠取到約束注解里的所有屬性值,并且也只能取到那些屬性的值

but,有的時候為了友好展示筷转,我們需要自定義message里可取的值怎么辦呢?下面給個例子悬而,讓大家知道怎么自定義可使用占位符的參數(shù)(備注:需要基于自定義注解):

自定義一個性別約束注解

@Documented
@Retention(RUNTIME)
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Constraint(validatedBy = {GenderConstraintValidator.class}) 
public @interface Gender {

    // 三個必備的基本屬性
    String message() default "{com.fsx.my.gender.message}";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};

    int gender() default 0; //0:男生  1:女生
}

配置的消息資源是:

com.fsx.my.gender.message=[自定義消息]此處只能允許性別為[{zhGenderValue}]的

很顯然呜舒,此處我們需要讀取zhGenderValue這個自定義的屬性值,并且希望它是中文笨奠。所以看看下面我實現(xiàn)的這個校驗器吧:

public class GenderConstraintValidator implements ConstraintValidator<Gender, Integer> {

    int genderValue;

    @Override
    public void initialize(Gender constraintAnnotation) {
        genderValue = constraintAnnotation.gender();
    }

    @Override
    public boolean isValid(Integer value, ConstraintValidatorContext context) {
        //添加參數(shù)  校驗失敗的時候可用
        HibernateConstraintValidatorContext hibernateContext = context.unwrap(HibernateConstraintValidatorContext.class);
        hibernateContext.addMessageParameter("zhGenderValue", genderValue == 0 ? "男" : "女"); // 友好展示
        //hibernateContext.buildConstraintViolationWithTemplate("{zhGenderValue}").addConstraintViolation();

        if (value == null) {
            return false; // null is not valid
        }
        return value == genderValue;
    }
}

運行單測:

@Getter
@Setter
@ToString
public class Person {
    @Gender(gender = 0)
    private Integer personGender;
}

    public static void main(String[] args) {
        Person person = new Person();
        person.setPersonGender(1);

        Validator validator = Validation.byProvider(HibernateValidator.class).configure().failFast(false)
                .buildValidatorFactory().getValidator();
        Set<ConstraintViolation<Person>> result = validator.validate(person);

        // 輸出錯誤消息
        result.stream().map(v -> v.getPropertyPath() + " " + v.getMessage() + ": " + v.getInvalidValue())
                .forEach(System.out::println);
    }

打印如下:

personGender [自定義消息]此處只能允許性別為[男]的: 1

完美(效果達到)

總結

如果說前面文章是用機袭蝗,那這篇可以稱作是玩機了Bean Validation是java官方定義的bean驗證標準般婆,現(xiàn)在最新的版本為2.x到腥,hibernate validator作為其標準實現(xiàn),對其進行了擴展蔚袍,增加了多種約束乡范,如果仍然不能滿足業(yè)務需求,我們還可以自定義約束啤咽。
數(shù)據(jù)校驗Bean Validation這一大塊的內(nèi)容到此就告一段落了晋辆,希望講解的所有內(nèi)容能給你實際工作中帶來幫助,祝好~

知識交流

若文章格式混亂宇整,可點擊原文鏈接-原文鏈接-原文鏈接-原文鏈接-原文鏈接

==The last:如果覺得本文對你有幫助瓶佳,不妨點個贊唄。當然分享到你的朋友圈讓更多小伙伴看到也是被作者本人許可的~==

若對技術內(nèi)容感興趣可以加入wx群交流:Java高工鳞青、架構師3群霸饲。
若群二維碼失效,請加wx號:fsx641385712(或者掃描下方wx二維碼)臂拓。并且備注:"java入群" 字樣贴彼,會手動邀請入群

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市埃儿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌融涣,老刑警劉巖童番,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異威鹿,居然都是意外死亡剃斧,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進店門忽你,熙熙樓的掌柜王于貴愁眉苦臉地迎上來幼东,“玉大人,你說我怎么就攤上這事「罚” “怎么了脓杉?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長简逮。 經(jīng)常有香客問我球散,道長,這世上最難降的妖魔是什么散庶? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任蕉堰,我火速辦了婚禮,結果婚禮上悲龟,老公的妹妹穿的比我還像新娘屋讶。我一直安慰自己,他們只是感情好须教,可當我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布皿渗。 她就那樣靜靜地躺著,像睡著了一般没卸。 火紅的嫁衣襯著肌膚如雪羹奉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天约计,我揣著相機與錄音诀拭,去河邊找鬼。 笑死煤蚌,一個胖子當著我的面吹牛耕挨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播尉桩,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼筒占,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蜘犁?” 一聲冷哼從身側響起翰苫,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎这橙,沒想到半個月后奏窑,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡屈扎,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年埃唯,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鹰晨。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡墨叛,死狀恐怖止毕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情漠趁,我是刑警寧澤扁凛,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站棚潦,受9級特大地震影響令漂,放射性物質發(fā)生泄漏。R本人自食惡果不足惜丸边,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一叠必、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧妹窖,春花似錦纬朝、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蜓萄,卻和暖如春隅茎,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背嫉沽。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工辟犀, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人绸硕。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓堂竟,卻偏偏與公主長得像,于是被迫代替她去往敵國和親玻佩。 傳聞我的和親對象是個殘疾皇子出嘹,可洞房花燭夜當晚...
    茶點故事閱讀 42,828評論 2 345

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