草庐IT

springboot参数校验

一只小T 2023-04-04 原文

1.前言

在项目里面,我们需要对前端传入的参数做一个简单的简单的校验,避免出现脏数据和业务逻辑错误。如果每个接口单独写校验逻辑的话,我们需要在controller层做逻辑判断。参数较少时,还勉强能够接受,如果参数和接口较多,无形中加重了工作量,也多了很多重复代码。所以引入注解式参数校验很有必要。

2.引入方式

2.1引入jar包

本文是基于springboot来实现参数校验,引入方式很简单,在pom中引入spring-boot-starter-validation即可。

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

2.2全局异常捕获

一般情况下,我们使用参数校验都需要返回异常信息,搭配全局异常捕获食用效果最佳。这里我就不介绍异常捕获的工作原理了,简单贴一下代码以供参考。先不需要考虑这的捕获器1、2、3、4是干什么的,下面会介绍作用。

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
	//捕获器1
    @ExceptionHandler(value = {MissingServletRequestParameterException.class})
    public ResponseVO<String> handleMissingServletRequestParameterException(MissingServletRequestParameterException ex) {
        if (log.isErrorEnabled()) {
            log.error(ex.getMessage(), ex);
        }
        return ResponseVO.error(ResponseConstant.ERROR_CODE, String.format("缺少必要参数[%s]", ex.getParameterName()), "");
    }
    //捕获器2
    @ExceptionHandler(value = {MethodArgumentNotValidException.class, BindException.class})
    public ResponseVO<String> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
        if (log.isErrorEnabled()) {
            log.error(ex.getMessage(), ex);
        }
        BindingResult result = ex.getBindingResult();
        FieldError error = result.getFieldError();
        return ResponseVO.error(ResponseConstant.ERROR_CODE, null == error ? ResponseConstant.ERROR_MESSAGE : error.getDefaultMessage(), "");
    }
    //捕获器3
    @ExceptionHandler(value = {BindException.class})
    public ResponseVO<String> handleBindException(BindException ex) {
        if (log.isErrorEnabled()) {
            log.error(ex.getMessage(), ex);
        }
        BindingResult result = ex.getBindingResult();
        FieldError error = result.getFieldError();
        return ResponseVO.error(ResponseConstant.ERROR_CODE, null == error ? ResponseConstant.ERROR_MESSAGE : error.getDefaultMessage(), "");
    }
    //捕获器4
    @ExceptionHandler(value = {ConstraintViolationException.class})
    public ResponseVO<String> handleConstraintViolationException(ConstraintViolationException ex) {
        if (log.isErrorEnabled()) {
            log.error(ex.getMessage(), ex);
        }
        Optional<ConstraintViolation<?>> first = ex.getConstraintViolations().stream().findFirst();
        return ResponseVO.error(ResponseConstant.ERROR_CODE, first.isPresent() ? first.get().getMessage() : ResponseConstant.ERROR_MESSAGE, "");
    }
    //其他所有异常捕获器
    @ExceptionHandler(Exception.class)
    public ResponseVO<String> otherErrorDispose(Exception e) {
        // 打印错误日志
        log.error("错误代码({}),错误信息({})", ResponseConstant.ERROR_CODE, e.getMessage());
        e.printStackTrace();
        return ResponseVO.error(ResponseConstant.ERROR_CODE, ResponseConstant.ERROR_MESSAGE, e.getMessage());
    }
}

自定义异常返回和自定义常量

//自定义接口响应类
@Data
public class ResponseVO<T> implements Serializable {
    // 状态码: 0-成功,其他-失败
    private final Integer code;
    // 返回信息
    private final String message;
    //返回值
    private final T data;
    //是否成功
    private final Boolean success;
    // 成功返回
    public static <T> ResponseVO<T> success(T data) {
        return new ResponseVO<>(data);
    }
    // 失败返回
    public static <T> ResponseVO<T> error(Integer code, String message, T data) {
        return new ResponseVO<>(code, message, data);
    }
    public ResponseVO(T data) {
        this.code = ResponseConstant.SUCCESS_CODE;
        this.message = ResponseConstant.OK;
        this.data = data;
        this.success = true;
    }
    public ResponseVO(Integer code, String message, T data) {
        this.code = code;
        this.message = message;
        this.data = data;
        this.success = code == ResponseConstant.SUCCESS_CODE;
    }
}
//自定义常量类
public class ResponseConstant {
    public static final String OK = "OK";
    public static final String ERROR = "error";
    public static final int SUCCESS_CODE = 200;
    public static final int ERROR_CODE = 500;
    public static final String ERROR_MESSAGE = "操作失败!!";
}

3.可能会遇到的问题

这里我就不介绍使用方式了,网上有很多详细的案例,包括每个注解的作用介绍,分组校验和自定义校验的使用方法(话说我自己都没有用过,只用过简单的注解)。

3.1注解不生效

