通过Spring @Validated 更优雅的实现参数校验
背景:我们在实现接口逻辑的时候,严谨的做法是给每个请求对象的参数增加相关校验,比如:
- ID,是否必填(修改/删除)
- 手机号,格式+长度,是否必填
- 邮箱,格式+长度,是否必填
- 姓名,长度,是否必填
- 身份证,格式+长度,是否必填
比较直接的实现就是方法逻辑加上:
Assert.notNull(, "id不能为空");
Assert.hasText(phone, "手机号不能为空");
这样把参数校验和业务逻辑混在一起,不是很美观,那这部分代码可用独立出去吗?实际上spring
validation 已经帮我们实现了,那么我们要做的就4个步骤:
- endpoint 在@RequestBody 后面增加 @Validated
- feign-client 在@RequestBody 后面增加 @Validated
- 在具体的(create/update/delete)req对象增加具体的校验注解,@NotNull @Email @Length等等
- 全局异常处理,因为被校验拦截的请求会抛出400(MethodArgumentNotValidException),Bad Request,通过全局异常处理,我们可以把具体的异常信息,以更友好的方式返回给前端
@RestController
public class MyErrorController extends BasicErrorController {
public MyErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties) {
super(errorAttributes, errorProperties);
}
public MyErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties, List<ErrorViewResolver> errorViewResolvers) {
super(errorAttributes, errorProperties, errorViewResolvers);
}
private static final String METHOD_ARGUMENT_NOT_VALID_EXCEPTION = "org.springframework.web.bind.MethodArgumentNotValidException";
private static final String HTTP_MESSAGE_NOT_READABLE_EXCEPTION = "org.springframework.http.converter.HttpMessageNotReadableException";
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
WebRequest webRequest = new ServletWebRequest(request);
Throwable e = getError(webRequest);
HttpStatus httpStatus = getStatus(request);
if (httpStatus == HttpStatus.NO_CONTENT) {
return new ResponseEntity(httpStatus);
}
Map<String, Object> body = getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.ALL));
if (e == null) {
String exception = String.valueOf(body.get("exception"));
if (METHOD_ARGUMENT_NOT_VALID_EXCEPTION.equals(exception)) {
Object errors = body.get("errors");
if (errors instanceof List) {
List errorsArr = (List) errors;
if (errorsArr != null && !errorsArr.isEmpty()) {
FieldError fieldError = (FieldError) errorsArr.get(0);
String defaultMessage = fieldError.getDefaultMessage();
String field = fieldError.getField();
String code = fieldError.getCode();
if (StringUtils.isEmpty(defaultMessage)) {
defaultMessage = String.format("参数%s-%s校验不通过", field, code);
} else if ("NotNull".equals(code) && "不能为null".equals(defaultMessage)) {
defaultMessage = "参数" + field + "不能为空";
}
body.put("message", defaultMessage);
body.remove("error");
body.remove("errors");
return new ResponseEntity(body, httpStatus);
}
}
}
if (HTTP_MESSAGE_NOT_READABLE_EXCEPTION.equals(exception)) {
return new ResponseEntity(body, httpStatus);
}
String error = body.get("error").toString();
body.put("message", error);
body.remove("error");
return new ResponseEntity(body, httpStatus);
} else {
if (e instanceof NestedServletException) {
e = e.getCause();
}
}
if (e instanceof SimpleException) {
SimpleException simpleException = (SimpleException) e;
String message = e.getMessage();
if (StringUtils.isEmpty(message)) {
message = simpleException.getRespMessage();
}
HttpStatus status = HttpStatus.valueOf(simpleException.getStatus());
body.put("status", status.value());
body.put("error", status.name());
body.put("message", message);
return new ResponseEntity(body, status);
} else {
String message = e.getMessage();
if (StringUtils.isEmpty(message)) {
message = "系统繁忙,请稍后再试";
}
body.put("message", message);
return new ResponseEntity(body, httpStatus);
}
}
private Throwable getError(WebRequest webRequest) {
return (Throwable) this.getAttribute(webRequest, "javax.servlet.error.exception");
}
private Object getAttribute(RequestAttributes requestAttributes, String name) {
return requestAttributes.getAttribute(name, 0);
}
}
/** 呼叫中心用户 */
@lombok.Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class ChatUserCreateReq extends org.xt.maven.plugin.dto.EntityReq {
/**
* 邮箱
*/
@NotNull(message = "邮箱不能为空")
@Email
private java.lang.String email;
/**
* 手机号
*/
@NotNull
@Pattern(regexp = "")
private java.lang.String phoneNo;
/**
* 用户名
*/
@NotNull
@Length(max = 32)
private java.lang.String username;
/**
* 昵称
*/
private java.lang.String nickName;
/**
* 性别
*/
private java.lang.Integer gender;
/**
* 头像
*/
private java.lang.String avatar;
/**
* 状态
*/
private java.lang.Integer status;
/**
* 最近一次登录ip
*/
private java.lang.String lastLoginIp;
/**
* 最近一次登录时间
*/
private java.time.LocalDateTime lastLoginTime;
/**
* Default constructor.
*/
public ChatUserCreateReq() {
}
}
@EnableAutoConfiguration
@RestController
@RequestMapping("/api/chat/user")
@Api(description = "呼叫中心用户")
public class ChatUserEndpoint{
@javax.annotation.Resource
private ChatUserApiService chatUserApiService;
@ApiOperation("新增")
@RequestMapping(value = "/create",method = RequestMethod.POST)
public Response<ChatUserCreateResp> create(@RequestBody @Validated ChatUserCreateReq req, final HttpServletRequest request) throws SimpleException {
return chatUserApiService.create(req);
}
@ApiOperation(value = "查询")
@RequestMapping(value = "/get", method = RequestMethod.POST)
public Response<ChatUserGetResp> get(@RequestBody @Validated ChatUserGetReq req, final HttpServletRequest request) throws SimpleException {
return chatUserApiService.get(req);
}
@ApiOperation(value = "更新")
@RequestMapping(value = "/update",method = RequestMethod.POST)
public Response<ChatUserUpdateResp> update(@RequestBody @Validated ChatUserUpdateReq req, final HttpServletRequest request) throws SimpleException {
return chatUserApiService.update(req);
}
@ApiOperation(value = "删除")
@RequestMapping(value = "/delete", method = RequestMethod.POST)
public Response<ChatUserDeleteResp> delete(@RequestBody @Validated ChatUserDeleteReq req, final HttpServletRequest request) throws SimpleException {
return chatUserApiService.delete(req);
}
@ApiOperation(value = "分页查询")
@RequestMapping(value = "/find", method = RequestMethod.POST)
public Response<PageInfo<ChatUserFindResp>> find(@RequestBody ChatUserFindReq req, final HttpServletRequest request) throws SimpleException {
return chatUserApiService.find(req);
}
}
@org.springframework.cloud.openfeign.FeignClient(name = "${feign.chat.name}", url = "${feign.chat.url:}", contextId = "feignChatUserApiService", primary = false)
public interface ChatUserApiService {
/**
* 分页查询
*
* @param req
* @return
* @throws SimpleException
*/
@org.springframework.web.bind.annotation.PostMapping("/api/chat/user/find")
Response<com.github.pagehelper.PageInfo<ChatUserFindResp>> find(@RequestBody ChatUserFindReq req);
/**
* 获取对象
*
* @param req
* @return
* @throws SimpleException
*/
@org.springframework.web.bind.annotation.PostMapping("/api/chat/user/get")
Response<ChatUserGetResp> get(@RequestBody @Validated ChatUserGetReq req);
/**
* 创建对象
*
* @param req
* @return
* @throws SimpleException
*/
@org.springframework.web.bind.annotation.PostMapping("/api/chat/user/create")
Response<ChatUserCreateResp> create(@RequestBody @Validated ChatUserCreateReq req);
/**
* 删除对象
*
* @param req
* @return
* @throws SimpleException
*/
@org.springframework.web.bind.annotation.PostMapping("/api/chat/user/delete")
Response<ChatUserDeleteResp> delete(@RequestBody @Validated ChatUserDeleteReq req);
/**
* 更新对象
*
* @param req
* @return
* @throws SimpleException
*/
@org.springframework.web.bind.annotation.PostMapping("/api/chat/user/update")
Response<ChatUserUpdateResp> update(@RequestBody @Validated ChatUserUpdateReq req);
}