SpringBoot 2.X Kotlin系列之數據校驗和異常處理

image

在開發項目時,咱們常常須要在先後端都校驗用戶提交的數據,判斷提交的數據是否符合咱們的標準,包括字符串長度,是否爲數字,或者是否爲手機號碼等;這樣作的目的主要是爲了減小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包。前端

這裏簡單講解一下註解的常見用法:java

  • @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層以後發生異常處理。代碼以下:json

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

這裏若是有參數錯誤就直接拋出參數異常,而後交給全局異常處理器來捕獲。app

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,這個註解主要是方便咱們定義本身的校驗規則,假如我這裏須要校驗前端傳入的生日,是否符合我所須要的格式,以下所示:ide

@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": "學校"
    }
}

本章內容就到此結束了,若是錯誤的地方歡迎你們及時指出,以爲有用的話就點個贊,謝謝❤ui

相關文章
相關標籤/搜索