SpringBoot2.X Kotlin系列之数据校验和异常处理详解

网友投稿 326 2023-01-07


SpringBoot2.X Kotlin系列之数据校验和异常处理详解

在开发项目时,我们经常需要在前后端都校验用户提交的数据,判断提交的数据是否符合我们的标准,包括字符串长度,是否为数字,或者是否为手机号码等;这样做的目的主要是为了减少SQL注入攻击的风险以及脏数据的插入。提到数据校验我们通常还会提到异常处理,因为为了安全起见,后端出现的异常我们通常不希望直接抛到客户端,而是经过我们的处理之后再返回给客户端,这样做主要是提升系统安全性,另外就是给予用户友好的提示。

定义实体并加上校验注解

class StudentForm() {

@NotBank(message = '生日不能为空')

var birthday: String = ""

@NotBlank(message = "Id不能为空")

var id:String = ""

@NotBlank(message = "年龄不能为空")

var age:String = ""

@NotEmpty(message = "兴趣爱好不能为空")

var Interests:List = Collections.emptyList()

@NotBlank(message = "学校不能为空")

var school: String = ""

override fun toString(): String {

return ObjectMapper().writeValueAsString(this)

}

}

这里首先使用的是基础校验注解,位于javax.validation.constraints下,常见注解有@NotNull、@NotEmpty、@Max、@Email、@NotBank、@Size、@Pattern,当然出了这些还有很多注解,这里就不在一一讲解,想了解更多的可以咨询查看jar包。

这里简单讲解一下注解的常见用法:

@NotNull: 校验一个对象是否为Null

@NotBank: 校验字符串是否为空串

@NotEmpty: 校验List、Map、Set是否为空

@Email: 校验是否为邮箱格式

@Max @Min: 校验Number或String是否在指定范围内

@Size: 通常需要配合@Max @Min一期使用

@Pattern: 配合自定义正则表达式校验

定义返回状态枚举

enum class ResultEnums(var code:Int, var msg:String) {

SUCCESS(200, "成功"),

SYSTEM_ERROR(500, "系统繁忙,请稍后再试"),

}

自定义异常

这里主要是参数校验,所以定义一个运行时异常,代码如下:

class ParamException(message: String?) : RuntimeException(message) {

var code:Int = ResultEnums.SUCCESS.code

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

this.code = code

}

}

统一返回结构体定义

class ResultVo {

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)

}

}

全局异常处理

这里的全局异常处理,是指请求到达Controller层之后发生异常处理。代码如下:

@RestControllerAdvice

class RestExceptionHandler {

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

@ExceptionHandler(Exception::class)

@ResponseBody

fun handler(exception: Exception): ResultVo {

logger.error("全局异常:{}", exception)

return ResultVo(500, "系统异常", "")

}

@ExceptionHandler(ParamException::class)

@ResponseBody

fun handler(exception: ParamException): ResultVo {

logger.error("参数异常:{}", exception.localizedMessage)

return ResultVo(exception.code, exception.localizedMessage, "")

}

}

这里得和Java处理的方式大同小异,无疑就是更加简洁了而已。

编写校验工具

object ValidatorUtils {

private val validator = Validation.buildDefaultValidatorFactory().validator

/**

* 校验对象属性

* @param obj 被校验对象

* @param 泛型

* @return Map

*/

fun validate(obj: Any): Map {

var errorMap: Map? = 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

}

/**

* 校验对象属性

* @param obj 被校验对象

* @param 泛型

* @return List

*/

fun validata(obj: Any): List {

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()

}

}

抽象校验方法

因为校验是通用的,几乎大部分接口都需要检验传入参数,所以我们把校验方法抽出来放在通用Controller层里,通用层这里不建议使用Class或者是抽象类,而是使用interface,定义如下:

@Throws(ParamException::class)

fun validate(t:Any) {

val errorMap = ValidatorUtils.validate(t).toMutableMap()

if (errorMap.isNotEmpty()) {

throw ParamException(ResultEnums.SYSTEM_ERROR.code, errorMap.toString())

}

}

这里如果有参数错误就直接抛出参数异常,然后交给全局异常处理器来捕获。

Controller层编写

@PostMapping("/student")

fun create(@RequestBody studentForm: StudentForm): ResultVo {

this.validate(studentForm)

val studentDTO = StudentDTO()

BeanUtils.copyProperties(studentForm, studentDTO)

return ResultVo(200, "", studentDTO)

}

1.传入一个空对象: 返回结果:

{

"status": 500,

"msg": "{school=学校不能为空, id=Id不能为空, age=年龄不能为空, Interests=兴趣爱好不能为空}",

"data": ""

}

自定义校验规则

本篇文章开始之前我们提到过@Pattern,这个注解主要是方便我们定义自己的校验规则,假如我这里需要校验前端传入的生日,是否符合我所需要的格式,如下所示:

@NotBlank(message = "生日不能为空")

@Pattern(regexp="^(19|20)\\d{2}-(1[0-2]|0?[1-9])-(0?[1-9]|[1-2][0-9]|3[0-1])$", message="不是生日格式")

var birthday: String = ""

这里的校验逻辑可能不完善,大家使用的时候需要注意。

修改完成后我再次请求

请求示例

空值

入参:

{

"age": "10",

"id": "1",

"school": "学校",

"interests": ["户外运动"],

"birthday": ""

}

出参:

{

"status": 500,

"msg": "{birthday=生日不能为空}",

"data": ""

}

错误参数

入参:

{

"age": "10",

"id": "1",

"school": "学校",

"interests": ["户外运动"],

"birthday": "1989-20-20"

}

出参:

{

"status": 500,

"msg": "{birthday=不是生日格式}",

"data": ""

}

正确示例

入参:

{

"age": "10",

"id": "1",

"school": "学校",

"interests": ["户外运动"],

"birthday": "1999-01-01"

}

出参:

{

"status": 200,

"msg": "",

"data": {

"id": "1",

"birthday": "1999-01-01",

"age": "http://10",

"school": "学校"

}

}


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:介绍接口自动化框架的搭建(接口自动化平台方案)
下一篇:java 是否实现接口(java接口怎么实现)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~