springMVC请求参数校验

下下下周,争取做只水煮鱼~~~
算了吧,买现成的调料吧~~~


fish.png

1 场景

JavaWeb后台应用程序,具体的执行方法,收到请求,需要对请求的数据进行基础校验,如字符串长度限制、正则校验、数字区间校验等。

推荐在springMVC中对前台的请求参数进行统一校验,校验方式建议采用JSR30标准进行校验。

1.1 普通校验方式

最简单的校验方式是,对请求的参数手动一个个进行校验,如下代码:

@GetMapping("saveWithOld")
public JSONObject saveWithOld(User user) {
    JSONObject result = new JSONObject();
    if (user.getUserCode() == null || user.getUserCode() == "") {
        result.put("success", true);
        result.put("message", "用户代码不可为空");
        return result;
    }
    if (user.getUserName() == null || user.getUserName() == "") {
        result.put("success", true);
        result.put("message", "用户名称不可为空");
        return result;
    }
    // do something ......

    result.put("success", true);
    return result;
}

这种方式,代码量非常大代码非常不友好

1.2 springMVC校验方式

springMVC,在执行后台方法之前,可以对请求的数据通过注解进行校验。此校验方式基于JSR303规范

如下代码所示:

@Data
public class User {
    @NotNull(message = "用户代码不可为空")
    private String userCode;
}
@GetMapping("saveWithNormal")
    public JSONObject saveWithNormal(@Valid User user) {
        JSONObject result = new JSONObject();
        result.put("success", true);
        result.put("message", user.toString());
        return result;
    }
@PostMapping("saveWithRequestParam")
public JSONObject saveWithRequestParam(@NotNull(message = "用户代码不可为空") String userCode) {
    JSONObject result = new JSONObject();
    result.put("success", true);
    result.put("message", userCode);
    return result;
}

此种方式,可以使用注解,已更简单的方式对请求参数进行校验。

3 版本说明

本文中代码涉及到的相关版本如下:

3.1 JDK

JDK1.8

3.2 maven依赖

spring-boot-starter-web中已包含了我们需要的依赖。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.2.9.RELEASE</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.7 </version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.18</version>
    <scope>provided</scope>
</dependency>

2 名词关系说明

这里讲下springMVC中使用JSR303进行参数校验,相关的名词含义及名词之间的关系说明

2.1 基本说明

springMVC基于JSR303规范进行校验。

官网说明:https://jcp.org/en/jsr/detail?id=303

规范的相关说明如下:

JSR是Java Specification Requests的缩写,意思是Java 规范提案 。
JSR-303 是JAVA EE 6 中的一项子规范,叫做Bean Validation
Hibernate Validator是 Bean Validation的参考实现
Hibernate Validator提供了JSR 303 规范中所有内置 constrain(约束)的实现,除此之外还有一些附加的constraint(约束)

2.2 详细说明

关于springMVC请求参数校验,涉及几个对应的名词,如下是:

名词 说明
constraint(约束) 对参数的校验约束注解,如@NotNull表示参数不可以为Null
校验注解 为元素加上约束后,有时候需要在参数前加上校验注解来开启验证。
相关注解有@Valid@Validated,如没有用到注解独有的特性(分组、嵌套)等,用哪个注解都一样。

需注意不是所有的校验都需要开启校验,如下不需要加上校验注解:
saveWithRequestParam(@NotNull(message = "用户代码不可为空") String userCode)
但是需要在Controller类上加上注解@Valid或@Validated
JSR303规范 行业规范标准,包括校验的constraint(约束,如@NotNull)开启校验注解@Valid
体现:代码中体现为注解、接口无具体实现代码
jar包:jakarta.validation-api-2.0.2.jar
约束注解:javax.validation.constraints包下注解+hibernate增强注解org.hibernate.validator.constraints
校验注解:javax.validation.Valid
Hibernate Validator Hibernate对JSR303规范中的约束constraint具体代码实现
jar包:hibernate-validator-6.0.20.Final.jar
增强:在原有JSR303的constraint(约束)增加了约束(如@Range)
spring JSR303 spring对JSR303的包装,对原有的校验进行了增强
增强:分组校验顺序校验
缺点:不支持嵌套校验
约束注解:javax.validation.constraints包下注解+hibernate增强注解org.hibernate.validator.constraints
校验注解:org.springframework.validation.annotation.Validated
增强说明:所谓的包装和增强,只是将@Valid注解扩展为@Validated注解。约束注解和JSR303一样。