注解不生效的情况有很多,主要参考的解决思路:jar包冲突、加错注解、少了关键注解。
(1)jar包冲突可能是引入的时候引入了多个版本的jar包,注意检查pom,在springboot中只需要安装上述方式引入即可,无需再引入其他validator相关jar包。
(2)加错注解,主要是看你引入的注解是不是在下述这个路径下的。

(3)少了关键注解

//@Validated
@RestController
@RequestMapping("test")
public class TestController {
    @GetMapping("test1")
    public ResponseVO<Integer> test2(@NotNull(message = "最小值不能为空") Integer minNum,
                                     @NotNull(message = "最大值不能为空") @Min(value = 10,message = "参数必须大于10") Integer maxNum) {
        return ResponseVO.success(11);
    }
}

如上明明写了@NotNull,也确认了引入的注解是对的,但是就是不返回错误信息。像这种参数没有放在一个对象中,而是直接写在接口上的情况,需要在类上加@Validated注解,否则不会生效。

@Validated
@RestController
@RequestMapping("test")
public class TestController {
	@GetMapping("test3")
    public ResponseVO<Integer> test3(BlacklistPageParamVO vo) {
        return ResponseVO.success(11);
    }
    @PostMapping("test4")
    public ResponseVO<Integer> test4(@Valid @RequestBody BlacklistPageParamVO vo) {
        return ResponseVO.success(11);
    }
}
@Data
public class BlacklistPageParamVO{
    @NotBlank(message = "日期不能为空")
    private String date;
    @NotBlank(message = "日期2不能为空")
    private String date2;
    @Valid
    @NotNull(message = "内部对象不能为空")
    private InnerVO innerVO;
}
@Data
public class InnerVO {
    @NotNull(message = "num1不能为空")
    private Integer num1;
    @NotNull(message = "num2不能为空")
    private Integer num2;
}

第二种情况就是,我的校验字段在一个对象里面,这个时候需要在对象前面加上@Valid注解,所以test3的检验不会生效。这里扩展一下如果一个对象中还有另一个对象,且内部的对象也有需要检验的字段,需要给这个内部对象也加上@Valid注解才会生效。

3.2注解生效但返回的不是自定义信息

@Validated
@RestController
@RequestMapping("test")
public class TestController {
    @GetMapping("test1")
    public ResponseVO<Integer> test1(@NotNull(message = "最小值不能为空") @RequestParam Integer minNum,
                                     @NotNull(message = "最大值不能为空") @RequestParam Integer maxNum) {
        return ResponseVO.success(11);
    }
}

像上面这种情况如果在参数前加了@RequestParam表示参数必传,可以理解为作用和@NotNull是一样的。此时调用接口时,忘记传参数minNum了,我期待返回的是“最小值不能为空”,但是实际上返回的是“Required Integer parameter ‘minNum’ is not present”,且控制台打印的错误日志如下:

从错误名称,我们就可以看出,这是由于加了@RequestParam注解导致,让你传,你不传,所以报了这个错。这个错误对应的就是我们上文中的捕获器1。而且由此可以看出MissingServletRequestParameterException异常是比validation的异常优先级高的。

4.几个异常捕获器的作用

4.1捕获器1

MissingServletRequestParameterException
加了@RequestParam注解,但是接口调用时没有传指定的参数(注意:是没有传,而不是传了,但是值是null)。

4.2捕获器2

MethodArgumentNotValidException
经过测试,当校验的参数放在对象中,接口的请求方式是post请求,用@Valid @RequestBody方式接受参数时,如果报错,会被该捕获器捕获。

4.3捕获器3

BindException
经过测试,当校验参数写在类中,接口请求方式是get请求时,报错会被该捕获器捕获。

4.4捕获器4

ConstraintViolationException
传了值,但是不符合要求。@NotNull(message = “最大值不能为空”) @Min(value = 10,message = “参数必须大于10”),要求传非null值,且值必须大于10,否则会返回错误信息。经过测试,当校验参数直接写在接口上,而不是写在类中,报错会被该捕获器捕获。

4个捕获器对应4种不同的场景。

