JSR303参数校验配置

2021-12-22  本文已影响0人  不减肥到一百三不改名字

一、环境

二、项目配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.yxl</groupId>
    <artifactId>yxl</artifactId>
    <packaging>pom</packaging>
    <version>1.0.0</version>
    <modules>
        <module>yxl_project01</module>
    </modules>
    <name>yxl</name>
    <description>公共项目</description>

    <properties>
        <druid-version>1.2.6</druid-version>
        <mybatis-plus-version>3.4.1</mybatis-plus-version>
        <mysql-connector-version>8.0.23</mysql-connector-version>
        <lombok-version>1.18.18</lombok-version>
        <hutool-version>5.6.5</hutool-version>
        <httpcore-version>4.4.12</httpcore-version>
        <validation-version>2.0.1.Final</validation-version>

    </properties>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.5.6</version>
                <scope>import</scope>
                <type>pom</type>
            </dependency>

            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid-spring-boot-starter</artifactId>
                <version>${druid-version}</version>
            </dependency>

            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>${mybatis-plus-version}</version>
            </dependency>

            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>${mysql-connector-version}</version>
            </dependency>

            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>${lombok-version}</version>
            </dependency>

            <dependency>
                <groupId>cn.hutool</groupId>
                <artifactId>hutool-all</artifactId>
                <version>${hutool-version}</version>
            </dependency>

            <dependency>
                <groupId>org.apache.httpcomponents</groupId>
                <artifactId>httpcore</artifactId>
                <version>${httpcore-version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

</project>

注:
spirngboot升级到2.3之后,hibernate-validator消失,需要自己手动加入jar包

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

或者是自己手动引入依赖

<dependency>
      <groupId>org.hibernate.validator</groupId>
      <artifactId>hibernate-validator</artifactId>
      <version>6.0.17.Final</version>
 </dependency>
package com.yxl.common.util;

import org.apache.http.HttpStatus;

import java.util.HashMap;
import java.util.Map;

/**
 * @Classname R
 * @Description TODO
 * @Date 2021-12-20 17:19
 * @Created by yxl
 */
public class R extends HashMap<String,Object> {
    private static final long serialVersionUID = 1L;

    public R(){
        put("code",0);
        put("msg","success");
    }

    public static R error() {
        return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, "未知异常,请联系管理员");
    }

    public static R error(String msg) {
        return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, msg);
    }

    public static R error(int code, String msg) {
        R r = new R();
        r.put("code", code);
        r.put("msg", msg);
        return r;
    }

    public static R ok(String msg) {
        R r = new R();
        r.put("msg", msg);
        return r;
    }

    public static R ok(Map<String, Object> map) {
        R r = new R();
        r.putAll(map);
        return r;
    }

    public static R ok() {
        return new R();
    }

    public R put(String key, Object value) {
        super.put(key, value);
        return this;
    }
}
package com.yxl.project01.controller;

import com.yxl.common.util.R;
import com.yxl.project01.entity.User;
import com.yxl.project01.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * @Classname UserController
 * @Description TODO
 * @Date 2021-12-20 17:14
 * @Created by yxl
 */
@RestController
@RequestMapping("/user")
public class UserController {

}
package com.yxl.project01.dao;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yxl.project01.entity.User;
import org.apache.ibatis.annotations.Mapper;

/**
 * @Classname UserMapper
 * @Description TODO
 * @Date 2021-12-20 17:10
 * @Created by yxl
 */
@Mapper
public interface UserMapper extends BaseMapper<User> {

}
package com.yxl.project01.entity;

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.util.Date;

/**
 * @Classname User
 * @Description TODO
 * @Date 2021-12-20 17:07
 * @Created by yxl
 */
@Data
@TableName("tb_user")
public class User {

    @TableId
    private Long id;

    private String name;

    private Integer age;

    private Date birthday;

    private String icon;

    private Integer status;

    private String description;
}
package com.yxl.project01.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.yxl.project01.entity.User;

/**
 * @Classname UserService
 * @Description TODO
 * @Date 2021-12-20 17:11
 * @Created by yxl
 */
public interface UserService extends IService<User> {

}
package com.yxl.project01.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yxl.project01.dao.UserMapper;
import com.yxl.project01.entity.User;
import com.yxl.project01.service.UserService;
import org.springframework.stereotype.Service;

/**
 * @Classname UserServiceImpl
 * @Description TODO
 * @Date 2021-12-20 17:12
 * @Created by yxl
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
package com.yxl.project01;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @Classname yxlProject01Application
 * @Description TODO
 * @Date 2021-12-20 17:03
 * @Created by yxl
 */
@SpringBootApplication
@MapperScan("com.yxl.project01.dao")
public class yxlProject01Application {

    public static void main(String[] args) {
        SpringApplication.run(yxlProject01Application.class,args);
    }
}

三、JSR303参数校验配置

1、在UserController中新增save接口用来添加用户