2.3 关系图

一图胜千言。参数校验的相关说明,关系图如下:

spring参数校验.jpg

3 校验流程

3.1 对象参数

3.1.1 说明

对象参数中进行约束校验。需满足以下条件:

(1)在mapping方法中通过注解@Valid或@Validated指定要校验的参数对象

如下:

@GetMapping("saveWithNormal")
public JSONObject saveWithNormal(@Valid User user) {......}

(2)在对象参数对应的类中,对需要校验的参数加上约束注解

如下:

@Data
public class User {
    /**
     * 用户代码
     */
    @NotNull(message = "用户代码不可为空")
    private String userCode;
}
3.1.2 校验流程

校验失败后,需要对失败的异常信息进行处理,处理方式有两种:

1、在mapping方法上加上参数BindingResult bindingResult

此种方式,校验失败后,会将异常信息封装到参数对象bindingResult中,可以自行对其中的异常信息进行处理,封装错位信息,返回请求结果

这种情况,需要每个请求,都对参数BindingResult进行处理,较为繁琐,不建议此种方式。

如下:

@GetMapping("saveWithBind")
public JSONObject saveWithBind(@Valid User user, BindingResult bindingResult) {
    // --------------------[手动检测验证是否通过]--------------------
    if (bindingResult.hasErrors()) {
        for (FieldError fieldError : bindingResult.getFieldErrors()) {
            JSONObject result = new JSONObject();
            result.put("success", false);
            result.put("message", fieldError.getDefaultMessage());
            return result;
        }
    }
    // --------------------[验证检测通过后执行其他操作]--------------------
    // ......
    JSONObject result = new JSONObject();
    result.put("success", true);
    result.put("message", user.toString());
    return result;
}

2、定义spring全局异常处理,捕捉对应的异常信息,进行统一处理

mapping方法上不加参数BindingResult bindingResult,校验失败后,会抛出异常,异常信息,通过spring全局异常管理,统一对抛出的异常信息进行处理,处理后统一封装错位信息。这种方式,代码量较少,且处理错误信息集中,推荐此种方式。

如下代码:

@Data
public class User {
    /**
     * 用户代码
     */
    @NotNull(message = "用户代码不可为空")
    private String userCode;
}
// 参数校验失败,抛出异常:org.springframework.validation.BindException
@GetMapping("saveWithNormal")
public JSONObject saveWithNormal(@Valid User user) {
    JSONObject result = new JSONObject();
    result.put("success", true);
    result.put("message", user.toString());
    return result;
}
/**
   * 捕捉全局异常:org.springframework.validation.BindException
   * <div>普通请求的参数,校验失败,抛出此异常</div>
   * <div>如:(@Valid User user)</div>
   *
   * @param exception
   * @return
   */
@ExceptionHandler(BindException.class)
public JSONObject handlerBindException(BindException exception) {
    log.info("全局异常[BindException]:" + exception.getMessage());
    JSONObject result = new JSONObject();
    result.put("success", false);
    if (exception != null) {
        String message = exception.getBindingResult().getFieldErrors().stream().filter(e -> e != null).map(FieldError::getDefaultMessage).collect(Collectors.joining(","));
        result.put("message", message);
    }
    return result;
}

需注意:参数上@Valid和@Validated使用方式的不同,校验失败后,会抛出不同的异常

如下:

