SpringBoot 2.X Kotlin系列之数据校验和异常处理

栏目: Java · 发布时间: 5年前

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

SpringBoot 2.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<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 : 校验 ListMapSet 是否为空
  • @Email : 校验是否为邮箱格式
  • @Max @Min : 校验 NumberString 是否在指定范围内
  • @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": "学校"
    }
}

本章内容就到此结束了,如果错误的地方欢迎大家及时指出,觉得有用的话就点个赞,谢谢❤


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

微商思维

微商思维

龚文祥、罗剑锋、触电会 / 金城出版社 / 2018-7 / 88.00元

微商不仅仅是一种继传统实体、电商之后的革命性新兴商业形态,更是一种能够写入中国商业史的思潮。龚文祥新著《微商思维》,从道的层面对广大微商人的商业实践智慧进行了高度浓缩与抽象总结,站在更高的视角解读微商背后的商业逻辑与本质。 本书前半部分,主要从本质、品牌、营销等几个方面,阐述了微商思维的内涵及应用场景,帮助读者了解并认识这种革命性的商业思维。 后半部分主要是触电会社群内部各位大咖的实操......一起来看看 《微商思维》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

SHA 加密
SHA 加密

SHA 加密工具