天天看点

在SpringMVC框架中统一处理异常及请求参数验证(5)

9. 请求参数验证

对于服务器端的开发而言,所有由客户端提交的请求参数都应该将其视为是不可靠的,例如“用户名”可能是1个字母,或其它基本格式不正确(长度、组成字符)的问题,即使客户端本身就存在检查的机制也是不可靠的,毕竟客户端存在被篡改的可能性,或者非浏览器的客户端也可能存在用户使用的版本没有更新而导致请求参数格式有误的问题!所以,服务器端在接收到请求参数的第一时间就应该检查这些参数的有效性!

注意:即使服务器端进行了所有参数的检查,客户端的检查也是必须存在的!主要是将绝大部分错误的请求拦截下来,以减少服务器端的压力!

实现服务器端检查时,可以使用hibernate-validation来实现,目前,它已经被整合到spring-boot-starter-validation了,所以,先在项目中添加该依赖:

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

关于这个验证机制,其做法是针对某个对象的属性进行验证,在需要验证的属性之前可以添加一些注解表示验证规则,常用的注解有:

@NotNull:不允许没有值,即不允许是null;

@NotEmpty:不允许为空字符串值,即字符串的长度必须大于0;

@NotBlank:不允许为空白,即字符串中必须包含除了空白以外的字符,例如" "也是错的;

@Pattern:可以在注解参数中定义验证时使用的正则表达式;

@Size:验证字符串值的长度是否在某个区间范围之内;

其它……

例如,可以在User类的属性之前添加验证相关的注解,例如,先在password属性之前添加验证的注解:

/**

* 密码

*/

@TableField("password")

@NotBlank(message = "密码不允许为空!")

@Size(min = 4, max = 16, message = "密码必须是4~16个字符!")

private String password;

然后,需要在控制器类中,在处理请求的方法的参数列表中,在被验证的对象之前添加@Valid或@Validated注解,之后添加BindingResult参数,在处理请求的方法体中,判断BindingResult参数以得到验证结果:

// http://localhost:8080/portal/user/student/register?inviteCode=JSD2003-111111&nickname=Hello&phone=13800138002&password=1234
@RequestMapping("/student/register")
public R studentRegister(String inviteCode,
    @Validated User user, BindingResult bindingResult) {
    if (inviteCode == null || inviteCode.length() < 4 || inviteCode.length() > 20) {
        throw new ParameterValidationException("邀请码必须是4~20个字符!");
    }
    if (bindingResult.hasErrors()) {
        String errorMessage = bindingResult
                .getFieldError().getDefaultMessage();
        log.debug("validation has error : {}", errorMessage);
        throw new ParameterValidationException(errorMessage);
    }
    userService.registerStudent(user, inviteCode);
    return R.ok();
}      

关于以上验证:

被验证的必须是1个对象;

封装验证结果的BindingResult必须声明在被验证的参数之后;

验证框架并不能完成所有验证需求,如果某些验证规则是验证框架无法做法的,则自己编写验证规则即可;

如果验证过程中出现错误,并且在控制器中并没有使用BindingResult接收错误信息,就会抛出BindException,在统一处理异常的代码中直接处理这个异常也是可以的;

以上演示代码还会涉及R和GlobalExceptionHandler这2个类中和其它相关的内容。

10. 注册前端页面测试

为了避免Spring Security拦截异步请求,需要自定义配置类,继承自WebSecurityConfigurerAdapter,重写protected

void configure(HttpSecurity http),调用参数对象的csrf().disable()方法,网页中才可以正常提交AJAX请求!
@Override
protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
}      

前端将使用Vue访问页面元素,使用AJAX提交异步请求并处理结果,关于这2个前端框架的基本使用,演示如下:

在SpringMVC框架中统一处理异常及请求参数验证(5)

测试页面代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>学生注册</title>
    <script src="jquery.min.js"></script>
    <script src="vue.js"></script>
    <style>
        .err { color: red; }
    </style>
</head>
<body>
<h1>学生注册</h1>
<div id="register">
    <form id="form-register" v-on:submit.prevent="register" action="/portal/user/student/register">
        <table>
            <tr>
                <td>邀请码</td>
                <td><input name="inviteCode"><span class="err" v-text="inviteCodeMessage"></span></td>
            </tr>
            <tr>
                <td>手机号码</td>
                <td><input name="phone"><span class="err" v-text="phoneMessage"></span></td>
            </tr>
            <tr>
                <td>昵称</td>
                <td><input name="nickname"></td>
            </tr>
            <tr>
                <td>密码</td>
                <td><input name="password"></td>
            </tr>
            <tr>
                <td>确认密码</td>
                <td><input name="confirmPassword"></td>
            </tr>
            <tr>
                <td>&nbsp;</td>
                <td><input value="用户注册" type="submit"></td>
            </tr>
        </table>
    </form>
</div>
<script>
    // 创建Vue数据模型
    let app = new Vue({
        el: '#register',
        data:{
            inviteCodeMessage: null,
            phoneMessage: null
        },
        methods: {
            register: function () {
                // alert("准备注册!");
                app.inviteCodeMessage = null;
                app.phoneMessage = null;
                $.ajax({
                    url: '/portal/user/student/register',
                    data: $('#form-register').serialize(),
                    type: 'post',
                    dataType: 'json',
                    success: function(json) {
                        console.log(json);
                        if (json.state == 2000) {
                            alert("注册成功!");
                        } else if (json.state == 4001 || json.state == 4002) {
                            app.inviteCodeMessage = json.message;
                        } else if (json.state == 4003) {
                            app.phoneMessage = json.message;
                        } else {
                            alert("注册失败!" + json.message);
                        }
                    }
                });
            }
        }
    });
</script>
</body>
</html>