/**
  * 捕捉全局异常:org.springframework.validation.BindException
  * <div>普通请求的参数,校验失败,抛出此异常</div>
  * <div>如:(@Valid User user)</div>
  * @param exception
  * @return
  */
@ExceptionHandler(BindException.class)
public JSONObject handlerBindException(BindException exception) {......}
/**
  * 捕捉全局异常:org.springframework.web.bind.MethodArgumentNotValidException
  * <div>@RequestBody修饰的参数,校验失败,抛出此异常</div>
  * <div>如:(@RequestBody @Valid User user)</div>
  * @param exception
  * @return
  */
@ExceptionHandler({MethodArgumentNotValidException.class})
public JSONObject handleMethodArgumentNotValidException(MethodArgumentNotValidException exception) {......}

总结校验流程图如下:

对象参数校验流程.jpg

3.2 普通类型参数

3.2.1 说明

普通类型参数中进行约束校验。需满足以下条件:

(1)在Controller类上通过注解@Validated开启校验

注意:需为@Validated注解,而不是@Valid注解

如下:

@Validated
@RestController
@RequestMapping("user")
public class UserController {......}

(2)在mapping方法普通类型参数前面加上约束注解

如下:

// 参数校验失败,抛出异常:javax.validation.ConstraintViolationException
@PostMapping("saveWithRequestParam")
public JSONObject saveWithRequestParam(@NotNull(message="用户代码不可为空") String userCode){......}
// 参数校验失败,抛出异常:org.springframework.web.bind.ConstraintViolationException
@GetMapping("saveWithRestful/{userCode}")
public JSONObject saveWithRest(@PathVariable("userCode") @Length(max = 10,message="用户代码不可超过10位") String userCode) {......}
3.2.1 校验流程

校验结果的处理流程同《3.1对象参数》

需注意:如使用全局异常捕捉,校验失败后抛出的异常如下:

 /**
   * 捕捉全局异常:javax.validation.ConstraintViolationException
   * <div>直接在参数上加的校验,校验失败,抛出此异常</div>
   * <div>如:(@NotNull(message = "用户代码不可为空") String userCode)</div>
   *
   * @param exception
   * @return
   */
@ExceptionHandler(ConstraintViolationException.class)
public JSONObject handlerConstraintViolationException(ConstraintViolationException exception) {......}

总结校验流程图如下:

普通参数校验流程.jpg

4 嵌套校验

嵌套校验,准确来说,是对象内约束的嵌套校验。指的是校验A对象,A对象内有个属性B是对象,B对象内部属性仍然有约束。需要对A对象的约束+A对象内B对象的约束进行校验,这种就是嵌套约束。

4.1 代码示例

这里既校验参数user中的userCode约束又需要校验user中的属性对象department中的departmentCode的约束

  • 实体定义
@Data
public class Department {
    @NotNull(message = "部门代码不可为空")
    private String departmentCode;
}
@Data
public class User {
    @NotNull(message = "用户代码不可为空")
    private String userCode;
    
    @Valid
    @NotNull(message = "部门不可为空")
    private Department department;
    
    private String userName;
}
  • mapping方法
@GetMapping("saveWithLevel")
public JSONObject saveWithLevel(@Valid User user) {
    JSONObject result = new JSONObject();
    result.put("success", true);
    result.put("message", user.toString());
    return result;
}
  • 异常处理
@ExceptionHandler(BindException.class)
public JSONObject handlerBindException(BindException exception) {
    log.info("全局异常[BindException]:" + exception.getMessage());
    JSONObject result = new JSONObject();
    result.put("success", false);
    if (exception != null) {
        String message = exception.getBindingResult().getFieldErrors().stream().filter(e -> e != null).map(FieldError::getDefaultMessage).collect(Collectors.joining(","));
        result.put("message", message);
    }
    return result;
}

4.2 代码测试

  • 请求信息

http://localhost:8080/user/saveWithLevel?department.departmentCode=001

  • 返回结果
{"success":false,"message":"部门代码长度需在5~10之间,用户代码不可为空"}

