SpringBoot validator 完美实现+统一封装错误提示

63 篇文章 18 订阅

1、杜绝通篇 if else 参数判断!!!
2、普通参数方式
3、实体对象参数方式

效果
参数不合法,自动使用统一异常返回错误,例如:

{
    "code": "-3",
    "message": "参数{age}最小18"
}

实现代码
代码一次性给全,主要包含如下几个Java类,拿过去拷贝到项目中就能用:
1、ValidatorController
2、ValidatorVo
3、ExampleValidatorService
4、ResultCode
5、ResultResponseBodyAdvice
6、ResultVO_IGNORE
7、ResultVO


import javax.validation.constraints.Email;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import link.nuggets.user.module.example.model.ValidatorVo;
import link.nuggets.user.module.example.service.ExampleValidatorService;

/**
 * Validator示例
 * 
 * @author 单红宇
 * @date 2019年7月8日
 *
 */
@Validated // 注意!1) 如果想在参数中使用 @NotNull 这种注解校验,就必须在类上添加 @Validated;2) 如果方法中的参数是对象类型,则必须要在参数对象前面添加 @Validated
@RestController
@RequestMapping("/example/valid")
public class ValidatorController {
	
	@Autowired
	private ExampleValidatorService exampleValidatorService;

	/**
	 * 直接参数校验
	 * 要特别提醒的是,验证框架里面大部分都不需要我们显示设置message,每个注解框架都给了一个默认提示语,大多数提示还都比较友好
	 * 
	 * @param email
	 * @return
	 */
	@GetMapping("/test1")
	public String test1(@NotNull(message = "不能为空") @Size(max = 32, min = 6, message = "长度需要在6-32之间") @Email String email) {
		return "OK";
	}

	/**
	 * 实体类校验
	 * 
	 * @param validatorVo
	 * @return
	 */
	@GetMapping("/test2")
	public String test2(@Validated ValidatorVo validatorVo) {
		return "Validator OK";
	}

	/**
	 * 内部Service校验
	 * 
	 * @return
	 */
	@GetMapping("/test3")
	public String test3() {
		return exampleValidatorService.show("16");
	}

}


import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import lombok.Data;

/**
 * 验证示例VO
 * 
 * @author 单红宇
 * @date 2019年7月10日
 *
 */
@Data
public class ValidatorVo {

	@NotNull(message = "不能为空")
	@Size(max = 16, min = 6, message = "字符串长度需要在6-16之间")
	private String name;
	@Max(value = 100, message = "最大100")
	@Min(value = 18, message = "最小18")
	private String age;
}


import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

/**
 * 示例
 * 
 * @author 单红宇
 * @date 2019年7月10日
 *
 */
@Validated
@Service
public class ExampleValidatorService {

	private static final Logger logger = LoggerFactory.getLogger(ExampleValidatorService.class);

	public String show(@NotNull(message = "不能为空") @Min(value = 18, message = "最小18") String age) {
		logger.info("age = {}", age);
		return age;
	}

}


/**
 * 统一返回值
 * 
 * @author 单红宇
 * @date   2019年6月26日
 *
 */
public enum ResultCode {

	/** 操作成功 */
	SUCCESS("1", "成功"),

	/** 操作失败 */
	FAIL("0", "失败"),
	
	/** 操作失败 */
	NULL("-1", "数据不存在"),

	/** 系统发生异常 */
	EXCEPTION("-2", "系统异常"),
	
	/** 没有权限 */
	FORBIDDEN("9403", "没有权限"),
	
	/** 参数错误 */
	PARAM_INVALID("-3", "参数错误");
	
	private String code;
	private String msg;
	
	private ResultCode(String code, String msg) {
		this.code = code;
		this.msg = msg;
	}

	public String code() {
		return code;
	}

	public String msg() {
		return msg;
	}

}


import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;

import org.hibernate.validator.internal.engine.path.PathImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