有关springboot参数校验的更多相关文章

  1. ruby-on-rails - 如何在 ruby​​ 中使用两个参数异步运行 exe? - 2

    exe应该在我打开页面时运行。异步进程需要运行。有什么方法可以在ruby​​中使用两个参数异步运行exe吗?我已经尝试过ruby​​命令-system()、exec()但它正在等待过程完成。我需要用参数启动exe,无需等待进程完成是否有任何ruby​​gems会支持我的问题? 最佳答案 您可以使用Process.spawn和Process.wait2:pid=Process.spawn'your.exe','--option'#Later...pid,status=Process.wait2pid您的程序将作为解释器的子进程执行。除

  2. ruby - RSpec - 使用测试替身作为 block 参数 - 2

    我有一些Ruby代码,如下所示:Something.createdo|x|x.foo=barend我想编写一个测试,它使用double代替block参数x,这样我就可以调用:x_double.should_receive(:foo).with("whatever").这可能吗? 最佳答案 specify'something'dox=doublex.should_receive(:foo=).with("whatever")Something.should_receive(:create).and_yield(x)#callthere

  3. ruby - 如何在 Ruby 中拆分参数字符串 Bash 样式? - 2

    我正在为一个项目制作一个简单的shell,我希望像在Bash中一样解析参数字符串。foobar"helloworld"fooz应该变成:["foo","bar","helloworld","fooz"]等等。到目前为止,我一直在使用CSV::parse_line,将列分隔符设置为""和.compact输出。问题是我现在必须选择是要支持单引号还是双引号。CSV不支持超过一个分隔符。Python有一个名为shlex的模块:>>>shlex.split("Test'helloworld'foo")['Test','helloworld','foo']>>>shlex.split('Test"

  4. ruby - 检查方法参数的类型 - 2

    我不确定传递给方法的对象的类型是否正确。我可能会将一个字符串传递给一个只能处理整数的函数。某种运行时保证怎么样?我看不到比以下更好的选择:defsomeFixNumMangler(input)raise"wrongtype:integerrequired"unlessinput.class==FixNumother_stuffend有更好的选择吗? 最佳答案 使用Kernel#Integer在使用之前转换输入的方法。当无法以任何合理的方式将输入转换为整数时,它将引发ArgumentError。defmy_method(number)

  5. ruby-on-rails - 在默认方法参数中使用 .reverse_merge 或 .merge - 2

    两者都可以defsetup(options={})options.reverse_merge:size=>25,:velocity=>10end和defsetup(options={}){:size=>25,:velocity=>10}.merge(options)end在方法的参数中分配默认值。问题是:哪个更好?您更愿意使用哪一个?在性能、代码可读性或其他方面有什么不同吗?编辑:我无意中添加了bang(!)...并不是要询问nobang方法与bang方法之间的区别 最佳答案 我倾向于使用reverse_merge方法:option

  6. ruby - 定义方法参数的条件 - 2

    我有一个只接受一个参数的方法:defmy_method(number)end如果使用number调用方法,我该如何引发错误??通常,我如何定义方法参数的条件?比如我想在调用的时候报错:my_method(1) 最佳答案 您可以添加guard在函数的开头,如果参数无效则引发异常。例如:defmy_method(number)failArgumentError,"Inputshouldbegreaterthanorequalto2"ifnumbereputse.messageend#=>Inputshouldbegreaterthano

  7. ruby - rails 3 redirect_to 将参数传递给命名路由 - 2

    我没有找到太多关于如何执行此操作的信息,尽管有很多关于如何使用像这样的redirect_to将参数传递给重定向的建议:action=>'something',:controller=>'something'在我的应用程序中,我在路由文件中有以下内容match'profile'=>'User#show'我的表演Action是这样的defshow@user=User.find(params[:user])@title=@user.first_nameend重定向发生在同一个用户Controller中,就像这样defregister@title="Registration"@user=Use

  8. ruby - 字符串文字中的转义状态作为 `String#tr` 的参数 - 2

    对于作为String#tr参数的单引号字符串文字中反斜杠的转义状态,我觉得有些神秘。你能解释一下下面三个例子之间的对比吗?我特别不明白第二个。为了避免复杂化,我在这里使用了'd',在双引号中转义时不会改变含义("\d"="d")。'\\'.tr('\\','x')#=>"x"'\\'.tr('\\d','x')#=>"\\"'\\'.tr('\\\d','x')#=>"x" 最佳答案 在tr中转义tr的第一个参数非常类似于正则表达式中的括号字符分组。您可以在表达式的开头使用^来否定匹配(替换任何不匹配的内容)并使用例如a-f来匹配一

  9. ruby-on-rails - 如何生成传递一些自定义参数的 `link_to` URL? - 2

    我正在使用RubyonRails3.0.9,我想生成一个传递一些自定义参数的link_toURL。也就是说,有一个articles_path(www.my_web_site_name.com/articles)我想生成如下内容:link_to'Samplelinktitle',...#HereIshouldimplementthecode#=>'http://www.my_web_site_name.com/articles?param1=value1¶m2=value2&...我如何编写link_to语句“alàRubyonRailsWay”以实现该目的?如果我想通过传递一些

  10. ruby - rbenv 安装 ruby​​ 校验和不匹配 osx - 2

    我已经在mountainlion上成功安装了rbenv和ruby​​build。运行rbenvinstall1.9.3-p392结束于:校验和不匹配:ruby-1.9.3-p392.tar.gz(文件已损坏)预期f689a7b61379f83cbbed3c7077d83859,得到1cfc2ff433dbe80f8ff1a9dba2fd5636它正在下载的文件看起来没问题,如果我使用curl手动下载文件,我会得到同样不正确的校验和。有没有人遇到过这个?他们是如何解决的? 最佳答案 tl:博士;使用浏览器从http://ftp.rub

随机推荐