Spring Boot validator参数验证restful自定义错误码响应

Java框架

浏览数:72

2020-6-1

关于spring web应用中关于如何使用 Bean Validation API和hibernate-validator的文章已经很多,本文就不再重复叙述,今天要介绍的重点是在SpringBoot restful服务中如何根据不同验证错误响应不同的自定义错误码。下面直接上代码。

一、定义restful统一结果返回

阿里java开发手册中定义的一段参考【“对于公司外的 http/api 开放接口必须使用“错误码”; 而应用内部推荐异常抛出;跨应用间 RPC 调用优先考虑使用 Result 方式,封装 isSuccess()方法、 “错误码”、 “错误简短信息”。】。因此这里也定义个返回结构。

public class CommonResult<T> implements Serializable {

	/**
	 * serialVersionUID:.
	 */
	private static final long serialVersionUID = -7268040542410707954L;

	/**
	 * 是否成功
	 */
	private boolean success = false;

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

	/**
	 * 装在数据
	 */
	private T data;

	/**
	 * 错误代码
	 */
	private String code;

	/**
	 * 默认构造器
	 */
	public CommonResult() {

	}

	/**
	 * @param success 是否成功
	 * @param message 返回的消息
	 */
	public CommonResult(boolean success, String message) {
		this.success = success;
		this.message = message;
	}

	/**
	 * @param success 是否成功
	 */
	public CommonResult(boolean success) {
		this.success = success;
	}

	/**
	 * @param code    error code
	 * @param message success or error messages
	 */
	public CommonResult(String code, String message) {
		this.code = code;
		this.message = message;
	}

	/**
	 * @param success 是否成功
	 * @param message 消息
	 * @param data    数据
	 */
	public CommonResult(boolean success, String message, T data) {
		this.success = success;
		this.message = message;
		this.data = data;
	}
    //省略get set
}

二、定义一个错误码枚举

在有需要国际化的项目,当然选择通过i18n来配置更好,此处为了简单直接采用枚举定义。这里定义的错误仅供参考,不同公司每个应用在实际情况下可能都不大一样。

/**
 * 错误代码枚举类
 *
 */
public enum ErrorCodeEnum {

    SUCCESS("0000", "success"),

    PARAM_EMPTY("1001", "必选参数为空"),

    PARAM_ERROR("1002", "参数格式错误"),

    UNKNOWN_ERROR("9999", "系统繁忙,请稍后再试....");

    private String code;

    private String desc;

    ErrorCodeEnum(String code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    public String getCode() {
        return this.code;
    }


    public String getDesc() {
        return desc;
    }

     @Override
    public String toString() {
        return "ErrorCodeEnum{" +
                "code='" + code + '\'' +
                ", desc='" + desc + '\'' +
                '}';
    }
}

三、静态封装CommonResult

静态封装CommonResult主要是方便在项目中快速根据逻辑写返回结果代码。

/**
 * 公共响应结果成功失败的静态方法调用
 *
 */
public class ResultUtil {


    /**
     * return success
     *
     * @param data
     * @return
     */
    public static <T> CommonResult<T> returnSuccess(T data) {
        CommonResult<T> result = new CommonResult();
        result.setCode(ErrorCodeEnum.SUCCESS.getCode());
        result.setSuccess(true);
        result.setData(data);
        result.setMessage(ErrorCodeEnum.SUCCESS.getDesc());
        return result;
    }

    /**
     * return error
     *
     * @param code error code
     * @param msg  error message
     * @return
     */
    public static CommonResult returnError(String code, String msg) {
        CommonResult result = new CommonResult();
        result.setCode(code);
        result.setData("");
        result.setMessage(msg);
        return result;

    }

    /**
     * use enum
     *
     * @param status
     * @return
     */
    public static CommonResult returnError(ErrorCodeEnum status) {
        return returnError(status.getCode(), status.getDesc());
    }
}

四、定义BaseController来处理验证错误自定义错误码返回

/**
 * BaseController
 *
 */
public abstract class BaseController {

    private static final Logger LOGGER = LoggerFactory.getLogger(BaseController.class);

    /**
     * validate params
     *
     * @param bindingResult
     * @return
     */
    protected CommonResult validParams(BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            FieldError fieldError = bindingResult.getFieldError();
            return processBindingError(fieldError);
        }
        return ResultUtil.returnSuccess("");
    }