/**
 * Controller 统一返回包装
 * 
 * @author 单红宇
 * @date 2019年6月26日
 *
 */
@ControllerAdvice
public class ResultResponseBodyAdvice implements ResponseBodyAdvice<Object> {

	private static final Logger logger = LoggerFactory.getLogger(ResultResponseBodyAdvice.class);

	@Override
	public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
			Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
			ServerHttpResponse response) {
		// 此处获取到request 为特殊需要的时候处理使用
//		HttpServletRequest req = ((ServletServerHttpRequest) serverHttpRequest).getServletRequest();
		// 下面处理统一返回结果(统一code、msg、sign 加密等)
		if (selectedConverterType == MappingJackson2HttpMessageConverter.class
				&& (selectedContentType.equals(MediaType.APPLICATION_JSON)
						|| selectedContentType.equals(MediaType.APPLICATION_JSON_UTF8))) {
			if (body == null) {
				return ResultVO.NULL;
			} else if (body instanceof ResultVO) {
				return body;
			} else {
				// 异常
				if (returnType.getExecutable().getDeclaringClass().isAssignableFrom(BasicErrorController.class)) {
					ResultVO vo = new ResultVO(ResultCode.EXCEPTION);
					HttpServletRequest req = ((ServletServerHttpRequest) request).getServletRequest();
					if (req.getRequestURL().toString().contains("localhost")
							|| req.getRequestURL().toString().contains("127.0.0.1"))
						vo.setData(body);
					return vo;
				} else {
					return new ResultVO(body);
				}
			}
		}
		return body;
	}

	@Override
	public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
		if (returnType.hasMethodAnnotation(ResultVO_IGNORE.class))
			return false;
		return true;
	}

	/**
	 * Validator 参数校验异常处理
	 * 
	 * @param ex
	 * @return
	 */
	@ExceptionHandler(value = BindException.class)
	public ResponseEntity<Object> handleMethodVoArgumentNotValidException(BindException ex) {
		FieldError err = ex.getFieldError();
		// err.getField() 读取参数字段
		// err.getDefaultMessage() 读取验证注解中的message值
		String message = "参数{".concat(err.getField()).concat("}").concat(err.getDefaultMessage());
		logger.info("{} -> {}", err.getObjectName(), message);
		return new ResponseEntity<Object>(new ResultVO(ResultCode.PARAM_INVALID, message), HttpStatus.OK);
	}

	/**
	 * Validator 参数校验异常处理
	 * 
	 * @param ex
	 * @return
	 */
	@ExceptionHandler(value = ConstraintViolationException.class)
	public ResponseEntity<Object> handleMethodArgumentNotValidException(ConstraintViolationException ex) {
		Set<ConstraintViolation<?>> constraintViolations = ex.getConstraintViolations();
		for (ConstraintViolation<?> constraintViolation : constraintViolations) {
			PathImpl pathImpl = (PathImpl) constraintViolation.getPropertyPath();
			// 读取参数字段,constraintViolation.getMessage() 读取验证注解中的message值
			String paramName = pathImpl.getLeafNode().getName();
			String message = "参数{".concat(paramName).concat("}").concat(constraintViolation.getMessage());
			logger.info("{} -> {} -> {}", constraintViolation.getRootBeanClass().getName(), pathImpl.toString(), message);
			return new ResponseEntity<Object>(new ResultVO(ResultCode.PARAM_INVALID, message), HttpStatus.OK);
		}
		return new ResponseEntity<Object>(new ResultVO(ResultCode.PARAM_INVALID, ex.getMessage()), HttpStatus.OK);
	}

}


import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Controller默认进行ResultVO包装,对于特殊不需要的,使用该注解可以忽略包装
 * 
 * @author 单红宇
 * @date 2019年6月26日
 *
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResultVO_IGNORE {

}


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.Gson;

/**
 * Controller 统一定义返回类
 * 
 * @author 单红宇
 * @date 2019年6月26日
 *
 */
public class ResultVO {

