SpringBoot2.X Kotlin系列之数据校验和异常处理详解
在开发项目时,我们经常需要在前后端都校验用户提交的数据,判断提交的数据是否符合我们的标准,包括字符串长度,是否为数字,或者是否为手机号码等;这样做的目的主要是为了减少SQL注入攻击的风险以及脏数据的插入。提到数据校验我们通常还会提到异常处理,因为为了安全起见,后端出现的异常我们通常不希望直接抛到客户端,而是经过我们的处理之后再返回给客户端,这样做主要是提升系统安全性,另外就是给予用户友好的提示。
定义实体并加上校验注解
classStudentForm(){ @NotBank(message='生日不能为空') varbirthday:String="" @NotBlank(message="Id不能为空") varid:String="" @NotBlank(message="年龄不能为空") varage:String="" @NotEmpty(message="兴趣爱好不能为空") varInterests:List=Collections.emptyList() @NotBlank(message="学校不能为空") varschool:String="" overridefuntoString():String{ returnObjectMapper().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:配合自定义正则表达式校验
定义返回状态枚举
enumclassResultEnums(varcode:Int,varmsg:String){ SUCCESS(200,"成功"), SYSTEM_ERROR(500,"系统繁忙,请稍后再试"), }
自定义异常
这里主要是参数校验,所以定义一个运行时异常,代码如下:
classParamException(message:String?):RuntimeException(message){ varcode:Int=ResultEnums.SUCCESS.code constructor(code:Int,message:String?):this(message){ this.code=code } }
统一返回结构体定义
classResultVo{ varstatus:Int=ResultEnums.SUCCESS.code varmsg:String="" vardata:T?=null constructor() constructor(status:Int,msg:String,data:T){ this.status=status this.data=data this.msg=msg } overridefuntoString():String{ returnObjectMapper().writeValueAsString(this) } }
全局异常处理
这里的全局异常处理,是指请求到达Controller层之后发生异常处理。代码如下:
@RestControllerAdvice classRestExceptionHandler{ privatevallogger:Logger=LoggerFactory.getLogger(this.javaClass) @ExceptionHandler(Exception::class) @ResponseBody funhandler(exception:Exception):ResultVo{ logger.error("全局异常:{}",exception) returnResultVo(500,"系统异常","") } @ExceptionHandler(ParamException::class) @ResponseBody funhandler(exception:ParamException):ResultVo { logger.error("参数异常:{}",exception.localizedMessage) returnResultVo(exception.code,exception.localizedMessage,"") } }
这里得和Java处理的方式大同小异,无疑就是更加简洁了而已。
编写校验工具
objectValidatorUtils{ privatevalvalidator=Validation.buildDefaultValidatorFactory().validator /** *校验对象属性 *@paramobj被校验对象 *@param泛型 *@returnMap */ funvalidate(obj:Any):Map{ varerrorMap:Map ?=null valset=validator.validate(obj,Default::class.java) if(CollectionUtils.isEmpty(set)){ returnemptyMap() } errorMap=set.map{it.propertyPath.toString()toit.message}.toMap() returnerrorMap } /** *校验对象属性 *@paramobj被校验对象 *@param 泛型 *@returnList */ funvalidata(obj:Any):List{ valset=validator.validate(obj,Default::class.java) returnif(CollectionUtils.isEmpty(set)){ emptyList() }elseset.stream() .filter{Objects.nonNull(it)} .map{it.message} .toList() } }
抽象校验方法
因为校验是通用的,几乎大部分接口都需要检验传入参数,所以我们把校验方法抽出来放在通用Controller层里,通用层这里不建议使用Class或者是抽象类,而是使用interface,定义如下:
@Throws(ParamException::class) funvalidate(t:Any){ valerrorMap=ValidatorUtils.validate(t).toMutableMap() if(errorMap.isNotEmpty()){ throwParamException(ResultEnums.SYSTEM_ERROR.code,errorMap.toString()) } }
这里如果有参数错误就直接抛出参数异常,然后交给全局异常处理器来捕获。
Controller层编写
@PostMapping("/student") funcreate(@RequestBodystudentForm:StudentForm):ResultVo{ this.validate(studentForm) valstudentDTO=StudentDTO() BeanUtils.copyProperties(studentForm,studentDTO) returnResultVo(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="不是生日格式") varbirthday: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":"10", "school":"学校" } }
本章内容就到此结束了,希望对大家的学习有所帮助,也希望大家多多支持毛票票。