SpringMVC+FastJson+hibernate-validator整合完整示例

一:hibernate-validator 基础

  1. 简介:
    通过使用注解Annotations 给类或者类的属性加上约束(constraint),在运行期检查属性值的合法性.

  2. 作用:
    在API接口开发中参数校验是非常重要的事情,因为客户端很可能会少传参数,或者值不合法,甚至参数值是恶意的,所以对客户端传来的参数的合法性就必须要校验了,其中将参数值的校验规则通过注解的形式注解到属性上是一种比较优雅的方式。

  3. 常用的约束注解
    @Null 被注释的元素必须为 null
    @NotNull 被注释的元素必须不为 null
    @AssertTrue 被注释的元素必须为 true
    @AssertFalse 被注释的元素必须为 false
    @Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
    @Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
    @DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
    @DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
    @Size(max=, min=) 被注释的元素的大小必须在指定的范围内
    @Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
    @Past 被注释的元素必须是一个过去的日期
    @Future 被注释的元素必须是一个将来的日期
    @Pattern(regex=,flag=) 被注释的元素必须符合指定的正则表达式
    Hibernate Validator 附加的 constraint
    @NotBlank(message =) 验证字符串非null,且长度必须大于0
    @Email 被注释的元素必须是电子邮箱地址
    @Length(min=,max=) 被注释的字符串的大小必须在指定的范围内
    @NotEmpty 被注释的字符串的必须非空
    @Range(min=,max=,message=) 被注释的元素必须在合适的范围内
    @URL(protocol=,host=, port=, regexp=, flags=) 被注释的字符串必须是一个有效的url
    @CreditCardNumber 被注释的字符串必须通过Luhn校验算法,银行卡,信用卡等号码一般都用Luhn计算合法性
    @ScriptAssert(lang=, script=, alias=) 要有Java Scripting API 即JSR 223 (“Scripting for the JavaTM Platform”)的实现
    @SafeHtml(whitelistType=, additionalTags=) classpath中要有jsoup包

  4. 初识hibernate-validator

public class Address {

    @NotNull private String line1;
    private String line2;
    private String zip;
    private String state;

    @Length(max = 20)
    @NotNull
    private String country;

    @Range(min = -2, max = 50, message = "Floor out of range")
    public int floor;

    // getter&&setter
}

