基本概要
接口数据校验基本上是每个系统必须做的事情,后端服务永远不要将数据校验寄托于前端页面的校验。因为很多时候当别人获取到身份凭证时,就可以通过其他工具调过前端界面进行接口请求,因此接口参数在后端必须再次进行一次校验。而像Spring这种框架提供了校验接口数据的功能,比如Spring Validator可以通过注解配置的方式,校验数据。如果未使用Spring Validator,那么很多校验基本都是在控制层Controller进行接口数据校验。当需要校验参数过多时,Controller层似乎就变成非常臃肿,因此接口数据的校验应该放到接口对象本身去处理。这并不涉及任何技术点,而是提供一种代码处理的思路
整体结构流程
当一个接口确定对外暴露时,就表明这个接口存在一定的风险点,除了需要对请求者身份的校验,更应该对接口参数进行校验。一般接口参数校验的范围包括一下几点:参数为空;参数为NULL;参数长度超长;参数格式不正确,比如手机号格式不对或者邮箱格式不对
。很多人希望在控制层Controller进行参数校验,因此导致Controller就因为一个参数校验导致代码是否臃肿和阅读困难。如果以下代码:
这还是参数较少的情况,如果一个接口需要校验的参数有几十个,那么Controller将变的更加臃肿。因此参数校验这一部分应该放到参数对象中来处理,也就是常说的解铃还须系铃人。
代码实现样例
更佳的处理方法就是将参数校验这一部分的功能移至参数对象本身,也叫做自检。这样的好处就是更小的侵入性以及复用性更高。案例中创建一个请求对象SaveUserRequestVO,用于接受外部传来的数据,并在SaveUserRequestVO中定义一个valid方法来进行自身数据校验。
import com.alibaba.fastjson.JSONObject;
import com.common.exception.BusinessException;
import com.common.exception.ErrorCode;
import lombok.Data;
import org.springframework.util.StringUtils;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
import java.io.Serializable;
import java.util.List;
import java.util.stream.Collectors;
/**
* @Author: Greyfus
* @Create: 2024-07-30 19:50
* @Version: 1.0.0
* @Description:保存用户请求参数VO
*/
@Data
public class SaveUserRequestVO implements Serializable {
//用户名称
private String userName;
//用户编码
private String userCode;
//手机
private String phone;
//年级
private Integer age;
//邮箱
private String email;
/**
* 自身参数校验
*
* @return Errors
*/
public void valid() {
Errors errors = new BeanPropertyBindingResult(this, this.getClass().getName());
if (StringUtils.isEmpty(userName)) {
errors.rejectValue("userName", "userName.required", "property userName is missing");
}
if (StringUtils.isEmpty(userCode)) {
errors.rejectValue("userCode", "userCode.required", "property userCode is missing");
}
if (StringUtils.isEmpty(phone)) {
errors.rejectValue("phone", "phone.required", "property phone is missing");
}
if (age == null) {
errors.rejectValue("age", "age.required", "property age is missing");
}
if (StringUtils.isEmpty(email)) {
errors.rejectValue("email", "email.required", "property email is missing");
}
if (errors.hasErrors()) {//判断参数是否存在错误
List<ValidatorError> validatorErrors = errors.getAllErrors().stream().map(error -> {
return new ValidatorError(((FieldError) error).getField(), error.getDefaultMessage());
}).collect(Collectors.toList());
throw new BusinessException(ErrorCode.INVALID_PARAMS.code(), JSONObject.toJSONString(validatorErrors));//抛出自检异常信息
}
}
}
ValidatorError用于收集参数校验的错误信息,当某个参数校验不过时,则会调用errors.rejectValue方法将错误信息注册到Errors对象,当校验参数完成,则通过判断Errors是否有注册的错误信息,存在错误信息则将信息存入ValidatorError,再通过ValidatorError把信息返回给客户端。
import lombok.Data;
/**
* @Author: Greyfus
* @Create: 2024-07-30 20:19
* @Version: 数据校验错误对象
* @Description: 1.0.0
*/
@Data
public class ValidatorError {
//字段名称
private String propertyName;
//错误信息
private String errorMsg;
public ValidatorError(String propertyName, String errorMsg) {
this.propertyName = propertyName;
this.errorMsg = errorMsg;
}
public ValidatorError() {
}
}
当参数校验逻辑放到参数对象自身后,对Controller层的代码侵入就会变的极小,业务代码也会变的更加清晰,这跟技术并无关联,而是一种解决方法的思路。在Controller层只需调用saveUserRequestVO.valid方法即可完成参数校验,并且可以被重复使用,也减小重复编码的工作量。
import com.common.exception.BusinessException;
import com.common.web.HttpUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* @Author: Greyfus
* @Create: 2024-07-30 19:49
* @Version:
* @Description:
*/
@RequestMapping("web/user")
@Controller
public class UserSaveController extends BaseController {
@RequestMapping(value = "/save", method = RequestMethod.POST, produces = {MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity<?> save(@RequestBody SaveUserRequestVO saveUserRequestVO) throws BusinessException {
saveUserRequestVO.valid();//参数自身校验
return new ResponseEntity<>(success(), HttpUtils.SUCCESS_HTTP_HEADERS, HttpStatus.OK);//响应成功信息
}
}