可见嵌套校验起作用了,对象user的内部普通属性userCode和内部对象department的自己的约束都起作用了。

4.3 总结

  • 实现嵌套校验,在被校验对象的内部属性对象上,必须加上@Valid注解

  • mapping方法参数前,用@Valid注解和@Validated没有区别

5 分组校验

同一个javaBean,我们加上约束注解后,这个javaBean作为请求参数的对象类型,其中的约束注解,会对参数对象的内容进行校验。

有时候,不同的请求我们会使用相同的javaBean作为对象的参数类型,如新增用户更新用户我们都会使用用户这个JavaBean作为请求参数的封装对象。

5.1 代码示例

比如,我们新增用户,需要设置密码;更新用户,不需要设置密码。

代码如下:

  • 分组类型

分组接口不需要有实现,仅仅作为一个分组类型

public interface Add {
}
public interface Edit {
}
  • 实体定义

通过约束中的group参数,来指定对应的分组类型,可以指定多个

@Data
public class User {
    @NotNull(message = "用户代码不可为空", groups = {Add.class, Edit.class})
    private String userCode;
    
    @NotNull(message = "密码不可为空", groups = {Add.class})
    private String password;
}
  • mapping方法

校验方式,只能指定@Validated,其中的value为这个参数的分组类型,和类中约束注解的groups属性相对性可以指定多个

@GetMapping("groupAdd")
public JSONObject groupAdd(@Validated(Add.class) User user) {
    JSONObject result = new JSONObject();
    result.put("success", true);
    result.put("message", user.toString());
    return result;
}

@GetMapping("groupEdit")
public JSONObject groupEdit(@Validated(Edit.class) User user) {
    JSONObject result = new JSONObject();
    result.put("success", true);
    result.put("message", user.toString());
    return result;
}
  • 异常处理

同4.1

5.2 代码测试

5.3 总结

(1)分组校验中,定义的分组类型接口,不需要有实现内容,仅仅是作为分组的一个类型存在,不同的业务,可以共用相同的类型。

(2)约束中的分组类型,可以定义多个。

(3)@Validated中的分组类型,也可以指定多个。

(4)校验的时候,根据@Validated中指定分组类型,去找校验对象中的对应有此分组类型的约束,进行校验。

(5)指定分组后,不满足分组的约束(不加分组的约束为默认分组,也是一种分组),不会进行校验

6 顺序校验

如不进行顺序校验配置,校验对象内的属性,校验顺序是随机的。

有时候想先校验比较简单的约束,再校验复杂的,因此需要指定约束的校验顺序。可以结合《7 验证将检测到第一个约束违例时停止》一起使用。

6.1 代码示例

  • 分组类型
// 分组类型:第一个执行
public interface FirstCheck {
}
// 分组类型:第二个执行
public interface SecondCheck {
}
// 待顺序的分组类型组
@GroupSequence({FirstCheck.class, SecondCheck.class})
public interface UserGroupCheck {
}
  • 实体定义
@Data
public class User {
    @NotNull(message = "用户代码不可为空", groups = {FirstCheck.class})
    private String userCode;
    
    @NotNull(message = "密码不可为空", groups = {SecondCheck.class})
    private String password;
    
    @NotNull(message = "用户名不可为空")
    private String userName;
}
  • mapping方法
@GetMapping("orderCheck")
public JSONObject orderCheck(@Validated(UserGroupCheck.class) User user) {
    JSONObject result = new JSONObject();
    result.put("success", true);
    result.put("message", user.toString());
    return result;
}
  • 异常处理

同4.1