二:整合校验 hibernate-validator
该示例是在SpringMVC+FastJson整合(http://blog.csdn.net/vbirdbest/article/details/72472149)基础上进行集成的,可以先看一下这篇文章(有源码供下载),在该文章的基础上整合hibernate-validator

整合步骤:
1、在pom.xml中引入hibernate-validator依赖

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>5.4.1.Final</version>
</dependency>

2、在[xxx]-servlet.xml中配置验证器:HibernateValidator

<!-- <mvc:annotation-driven> 增加验证器属性validator="validator" -->
<mvc:annotation-driven validator="validator">
        <mvc:message-converters register-defaults="true">
            <!-- 配置Fastjson 替换原来的jackson支持 -->
            <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
                <property name="supportedMediaTypes">
                    <list>
                        <value>text/html;charset=UTF-8</value>
                        <value>application/json</value>
                    </list>
                </property>
                <property name="features">
                    <list>
                        <value>QuoteFieldNames</value> 
                        <value>WriteMapNullValue</value>  
                    </list>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>

    <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
       <property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>  
       <property name="validationMessageSource" ref="messageSource"/>  
    </bean>

    <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
        <property name="basenames">  
        <list>   
            <value>classpath:conf/settings/validation</value>  
            <value>classpath:org/hibernate/validator/ValidationMessages</value>  
        </list>  
        </property>  
        <property name="useCodeAsDefaultMessage" value="false"/>  
        <property name="defaultEncoding" value="UTF-8"/>  
        <property name="cacheSeconds" value="60"/>  
    </bean>

3、在src/main/resources/conf/settings位置定义验证消息文件描述 validation.properties

validation.common.not.null=该字段不能为空
validation.common.not.range=长度非法
validation.common.format.error=格式错误

validation.param.age=年龄未满18周岁

rep.error.unknown=未知错误

4、新建实体类以供测试

UserEntity

package com.mengdee.manage.validator;

import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Null;
import javax.validation.constraints.Pattern;

import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.NotBlank;

public class UserEntity {

    @Null(groups={GroupA.class})
    @NotNull(groups={GroupB.class})
    @Min(value = 1, message="id值必须大于0", groups={GroupB.class})
    private Long id;

    @NotBlank(groups={GroupA.class, GroupB.class})
    @Pattern(regexp="^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,20}$", message="由6-21字母和数字组成,不能是纯数字或纯英文", groups={GroupA.class, GroupB.class})
    private String password;

    @NotBlank(groups={GroupA.class, GroupB.class})
    @Pattern(regexp="^((13[0-9])|(15[^4,\\D])|(18[0,3-9]))\\d{8}$", message="手机号格式不正确")
    private String phone;


    @NotBlank(groups={GroupB.class})
    @Length(min=6, max=12, message="昵称长度为6到12位")
    private String nickname;

    @Min(value=18, message="{validation.param.age}")
    private int age;

    @NotBlank(groups={GroupA.class})
    @Email(message="{validation.common.format.error}")
    private String email;

    @NotBlank
    @Length(min=3, max=10, message="{validation.common.not.range}")
    private String username;


    public UserEntity() {

    }


    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getNickname() {
        return nickname;
    }

    public void setNickname(String nickname) {
        this.nickname = nickname;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }
}

UserModel

package com.mengdee.manage.validator;

import javax.validation.constraints.Min;
import javax.validation.constraints.Pattern;

import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.NotBlank;

public class UserModel {

    @Min(value = 1, message="id值必须大于0")
    private long id;

    @NotBlank
    @Length(min=6, max=12, message="昵称长度为6到12位")
    private String nickname;

    @Min(value=18, message="{validation.param.age}")
    private int age;

    @NotBlank
    @Email(message="{validation.common.format.error}")
    private String email;

    @NotBlank
    @Pattern(regexp="^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,20}$", message="由6-21字母和数字组成,不能是纯数字或纯英文")
    private String password;

    @NotBlank
    @Length(min=3, max=10, message="{validation.common.not.range}")
    private String username;

    @NotBlank
    @Pattern(regexp="^((13[0-9])|(15[^4,\\D])|(18[0,3-9]))\\d{8}$", message="手机号格式不正确")
    private String phone;


    public UserModel() {

    }


    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getNickname() {
        return nickname;
    }

    public void setNickname(String nickname) {
        this.nickname = nickname;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }   
}

UserDetail :

package com.mengdee.manage.validator;

import org.hibernate.validator.constraints.NotBlank;

public class UserDetail {

    private Long id;

    @NotBlank
    private String address;

    @NotBlank
    private String company;




    public UserDetail() {

    }


    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getAddress() {
        return address;
    }
    public void setAddress(String address) {
        this.address = address;
    }
    public String getCompany() {
        return company;
    }
    public void setCompany(String company) {
        this.company = company;
    }
}

使用ValidController 进行测试

package com.mengdee.manage.controller;

import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;

import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import com.mengdee.manage.validator.UserModel;

@Controller
@RequestMapping("/Valid")
public class ValidController extends BaseController {

    // http://localhost:8081/platform-springmvc-webapp/Valid/test?age=18&nickname=mengdee&id=1&email=123@qq.com&password=root123&username=123&phone=18321758957
    @RequestMapping(value = "/test", method = RequestMethod.GET)
    public @ResponseBody Object validation(HttpServletRequest request, @Valid UserModel user, BindingResult bindingResult){
        if (bindingResult.hasErrors()) {
            FieldError fieldError = bindingResult.getFieldError();

            return super.ResponseJsonError(fieldError);
        }

        return "ok";
    }


    // 一个方法同时校验多个对象需要绑定多个结果BindingResult,出现一个@Valid就对应后面声明的一个BindingResult
    @RequestMapping(value = "/test2", method = RequestMethod.GET)
    public @ResponseBody Object validationMore(HttpServletRequest request, 
            @Valid UserModel user, BindingResult bindingResult,
            @Valid UserDetail userDetail, BindingResult bindingResult2){
        if (bindingResult.hasErrors()) {
            FieldError fieldError = bindingResult.getFieldError();

            return super.ResponseJsonError(fieldError);
        }

        if (bindingResult2.hasErrors()) {
            FieldError fieldError = bindingResult2.getFieldError();

            return super.ResponseJsonError(fieldError);
        }

        return "ok";
    }
}

使用注意:
1、对于多个字段,系统验证的顺序好像和字段声明的顺序是不一样的,好像是无须的

2、对于每个验证如果没有message属性,系统会使用默认的,如对应@NotBlank相当于@NotBlank(message=”不能为空”)

3、对于引用类型如String等类型,一定要结合@NotNull、@NotEmpty或者@NotBlank来配合使用,如果不写空的约束,经测试,该字段是不参与校验的,如单独使用@Pattern、@Length、@Email等是不会进行验证的,必须要使用空的约束来限制

@Min:用于基本数据类型,如int、long等

@NotNull: 任何对象的value不能为null
@NotEmpty:集合对象的元素不为0,即集合不为空,也可以用于字符串不为null
@NotBlank:只能用于字符串不为null,并且字符串trim()以后length要大于0

三:分组校验@Validated
@Valid是属于javax.validation.Valid里的。
@Validated是@Valid 的一次封装,是spring提供的校验机制使用(org.springframework.validation.annotation.Validated) ,@Valid不提供分组功能

  1. 分组的作用(使用场景):
    每个校验的注解约束都有一个groups属性,用于指定该约束是属于哪个组的,这样在同一个字段上就可以配置多套约束,在使用的时候只需要指定使用那套约束即可,例如对于注册用户和修改用户信息时,注册时id必须为空,修改用户信息时id必须不能为空,在使用的时候只需要将这两种约束分配到不同的组中即可,如添加时使用组A的约束,更新时使用组B的约束

  2. 分组就是一个空接口interface
    GroupA 和 GroupB

package com.mengdee.manage.validator;

public interface GroupA {

}

package com.mengdee.manage.validator;

public interface GroupB {

}

在使用时指定具体使用那套分组的约束@Validated({GroupA.class})

  1. 组序列@GroupSequence
    默认情况下,不同组别的约束验证是无序的,组序列就是按照分组的前后顺序依次验证,如先验证GroupA组的约束,再验证GroupB组的约束。如果对组的校验顺序有要求,例如必须先校验A组再校验B组,可以使用@GroupSequence来定义每个组的顺序

使用场景:
(1)第二个组中的约束验证依赖于一个稳定状态来运行,而这个稳定状态是由第一个组来进行验证的。

(2)某个组的验证比较耗时,CPU 和内存的使用率相对比较大,最优的选择是将其放在最后进行验证。因此,在进行组验证的时候尚需提供一种有序的验证方式,这就提出了组序列的概念。

一个组可以定义为其他组的序列,使用它进行验证的时候必须符合该序列规定的顺序。在使用组序列验证的时候,如果序列前边的组验证失败,则后面的组将不再给予验证。
使用注解GroupSequence定义组序列:GroupAB

package com.mengdee.manage.validator;

import javax.validation.GroupSequence;

@GroupSequence({GroupA.class, GroupB.class})
public interface GroupAB {

}

ValidationController

package com.mengdee.manage.controller;

import javax.servlet.http.HttpServletRequest;

import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import com.mengdee.manage.validator.GroupA;
import com.mengdee.manage.validator.GroupAB;
import com.mengdee.manage.validator.GroupB;
import com.mengdee.manage.validator.UserEntity;

@Controller
@RequestMapping("/validated")
public class ValidationController extends BaseController {

    // 校验时指定了GroupA,那么只校验约束中包含GroupA的约束,没有包含就不校验,例如对于phone, @NotBlank指定的分组,而@Pattern没有指定分组,那么只校验空着一个约束,不校验手机号格式
    // http://localhost:8081/platform-springmvc-webapp/validated/groupA?password=root123&phone=123
    @RequestMapping(value = "/groupA", method = RequestMethod.GET)
    public @ResponseBody Object groupA(HttpServletRequest request, @Validated({GroupA.class}) UserEntity user, BindingResult bindingResult){
        if (bindingResult.hasErrors()) {
            FieldError fieldError = bindingResult.getFieldError();

            return this.ResponseJsonError(fieldError);
        }

        return "ok";
    }

    // http://localhost:8081/platform-springmvc-webapp/validated/groupB?phone=123&password=root123&id=1
    @RequestMapping(value = "/groupB", method = RequestMethod.GET)
    public @ResponseBody Object groupB(HttpServletRequest request, @Validated({GroupB.class}) UserEntity user, BindingResult bindingResult){
        if (bindingResult.hasErrors()) {
            FieldError fieldError = bindingResult.getFieldError();

            return this.ResponseJsonError(fieldError);
        }

        return "ok";
    }

    // groupAB
    // http://localhost:8081/platform-springmvc-webapp/validated/groupAB?phone=111&password=root123&nickname=123&email=xxx@qq.com
    // @Validated({GroupA.class, GroupB.class}):GroupA和GroupB的关系是或的关系,就像数据库中的OR一样,只要满足一个条件就会对该约束进行校验,同时使用多个组注意多个组之间没有先后属性之说,并不是先校验组A,然后再校验组B
    // 因为id的为空和不为空的约束都会进行检查,所以先注释掉该属性
    @RequestMapping(value = "/groupAB", method = RequestMethod.GET)
    public @ResponseBody Object groupAB(HttpServletRequest request, @Validated({GroupA.class, GroupB.class}) UserEntity user, BindingResult bindingResult){
        if (bindingResult.hasErrors()) {
            FieldError fieldError = bindingResult.getFieldError();

            return this.ResponseJsonError(fieldError);
        }

        return "ok";
    }

    // default
    // http://localhost:8081/platform-springmvc-webapp/default?email=xxx@163.com&age=18
    // @Validated 如果没有指定groups则验证没有分组的属性(此时和@Valid功能一样),如果一个字段上有多个约束,都必须没有指定组,如果部分约束指定的组,部分约束没有指定约束,那么在使用@Validated时不进行检查的
    @RequestMapping(value = "/default", method = RequestMethod.GET)
    public @ResponseBody Object defaultGroup(HttpServletRequest request, @Validated UserEntity user, BindingResult bindingResult){
        if (bindingResult.hasErrors()) {
            FieldError fieldError = bindingResult.getFieldError();

            return this.ResponseJsonError(fieldError);
        }

        return "ok";
    }

    //localhost:8081/platform-springmvc-webapp/validated/sequence?phone=123&password=root123&email=123&nickname=123
    // 对于一个属性上有多个约束,并且多个约束不都在同一个组,那么在检查的时候顺序是根据GroupSequence定义的先后顺序来检查的
    @RequestMapping(value = "/sequence", method = RequestMethod.GET)
    public @ResponseBody Object sequence(HttpServletRequest request, @Validated({GroupAB.class}) UserEntity user, BindingResult bindingResult){
        if (bindingResult.hasErrors()) {
            FieldError fieldError = bindingResult.getFieldError();

            return this.ResponseJsonError(fieldError);
        }

        return "ok";
    }
}

四:自定义hibernate validation注解
当hibernate validation提供的注解不能满足需求时,可以自定义校验约束。

自定义注解约束步骤:

创建注解
创建注解对应的约束验证类
使用注解
测试注解
创建注解 @Phone

package com.mengdee.manage.validator;

import java.lang.annotation.Target;

import javax.validation.Constraint;
import javax.validation.Payload;

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;

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

    String message() default "手机号格式错误";
    String regexp() default "^((13[0-9])|(15[^4,\\D])|(18[0,3-9]))\\d{8}$";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default { };


    @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
    @Retention(RUNTIME)
    @Documented
    public @interface List {
        Phone[] value();
    }   

}

