路径参数@PathVariable,参数校验:@Validated(Controller上)+@Pattern

发布时间:2023年12月21日

说明

路径参数 进行合法性校验。

在这里插入图片描述

主要使用场景为:校验 id 的长度,是否符合要求。

如果id长度不对,说明一定找不到此id对应的数据,也就不需要去操作数据库了,直接在接口层就拦截掉无效请求。

比如:通过ID查询一条记录、通过ID删除一条记录、通过ID修改一条记录,都是通过id来找到这条记录的,都应该对id进行合法性校验。

校验逻辑

有效格式

本文中的例子,是对删除用户接口的用户ID参数进行校验,此参数是路径参数,其正确格式应该为19位数字(雪花算法生成的ID)。

核心代码

开启校验,主要包含两个注解:

  1. 开启校验的注解:@Validated
  2. 校验格式的注解:@Pattern

异常统一处理:

  • ConstraintViolationException

校验不通过会抛出异常 ConstraintViolationException,需要在异常统一处理中,加入对应的处理逻辑,向前端返回适当的响应。

开启校验的注解:@Validated

@Slf4j
@RestController
@RequestMapping("response")
@Tag(name = "响应统一封装")
@Validated
public class ResponseController {
    // 接口代码,省略。。。
}

@Validated,是 @Valid 的变体,本例需要对接口方法的参数直接进行校验,所以使用 @Validated(经过测试,使用@Valid无效,不能开启校验)。

@Validated 官方解释如下:

JSR-303的javax.validation.Validity的变体,支持验证组的规范。
可以与Spring MVC处理程序方法参数一起使用。

在这里插入图片描述

校验格式的注解:@Pattern

使用位置为:需要校验的路径参数前;

包含的内容有:参数的校验格式 和 校验不通过时的提示语。

    @ApiLog
    @DeleteMapping("users/{id}")
    @Operation(summary = "删除用户")
    @Parameter(name = "id", description = "用户ID", example = "1234567890123456789")
    public void deleteUser(@PathVariable @Pattern(regexp = "^\\d{19}$", message = "id应为19位数字") String id) {
        log.info("测试,删除用户,DELETE请求。id=" + id);
    }

异常统一处理:ConstraintViolationException

    @ExceptionHandler
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Result<String> handleException(ConstraintViolationException e, HandlerMethod handlerMethod) {
        logInfo(e, handlerMethod);

        String userMessage = UserTipGenerator.getUserMessage(e);
        HttpStatus httpStatus = HttpStatus.BAD_REQUEST;
        String errorMessage = String.format("【参数校验异常】(错误数量:%s):%s", e.getConstraintViolations()
                .size(), e.getMessage());
        return Result.fail(userMessage, String.valueOf(httpStatus.value()), errorMessage);
    }

不进行异常处理的接口响应

在这里插入图片描述

异常统一处理后的接口响应

在这里插入图片描述

异常统一处理代码

在这里插入图片描述

完整示例代码

接口校验

package com.example.web.response.controller;

import com.example.core.log.annotation.ApiLog;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.constraints.Pattern;

@Slf4j
@RestController
@RequestMapping("response")
@Tag(name = "响应统一封装")
@Validated
public class ResponseController {

    @ApiLog
    @DeleteMapping("users/{id}")
    @Operation(summary = "删除用户")
    @Parameter(name = "id", description = "用户ID", example = "1234567890123456789")
    public void deleteUser(@PathVariable @Pattern(regexp = "^\\d{19}$", message = "id应为19位数字") String id) {
        log.info("测试,删除用户,DELETE请求。id=" + id);
    }
    
    // 其他接口省略 ...

}

异常统一处理

package com.example.core.advice;

