欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2023-12-09 17:50:51
在开发项目时,我们经常需要在前后端都校验用户提交的数据,判断提交的数据是否符合我们的标准,包括字符串长度,是否为数字,或者是否为手机号码等;这样做的目的主要是为了减少sql...

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

定义实体并加上校验注解

class studentform() {
 
 @notbank(message = '生日不能为空')
 var birthday: string = ""

 @notblank(message = "id不能为空")
 var id:string = ""

 @notblank(message = "年龄不能为空")
 var age:string = ""

 @notempty(message = "兴趣爱好不能为空")
 var interests:list<string> = 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<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)
 }
}

全局异常处理

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

@restcontrolleradvice
class restexceptionhandler {

 private val logger:logger = loggerfactory.getlogger(this.javaclass)

 @exceptionhandler(exception::class)
 @responsebody
 fun handler(exception: exception): resultvo<string> {
  logger.error("全局异常:{}", exception)
  return resultvo(500, "系统异常", "")
 }

 @exceptionhandler(paramexception::class)
 @responsebody
 fun handler(exception: paramexception): resultvo<string> {
  logger.error("参数异常:{}", exception.localizedmessage)
  return resultvo(exception.code, exception.localizedmessage, "")
 }

}

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

编写校验工具

object validatorutils {

 private val validator = validation.builddefaultvalidatorfactory().validator

 /**
  * 校验对象属性
  * @param obj 被校验对象
  * @param <t> 泛型
  * @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
 }

 /**
  * 校验对象属性
  * @param obj 被校验对象
  * @param <t> 泛型
  * @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()
 }
}

抽象校验方法

因为校验是通用的,几乎大部分接口都需要检验传入参数,所以我们把校验方法抽出来放在通用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<studentdto> {
 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": "10",
  "school": "学校"
 }
}

本章内容就到此结束了,希望对大家的学习有所帮助,也希望大家多多支持。