创建手机号注解对应的约束验证类PhoneConstraint

package com.mengdee.manage.validator;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class PhoneConstraint implements ConstraintValidator<Phone, String> {

    private String regexp;

    @Override
    public void initialize(Phone phoneAnnotation) {
        this.regexp = phoneAnnotation.regexp();
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (value == null) {
            return true;        //  HV000028: Unexpected exception during isValid call
        }

        if (value.matches(regexp)) {
            return true;
        }
        return false;
    }

}

在属性上使用注解@Phone

package com.mengdee.manage.validator;

import org.hibernate.validator.constraints.NotBlank;

public class UserDetail {
    @NotBlank
    @Phone
    private String phone;

    public UserDetail() {

    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }
}

测试注解


//  http://localhost:8081/platform-springmvc-webapp/Valid/test3?address=123&company=456&phone=123
    @RequestMapping(value = "/test3", method = RequestMethod.GET)
    public @ResponseBody Object validationCustomAnnotation(HttpServletRequest request, 
            @Valid UserDetail userDetail, BindingResult bindingResult){
        if (bindingResult.hasErrors()) {
            FieldError fieldError = bindingResult.getFieldError();

            return super.ResponseJsonError(fieldError);
        }

        return "ok";
    }

完整代码下载:http://download.csdn.net/detail/vbirdbest/9849719

参考文章:
hibernate validation文档http://docs.jboss.org/hibernate/validator/5.2/reference/en-US/html_single/#validator-customconstraints
自定义注解:http://blog.csdn.net/ruangong1203/article/details/51002360

深入理解Java:注解(Annotation)自定义注解入门:http://www.cnblogs.com/peida/archive/2013/04/24/3036689.html

原文:https://blog.csdn.net/vbirdbest/article/details/72620957

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

推荐阅读更多精彩内容