/**
     * JSR303配置案例
     */
    @PostMapping("/save")
    public R save(@RequestBody User user){
        userService.save(user);
        return R.ok();
    }

2、使用Postman对接口进行测试

测试数据

{
"name": "张三",
"age": 20,
"birthday": "1995-03-02",
"icon": "http://www.baidu.com",
"status": "0",
"description": "描述"
}
测试结果如下:


从上边图可以看出来执行成功了,也新增到数据库了。
那么我们现在把所有新增的字段都置空,看新增的时候会怎么样?

{
"name": "",
"age": "",
"birthday": "",
"icon": "",
"status": "",
"description": ""
}



从执行结果中可以看出,新增依旧是执行成功的,但是数据库加入的都是空数据,这样肯定是不行的,那么我们怎么样能限制到新增的时候给每个属性加限制呢?就比如说名称不能为空,url必须是合法的url地址等等。这时候我们就用到了JSR303参数校验注解了。演示如下:

1、在User.Class中新增校验注解如下:

package com.yxl.project01.entity;

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.Date;

/**
 * @Classname User
 * @Description TODO
 * @Date 2021-12-20 17:07
 * @Created by yxl
 */
@Data
@TableName("tb_user")
public class User {

    @TableId
    private Long id;

    @NotBlank(message = "用户名不能为空")
    private String name;

    @NotNull(message = "年龄不能为空")
    private Integer age;

    @NotNull(message = "出生日期不能为空")
    private Date birthday;

    @NotBlank(message = "头像不能为空")
    private String icon;

    private Integer status;

    private String description;
}

再此对@NotBlank、@NotNull、@NotEmpty三个注解做一个解释

@NotBlank:只用于String,不能为null且trim()之后size>0
@NotNull:用在基本数据类型上面。不能为null,但可以为empty,没有Size的约束
@NotEmpty:用在集合类上面。加了@NotEmpty的String类、Collection、Map、数组,是不能为null或者长度为0的(String Collection Map的isEmpty()方法)

2、在Controller中使用校验注解

/**
     * JSR303配置案例
     */
    @PostMapping("/save")
    public R save(@Valid @RequestBody User user){
        userService.save(user);
        return R.ok();
    }

3、重启服务器,再次使用空数值测试结果如下:

{
    "timestamp": "2021-12-22T07:19:17.464+0000",
    "status": 400,
    "error": "Bad Request",
    "errors": [
        {
            "codes": [
                "NotNull.user.age",
                "NotNull.age",
                "NotNull.java.lang.Integer",
                "NotNull"
            ],
            "arguments": [
                {
                    "codes": [
                        "user.age",
                        "age"
                    ],
                    "arguments": null,
                    "defaultMessage": "age",
                    "code": "age"
                }
            ],
            "defaultMessage": "年龄不能为空",
            "objectName": "user",
            "field": "age",
            "rejectedValue": null,
            "bindingFailure": false,
            "code": "NotNull"
        },
        {
            "codes": [
                "NotNull.user.birthday",
                "NotNull.birthday",
                "NotNull.java.util.Date",
                "NotNull"
            ],
            "arguments": [
                {
                    "codes": [
                        "user.birthday",
                        "birthday"
                    ],
                    "arguments": null,
                    "defaultMessage": "birthday",
                    "code": "birthday"
                }
            ],
            "defaultMessage": "出生日期不能为空",
            "objectName": "user",
            "field": "birthday",
            "rejectedValue": null,
            "bindingFailure": false,
            "code": "NotNull"
        },
        {
            "codes": [
                "NotBlank.user.icon",
                "NotBlank.icon",
                "NotBlank.java.lang.String",
                "NotBlank"
            ],
            "arguments": [
                {
                    "codes": [
                        "user.icon",
                        "icon"
                    ],
                    "arguments": null,
                    "defaultMessage": "icon",
                    "code": "icon"
                }
            ],
            "defaultMessage": "头像不能为空",
            "objectName": "user",
            "field": "icon",
            "rejectedValue": "",
            "bindingFailure": false,
            "code": "NotBlank"
        },
        {
            "codes": [
                "NotBlank.user.name",
                "NotBlank.name",
                "NotBlank.java.lang.String",
                "NotBlank"
            ],
            "arguments": [
                {
                    "codes": [
                        "user.name",
                        "name"
                    ],
                    "arguments": null,
                    "defaultMessage": "name",
                    "code": "name"
                }
            ],
            "defaultMessage": "用户名不能为空",
            "objectName": "user",
            "field": "name",
            "rejectedValue": "",
            "bindingFailure": false,
            "code": "NotBlank"
        }
    ],
    "message": "Validation failed for object='user'. Error count: 4",
    "path": "/user/save"
}

从结果看参数校验注解已生效。
但是回过头来香,如果是前后端分离项目的话,返回给前端这种格式是不行的,那么我们就可以利用全局异常处理,统一来返回结果给前端了。

4、在全局异常处理类中增加异常处理方法专门对MethodArgumentNotValidException.Class异常类进行处理,如下:

package com.yxl.project01.exception;

import com.yxl.common.exception.BizCodeEnume;
import com.yxl.common.util.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.HashMap;
import java.util.Map;

/**
 * @Classname GlobalExceptionHandler
 * @Description TODO
 * @Date 2021-12-22 11:13
 * @Created by yxl
 */
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    @ExceptionHandler(ArithmeticException.class)
    public R arithmeticExceptionHandler(ArithmeticException e){
        return R.error(BizCodeEnume.ARITHMETIC_EXCEPTION.getCode(),BizCodeEnume.ARITHMETIC_EXCEPTION.getMsg());
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public R methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e){
        log.error("数据校验出现问题{},异常类型:{}",e.getMessage().getClass());
        BindingResult result = e.getBindingResult();
        Map<String,String> map = new HashMap<>();
        result.getFieldErrors().forEach(fieldError -> {
            map.put(fieldError.getField(),fieldError.getDefaultMessage());
        });
        return R.error(BizCodeEnume.VAILD_EXCEPTION.getCode(),BizCodeEnume.VAILD_EXCEPTION.getMsg()).put("data",map);
    }
}

其中BizCodeEnume枚举类如下:

package com.yxl.common.exception;

/**
 * @Classname BizCodeEnume
 * @Description TODO
 * @Date 2021-12-22 11:29
 * @Created by yxl
 */
public enum BizCodeEnume {

    UNKNOW_EXCEPTION(10000,"系统未知异常"),
    VAILD_EXCEPTION(10001,"参数格式校验失败"),
    ARITHMETIC_EXCEPTION(10001,"算术异常"),;

    private int code;
    private String msg;

    private BizCodeEnume(int code, String msg){
        this.code = code;
        this.msg = msg;
    }

    public int getCode(){
        return code;
    }

    public String getMsg(){
        return msg;
    }
}

新增全局异常处理后我们再测试一下,结果如下:

{
    "msg": "参数格式校验失败",
    "code": 10001,
    "data": {
        "birthday": "出生日期不能为空",
        "icon": "头像不能为空",
        "name": "用户名不能为空",
        "age": "年龄不能为空"
    }
}

这就达到了我们想要的结果。

三、参数校验分组功能

参数校验分组功能主要是针对哪些方法使用哪些参数校验的,比如:新增用户前端不需要传id等字段,修改的时候前端必须传id,新增和修改都必须要传name等。这种场景的话,我们就需要用到分组功能了。
1、我们在UserController.class中新增修改接口,如下:

/**
     * 参数校验分组功能
     */
    @PostMapping("/update")
    public R update(@RequestBody User user){
        userService.updateById(user);
        return R.ok();
    }

2、配置User实体类

package com.yxl.project01.entity;

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.yxl.common.valid.SaveGroup;
import com.yxl.common.valid.UpdateGroup;
import lombok.Data;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Null;
import java.util.Date;

/**
 * @Classname User
 * @Description TODO
 * @Date 2021-12-20 17:07
 * @Created by yxl
 */
@Data
@TableName("tb_user")
public class User {

    @TableId
    @NotNull(message = "修改需要传递id",groups = {UpdateGroup.class})
    @Null(message = "新增不需要传递id",groups = {SaveGroup.class})
    private Long id;

    @NotBlank(message = "用户名不能为空",groups = {SaveGroup.class,UpdateGroup.class})
    private String name;

    @NotNull(message = "年龄不能为空")
    private Integer age;

    @NotNull(message = "出生日期不能为空")
    private Date birthday;

    @NotBlank(message = "头像不能为空")
    private String icon;

    private Integer status;

    private String description;
}

其中SaveGroup.class和UpdateGroup.class为groups分组需要传递的标识符,即一个空的接口。如下:

package com.yxl.common.valid;

/**
 * @Classname SaveGroup
 * @Description TODO
 * @Date 2021-12-22 15:42
 * @Created by yxl
 */
public interface SaveGroup {
}
package com.yxl.common.valid;

/**
 * @Classname UpdateGroup
 * @Description TODO
 * @Date 2021-12-22 15:42
 * @Created by yxl
 */
public interface UpdateGroup {
}

3、在UserController中save接口和update接口增加注解

/**
     * JSR303配置案例
     */
    @PostMapping("/save")
    public R save(@Validated({SaveGroup.class}) @RequestBody User user){
        userService.save(user);
        return R.ok();
    }

    /**
     * 参数校验分组功能
     */
    @PostMapping("/update")
    public R update(@Validated({UpdateGroup.class}) @RequestBody User user){
        userService.updateById(user);
        return R.ok();
    }

4、执行修改接口,提示修改需要传递id。如下:


我们传一个id进去,修改一条数据试试:



数据库数据已更改

5、执行新增接口,提示新增不需要传id。如下:


我们把Id去掉在测试




数据库已新增成功

上一篇下一篇

猜你喜欢

热点阅读