    /**
     * 根据spring binding 错误信息自定义返回错误码和错误信息
     *
     * @param fieldError
     * @return
     */
    private CommonResult processBindingError(FieldError fieldError) {
        String code = fieldError.getCode();
        LOGGER.debug("validator error code: {}", code);
        switch (code) {
            case "NotEmpty":
                return ResultUtil.returnError(ErrorCodeEnum.PARAM_EMPTY.getCode(), fieldError.getDefaultMessage());
            case "NotBlank":
                return ResultUtil.returnError(ErrorCodeEnum.PARAM_EMPTY.getCode(), fieldError.getDefaultMessage());
            case "NotNull":
                return ResultUtil.returnError(ErrorCodeEnum.PARAM_EMPTY.getCode(), fieldError.getDefaultMessage());
            case "Pattern":
                return ResultUtil.returnError(ErrorCodeEnum.PARAM_ERROR.getCode(), fieldError.getDefaultMessage());
            case "Min":
                return ResultUtil.returnError(ErrorCodeEnum.PARAM_ERROR.getCode(), fieldError.getDefaultMessage());
            case "Max":
                return ResultUtil.returnError(ErrorCodeEnum.PARAM_ERROR.getCode(), fieldError.getDefaultMessage());
            case "Length":
                return ResultUtil.returnError(ErrorCodeEnum.PARAM_ERROR.getCode(), fieldError.getDefaultMessage());
            case "Range":
                return ResultUtil.returnError(ErrorCodeEnum.PARAM_ERROR.getCode(), fieldError.getDefaultMessage());
            case "Email":
                return ResultUtil.returnError(ErrorCodeEnum.PARAM_ERROR.getCode(), fieldError.getDefaultMessage());
            case "DecimalMin":
                return ResultUtil.returnError(ErrorCodeEnum.PARAM_ERROR.getCode(), fieldError.getDefaultMessage());
            case "DecimalMax":
                return ResultUtil.returnError(ErrorCodeEnum.PARAM_ERROR.getCode(), fieldError.getDefaultMessage());
            case "Size":
                return ResultUtil.returnError(ErrorCodeEnum.PARAM_ERROR.getCode(), fieldError.getDefaultMessage());
            case "Digits":
                return ResultUtil.returnError(ErrorCodeEnum.PARAM_ERROR.getCode(), fieldError.getDefaultMessage());
            case "Past":
                return ResultUtil.returnError(ErrorCodeEnum.PARAM_ERROR.getCode(), fieldError.getDefaultMessage());
            case "Future":
                return ResultUtil.returnError(ErrorCodeEnum.PARAM_ERROR.getCode(), fieldError.getDefaultMessage());
            default:
                return ResultUtil.returnError(ErrorCodeEnum.UNKNOWN_ERROR);
        }
    }
}

五、验证实例

这里直接给出一个简单的参数验证例子。

Controller继承上面写的BaseController

/**
 * 关于Validator使用测试
 *
 */

@RestController
@RequestMapping("validator")
public class ValidatorTestController extends BaseController {

    private static final Logger LOGGER = LoggerFactory.getLogger(ValidatorTestController.class);

    /**
     * validate验证测试
     *
     * @param leader
     * @param bindingResult
     * @return
     */
    @PostMapping("/test")
    public CommonResult testSimpleValidate(@Valid @RequestBody Leader leader, BindingResult bindingResult) {
        LOGGER.debug("ReqParams:{}", JSON.toJSONString(leader));
        CommonResult result = validParams(bindingResult);
        if (!result.isSuccess()) {
            return result;
        }
        return ResultUtil.returnSuccess("");
    }
}

入参对象Leader代码

public class Leader {

    /**
     * 姓名
     */
    @NotEmpty
    private String name;

    /**
     * 生日
     */
    @Pattern(regexp = "^[0-9]{4}-[0-9]{2}-[0-9]{2}$", message = "出生日期格式不正确")
    private String birthday;

    /**
     * 年龄
     */
    @Min(value = 0)
    private Integer age;

    //省略gettes and  setters

}

这时项目已经已经完全可以根据验证错误来返回自定义的错误码和提示了。

本例所涉及源代码:https://github.com/shalousun/api-doc-test

总结

在一些对外服务提供restful的应用中,根据不同的验证错误返回其实是避免不了的。当然实现的方式可以有种,而本文所采用的方式相对来说简单易懂。

版权申明:转载请注明出处,否则后果自负

作者:上官胡闹