在開發項目時,咱們常常須要在先後端都校驗用戶提交的數據,判斷提交的數據是否符合咱們的標準,包括字符串長度,是否爲數字,或者是否爲手機號碼等;這樣作的目的主要是爲了減小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
: 校驗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層以後發生異常處理。代碼以下: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
@PostMapping("/student") fun create(@RequestBody studentForm: StudentForm): ResultVo<StudentDTO> { this.validate(studentForm) val studentDTO = StudentDTO() BeanUtils.copyProperties(studentForm, studentDTO) return ResultVo(200, "", studentDTO) }
{ "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