import com.example.core.advice.util.ErrorMessageGenerator;
import com.example.core.advice.util.UserTipGenerator;
import com.example.core.model.BusinessException;
import com.example.core.model.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.HandlerMethod;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolationException;
import java.util.List;

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    private final HttpServletRequest request;

    public GlobalExceptionHandler(HttpServletRequest request) {
        this.request = request;
    }

    /**
     * Get请求,参数校验异常:对象参数校验。
     */
    @ExceptionHandler
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Result<Void> handleException(BindException e, HandlerMethod handlerMethod) {
        logInfo(e, handlerMethod);

        List<FieldError> fieldErrors = e.getFieldErrors();
        String userMessage = UserTipGenerator.getUserMessage(fieldErrors);
        String errorMessageCore = ErrorMessageGenerator.getErrorMessage(fieldErrors);

        HttpStatus httpStatus = HttpStatus.BAD_REQUEST;
        String errorMessage = String.format("【参数校验异常】(错误数量:%s):%s", e.getErrorCount(), errorMessageCore);
        return Result.fail(userMessage, String.valueOf(httpStatus.value()), errorMessage);
    }

    @ExceptionHandler
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Result<String> handleException(ConstraintViolationException e, HandlerMethod handlerMethod) {
        logInfo(e, handlerMethod);

        String userMessage = UserTipGenerator.getUserMessage(e);
        HttpStatus httpStatus = HttpStatus.BAD_REQUEST;
        String errorMessage = String.format("【参数校验异常】(错误数量:%s):%s", e.getConstraintViolations()
                .size(), e.getMessage());
        return Result.fail(userMessage, String.valueOf(httpStatus.value()), errorMessage);
    }

    /**
     * 业务异常处理
     */
    @ExceptionHandler
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Result<Void> handleException(BusinessException e, HandlerMethod handlerMethod) {
        logInfo(e, handlerMethod);
        return Result.fail(e.getUserMessage(), e.getErrorCode(), e.getErrorMessage());
    }

    private void logInfo(Exception e, HandlerMethod handlerMethod) {
        String message = getLogMessage(e, handlerMethod);
        log.info(message, e);
    }

    private String getLogMessage(Exception e, HandlerMethod handlerMethod) {
        String exceptionName = e.getClass()
                .getName();
        String requestMethod = request.getMethod();
        String url = request.getRequestURI();
        String className = handlerMethod.getBean()
                .getClass()
                .getName();
        String methodName = handlerMethod.getMethod()
                .getName();

        return String.format("\n接口:[%s:%s]\n异常名称:[%s]\n出现异常的方法:[%s.%s]\n异常信息:\n%s", requestMethod, url, exceptionName, className, methodName, e.getMessage());
    }

}

package com.example.core.advice.util;

import org.springframework.util.CollectionUtils;
import org.springframework.validation.FieldError;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.List;
import java.util.Set;

/**
 * 用户提示生成器。
 *
 * @author songguanxun
 * @date 2023-8-24
 */
public class UserTipGenerator {

    /**
     * 获取用户提示(参数校验异常时)
     */
    public static String getUserMessage(List<FieldError> errors) {
        StringBuilder stringBuilder = new StringBuilder();
        errors.forEach(error -> {
            String defaultMessage = error.getDefaultMessage();
            String numberFormatExceptionName = NumberFormatException.class.getName();
            if (defaultMessage != null && defaultMessage.contains(numberFormatExceptionName)) {
                String message = String.format("数字格式异常,当前输入为:[%s]", error.getRejectedValue());
                stringBuilder.append(message)
                        .append(";");
            } else {
                stringBuilder.append(defaultMessage)
                        .append(";");
            }
        });
        return stringBuilder.toString();
    }

    public static String getUserMessage(ConstraintViolationException e) {
        Set<ConstraintViolation<?>> sets = e.getConstraintViolations();
        if (CollectionUtils.isEmpty(sets)) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        sets.forEach(error -> sb.append(error.getMessage())
                .append(";"));
        return sb.toString();
    }

}

校验效果

成功

在这里插入图片描述

失败

在这里插入图片描述

文章来源:https://blog.csdn.net/sgx1825192/article/details/134581861
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。