	private static final Logger logger = LoggerFactory.getLogger(ResultVO.class);

	public static final ResultVO SUCCESS = new ResultVO(ResultCode.SUCCESS);
	public static final ResultVO FAIL = new ResultVO(ResultCode.FAIL);
	public static final ResultVO FORBIDDEN = new ResultVO(ResultCode.FORBIDDEN);
	public static final ResultVO NULL = new ResultVO(ResultCode.NULL);
	public static final ResultVO EXCEPTION = new ResultVO(ResultCode.EXCEPTION);
	public static final ResultVO PARAM_INVALID = new ResultVO(ResultCode.PARAM_INVALID);

	/**
	 * 返回代码
	 */
	private String code;

	/**
	 * 返回信息
	 */
	private String message;

	/**
	 * 返回数据
	 */
	private Object data;

	public Object getData() {
		return data;
	}

	public void setData(Object data) {
		this.data = data;
	}

	/**
	 * 默认构造,返回操作正确的返回代码和信息
	 */
	public ResultVO() {
		this.setCode(ResultCode.SUCCESS.code());
		this.setMessage(ResultCode.SUCCESS.msg());
	}

	/**
	 * 构造一个返回特定代码的ResultVO对象
	 * 
	 * @param code
	 */
	public ResultVO(ResultCode code) {
		this.setCode(code.code());
		this.setMessage(code.msg());
	}

	public ResultVO(String code, String message) {
		super();
		this.setCode(code);
		this.setMessage(message);
	}

	/**
	 * 默认值返回,默认返回正确的code和message
	 * 
	 * @param data
	 */
	public ResultVO(Object data) {
		ResultCode rc = data == null ? ResultCode.NULL : ResultCode.SUCCESS;
		this.setCode(rc.code());
		this.setMessage(rc.msg());
		this.setData(data);
	}

	/**
	 * 构造返回代码,以及自定义的错误信息
	 * 
	 * @param code
	 * @param message
	 */
	public ResultVO(ResultCode code, String message) {
		this.setCode(code.code());
		this.setMessage(message);
	}

	/**
	 * 构造自定义的code,message,以及data
	 * 
	 * @param code
	 * @param message
	 * @param data
	 */
	public ResultVO(ResultCode code, String message, Object data) {
		this.setCode(code.code());
		this.setMessage(message);
		this.setData(data);
	}

	public String getMessage() {
		return message;
	}

	public void setMessage(String message) {
		this.message = message;
	}

	public String getCode() {
		// request请求响应的时候,一定会走到这里,判断如果code不是成功状态,就输出日志
		if (!ResultCode.SUCCESS.code().equals(code))
			logger.info("ResultVO={}", new Gson().toJson(this));
		return code;
	}

	public void setCode(String code) {
		this.code = code;
	}

}

一个不需要强调的提示,springboot 的web项目正常都是已经包含如下依赖了,该依赖里已经包含了validation-api

	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>

validation-api 的注解清单

注释描述
@AssertFalse被注释的元素必须为 false
@AssertTrue同@AssertFalse
@DecimalMax被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin同DecimalMax
@Digits带批注的元素必须是一个在可接受范围内的数字
@Email顾名思义
@Future将来的日期
@FutureOrPresent现在或将来
@Max被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Min被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Negative带注释的元素必须是一个严格的负数(0为无效值)
@NegativeOrZero带注释的元素必须是一个严格的负数(包含0)
@NotBlank同StringUtils.isNotBlank
@NotEmpty同StringUtils.isNotEmpty
@NotNull不能是Null
@Null元素是Null
@Past被注释的元素必须是一个过去的日期
@PastOrPresent过去和现在
@Pattern被注释的元素必须符合指定的正则表达式
@Positive被注释的元素必须严格的正数(0为无效值)
@PositiveOrZero被注释的元素必须严格的正数(包含0)
@Szie带注释的元素大小必须介于指定边界(包括)之间

(END)

  • 8
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

catoop

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值