6.2 代码测试

  • 请求1

    • 请求

    http://localhost:8080/user/orderCheck

    • 结果
    {"success":false,"message":"用户代码不可为空"}
    
  • 请求2

    • 请求

    http://localhost:8080/user/orderCheck?userCode=001

    • 结果
    {"success":false,"message":"密码不可为空"}
    

    请求3

    • 请求

    http://localhost:8080/user/orderCheck?userCode=001&password=123456

    • 结果
    {"success":true,"message":"User(userCode=001, password=123456, userName=null)"}
    
  • 特殊请求

    • 变更

      将实体类进行变更,userName上的约束也加上分组为FirstCheck。此时userCode和userName的约束分组均为FirstCheck。

      如下:

      @Data
      public class User {
          @NotNull(message = "用户代码不可为空", groups = {FirstCheck.class})
          private String userCode;
          
          @NotNull(message = "密码不可为空", groups = {SecondCheck.class})
          private String password;
          
          @NotNull(message = "用户名不可为空", groups = {FirstCheck.class})
          private String userName;
      }
      
    • 请求

      http://localhost:8080/user/orderCheck

    • 结果

{"success":false,"message":"用户代码不可为空,用户名不可为空"}


或

```json
{"success":false,"message":"用户名不可为空,用户代码不可为空"}

可以看出,当一个分组内有多个约束,约束的校验顺序仍然是随机的

6.3 总结

(1)根据参数中的分组对应的接口中@GroupSequence指定的分组类型的顺序进行加校验

(2)只有当一个分组内的所有约束都校验通过后,才会进入下一个分组进行校验。

(3)顺序校验,指的是@GroupSequence内配置的分组的顺序,当一个分组内有多个约束这个分组内约束校验顺序仍然随机

7 验证将检测到第一个约束违例时停止

默认,有多个约束的情况下,将会对所有参数进行校验,如果存在校验失败的约束,返回的校验结果(BindingResult或对应Exception)中会有所有的参数校验错误信息。即如果多个不满足约束,则返回结果中会有多个失败信息

有时候,我们只需要返回第一个一个校验失败的约束信息就好,校验到一个约束失败后,没有必要再花费代价进行其他约束校验。

springBoot中,参数校验的实现,基于MethodValidationPostProcessor

@Bean
@ConditionalOnMissingBean
public static MethodValidationPostProcessor methodValidationPostProcessor(Environment environment,
                                                                          @Lazy Validator validator) {
    MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
    boolean proxyTargetClass = environment.getProperty("spring.aop.proxy-target-class", Boolean.class, true);
    processor.setProxyTargetClass(proxyTargetClass);
    processor.setValidator(validator);
    return processor;
}

这个postProcessor的校验配置基于spring中的beanValidator,我们创建自己的Validator的bean,配置failFast,即可实现验证将检测到第一个约束违例时停止这个要求。

实现代码如下:

@Bean
public Validator validator() {
    HibernateValidatorConfiguration configuration = Validation.byProvider(HibernateValidator.class).configure();
    //验证将检测到第一个约束违例时停止
    configuration.failFast(true);
    ValidatorFactory validatorFactory = configuration.buildValidatorFactory();
    return validatorFactory.getValidator();
}

或使用更简洁的写法:

@Bean
public Validator validator() {
    ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
        .configure()
        //验证将检测到第一个约束违例时停止
        .failFast(true)
        .buildValidatorFactory();
    return validatorFactory.getValidator();
}

failFastHibernateValidatorConfiguration中的一个属性配置,配置中还有其他配置属性,可以定制我们的校验器

8 自定义校验器

自定义校验器,注意点比较多,不是本文的重点,暂时不进行记录,后续有时间会有专门的文章进行分析。

9 生产环境配置

前面说的都是原理和使用细节,这里记录下生产环境,需要进行哪些全局配置。

9.1 全局异常处理

建议使用全局异常处理,对请求的异常信息进行统一处理。

代码如下:

import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.stream.Collectors;

/**
 * 统一异常处理
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
    
    /**
     * 捕捉全局异常:org.springframework.web.bind.MethodArgumentNotValidException
     * <div>@RequestBody修饰的参数,校验失败,抛出此异常</div>
     * <div>如:xxxAction(@RequestBody @Valid User user)</div>
     * @param exception
     * @return
     */
    @ExceptionHandler({MethodArgumentNotValidException.class})
    public JSONObject handleMethodArgumentNotValidException(MethodArgumentNotValidException exception) {
        log.info("全局异常[MethodArgumentNotValidException]:" + exception.getMessage());
        JSONObject result = new JSONObject();
        result.put("success", false);
        if (exception != null) {
            String message = exception.getBindingResult().getFieldErrors().stream().filter(e -> e != null).map(FieldError::getDefaultMessage).collect(Collectors.joining(","));
            result.put("message", message);
        }
        return result;
    }
    
    /**
     * 捕捉全局异常:org.springframework.validation.BindException
     * <div>普通请求的参数,校验失败,抛出此异常</div>
     * <div>如:xxxAction(@Valid User user)</div>
     * @param exception
     * @return
     */
    @ExceptionHandler(BindException.class)
    public JSONObject handlerBindException(BindException exception) {
        log.info("全局异常[BindException]:" + exception.getMessage());
        JSONObject result = new JSONObject();
        result.put("success", false);
        if (exception != null) {
            String message = exception.getBindingResult().getFieldErrors().stream().filter(e -> e != null).map(FieldError::getDefaultMessage).collect(Collectors.joining(","));
            result.put("message", message);
        }
        return result;
    }
    
    /**
     * 捕捉全局异常:javax.validation.ConstraintViolationException
     * <div>直接在参数上加的校验,校验失败,抛出此异常</div>
     * <div>如:xxxAction(@NotNull(message = "用户代码不可为空") String userCode)</div>
     * <div>如:xxxAction(@PathVariable("userCode") @Length(max = 10,message="用户代码不可超过10位") String userCode)</div>
     * @param exception
     * @return
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public JSONObject handlerConstraintViolationException(ConstraintViolationException exception) {
        log.info("全局异常[ConstraintViolationException]:" + exception.getMessage());
        JSONObject result = new JSONObject();
        result.put("success", false);
        if (exception != null) {
            result.put("message", exception.getConstraintViolations().stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(";")));
        }
        return result;
    }
    
}

参数校验失败,返回的错误json信息如下,可以根据项目的实际情况进行定制:

{"success":false,"message":"用户代码不可超过10位"}

9.2 验证将检测到第一个约束违例时停止

import org.hibernate.validator.HibernateValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;

@Configuration
public class ValidConfig {
    @Bean
    public Validator validator() {
        ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
                .configure()
                //验证将检测到第一个约束违例时停止
                .failFast(true)
                .buildValidatorFactory();
        return validatorFactory.getValidator();
    }
}

10 补充

10.1 校验限制

@Valid支持嵌套验证、@Validated支持分组验证排序验证(准确来说,排序验证也是分组验证的一种)。

无法实现:“嵌套验证+分组验证”和“嵌套验证+排序验证”这种组合形式的验证。

10.2 建议

虽然JSR303支持自定义校验器,笔者不建议将太复杂的校验交给JSR303的标准进行校验

如果是参数基本的属性校验(是否为空、长度、大小、枚举、正则格式),可以以这种形式进行校验。

但是如果是太复杂的校验,如需要连接数据库进行业务判断的校验,笔者仍然建议在具体的业务代码中进行校验。

10.2 校验顺序的随机性

如不使用@Validated指定约束的校验顺序,所有约束的校验顺序是随机的,即相同的情况,返回的校验结果的顺序可能不一样。

10.3 一个字段多个约束

同一个字段可以加多个约束注解,并不是只能有一个约束注解。如下:

@NotNull(message = "用户代码不可为空")
@Length(min = 5, max = 10, message = "用户代码长度需在5~10之间")
private String userCode;

如userCode为空,则抛出异常:用户代码不可为空

如userCode不为空,则校验约束:用户代码长度需在5~10之间

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