SpringBoot实现优雅的参数校验

1,662 阅读1分钟

1.引入依赖

<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.0.10.Final</version>
</dependency>

2.定义参数校验异常拦截器

package com.example.demo.exception.global;

import com.alibaba.cola.dto.SingleResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingPathVariableException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.stream.Collectors;

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    @ExceptionHandler({ConstraintViolationException.class,
            MethodArgumentNotValidException.class,
            ServletRequestBindingException.class,
            BindException.class})
    public SingleResponse<Object> handleValidationException(Exception e) {
        String msg;
        if (e instanceof MethodArgumentNotValidException) {
            MethodArgumentNotValidException t = (MethodArgumentNotValidException) e;
            msg = getBindingResultMsg(t.getBindingResult());
        } else if (e instanceof BindException) {
            BindException t = (BindException) e;
            msg = getBindingResultMsg(t.getBindingResult());
        } else if (e instanceof ConstraintViolationException) {
            ConstraintViolationException t = (ConstraintViolationException) e;
            msg = t.getConstraintViolations().stream()
                    .map(ConstraintViolation::getMessage)
                    .collect(Collectors.joining(","));
        } else if (e instanceof MissingServletRequestParameterException) {
            MissingServletRequestParameterException t = (MissingServletRequestParameterException) e;
            msg = t.getParameterName() + " 不能为空";
        } else if (e instanceof MissingPathVariableException) {
            MissingPathVariableException t = (MissingPathVariableException) e;
            msg = t.getVariableName() + " 不能为空";
        } else {
            msg = "必填参数缺失";
        }
        return SingleResponse.buildFailure(String.valueOf(ErrorCode.PARAMETER_ERROR.getCode()), msg);
    }
    
    private String getBindingResultMsg(BindingResult bindingResult) {
        StringBuilder stringBuilder = new StringBuilder();
        if (bindingResult.hasErrors()) {
            for (ObjectError error : bindingResult.getAllErrors()) {
                stringBuilder.append(error.getDefaultMessage()).append(";");
            }
        }
        return stringBuilder.toString();
    }
    
}

这里推荐一个常用的api包

<dependency>
    <groupId>com.alibaba.cola</groupId>
    <artifactId>cola-component-dto</artifactId>
    <version>4.0.1</version>
</dependency>

3.校验参数

package com.example.api.domain.message.dto;

import lombok.Data;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import java.io.Serializable;
import java.util.List;

/**
 * com.avatar.socketio.server.domain.message.dto
 * Description:
 * 发送消息参数
 *
 * @author jack
 * @date 2021/06/24 5:31 下午
 */
@Data
public class MessageDTO implements Serializable {

    private static final long serialVersionUID = -4201486472018401037L;

    /**
     * 消息内容
     */
    @NotBlank
    private String content;

    /**
     * 发送方
     */
    private String sender;

    /**
     * 接收方
     */
    @NotEmpty(message = "接收方不能为空")
    private List<String> receiverList;

    /**
     * 命名空间
     */
    @NotBlank(message = "命名空间不能为空")
    private String namespace;
}

4.测试

package com.example.controller.message;

import com.alibaba.cola.dto.SingleResponse;
import com.avatar.api.common.response.PlainResult;
import com.avatar.api.common.utils.ResultUtils;
import com.avatar.socketio.api.domain.message.dto.BroadCastMessageDTO;
import com.avatar.socketio.api.domain.message.dto.MessageDTO;
import com.avatar.socketio.server.service.message.MessageService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

@RestController
@RequestMapping("/api/v1/message")
@Slf4j
@RequiredArgsConstructor
public class MessageController {

    @PostMapping("/addMessage")
    public SingleResponse<Object> addMessage(@RequestBody @Validated MessageDTO messageDTO) {
        log.info("messageDTO:{}",messageDTO);
        return SingleResponse.of(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
    }
}

image.png

5. @Validated@Valid的区别

@Validated:用在方法入参上无法单独提供嵌套验证功能。不能用在成员属性(字段)上,也无法提示框架进行嵌套验证。能配合嵌套验证注解@Valid进行嵌套验证。

@Valid:用在方法入参上无法单独提供嵌套验证功能。能够用在成员属性(字段)上,提示验证框架进行嵌套验证。能配合嵌套验证注解@Valid进行嵌套验证。