SpringBoot2. X Kotlin Series Data Checksum Exception Handling Detailed Explanation

  • 2021-07-24 11:01:38
  • OfStack

When developing projects, we often need to check the data submitted by users at the front and back ends to judge whether the submitted data meets our standards, including string length, whether it is a number, or whether it is a mobile phone number; The main purpose of this is to reduce the risk of SQL injection attacks and the insertion of dirty data. When it comes to data verification, we usually mention exception handling, because for the sake of security, we usually don't want to throw exceptions directly to the client, but return them to the client after our processing, which is mainly to improve system security and give user-friendly prompts.

Define Entities with Validation Notes


class StudentForm() {
 
 @NotBank(message = ' Birthday cannot be empty ')
 var birthday: String = ""

 @NotBlank(message = "Id Cannot be empty ")
 var id:String = ""

 @NotBlank(message = " Age cannot be empty ")
 var age:String = ""

 @NotEmpty(message = " Hobbies cannot be empty ")
 var Interests:List<String> = Collections.emptyList()

 @NotBlank(message = " School cannot be empty ")
 var school: String = ""
 override fun toString(): String {
  return ObjectMapper().writeValueAsString(this)
 }
}

First of all, the basic verification annotations are used here, which are located under javax.validation. constraints. Common annotations are @ NotNull, @ NotEmpty, @ Max, @ Email, @ NotBank, @ Size, @ Pattern. Of course, there are many annotations out of these, which will not be explained here at 11. If you want to know more, you can consult and check jar package.

Here is a brief explanation of the common usage of 1 annotation:

@ NotNull: Verify whether an object is Null @ NotBank: Check whether the string is empty @ NotEmpty: Verify whether List, Map and Set are empty @ Email: Verify mailbox format @ Max @ Min: Verify that Number or String is within the specified range @ Size: Usually used in conjunction with @ Max @ Min1 @ Pattern: With custom regular expression validation

Define return status enumeration


enum class ResultEnums(var code:Int, var msg:String) {
 SUCCESS(200, " Success "),
 SYSTEM_ERROR(500, " The system is busy, please try again later "),

}

Custom exception

This is mainly parameter validation, so define a runtime exception with the following code:


class ParamException(message: String?) : RuntimeException(message) {
 var code:Int = ResultEnums.SUCCESS.code

 constructor(code:Int, message: String?):this(message) {
  this.code = code
 }
}

Reference 1 returns the structure definition


class ResultVo<T> {

 var status:Int = ResultEnums.SUCCESS.code

 var msg:String = ""

 var data:T? = null

 constructor()

 constructor(status:Int, msg:String, data:T) {
  this.status = status
  this.data = data
  this.msg = msg
 }

 override fun toString(): String {
  return ObjectMapper().writeValueAsString(this)
 }
}

Global exception handling

Global exception handling here refers to exception handling after the request arrives at Controller layer. The code is as follows:


@RestControllerAdvice
class RestExceptionHandler {

 private val logger:Logger = LoggerFactory.getLogger(this.javaClass)

 @ExceptionHandler(Exception::class)
 @ResponseBody
 fun handler(exception: Exception): ResultVo<String> {
  logger.error(" Global exception :{}", exception)
  return ResultVo(500, " System anomaly ", "")
 }

 @ExceptionHandler(ParamException::class)
 @ResponseBody
 fun handler(exception: ParamException): ResultVo<String> {
  logger.error(" Parameter exception :{}", exception.localizedMessage)
  return ResultVo(exception.code, exception.localizedMessage, "")
 }

}

Here and Java processing is similar, is undoubtedly more concise.

Write verification tools


object ValidatorUtils {

 private val validator = Validation.buildDefaultValidatorFactory().validator

 /**
  *  Verify object attributes 
  * @param obj  Verified object 
  * @param <T>  Generic type 
  * @return Map
 </T> */
 fun validate(obj: Any): Map<String, String> {
  var errorMap: Map<String, String>? = null
  val set = validator.validate(obj, Default::class.java)
  if (CollectionUtils.isEmpty(set)) {
   return emptyMap()
  }
  errorMap = set.map { it.propertyPath.toString() to it.message }.toMap()
  return errorMap
 }

 /**
  *  Verify object attributes 
  * @param obj  Verified object 
  * @param <T>  Generic type 
  * @return List
 </T> */
 fun validata(obj: Any): List<String> {
  val set = validator.validate(obj, Default::class.java)
  return if (CollectionUtils.isEmpty(set)) {
   emptyList()
  } else set.stream()
    .filter {Objects.nonNull(it)}
    .map { it.message }
    .toList()
 }
}

Abstract verification method

Because validation is universal, almost most interfaces need to verify the incoming parameters, so we extract the validation method and put it in the general Controller layer. The general layer does not recommend using Class or abstract classes here, but uses interface, which is defined as follows:


@Throws(ParamException::class)
fun validate(t:Any) {
 val errorMap = ValidatorUtils.validate(t).toMutableMap()
 if (errorMap.isNotEmpty()) {
  throw ParamException(ResultEnums.SYSTEM_ERROR.code, errorMap.toString())
 }
}

Here, if there is a parameter error, the parameter exception is thrown directly, and then handed over to the global exception handler to catch it.

Controller Layer Writing


@PostMapping("/student")
fun create(@RequestBody studentForm: StudentForm): ResultVo<StudentDTO> {
 this.validate(studentForm)
 val studentDTO = StudentDTO()
 BeanUtils.copyProperties(studentForm, studentDTO)
 return ResultVo(200, "", studentDTO)
}

1. Pass in 1 empty object: Return result:


{
 "status": 500,
 "msg": "{school= School cannot be empty , id=Id Cannot be empty , age= Age cannot be empty , Interests= Hobbies cannot be empty }",
 "data": ""
}

Custom validation rules

Before the beginning of this article, we mentioned @ Pattern. This annotation is mainly convenient for us to define our own verification rules. If I need to verify whether the birthday passed in from the front end meets the format I need, as follows:


@NotBlank(message = " Birthday cannot be empty ")
@Pattern(regexp="^(19|20)\\d{2}-(1[0-2]|0?[1-9])-(0?[1-9]|[1-2][0-9]|3[0-1])$", message=" Not in birthday format ")
var birthday: String = ""

The verification logic here may not be perfect, so we should pay attention when using it.

I request again after the modification is completed

Request sample

Null value


enum class ResultEnums(var code:Int, var msg:String) {
 SUCCESS(200, " Success "),
 SYSTEM_ERROR(500, " The system is busy, please try again later "),

}

0

Error parameter


enum class ResultEnums(var code:Int, var msg:String) {
 SUCCESS(200, " Success "),
 SYSTEM_ERROR(500, " The system is busy, please try again later "),

}

1

Correct example


enum class ResultEnums(var code:Int, var msg:String) {
 SUCCESS(200, " Success "),
 SYSTEM_ERROR(500, " The system is busy, please try again later "),

}

2

Related articles: