当前位置: 首页 > news >正文

Spring Boot 参数校验异常与错误编码映射方案

一、错误码定义与配置
  1. 错误码结构
    采用分层编码格式:[模块][错误类型][序号](如 1001 表示公共模块参数校验错误中的第一个错误)

  2. 配置文件(application.yml)

    yaml

    复制

    error:
      codes:
        # 公共模块错误码
        common:
          param_invalid: 1000
          # 校验子错误码
          constraints:
            NotNull: 1001
            Size: 1002
            Pattern: 1003
            Email: 1004
        # 用户模块错误码
        user:
          not_found: 2001
  3. 错误码枚举类

    java

    复制

    @Getter
    @AllArgsConstructor
    public enum ErrorCode {
        // 公共错误码
        PARAM_INVALID(1000, "error.param.invalid"),
        NOT_NULL(1001, "error.validation.notNull"),
        SIZE(1002, "error.validation.size"),
        PATTERN(1003, "error.validation.pattern"),
        EMAIL(1004, "error.validation.email"),
        
        // 业务错误码
        USER_NOT_FOUND(2001, "error.user.notFound");
    
        private final int code;
        private final String msgKey;
    }
二、统一异常处理
  1. 全局异常处理器

    java

    复制

    @RestControllerAdvice
    public class GlobalExceptionHandler {
    
        @Autowired
        private MessageSource messageSource;
    
        // 处理参数校验异常
        @ExceptionHandler(MethodArgumentNotValidException.class)
        public ResponseEntity<ErrorResponse> handleValidationException(
                MethodArgumentNotValidException ex, Locale locale) {
            
            List<FieldErrorVO> fieldErrors = ex.getBindingResult().getFieldErrors().stream()
                    .map(fieldError -> convertToFieldErrorVO(fieldError, locale))
                    .collect(Collectors.toList());
    
            String message = messageSource.getMessage(ErrorCode.PARAM_INVALID.getMsgKey(), null, locale);
            ErrorResponse response = new ErrorResponse(
                    ErrorCode.PARAM_INVALID.getCode(),
                    message,
                    fieldErrors
            );
            return ResponseEntity.badRequest().body(response);
        }
    
        private FieldErrorVO convertToFieldErrorVO(FieldError fieldError, Locale locale) {
            // 获取校验注解类型(如 NotNull, Size)
            String constraintType = fieldError.getCode();
            ErrorCode errorCode = getErrorCodeByConstraint(constraintType);
    
            // 解析消息参数
            Object[] args = Stream.of(Optional.ofNullable(fieldError.getArguments()).orElse(new Object[]{}))
                    .map(arg -> {
                        if (arg instanceof MessageSourceResolvable) {
                            return messageSource.getMessage((MessageSourceResolvable) arg, locale);
                        }
                        return arg;
                    })
                    .toArray();
    
            // 获取字段显示名称
            String fieldName = getFieldDisplayName(fieldError.getField());
    
            // 生成错误信息
            String errorMsg = messageSource.getMessage(
                    errorCode.getMsgKey(), 
                    new Object[]{fieldName, args}, 
                    locale
            );
    
            return new FieldErrorVO(
                    fieldError.getField(),
                    errorCode.getCode(),
                    errorMsg
            );
        }
    
        private ErrorCode getErrorCodeByConstraint(String constraintType) {
            return switch (constraintType) {
                case "NotNull" -> ErrorCode.NOT_NULL;
                case "Size" -> ErrorCode.SIZE;
                case "Pattern" -> ErrorCode.PATTERN;
                case "Email" -> ErrorCode.EMAIL;
                default -> ErrorCode.PARAM_INVALID;
            };
        }
    
        private String getFieldDisplayName(String fieldName) {
            // 实现字段名到中文的转换(可通过注解或配置文件)
            return switch (fieldName) {
                case "username" -> "用户名";
                case "password" -> "密码";
                default -> fieldName;
            };
        }
    }
三、响应实体类

java

复制

@Data
@NoArgsConstructor
@AllArgsConstructor
public class ErrorResponse {
    private int code;
    private String message;
    private List<FieldErrorVO> errors;
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public class FieldErrorVO {
    private String field;
    private int code;
    private String message;
}
四、国际化配置
  1. messages.properties

    properties

    复制

    error.param.invalid=参数校验失败
    error.validation.notNull={0}不能为空
    error.validation.size={0}长度必须在{1}到{2}之间
    error.validation.pattern={0}格式不正确
    error.validation.email=邮箱格式无效
    error.user.notFound=用户不存在
  2. messages_en_US.properties

    properties

    复制

    error.param.invalid=Parameter validation failed
    error.validation.notNull={0} cannot be empty
    error.validation.size={0} length must be between {1} and {2}
    error.validation.pattern={0} format is invalid
    error.validation.email=Invalid email format
    error.user.notFound=User not found
五、使用示例
  1. DTO 类

    java

    复制

    @Data
    public class UserDTO {
        @NotNull(message = "用户名不能为空")
        @Size(min = 3, max = 20, message = "用户名长度需在3到20个字符")
        private String username;
    
        @Email(message = "邮箱格式不正确")
        private String email;
    }
  2. Controller 方法

    java

    复制

    @PostMapping("/users")
    public ResponseEntity<?> createUser(@Valid @RequestBody UserDTO userDTO) {
        // 业务逻辑
        return ResponseEntity.ok().build();
    }
  3. 错误响应示例

    json

    复制

    {
      "code": 1000,
      "message": "参数校验失败",
      "errors": [
        {
          "field": "username",
          "code": 1001,
          "message": "用户名不能为空"
        },
        {
          "field": "email",
          "code": 1004,
          "message": "邮箱格式无效"
        }
      ]
    }
六、扩展配置
  1. 自定义字段显示名称

    java

    复制

    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface FieldLabel {
        String value();
    }
    
    // 在DTO中使用
    public class UserDTO {
        @FieldLabel("用户名")
        @NotNull
        private String username;
    }
    
    // 修改字段名获取逻辑
    private String getFieldDisplayName(FieldError fieldError) {
        try {
            Field field = fieldError.getField().getClass().getDeclaredField(fieldError.getField());
            FieldLabel annotation = field.getAnnotation(FieldLabel.class);
            return annotation != null ? annotation.value() : fieldError.getField();
        } catch (NoSuchFieldException e) {
            return fieldError.getField();
        }
    }
  2. 动态错误码配置

    yaml

    复制

    error:
      mappings:
        - constraint: javax.validation.constraints.NotNull
          code: 1001
          messageKey: not.null
        - constraint: org.hibernate.validator.constraints.Length
          code: 1002
          messageKey: invalid.length
七、方案优势
  1. 统一错误格式:标准化响应结构,前端处理更简单

  2. 精准错误定位:字段级错误码和提示信息

  3. 国际化支持:轻松扩展多语言版本

  4. 动态配置:通过YAML文件管理错误码映射

  5. 强类型校验:结合Java Validation规范

  6. 可维护性:错误码集中管理,避免散落各处

八、注意事项
  1. 校验顺序处理:多个校验注解同时失败时的处理顺序

  2. 敏感信息过滤:避免在错误信息中暴露敏感数据

  3. 性能优化:字段名反射获取需要缓存处理

  4. 文档同步:错误码列表需要与API文档保持同步

  5. 安全校验:结合Spring Security的权限校验体系

相关文章:

  • 什么是净利润
  • BGP实验(二)—路由反射器
  • 对vue VS react的理解
  • 【网络安全】CSV注入(附实战案例)
  • C#—闭包详解
  • 【docker】端口暴露
  • Webservice如何调用
  • zabbix学习笔记
  • 前端开发:Web蜜罐详解
  • Java实例化详解:从概念到实践的全方位解读
  • 如何把绿色可执行应用程序添加到Ubuntu的收藏夹Dock中
  • 【DvAdmin】基于腾讯云Cos实现资源预签名访问
  • 硬件工程师入门教程(四)
  • k8s面试题总结(十五)
  • Windows本地部署DeepSeek模型指南
  • react native
  • 前端 JavaScript 中快速发起多个下载请求时,解决浏览器的并发下载连接限制
  • 数字人源码部署-支持oem
  • Netty基础—4.NIO的使用简介二
  • 编程考古-VCL跨平台革命:CrossVCL如何让Delphi开发者梦想成真(上)
  • 价格周报|猪价继续回暖:二次育肥热度仍存,对猪价仍有一定支撑
  • 政治局会议深度|提出“设立新型政策性金融工具”有何深意?
  • 剪纸纹样“流动”在水乡,谁不忆江南
  • 欢迎回家!日本和歌山县4只大熊猫将于6月底送返中国
  • 政企研合力,科学监测分析服务消费
  • 谷歌一季度利润增超四成:云业务利润率上升,宏观环境可能影响广告业务