[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"article-spring-boot-zhongde-valid-he-validated-xiangjiegoujianjianzhuangdeshujuxiaoyantixi":3},{"id":4,"title":5,"slug":6,"summary":7,"content":8,"contentHtml":9,"wordCount":10,"readingTime":11,"categoryId":12,"tags":13,"coverImage":13,"thumbnail":13,"status":14,"isTop":15,"isRecommended":15,"allowComments":15,"password":13,"viewCount":16,"likeCount":15,"commentCount":15,"seoTitle":5,"seoKeywords":17,"seoDescription":18,"source":13,"sourceUrl":13,"publishTime":19},"2002405967373856770","Spring Boot 中的 @Valid 和 @Validated 详解：构建健壮的数据校验体系","spring-boot-zhongde-valid-he-validated-xiangjiegoujianjianzhuangdeshujuxiaoyantixi","## 1. 参数校验概述 在现代化的 Web 应用开发中，参数校验是保障系统健壮性的第一道防线。Spring Boot 通过集成 Bean Validation 规范，为我们提供了强大而灵活的校验机制。其中，`@Valid` 和 `@Validated` 是两个核心注解，它们虽然功能相似，但在不同场景下各有优势。 参数校验的主要目的： - **数据完整性**：确保接收到的数据符合预期格式 - **业务安全**：防止恶意或错误数据进入业务逻辑层 - **用户体验**：前端与后端校验结合，提供即时反馈 - **系统稳定**：减少因数据异常导致的系统崩溃 ## 2.","## 1. 参数校验概述\n\n在现代化的 Web 应用开发中，参数校验是保障系统健壮性的第一道防线。Spring Boot 通过集成 Bean Validation 规范，为我们提供了强大而灵活的校验机制。其中，`@Valid` 和 `@Validated` 是两个核心注解，它们虽然功能相似，但在不同场景下各有优势。\n\n参数校验的主要目的：\n- **数据完整性**：确保接收到的数据符合预期格式\n- **业务安全**：防止恶意或错误数据进入业务逻辑层\n- **用户体验**：前端与后端校验结合，提供即时反馈\n- **系统稳定**：减少因数据异常导致的系统崩溃\n\n## 2. @Valid 和 @Validated 的核心区别\n\n| 特性 | @Valid | @Validated |\n|------|--------|------------|\n| **来源** | JSR-303\u002FJSR-380 标准 (javax.validation) | Spring 框架自定义 |\n| **分组校验** | 不支持 | 支持 |\n| **嵌套校验** | 支持 | 支持 |\n| **使用场景** | 方法参数、对象属性 | 方法参数、类、方法 |\n| **校验顺序** | 不支持 | 支持 |\n| **校验级别** | 对象图级别 | 更细粒度的控制 |\n\n**选择建议**：\n- 简单对象校验 → 使用 `@Valid`\n- 需要分组校验 → 使用 `@Validated`\n- 方法参数校验 → 使用 `@Validated`\n\n## 3. 常用校验注解详解\n\n### 3.1 基础校验注解实战\n\n```java\npublic class UserDTO {\n    \n    @NotNull(message = \"ID不能为空\")\n    private Long id;\n    \n    @NotBlank(message = \"用户名不能为空\")\n    @Size(min = 2, max = 20, message = \"用户名长度必须在2-20之间\")\n    private String username;\n    \n    @Email(message = \"邮箱格式不正确\")\n    private String email;\n    \n    @Min(value = 0, message = \"年龄不能小于0\")\n    @Max(value = 150, message = \"年龄不能大于150\")\n    private Integer age;\n    \n    @Pattern(regexp = \"^1[3-9]\\\\d{9}$\", message = \"手机号格式不正确\")\n    private String phone;\n    \n    @AssertTrue(message = \"必须同意协议\")\n    private Boolean agreed;\n    \n    @Future(message = \"日期必须是未来时间\")\n    private LocalDateTime appointmentTime;\n    \n    @DecimalMin(value = \"0.0\", inclusive = false, message = \"金额必须大于0\")\n    @Digits(integer = 10, fraction = 2, message = \"金额格式不正确\")\n    private BigDecimal amount;\n    \n    \u002F\u002F 构造方法、getter、setter 省略\n}\n```\n\n**注解说明**：\n- `@NotNull`：值不能为null，但可以为空字符串\n- `@NotBlank`：字符串不能为null且必须包含非空白字符\n- `@NotEmpty`：集合、数组、字符串不能为null或空\n- `@Pattern`：使用正则表达式验证字符串格式\n\n### 3.2 集合与嵌套校验实战\n\n```java\npublic class BatchUserDTO {\n    \n    @NotEmpty(message = \"用户列表不能为空\")\n    @Valid  \u002F\u002F 关键：必须添加此注解才能校验集合内元素\n    private List\u003C@Valid UserDTO> users; \u002F\u002F Java 8+ 写法\n    \n    \u002F\u002F 嵌套对象校验示例\n    @Valid\n    @NotNull(message = \"创建者信息不能为空\")\n    private UserDTO creator;\n    \n    \u002F\u002F getter\u002Fsetter\n}\n```\n\n## 4. @Valid 深度使用指南\n\n### 4.1 Controller 层基础校验\n\n```java\n@RestController\n@RequestMapping(\"\u002Fapi\u002Fusers\")\n@Slf4j\npublic class UserController {\n    \n    @PostMapping\n    public ResponseEntity\u003CResult\u003CString>> createUser(\n            @Valid @RequestBody UserDTO userDTO) {\n        \u002F\u002F 只有当校验通过时，才会执行到此处的业务逻辑\n        log.info(\"创建用户: {}\", userDTO.getUsername());\n        return ResponseEntity.ok(Result.success(\"用户创建成功\"));\n    }\n    \n    \u002F\u002F 路径变量和查询参数校验需要结合@Validated使用\n}\n```\n\n### 4.2 复杂嵌套对象校验\n\n```java\npublic class AddressDTO {\n    @NotBlank(message = \"省份不能为空\")\n    private String province;\n    \n    @NotBlank(message = \"城市不能为空\")\n    private String city;\n    \n    @NotBlank(message = \"详细地址不能为空\")\n    private String detail;\n    \n    \u002F\u002F getter\u002Fsetter\n}\n\npublic class UserDetailDTO {\n    @NotBlank(message = \"用户名不能为空\")\n    private String username;\n    \n    @Valid  \u002F\u002F 关键注解：触发嵌套对象校验\n    @NotNull(message = \"地址信息不能为空\")\n    private AddressDTO address;\n    \n    \u002F\u002F 列表嵌套校验\n    @Valid\n    private List\u003CAddressDTO> addresses;\n    \n    \u002F\u002F getter\u002Fsetter\n}\n\n@PostMapping(\"\u002Fdetail\")\npublic ResponseEntity\u003CResult\u003CString>> createUserDetail(\n        @Valid @RequestBody UserDetailDTO userDetail) {\n    \u002F\u002F 处理业务逻辑\n    return ResponseEntity.ok(Result.success(\"用户详情创建成功\"));\n}\n```\n\n## 5. @Validated 高级特性实战\n\n### 5.1 分组校验：按场景精细化控制\n\n```java\n\u002F\u002F 定义校验分组接口\npublic interface CreateGroup {}  \u002F\u002F 创建时校验规则\npublic interface UpdateGroup {}  \u002F\u002F 更新时校验规则\npublic interface QueryGroup {}   \u002F\u002F 查询时校验规则\n\npublic class UserGroupDTO {\n    \n    \u002F\u002F 更新时ID不能为空，创建时ID必须为空\n    @Null(groups = CreateGroup.class, message = \"创建时ID必须为空\")\n    @NotNull(groups = UpdateGroup.class, message = \"更新时ID不能为空\")\n    private Long id;\n    \n    @NotBlank(groups = {CreateGroup.class, UpdateGroup.class}, \n             message = \"用户名不能为空\")\n    @Size(min = 2, max = 20, groups = {CreateGroup.class, UpdateGroup.class})\n    private String username;\n    \n    \u002F\u002F 创建时必须提供密码，更新时密码可选\n    @NotBlank(groups = CreateGroup.class, message = \"密码不能为空\")\n    @Size(min = 6, groups = CreateGroup.class, message = \"密码至少6位\")\n    private String password;\n    \n    \u002F\u002F getter\u002Fsetter\n}\n\n@RestController\n@RequestMapping(\"\u002Fapi\u002Fgroup-users\")\npublic class UserGroupController {\n    \n    @PostMapping\n    public ResponseEntity\u003CResult\u003CString>> createUser(\n            @Validated(CreateGroup.class) @RequestBody UserGroupDTO userDTO) {\n        \u002F\u002F 创建用户业务逻辑\n        return ResponseEntity.ok(Result.success(\"用户创建成功\"));\n    }\n    \n    @PutMapping\n    public ResponseEntity\u003CResult\u003CString>> updateUser(\n            @Validated(UpdateGroup.class) @RequestBody UserGroupDTO userDTO) {\n        \u002F\u002F 更新用户业务逻辑\n        return ResponseEntity.ok(Result.success(\"用户更新成功\"));\n    }\n}\n```\n\n### 5.2 方法级别参数校验\n\n```java\n@Service\n@Validated  \u002F\u002F 类级别添加@Validated，开启方法参数校验\n@Slf4j\npublic class UserService {\n    \n    \u002F**\n     * 方法参数校验：基本类型和包装类型\n     *\u002F\n    public UserDTO getUserById(@NotNull(message = \"用户ID不能为空\") Long id) {\n        log.info(\"查询用户ID: {}\", id);\n        \u002F\u002F 业务逻辑\n        return new UserDTO();\n    }\n    \n    \u002F**\n     * 方法参数校验：数值范围\n     *\u002F\n    public void updateAge(@Min(0) @Max(150) Integer age) {\n        log.info(\"更新年龄: {}\", age);\n        \u002F\u002F 业务逻辑\n    }\n    \n    \u002F**\n     * 多个参数校验\n     *\u002F\n    public List\u003CUserDTO> searchUsers(\n            @NotBlank String keyword,\n            @Min(1) Integer page,\n            @Range(min = 1, max = 100) Integer size) {\n        log.info(\"搜索用户: {}, 页码: {}, 大小: {}\", keyword, page, size);\n        return new ArrayList\u003C>();\n    }\n}\n\n@RestController\n@RequestMapping(\"\u002Fapi\u002Fservice-users\")\npublic class UserServiceController {\n    \n    @Autowired\n    private UserService userService;\n    \n    @GetMapping(\"\u002F{id}\")\n    public ResponseEntity\u003CResult\u003CUserDTO>> getUser(@PathVariable Long id) {\n        UserDTO user = userService.getUserById(id);\n        return ResponseEntity.ok(Result.success(user));\n    }\n    \n    @GetMapping(\"\u002Fsearch\")\n    public ResponseEntity\u003CResult\u003CList\u003CUserDTO>>> searchUsers(\n            @RequestParam String keyword,\n            @RequestParam(defaultValue = \"1\") Integer page,\n            @RequestParam(defaultValue = \"20\") Integer size) {\n        List\u003CUserDTO> users = userService.searchUsers(keyword, page, size);\n        return ResponseEntity.ok(Result.success(users));\n    }\n}\n```\n\n## 6. 自定义校验注解实战\n\n### 6.1 创建手机号校验注解\n\n```java\n\u002F**\n * 手机号校验注解\n *\u002F\n@Target({ElementType.FIELD, ElementType.PARAMETER})\n@Retention(RetentionPolicy.RUNTIME)\n@Constraint(validatedBy = PhoneValidator.class)\n@Documented\npublic @interface Phone {\n    \n    String message() default \"手机号格式不正确\";\n    \n    Class\u003C?>[] groups() default {};\n    \n    Class\u003C? extends Payload>[] payload() default {};\n}\n```\n\n### 6.2 实现校验逻辑\n\n```java\n\u002F**\n * 手机号校验器\n *\u002F\npublic class PhoneValidator implements ConstraintValidator\u003CPhone, String> {\n    \n    \u002F\u002F 手机号正则：1开头，第二位3-9，后面9位数字\n    private static final Pattern PHONE_PATTERN = \n        Pattern.compile(\"^1[3-9]\\\\d{9}$\");\n    \n    @Override\n    public void initialize(Phone constraintAnnotation) {\n        \u002F\u002F 初始化方法，可以获取注解中的参数\n    }\n    \n    @Override\n    public boolean isValid(String value, ConstraintValidatorContext context) {\n        \u002F\u002F 为空时跳过校验（结合@NotNull等注解使用）\n        if (value == null || value.trim().isEmpty()) {\n            return true;\n        }\n        \n        boolean matches = PHONE_PATTERN.matcher(value).matches();\n        \n        \u002F\u002F 自定义错误信息\n        if (!matches) {\n            context.disableDefaultConstraintViolation();\n            context.buildConstraintViolationWithTemplate(\"手机号格式不正确，应为11位数字且以1开头\")\n                   .addConstraintViolation();\n        }\n        \n        return matches;\n    }\n}\n```\n\n### 6.3 使用自定义注解\n\n```java\npublic class CustomUserDTO {\n    \n    @NotBlank(message = \"用户名不能为空\")\n    private String username;\n    \n    @Phone(message = \"请输入正确的手机号\")\n    private String phone;\n    \n    \u002F\u002F 自定义注解支持分组\n    @Phone(groups = CreateGroup.class)\n    private String contactPhone;\n    \n    \u002F\u002F getter\u002Fsetter\n}\n```\n\n## 7. 全局异常处理最佳实践\n\n```java\n\u002F**\n * 统一异常处理器\n *\u002F\n@RestControllerAdvice\n@Slf4j\npublic class GlobalExceptionHandler {\n    \n    \u002F**\n     * 处理@RequestBody参数校验异常\n     *\u002F\n    @ExceptionHandler(MethodArgumentNotValidException.class)\n    public ResponseEntity\u003CResult\u003C?>> handleMethodArgumentNotValidException(\n            MethodArgumentNotValidException ex) {\n        \n        log.warn(\"参数校验失败: {}\", ex.getMessage());\n        \n        \u002F\u002F 提取详细的字段错误信息\n        List\u003CFieldErrorVO> errors = ex.getBindingResult()\n                .getFieldErrors()\n                .stream()\n                .map(error -> new FieldErrorVO(\n                    error.getField(),\n                    error.getRejectedValue(),\n                    error.getDefaultMessage()))\n                .collect(Collectors.toList());\n        \n        Result\u003CList\u003CFieldErrorVO>> result = Result.fail(\"参数校验失败\", errors);\n        return ResponseEntity.badRequest().body(result);\n    }\n    \n    \u002F**\n     * 处理@RequestParam、@PathVariable参数校验异常\n     *\u002F\n    @ExceptionHandler(ConstraintViolationException.class)\n    public ResponseEntity\u003CResult\u003C?>> handleConstraintViolationException(\n            ConstraintViolationException ex) {\n        \n        log.warn(\"参数校验失败: {}\", ex.getMessage());\n        \n        List\u003CFieldErrorVO> errors = ex.getConstraintViolations()\n                .stream()\n                .map(violation -> {\n                    String path = violation.getPropertyPath().toString();\n                    \u002F\u002F 提取方法参数名\n                    String fieldName = path.contains(\".\") ? \n                        path.substring(path.lastIndexOf(\".\") + 1) : path;\n                    return new FieldErrorVO(\n                        fieldName,\n                        violation.getInvalidValue(),\n                        violation.getMessage());\n                })\n                .collect(Collectors.toList());\n        \n        Result\u003CList\u003CFieldErrorVO>> result = Result.fail(\"参数校验失败\", errors);\n        return ResponseEntity.badRequest().body(result);\n    }\n    \n    \u002F**\n     * 统一返回结果封装\n     *\u002F\n    @Data\n    @AllArgsConstructor\n    public static class Result\u003CT> {\n        private Boolean success;\n        private String message;\n        private T data;\n        private Long timestamp;\n        \n        public static \u003CT> Result\u003CT> success(T data) {\n            return new Result\u003C>(true, \"成功\", data, System.currentTimeMillis());\n        }\n        \n        public static \u003CT> Result\u003CT> fail(String message, T data) {\n            return new Result\u003C>(false, message, data, System.currentTimeMillis());\n        }\n    }\n    \n    \u002F**\n     * 字段错误信息封装\n     *\u002F\n    @Data\n    @AllArgsConstructor\n    public static class FieldErrorVO {\n        private String field;\n        private Object rejectedValue;\n        private String message;\n    }\n}\n```\n\n## 8. 完整实战示例\n\n### 8.1 Maven 依赖配置\n\n```xml\n\u003C!-- Spring Boot Web Starter -->\n\u003Cdependency>\n    \u003CgroupId>org.springframework.boot\u003C\u002FgroupId>\n    \u003CartifactId>spring-boot-starter-web\u003C\u002FartifactId>\n\u003C\u002Fdependency>\n\n\u003C!-- Validation Starter -->\n\u003Cdependency>\n    \u003CgroupId>org.springframework.boot\u003C\u002FgroupId>\n    \u003CartifactId>spring-boot-starter-validation\u003C\u002FartifactId>\n\u003C\u002Fdependency>\n\n\u003C!-- Lombok（可选） -->\n\u003Cdependency>\n    \u003CgroupId>org.projectlombok\u003C\u002FgroupId>\n    \u003CartifactId>lombok\u003C\u002FartifactId>\n    \u003Coptional>true\u003C\u002Foptional>\n\u003C\u002Fdependency>\n```\n\n### 8.2 综合控制器示例\n\n```java\n@RestController\n@RequestMapping(\"\u002Fapi\u002Fcomplete-users\")\n@Validated\n@Slf4j\npublic class CompleteUserController {\n    \n    @Autowired\n    private UserService userService;\n    \n    \u002F**\n     * 创建用户 - @RequestBody校验\n     *\u002F\n    @PostMapping\n    public ResponseEntity\u003CResult\u003CString>> createUser(\n            @Valid @RequestBody UserDTO userDTO) {\n        \u002F\u002F 业务处理\n        userService.createUser(userDTO);\n        return ResponseEntity.ok(Result.success(\"用户创建成功\"));\n    }\n    \n    \u002F**\n     * 批量创建 - 嵌套集合校验\n     *\u002F\n    @PostMapping(\"\u002Fbatch\")\n    public ResponseEntity\u003CResult\u003CString>> createUsers(\n            @Valid @RequestBody BatchUserDTO batchUserDTO) {\n        log.info(\"批量创建用户数量: {}\", batchUserDTO.getUsers().size());\n        return ResponseEntity.ok(Result.success(\"批量创建用户成功\"));\n    }\n    \n    \u002F**\n     * 查询用户 - 方法参数校验\n     *\u002F\n    @GetMapping(\"\u002Fsearch\")\n    public ResponseEntity\u003CResult\u003CList\u003CUserDTO>>> searchUsers(\n            @RequestParam @NotBlank String keyword,\n            @RequestParam @Min(value = 1, message = \"页码必须大于0\") Integer page,\n            @RequestParam @Range(min = 1, max = 100, message = \"每页大小1-100\") Integer size) {\n        \n        List\u003CUserDTO> users = userService.searchUsers(keyword, page, size);\n        return ResponseEntity.ok(Result.success(users));\n    }\n    \n    \u002F**\n     * 分组校验示例\n     *\u002F\n    @PostMapping(\"\u002Fgroup\")\n    public ResponseEntity\u003CResult\u003CString>> createUserWithGroup(\n            @Validated(CreateGroup.class) @RequestBody UserGroupDTO userDTO) {\n        return ResponseEntity.ok(Result.success(\"分组校验创建成功\"));\n    }\n    \n    @PutMapping(\"\u002Fgroup\")\n    public ResponseEntity\u003CResult\u003CString>> updateUserWithGroup(\n            @Validated(UpdateGroup.class) @RequestBody UserGroupDTO userDTO) {\n        return ResponseEntity.ok(Result.success(\"分组校验更新成功\"));\n    }\n}\n```\n\n## 9. 高级特性与最佳实践\n\n### 9.1 校验顺序控制\n\n```java\npublic class OrderedUserDTO {\n    \n    @NotBlank(message = \"用户名不能为空\", groups = FirstPriority.class)\n    private String username;\n    \n    @Email(message = \"邮箱格式不正确\", groups = SecondPriority.class)\n    private String email;\n    \n    \u002F\u002F 定义校验顺序接口\n    public interface FirstPriority {}\n    public interface SecondPriority {}\n}\n\n\u002F\u002F 使用@Validated指定校验顺序\n@PostMapping(\"\u002Fordered\")\npublic ResponseEntity\u003CString> createOrderedUser(\n        @Validated({FirstPriority.class, SecondPriority.class}) \n        @RequestBody OrderedUserDTO user) {\n    return ResponseEntity.ok(\"顺序校验成功\");\n}\n```\n\n### 9.2 条件性校验\n\n```java\npublic class ConditionalUserDTO {\n    \n    @NotBlank(message = \"用户名不能为空\")\n    private String username;\n    \n    \u002F\u002F 当type为1时，email不能为空\n    @AssertTrue(message = \"类型为1时必须填写邮箱\")\n    private boolean isEmailValid() {\n        return type != 1 || email != null;\n    }\n    \n    private Integer type;\n    private String email;\n}\n```\n\n### 9.3 国际化消息支持\n\n```properties\n# messages.properties\nuser.id.notnull=用户ID不能为空\nuser.name.size=用户名长度必须在{min}到{max}之间\n```\n\n```java\npublic class I18nUserDTO {\n    @NotNull(message = \"{user.id.notnull}\")\n    private Long id;\n    \n    @Size(min = 2, max = 20, message = \"{user.name.size}\")\n    private String username;\n}\n```\n\n## 10. 性能优化与生产建议\n\n### 10.1 校验性能优化\n\n```java\n@Configuration\npublic class ValidationConfig {\n    \n    \u002F**\n     * 自定义验证器，避免重复创建\n     *\u002F\n    @Bean\n    public Validator validator() {\n        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();\n        return factory.getValidator();\n    }\n}\n```\n\n### 10.2 生产环境最佳实践\n\n1. **分层校验策略**：\n   - Controller层：基础格式校验\n   - Service层：业务规则校验\n   - 数据库层：数据完整性校验\n\n2. **错误信息设计**：\n   - 前端友好：避免技术性描述\n   - 安全考虑：不暴露系统信息\n   - 一致性：统一错误格式\n\n3. **校验粒度控制**：\n   - 创建操作：严格校验\n   - 更新操作：选择性校验\n   - 查询操作：基础校验\n\n## 11. 总结与选择指南\n\n通过本文的详细讲解，我们可以看到 Spring Boot 校验体系的强大之处。在实际项目中：\n\n**选择 @Valid 的情况**：\n- 简单的对象图校验\n- 与 JPA 实体校验保持一致性\n- 不需要分组校验的简单场景\n\n**选择 @Validated 的情况**：\n- 需要分组校验（如按业务场景区分）\n- 方法参数校验（如Service层方法）\n- 需要更细粒度控制的复杂场景\n\n**核心价值**：\n- 提高代码健壮性，减少异常处理代码\n- 统一校验逻辑，提高代码可维护性\n- 提升开发效率，减少重复校验代码\n- 改善用户体验，提供即时反馈\n\n掌握 `@Valid` 和 `@Validated` 的正确使用，能够让你的 Spring Boot 应用在数据校验方面更加专业和可靠。根据具体业务场景灵活选择，才能发挥最大的效能。","\u003Ch2 data-line=\"0\" id=\"1. 参数校验概述\">1. 参数校验概述\u003C\u002Fh2>\n\u003Cp data-line=\"2\">在现代化的 Web 应用开发中，参数校验是保障系统健壮性的第一道防线。Spring Boot 通过集成 Bean Validation 规范，为我们提供了强大而灵活的校验机制。其中，\u003Ccode>@Valid\u003C\u002Fcode> 和 \u003Ccode>@Validated\u003C\u002Fcode> 是两个核心注解，它们虽然功能相似，但在不同场景下各有优势。\u003C\u002Fp>\n\u003Cp data-line=\"4\">参数校验的主要目的：\u003C\u002Fp>\n\u003Cul data-line=\"5\">\n\u003Cli data-line=\"5\">\u003Cstrong>数据完整性\u003C\u002Fstrong>：确保接收到的数据符合预期格式\u003C\u002Fli>\n\u003Cli data-line=\"6\">\u003Cstrong>业务安全\u003C\u002Fstrong>：防止恶意或错误数据进入业务逻辑层\u003C\u002Fli>\n\u003Cli data-line=\"7\">\u003Cstrong>用户体验\u003C\u002Fstrong>：前端与后端校验结合，提供即时反馈\u003C\u002Fli>\n\u003Cli data-line=\"8\">\u003Cstrong>系统稳定\u003C\u002Fstrong>：减少因数据异常导致的系统崩溃\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 data-line=\"10\" id=\"2. @Valid 和 @Validated 的核心区别\">2. @Valid 和 @Validated 的核心区别\u003C\u002Fh2>\n\u003Ctable data-line=\"12\">\n\u003Cthead data-line=\"12\">\n\u003Ctr data-line=\"12\">\n\u003Cth>特性\u003C\u002Fth>\n\u003Cth>@Valid\u003C\u002Fth>\n\u003Cth>@Validated\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody data-line=\"14\">\n\u003Ctr data-line=\"14\">\n\u003Ctd>\u003Cstrong>来源\u003C\u002Fstrong>\u003C\u002Ftd>\n\u003Ctd>JSR-303\u002FJSR-380 标准 (javax.validation)\u003C\u002Ftd>\n\u003Ctd>Spring 框架自定义\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr data-line=\"15\">\n\u003Ctd>\u003Cstrong>分组校验\u003C\u002Fstrong>\u003C\u002Ftd>\n\u003Ctd>不支持\u003C\u002Ftd>\n\u003Ctd>支持\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr data-line=\"16\">\n\u003Ctd>\u003Cstrong>嵌套校验\u003C\u002Fstrong>\u003C\u002Ftd>\n\u003Ctd>支持\u003C\u002Ftd>\n\u003Ctd>支持\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr data-line=\"17\">\n\u003Ctd>\u003Cstrong>使用场景\u003C\u002Fstrong>\u003C\u002Ftd>\n\u003Ctd>方法参数、对象属性\u003C\u002Ftd>\n\u003Ctd>方法参数、类、方法\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr data-line=\"18\">\n\u003Ctd>\u003Cstrong>校验顺序\u003C\u002Fstrong>\u003C\u002Ftd>\n\u003Ctd>不支持\u003C\u002Ftd>\n\u003Ctd>支持\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr data-line=\"19\">\n\u003Ctd>\u003Cstrong>校验级别\u003C\u002Fstrong>\u003C\u002Ftd>\n\u003Ctd>对象图级别\u003C\u002Ftd>\n\u003Ctd>更细粒度的控制\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp data-line=\"21\">\u003Cstrong>选择建议\u003C\u002Fstrong>：\u003C\u002Fp>\n\u003Cul data-line=\"22\">\n\u003Cli data-line=\"22\">简单对象校验 → 使用 \u003Ccode>@Valid\u003C\u002Fcode>\u003C\u002Fli>\n\u003Cli data-line=\"23\">需要分组校验 → 使用 \u003Ccode>@Validated\u003C\u002Fcode>\u003C\u002Fli>\n\u003Cli data-line=\"24\">方法参数校验 → 使用 \u003Ccode>@Validated\u003C\u002Fcode>\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 data-line=\"26\" id=\"3. 常用校验注解详解\">3. 常用校验注解详解\u003C\u002Fh2>\n\u003Ch3 data-line=\"28\" id=\"3.1 基础校验注解实战\">3.1 基础校验注解实战\u003C\u002Fh3>\n\n        \u003Cdetails  data-line=\"30\" class=\"md-editor-code\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">java\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-java\" language=java>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">class\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">UserDTO\u003C\u002Fspan> {\n    \n    \u003Cspan class=\"hljs-meta\">@NotNull(message = &quot;ID不能为空&quot;)\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">private\u003C\u002Fspan> Long id;\n    \n    \u003Cspan class=\"hljs-meta\">@NotBlank(message = &quot;用户名不能为空&quot;)\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-meta\">@Size(min = 2, max = 20, message = &quot;用户名长度必须在2-20之间&quot;)\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">private\u003C\u002Fspan> String username;\n    \n    \u003Cspan class=\"hljs-meta\">@Email(message = &quot;邮箱格式不正确&quot;)\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">private\u003C\u002Fspan> String email;\n    \n    \u003Cspan class=\"hljs-meta\">@Min(value = 0, message = &quot;年龄不能小于0&quot;)\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-meta\">@Max(value = 150, message = &quot;年龄不能大于150&quot;)\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">private\u003C\u002Fspan> Integer age;\n    \n    \u003Cspan class=\"hljs-meta\">@Pattern(regexp = &quot;^1[3-9]\\\\d{9}$&quot;, message = &quot;手机号格式不正确&quot;)\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">private\u003C\u002Fspan> String phone;\n    \n    \u003Cspan class=\"hljs-meta\">@AssertTrue(message = &quot;必须同意协议&quot;)\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">private\u003C\u002Fspan> Boolean agreed;\n    \n    \u003Cspan class=\"hljs-meta\">@Future(message = &quot;日期必须是未来时间&quot;)\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">private\u003C\u002Fspan> LocalDateTime appointmentTime;\n    \n    \u003Cspan class=\"hljs-meta\">@DecimalMin(value = &quot;0.0&quot;, inclusive = false, message = &quot;金额必须大于0&quot;)\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-meta\">@Digits(integer = 10, fraction = 2, message = &quot;金额格式不正确&quot;)\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">private\u003C\u002Fspan> BigDecimal amount;\n    \n    \u003Cspan class=\"hljs-comment\">\u002F\u002F 构造方法、getter、setter 省略\u003C\u002Fspan>\n}\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Cp data-line=\"64\">\u003Cstrong>注解说明\u003C\u002Fstrong>：\u003C\u002Fp>\n\u003Cul data-line=\"65\">\n\u003Cli data-line=\"65\">\u003Ccode>@NotNull\u003C\u002Fcode>：值不能为null，但可以为空字符串\u003C\u002Fli>\n\u003Cli data-line=\"66\">\u003Ccode>@NotBlank\u003C\u002Fcode>：字符串不能为null且必须包含非空白字符\u003C\u002Fli>\n\u003Cli data-line=\"67\">\u003Ccode>@NotEmpty\u003C\u002Fcode>：集合、数组、字符串不能为null或空\u003C\u002Fli>\n\u003Cli data-line=\"68\">\u003Ccode>@Pattern\u003C\u002Fcode>：使用正则表达式验证字符串格式\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch3 data-line=\"70\" id=\"3.2 集合与嵌套校验实战\">3.2 集合与嵌套校验实战\u003C\u002Fh3>\n\n        \u003Cdetails  data-line=\"72\" class=\"md-editor-code\" open=\"\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">java\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-java\" language=java>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">class\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">BatchUserDTO\u003C\u002Fspan> {\n    \n    \u003Cspan class=\"hljs-meta\">@NotEmpty(message = &quot;用户列表不能为空&quot;)\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-meta\">@Valid\u003C\u002Fspan>  \u003Cspan class=\"hljs-comment\">\u002F\u002F 关键：必须添加此注解才能校验集合内元素\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">private\u003C\u002Fspan> List&lt;\u003Cspan class=\"hljs-meta\">@Valid\u003C\u002Fspan> UserDTO&gt; users; \u003Cspan class=\"hljs-comment\">\u002F\u002F Java 8+ 写法\u003C\u002Fspan>\n    \n    \u003Cspan class=\"hljs-comment\">\u002F\u002F 嵌套对象校验示例\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-meta\">@Valid\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-meta\">@NotNull(message = &quot;创建者信息不能为空&quot;)\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">private\u003C\u002Fspan> UserDTO creator;\n    \n    \u003Cspan class=\"hljs-comment\">\u002F\u002F getter\u002Fsetter\u003C\u002Fspan>\n}\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Ch2 data-line=\"88\" id=\"4. @Valid 深度使用指南\">4. @Valid 深度使用指南\u003C\u002Fh2>\n\u003Ch3 data-line=\"90\" id=\"4.1 Controller 层基础校验\">4.1 Controller 层基础校验\u003C\u002Fh3>\n\n        \u003Cdetails  data-line=\"92\" class=\"md-editor-code\" open=\"\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">java\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-java\" language=java>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"hljs-meta\">@RestController\u003C\u002Fspan>\n\u003Cspan class=\"hljs-meta\">@RequestMapping(&quot;\u002Fapi\u002Fusers&quot;)\u003C\u002Fspan>\n\u003Cspan class=\"hljs-meta\">@Slf4j\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">class\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">UserController\u003C\u002Fspan> {\n    \n    \u003Cspan class=\"hljs-meta\">@PostMapping\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> ResponseEntity&lt;Result&lt;String&gt;&gt; \u003Cspan class=\"hljs-title function_\">createUser\u003C\u002Fspan>\u003Cspan class=\"hljs-params\">(\n            \u003Cspan class=\"hljs-meta\">@Valid\u003C\u002Fspan> \u003Cspan class=\"hljs-meta\">@RequestBody\u003C\u002Fspan> UserDTO userDTO)\u003C\u002Fspan> {\n        \u003Cspan class=\"hljs-comment\">\u002F\u002F 只有当校验通过时，才会执行到此处的业务逻辑\u003C\u002Fspan>\n        log.info(\u003Cspan class=\"hljs-string\">&quot;创建用户: {}&quot;\u003C\u002Fspan>, userDTO.getUsername());\n        \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> ResponseEntity.ok(Result.success(\u003Cspan class=\"hljs-string\">&quot;用户创建成功&quot;\u003C\u002Fspan>));\n    }\n    \n    \u003Cspan class=\"hljs-comment\">\u002F\u002F 路径变量和查询参数校验需要结合@Validated使用\u003C\u002Fspan>\n}\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Ch3 data-line=\"110\" id=\"4.2 复杂嵌套对象校验\">4.2 复杂嵌套对象校验\u003C\u002Fh3>\n\n        \u003Cdetails  data-line=\"112\" class=\"md-editor-code\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">java\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-java\" language=java>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">class\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">AddressDTO\u003C\u002Fspan> {\n    \u003Cspan class=\"hljs-meta\">@NotBlank(message = &quot;省份不能为空&quot;)\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">private\u003C\u002Fspan> String province;\n    \n    \u003Cspan class=\"hljs-meta\">@NotBlank(message = &quot;城市不能为空&quot;)\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">private\u003C\u002Fspan> String city;\n    \n    \u003Cspan class=\"hljs-meta\">@NotBlank(message = &quot;详细地址不能为空&quot;)\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">private\u003C\u002Fspan> String detail;\n    \n    \u003Cspan class=\"hljs-comment\">\u002F\u002F getter\u002Fsetter\u003C\u002Fspan>\n}\n\n\u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">class\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">UserDetailDTO\u003C\u002Fspan> {\n    \u003Cspan class=\"hljs-meta\">@NotBlank(message = &quot;用户名不能为空&quot;)\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">private\u003C\u002Fspan> String username;\n    \n    \u003Cspan class=\"hljs-meta\">@Valid\u003C\u002Fspan>  \u003Cspan class=\"hljs-comment\">\u002F\u002F 关键注解：触发嵌套对象校验\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-meta\">@NotNull(message = &quot;地址信息不能为空&quot;)\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">private\u003C\u002Fspan> AddressDTO address;\n    \n    \u003Cspan class=\"hljs-comment\">\u002F\u002F 列表嵌套校验\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-meta\">@Valid\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">private\u003C\u002Fspan> List&lt;AddressDTO&gt; addresses;\n    \n    \u003Cspan class=\"hljs-comment\">\u002F\u002F getter\u002Fsetter\u003C\u002Fspan>\n}\n\n\u003Cspan class=\"hljs-meta\">@PostMapping(&quot;\u002Fdetail&quot;)\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> ResponseEntity&lt;Result&lt;String&gt;&gt; \u003Cspan class=\"hljs-title function_\">createUserDetail\u003C\u002Fspan>\u003Cspan class=\"hljs-params\">(\n        \u003Cspan class=\"hljs-meta\">@Valid\u003C\u002Fspan> \u003Cspan class=\"hljs-meta\">@RequestBody\u003C\u002Fspan> UserDetailDTO userDetail)\u003C\u002Fspan> {\n    \u003Cspan class=\"hljs-comment\">\u002F\u002F 处理业务逻辑\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> ResponseEntity.ok(Result.success(\u003Cspan class=\"hljs-string\">&quot;用户详情创建成功&quot;\u003C\u002Fspan>));\n}\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Ch2 data-line=\"149\" id=\"5. @Validated 高级特性实战\">5. @Validated 高级特性实战\u003C\u002Fh2>\n\u003Ch3 data-line=\"151\" id=\"5.1 分组校验：按场景精细化控制\">5.1 分组校验：按场景精细化控制\u003C\u002Fh3>\n\n        \u003Cdetails  data-line=\"153\" class=\"md-editor-code\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">java\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-java\" language=java>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"hljs-comment\">\u002F\u002F 定义校验分组接口\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">interface\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">CreateGroup\u003C\u002Fspan> {}  \u003Cspan class=\"hljs-comment\">\u002F\u002F 创建时校验规则\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">interface\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">UpdateGroup\u003C\u002Fspan> {}  \u003Cspan class=\"hljs-comment\">\u002F\u002F 更新时校验规则\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">interface\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">QueryGroup\u003C\u002Fspan> {}   \u003Cspan class=\"hljs-comment\">\u002F\u002F 查询时校验规则\u003C\u002Fspan>\n\n\u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">class\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">UserGroupDTO\u003C\u002Fspan> {\n    \n    \u003Cspan class=\"hljs-comment\">\u002F\u002F 更新时ID不能为空，创建时ID必须为空\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-meta\">@Null(groups = CreateGroup.class, message = &quot;创建时ID必须为空&quot;)\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-meta\">@NotNull(groups = UpdateGroup.class, message = &quot;更新时ID不能为空&quot;)\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">private\u003C\u002Fspan> Long id;\n    \n    \u003Cspan class=\"hljs-meta\">@NotBlank(groups = {CreateGroup.class, UpdateGroup.class}, \n             message = &quot;用户名不能为空&quot;)\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-meta\">@Size(min = 2, max = 20, groups = {CreateGroup.class, UpdateGroup.class})\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">private\u003C\u002Fspan> String username;\n    \n    \u003Cspan class=\"hljs-comment\">\u002F\u002F 创建时必须提供密码，更新时密码可选\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-meta\">@NotBlank(groups = CreateGroup.class, message = &quot;密码不能为空&quot;)\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-meta\">@Size(min = 6, groups = CreateGroup.class, message = &quot;密码至少6位&quot;)\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">private\u003C\u002Fspan> String password;\n    \n    \u003Cspan class=\"hljs-comment\">\u002F\u002F getter\u002Fsetter\u003C\u002Fspan>\n}\n\n\u003Cspan class=\"hljs-meta\">@RestController\u003C\u002Fspan>\n\u003Cspan class=\"hljs-meta\">@RequestMapping(&quot;\u002Fapi\u002Fgroup-users&quot;)\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">class\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">UserGroupController\u003C\u002Fspan> {\n    \n    \u003Cspan class=\"hljs-meta\">@PostMapping\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> ResponseEntity&lt;Result&lt;String&gt;&gt; \u003Cspan class=\"hljs-title function_\">createUser\u003C\u002Fspan>\u003Cspan class=\"hljs-params\">(\n            \u003Cspan class=\"hljs-meta\">@Validated(CreateGroup.class)\u003C\u002Fspan> \u003Cspan class=\"hljs-meta\">@RequestBody\u003C\u002Fspan> UserGroupDTO userDTO)\u003C\u002Fspan> {\n        \u003Cspan class=\"hljs-comment\">\u002F\u002F 创建用户业务逻辑\u003C\u002Fspan>\n        \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> ResponseEntity.ok(Result.success(\u003Cspan class=\"hljs-string\">&quot;用户创建成功&quot;\u003C\u002Fspan>));\n    }\n    \n    \u003Cspan class=\"hljs-meta\">@PutMapping\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> ResponseEntity&lt;Result&lt;String&gt;&gt; \u003Cspan class=\"hljs-title function_\">updateUser\u003C\u002Fspan>\u003Cspan class=\"hljs-params\">(\n            \u003Cspan class=\"hljs-meta\">@Validated(UpdateGroup.class)\u003C\u002Fspan> \u003Cspan class=\"hljs-meta\">@RequestBody\u003C\u002Fspan> UserGroupDTO userDTO)\u003C\u002Fspan> {\n        \u003Cspan class=\"hljs-comment\">\u002F\u002F 更新用户业务逻辑\u003C\u002Fspan>\n        \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> ResponseEntity.ok(Result.success(\u003Cspan class=\"hljs-string\">&quot;用户更新成功&quot;\u003C\u002Fspan>));\n    }\n}\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Ch3 data-line=\"199\" id=\"5.2 方法级别参数校验\">5.2 方法级别参数校验\u003C\u002Fh3>\n\n        \u003Cdetails  data-line=\"201\" class=\"md-editor-code\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">java\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-java\" language=java>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"hljs-meta\">@Service\u003C\u002Fspan>\n\u003Cspan class=\"hljs-meta\">@Validated\u003C\u002Fspan>  \u003Cspan class=\"hljs-comment\">\u002F\u002F 类级别添加@Validated，开启方法参数校验\u003C\u002Fspan>\n\u003Cspan class=\"hljs-meta\">@Slf4j\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">class\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">UserService\u003C\u002Fspan> {\n    \n    \u003Cspan class=\"hljs-comment\">\u002F**\n     * 方法参数校验：基本类型和包装类型\n     *\u002F\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> UserDTO \u003Cspan class=\"hljs-title function_\">getUserById\u003C\u002Fspan>\u003Cspan class=\"hljs-params\">(\u003Cspan class=\"hljs-meta\">@NotNull(message = &quot;用户ID不能为空&quot;)\u003C\u002Fspan> Long id)\u003C\u002Fspan> {\n        log.info(\u003Cspan class=\"hljs-string\">&quot;查询用户ID: {}&quot;\u003C\u002Fspan>, id);\n        \u003Cspan class=\"hljs-comment\">\u002F\u002F 业务逻辑\u003C\u002Fspan>\n        \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">UserDTO\u003C\u002Fspan>();\n    }\n    \n    \u003Cspan class=\"hljs-comment\">\u002F**\n     * 方法参数校验：数值范围\n     *\u002F\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">void\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">updateAge\u003C\u002Fspan>\u003Cspan class=\"hljs-params\">(\u003Cspan class=\"hljs-meta\">@Min(0)\u003C\u002Fspan> \u003Cspan class=\"hljs-meta\">@Max(150)\u003C\u002Fspan> Integer age)\u003C\u002Fspan> {\n        log.info(\u003Cspan class=\"hljs-string\">&quot;更新年龄: {}&quot;\u003C\u002Fspan>, age);\n        \u003Cspan class=\"hljs-comment\">\u002F\u002F 业务逻辑\u003C\u002Fspan>\n    }\n    \n    \u003Cspan class=\"hljs-comment\">\u002F**\n     * 多个参数校验\n     *\u002F\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> List&lt;UserDTO&gt; \u003Cspan class=\"hljs-title function_\">searchUsers\u003C\u002Fspan>\u003Cspan class=\"hljs-params\">(\n            \u003Cspan class=\"hljs-meta\">@NotBlank\u003C\u002Fspan> String keyword,\n            \u003Cspan class=\"hljs-meta\">@Min(1)\u003C\u002Fspan> Integer page,\n            \u003Cspan class=\"hljs-meta\">@Range(min = 1, max = 100)\u003C\u002Fspan> Integer size)\u003C\u002Fspan> {\n        log.info(\u003Cspan class=\"hljs-string\">&quot;搜索用户: {}, 页码: {}, 大小: {}&quot;\u003C\u002Fspan>, keyword, page, size);\n        \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">ArrayList\u003C\u002Fspan>&lt;&gt;();\n    }\n}\n\n\u003Cspan class=\"hljs-meta\">@RestController\u003C\u002Fspan>\n\u003Cspan class=\"hljs-meta\">@RequestMapping(&quot;\u002Fapi\u002Fservice-users&quot;)\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">class\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">UserServiceController\u003C\u002Fspan> {\n    \n    \u003Cspan class=\"hljs-meta\">@Autowired\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">private\u003C\u002Fspan> UserService userService;\n    \n    \u003Cspan class=\"hljs-meta\">@GetMapping(&quot;\u002F{id}&quot;)\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> ResponseEntity&lt;Result&lt;UserDTO&gt;&gt; \u003Cspan class=\"hljs-title function_\">getUser\u003C\u002Fspan>\u003Cspan class=\"hljs-params\">(\u003Cspan class=\"hljs-meta\">@PathVariable\u003C\u002Fspan> Long id)\u003C\u002Fspan> {\n        \u003Cspan class=\"hljs-type\">UserDTO\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">user\u003C\u002Fspan> \u003Cspan class=\"hljs-operator\">=\u003C\u002Fspan> userService.getUserById(id);\n        \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> ResponseEntity.ok(Result.success(user));\n    }\n    \n    \u003Cspan class=\"hljs-meta\">@GetMapping(&quot;\u002Fsearch&quot;)\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> ResponseEntity&lt;Result&lt;List&lt;UserDTO&gt;&gt;&gt; \u003Cspan class=\"hljs-title function_\">searchUsers\u003C\u002Fspan>\u003Cspan class=\"hljs-params\">(\n            \u003Cspan class=\"hljs-meta\">@RequestParam\u003C\u002Fspan> String keyword,\n            \u003Cspan class=\"hljs-meta\">@RequestParam(defaultValue = &quot;1&quot;)\u003C\u002Fspan> Integer page,\n            \u003Cspan class=\"hljs-meta\">@RequestParam(defaultValue = &quot;20&quot;)\u003C\u002Fspan> Integer size)\u003C\u002Fspan> {\n        List&lt;UserDTO&gt; users = userService.searchUsers(keyword, page, size);\n        \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> ResponseEntity.ok(Result.success(users));\n    }\n}\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Ch2 data-line=\"260\" id=\"6. 自定义校验注解实战\">6. 自定义校验注解实战\u003C\u002Fh2>\n\u003Ch3 data-line=\"262\" id=\"6.1 创建手机号校验注解\">6.1 创建手机号校验注解\u003C\u002Fh3>\n\n        \u003Cdetails  data-line=\"264\" class=\"md-editor-code\" open=\"\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">java\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-java\" language=java>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"hljs-comment\">\u002F**\n * 手机号校验注解\n *\u002F\u003C\u002Fspan>\n\u003Cspan class=\"hljs-meta\">@Target({ElementType.FIELD, ElementType.PARAMETER})\u003C\u002Fspan>\n\u003Cspan class=\"hljs-meta\">@Retention(RetentionPolicy.RUNTIME)\u003C\u002Fspan>\n\u003Cspan class=\"hljs-meta\">@Constraint(validatedBy = PhoneValidator.class)\u003C\u002Fspan>\n\u003Cspan class=\"hljs-meta\">@Documented\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> \u003Cspan class=\"hljs-meta\">@interface\u003C\u002Fspan> Phone {\n    \n    String \u003Cspan class=\"hljs-title function_\">message\u003C\u002Fspan>\u003Cspan class=\"hljs-params\">()\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">default\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&quot;手机号格式不正确&quot;\u003C\u002Fspan>;\n    \n    Class&lt;?&gt;[] groups() \u003Cspan class=\"hljs-keyword\">default\u003C\u002Fspan> {};\n    \n    Class&lt;? \u003Cspan class=\"hljs-keyword\">extends\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Payload\u003C\u002Fspan>&gt;[] payload() \u003Cspan class=\"hljs-keyword\">default\u003C\u002Fspan> {};\n}\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Ch3 data-line=\"282\" id=\"6.2 实现校验逻辑\">6.2 实现校验逻辑\u003C\u002Fh3>\n\n        \u003Cdetails  data-line=\"284\" class=\"md-editor-code\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">java\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-java\" language=java>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"hljs-comment\">\u002F**\n * 手机号校验器\n *\u002F\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">class\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">PhoneValidator\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">implements\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">ConstraintValidator\u003C\u002Fspan>&lt;Phone, String&gt; {\n    \n    \u003Cspan class=\"hljs-comment\">\u002F\u002F 手机号正则：1开头，第二位3-9，后面9位数字\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">private\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">static\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">final\u003C\u002Fspan> \u003Cspan class=\"hljs-type\">Pattern\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">PHONE_PATTERN\u003C\u002Fspan> \u003Cspan class=\"hljs-operator\">=\u003C\u002Fspan> \n        Pattern.compile(\u003Cspan class=\"hljs-string\">&quot;^1[3-9]\\\\d{9}$&quot;\u003C\u002Fspan>);\n    \n    \u003Cspan class=\"hljs-meta\">@Override\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">void\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">initialize\u003C\u002Fspan>\u003Cspan class=\"hljs-params\">(Phone constraintAnnotation)\u003C\u002Fspan> {\n        \u003Cspan class=\"hljs-comment\">\u002F\u002F 初始化方法，可以获取注解中的参数\u003C\u002Fspan>\n    }\n    \n    \u003Cspan class=\"hljs-meta\">@Override\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> \u003Cspan class=\"hljs-type\">boolean\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">isValid\u003C\u002Fspan>\u003Cspan class=\"hljs-params\">(String value, ConstraintValidatorContext context)\u003C\u002Fspan> {\n        \u003Cspan class=\"hljs-comment\">\u002F\u002F 为空时跳过校验（结合@NotNull等注解使用）\u003C\u002Fspan>\n        \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (value == \u003Cspan class=\"hljs-literal\">null\u003C\u002Fspan> || value.trim().isEmpty()) {\n            \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> \u003Cspan class=\"hljs-literal\">true\u003C\u002Fspan>;\n        }\n        \n        \u003Cspan class=\"hljs-type\">boolean\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">matches\u003C\u002Fspan> \u003Cspan class=\"hljs-operator\">=\u003C\u002Fspan> PHONE_PATTERN.matcher(value).matches();\n        \n        \u003Cspan class=\"hljs-comment\">\u002F\u002F 自定义错误信息\u003C\u002Fspan>\n        \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (!matches) {\n            context.disableDefaultConstraintViolation();\n            context.buildConstraintViolationWithTemplate(\u003Cspan class=\"hljs-string\">&quot;手机号格式不正确，应为11位数字且以1开头&quot;\u003C\u002Fspan>)\n                   .addConstraintViolation();\n        }\n        \n        \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> matches;\n    }\n}\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Ch3 data-line=\"320\" id=\"6.3 使用自定义注解\">6.3 使用自定义注解\u003C\u002Fh3>\n\n        \u003Cdetails  data-line=\"322\" class=\"md-editor-code\" open=\"\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">java\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-java\" language=java>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">class\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">CustomUserDTO\u003C\u002Fspan> {\n    \n    \u003Cspan class=\"hljs-meta\">@NotBlank(message = &quot;用户名不能为空&quot;)\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">private\u003C\u002Fspan> String username;\n    \n    \u003Cspan class=\"hljs-meta\">@Phone(message = &quot;请输入正确的手机号&quot;)\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">private\u003C\u002Fspan> String phone;\n    \n    \u003Cspan class=\"hljs-comment\">\u002F\u002F 自定义注解支持分组\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-meta\">@Phone(groups = CreateGroup.class)\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">private\u003C\u002Fspan> String contactPhone;\n    \n    \u003Cspan class=\"hljs-comment\">\u002F\u002F getter\u002Fsetter\u003C\u002Fspan>\n}\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Ch2 data-line=\"339\" id=\"7. 全局异常处理最佳实践\">7. 全局异常处理最佳实践\u003C\u002Fh2>\n\n        \u003Cdetails  data-line=\"341\" class=\"md-editor-code\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">java\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-java\" language=java>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"hljs-comment\">\u002F**\n * 统一异常处理器\n *\u002F\u003C\u002Fspan>\n\u003Cspan class=\"hljs-meta\">@RestControllerAdvice\u003C\u002Fspan>\n\u003Cspan class=\"hljs-meta\">@Slf4j\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">class\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">GlobalExceptionHandler\u003C\u002Fspan> {\n    \n    \u003Cspan class=\"hljs-comment\">\u002F**\n     * 处理\u003Cspan class=\"hljs-doctag\">@RequestBody\u003C\u002Fspan>参数校验异常\n     *\u002F\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-meta\">@ExceptionHandler(MethodArgumentNotValidException.class)\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> ResponseEntity&lt;Result&lt;?&gt;&gt; handleMethodArgumentNotValidException(\n            MethodArgumentNotValidException ex) {\n        \n        log.warn(\u003Cspan class=\"hljs-string\">&quot;参数校验失败: {}&quot;\u003C\u002Fspan>, ex.getMessage());\n        \n        \u003Cspan class=\"hljs-comment\">\u002F\u002F 提取详细的字段错误信息\u003C\u002Fspan>\n        List&lt;FieldErrorVO&gt; errors = ex.getBindingResult()\n                .getFieldErrors()\n                .stream()\n                .map(error -&gt; \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">FieldErrorVO\u003C\u002Fspan>(\n                    error.getField(),\n                    error.getRejectedValue(),\n                    error.getDefaultMessage()))\n                .collect(Collectors.toList());\n        \n        Result&lt;List&lt;FieldErrorVO&gt;&gt; result = Result.fail(\u003Cspan class=\"hljs-string\">&quot;参数校验失败&quot;\u003C\u002Fspan>, errors);\n        \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> ResponseEntity.badRequest().body(result);\n    }\n    \n    \u003Cspan class=\"hljs-comment\">\u002F**\n     * 处理\u003Cspan class=\"hljs-doctag\">@RequestParam\u003C\u002Fspan>、\u003Cspan class=\"hljs-doctag\">@PathVariable\u003C\u002Fspan>参数校验异常\n     *\u002F\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-meta\">@ExceptionHandler(ConstraintViolationException.class)\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> ResponseEntity&lt;Result&lt;?&gt;&gt; handleConstraintViolationException(\n            ConstraintViolationException ex) {\n        \n        log.warn(\u003Cspan class=\"hljs-string\">&quot;参数校验失败: {}&quot;\u003C\u002Fspan>, ex.getMessage());\n        \n        List&lt;FieldErrorVO&gt; errors = ex.getConstraintViolations()\n                .stream()\n                .map(violation -&gt; {\n                    \u003Cspan class=\"hljs-type\">String\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">path\u003C\u002Fspan> \u003Cspan class=\"hljs-operator\">=\u003C\u002Fspan> violation.getPropertyPath().toString();\n                    \u003Cspan class=\"hljs-comment\">\u002F\u002F 提取方法参数名\u003C\u002Fspan>\n                    \u003Cspan class=\"hljs-type\">String\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">fieldName\u003C\u002Fspan> \u003Cspan class=\"hljs-operator\">=\u003C\u002Fspan> path.contains(\u003Cspan class=\"hljs-string\">&quot;.&quot;\u003C\u002Fspan>) ? \n                        path.substring(path.lastIndexOf(\u003Cspan class=\"hljs-string\">&quot;.&quot;\u003C\u002Fspan>) + \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>) : path;\n                    \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">FieldErrorVO\u003C\u002Fspan>(\n                        fieldName,\n                        violation.getInvalidValue(),\n                        violation.getMessage());\n                })\n                .collect(Collectors.toList());\n        \n        Result&lt;List&lt;FieldErrorVO&gt;&gt; result = Result.fail(\u003Cspan class=\"hljs-string\">&quot;参数校验失败&quot;\u003C\u002Fspan>, errors);\n        \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> ResponseEntity.badRequest().body(result);\n    }\n    \n    \u003Cspan class=\"hljs-comment\">\u002F**\n     * 统一返回结果封装\n     *\u002F\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-meta\">@Data\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-meta\">@AllArgsConstructor\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">static\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">class\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Result\u003C\u002Fspan>&lt;T&gt; {\n        \u003Cspan class=\"hljs-keyword\">private\u003C\u002Fspan> Boolean success;\n        \u003Cspan class=\"hljs-keyword\">private\u003C\u002Fspan> String message;\n        \u003Cspan class=\"hljs-keyword\">private\u003C\u002Fspan> T data;\n        \u003Cspan class=\"hljs-keyword\">private\u003C\u002Fspan> Long timestamp;\n        \n        \u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">static\u003C\u002Fspan> &lt;T&gt; Result&lt;T&gt; \u003Cspan class=\"hljs-title function_\">success\u003C\u002Fspan>\u003Cspan class=\"hljs-params\">(T data)\u003C\u002Fspan> {\n            \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Result\u003C\u002Fspan>&lt;&gt;(\u003Cspan class=\"hljs-literal\">true\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;成功&quot;\u003C\u002Fspan>, data, System.currentTimeMillis());\n        }\n        \n        \u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">static\u003C\u002Fspan> &lt;T&gt; Result&lt;T&gt; \u003Cspan class=\"hljs-title function_\">fail\u003C\u002Fspan>\u003Cspan class=\"hljs-params\">(String message, T data)\u003C\u002Fspan> {\n            \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Result\u003C\u002Fspan>&lt;&gt;(\u003Cspan class=\"hljs-literal\">false\u003C\u002Fspan>, message, data, System.currentTimeMillis());\n        }\n    }\n    \n    \u003Cspan class=\"hljs-comment\">\u002F**\n     * 字段错误信息封装\n     *\u002F\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-meta\">@Data\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-meta\">@AllArgsConstructor\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">static\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">class\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">FieldErrorVO\u003C\u002Fspan> {\n        \u003Cspan class=\"hljs-keyword\">private\u003C\u002Fspan> String field;\n        \u003Cspan class=\"hljs-keyword\">private\u003C\u002Fspan> Object rejectedValue;\n        \u003Cspan class=\"hljs-keyword\">private\u003C\u002Fspan> String message;\n    }\n}\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Ch2 data-line=\"432\" id=\"8. 完整实战示例\">8. 完整实战示例\u003C\u002Fh2>\n\u003Ch3 data-line=\"434\" id=\"8.1 Maven 依赖配置\">8.1 Maven 依赖配置\u003C\u002Fh3>\n\n        \u003Cdetails  data-line=\"436\" class=\"md-editor-code\" open=\"\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">xml\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-xml\" language=xml>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"hljs-comment\">&lt;!-- Spring Boot Web Starter --&gt;\u003C\u002Fspan>\n\u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">dependency\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">groupId\u003C\u002Fspan>&gt;\u003C\u002Fspan>org.springframework.boot\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">groupId\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">artifactId\u003C\u002Fspan>&gt;\u003C\u002Fspan>spring-boot-starter-web\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">artifactId\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">dependency\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n\n\u003Cspan class=\"hljs-comment\">&lt;!-- Validation Starter --&gt;\u003C\u002Fspan>\n\u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">dependency\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">groupId\u003C\u002Fspan>&gt;\u003C\u002Fspan>org.springframework.boot\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">groupId\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">artifactId\u003C\u002Fspan>&gt;\u003C\u002Fspan>spring-boot-starter-validation\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">artifactId\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">dependency\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n\n\u003Cspan class=\"hljs-comment\">&lt;!-- Lombok（可选） --&gt;\u003C\u002Fspan>\n\u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">dependency\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">groupId\u003C\u002Fspan>&gt;\u003C\u002Fspan>org.projectlombok\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">groupId\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">artifactId\u003C\u002Fspan>&gt;\u003C\u002Fspan>lombok\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">artifactId\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">optional\u003C\u002Fspan>&gt;\u003C\u002Fspan>true\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">optional\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">dependency\u003C\u002Fspan>&gt;\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Ch3 data-line=\"457\" id=\"8.2 综合控制器示例\">8.2 综合控制器示例\u003C\u002Fh3>\n\n        \u003Cdetails  data-line=\"459\" class=\"md-editor-code\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">java\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-java\" language=java>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"hljs-meta\">@RestController\u003C\u002Fspan>\n\u003Cspan class=\"hljs-meta\">@RequestMapping(&quot;\u002Fapi\u002Fcomplete-users&quot;)\u003C\u002Fspan>\n\u003Cspan class=\"hljs-meta\">@Validated\u003C\u002Fspan>\n\u003Cspan class=\"hljs-meta\">@Slf4j\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">class\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">CompleteUserController\u003C\u002Fspan> {\n    \n    \u003Cspan class=\"hljs-meta\">@Autowired\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">private\u003C\u002Fspan> UserService userService;\n    \n    \u003Cspan class=\"hljs-comment\">\u002F**\n     * 创建用户 - \u003Cspan class=\"hljs-doctag\">@RequestBody\u003C\u002Fspan>校验\n     *\u002F\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-meta\">@PostMapping\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> ResponseEntity&lt;Result&lt;String&gt;&gt; \u003Cspan class=\"hljs-title function_\">createUser\u003C\u002Fspan>\u003Cspan class=\"hljs-params\">(\n            \u003Cspan class=\"hljs-meta\">@Valid\u003C\u002Fspan> \u003Cspan class=\"hljs-meta\">@RequestBody\u003C\u002Fspan> UserDTO userDTO)\u003C\u002Fspan> {\n        \u003Cspan class=\"hljs-comment\">\u002F\u002F 业务处理\u003C\u002Fspan>\n        userService.createUser(userDTO);\n        \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> ResponseEntity.ok(Result.success(\u003Cspan class=\"hljs-string\">&quot;用户创建成功&quot;\u003C\u002Fspan>));\n    }\n    \n    \u003Cspan class=\"hljs-comment\">\u002F**\n     * 批量创建 - 嵌套集合校验\n     *\u002F\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-meta\">@PostMapping(&quot;\u002Fbatch&quot;)\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> ResponseEntity&lt;Result&lt;String&gt;&gt; \u003Cspan class=\"hljs-title function_\">createUsers\u003C\u002Fspan>\u003Cspan class=\"hljs-params\">(\n            \u003Cspan class=\"hljs-meta\">@Valid\u003C\u002Fspan> \u003Cspan class=\"hljs-meta\">@RequestBody\u003C\u002Fspan> BatchUserDTO batchUserDTO)\u003C\u002Fspan> {\n        log.info(\u003Cspan class=\"hljs-string\">&quot;批量创建用户数量: {}&quot;\u003C\u002Fspan>, batchUserDTO.getUsers().size());\n        \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> ResponseEntity.ok(Result.success(\u003Cspan class=\"hljs-string\">&quot;批量创建用户成功&quot;\u003C\u002Fspan>));\n    }\n    \n    \u003Cspan class=\"hljs-comment\">\u002F**\n     * 查询用户 - 方法参数校验\n     *\u002F\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-meta\">@GetMapping(&quot;\u002Fsearch&quot;)\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> ResponseEntity&lt;Result&lt;List&lt;UserDTO&gt;&gt;&gt; \u003Cspan class=\"hljs-title function_\">searchUsers\u003C\u002Fspan>\u003Cspan class=\"hljs-params\">(\n            \u003Cspan class=\"hljs-meta\">@RequestParam\u003C\u002Fspan> \u003Cspan class=\"hljs-meta\">@NotBlank\u003C\u002Fspan> String keyword,\n            \u003Cspan class=\"hljs-meta\">@RequestParam\u003C\u002Fspan> \u003Cspan class=\"hljs-meta\">@Min(value = 1, message = &quot;页码必须大于0&quot;)\u003C\u002Fspan> Integer page,\n            \u003Cspan class=\"hljs-meta\">@RequestParam\u003C\u002Fspan> \u003Cspan class=\"hljs-meta\">@Range(min = 1, max = 100, message = &quot;每页大小1-100&quot;)\u003C\u002Fspan> Integer size)\u003C\u002Fspan> {\n        \n        List&lt;UserDTO&gt; users = userService.searchUsers(keyword, page, size);\n        \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> ResponseEntity.ok(Result.success(users));\n    }\n    \n    \u003Cspan class=\"hljs-comment\">\u002F**\n     * 分组校验示例\n     *\u002F\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-meta\">@PostMapping(&quot;\u002Fgroup&quot;)\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> ResponseEntity&lt;Result&lt;String&gt;&gt; \u003Cspan class=\"hljs-title function_\">createUserWithGroup\u003C\u002Fspan>\u003Cspan class=\"hljs-params\">(\n            \u003Cspan class=\"hljs-meta\">@Validated(CreateGroup.class)\u003C\u002Fspan> \u003Cspan class=\"hljs-meta\">@RequestBody\u003C\u002Fspan> UserGroupDTO userDTO)\u003C\u002Fspan> {\n        \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> ResponseEntity.ok(Result.success(\u003Cspan class=\"hljs-string\">&quot;分组校验创建成功&quot;\u003C\u002Fspan>));\n    }\n    \n    \u003Cspan class=\"hljs-meta\">@PutMapping(&quot;\u002Fgroup&quot;)\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> ResponseEntity&lt;Result&lt;String&gt;&gt; \u003Cspan class=\"hljs-title function_\">updateUserWithGroup\u003C\u002Fspan>\u003Cspan class=\"hljs-params\">(\n            \u003Cspan class=\"hljs-meta\">@Validated(UpdateGroup.class)\u003C\u002Fspan> \u003Cspan class=\"hljs-meta\">@RequestBody\u003C\u002Fspan> UserGroupDTO userDTO)\u003C\u002Fspan> {\n        \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> ResponseEntity.ok(Result.success(\u003Cspan class=\"hljs-string\">&quot;分组校验更新成功&quot;\u003C\u002Fspan>));\n    }\n}\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Ch2 data-line=\"520\" id=\"9. 高级特性与最佳实践\">9. 高级特性与最佳实践\u003C\u002Fh2>\n\u003Ch3 data-line=\"522\" id=\"9.1 校验顺序控制\">9.1 校验顺序控制\u003C\u002Fh3>\n\n        \u003Cdetails  data-line=\"524\" class=\"md-editor-code\" open=\"\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">java\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-java\" language=java>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">class\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">OrderedUserDTO\u003C\u002Fspan> {\n    \n    \u003Cspan class=\"hljs-meta\">@NotBlank(message = &quot;用户名不能为空&quot;, groups = FirstPriority.class)\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">private\u003C\u002Fspan> String username;\n    \n    \u003Cspan class=\"hljs-meta\">@Email(message = &quot;邮箱格式不正确&quot;, groups = SecondPriority.class)\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">private\u003C\u002Fspan> String email;\n    \n    \u003Cspan class=\"hljs-comment\">\u002F\u002F 定义校验顺序接口\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">interface\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">FirstPriority\u003C\u002Fspan> {}\n    \u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">interface\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">SecondPriority\u003C\u002Fspan> {}\n}\n\n\u003Cspan class=\"hljs-comment\">\u002F\u002F 使用@Validated指定校验顺序\u003C\u002Fspan>\n\u003Cspan class=\"hljs-meta\">@PostMapping(&quot;\u002Fordered&quot;)\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> ResponseEntity&lt;String&gt; \u003Cspan class=\"hljs-title function_\">createOrderedUser\u003C\u002Fspan>\u003Cspan class=\"hljs-params\">(\n        \u003Cspan class=\"hljs-meta\">@Validated({FirstPriority.class, SecondPriority.class})\u003C\u002Fspan> \n        \u003Cspan class=\"hljs-meta\">@RequestBody\u003C\u002Fspan> OrderedUserDTO user)\u003C\u002Fspan> {\n    \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> ResponseEntity.ok(\u003Cspan class=\"hljs-string\">&quot;顺序校验成功&quot;\u003C\u002Fspan>);\n}\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Ch3 data-line=\"547\" id=\"9.2 条件性校验\">9.2 条件性校验\u003C\u002Fh3>\n\n        \u003Cdetails  data-line=\"549\" class=\"md-editor-code\" open=\"\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">java\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-java\" language=java>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">class\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">ConditionalUserDTO\u003C\u002Fspan> {\n    \n    \u003Cspan class=\"hljs-meta\">@NotBlank(message = &quot;用户名不能为空&quot;)\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">private\u003C\u002Fspan> String username;\n    \n    \u003Cspan class=\"hljs-comment\">\u002F\u002F 当type为1时，email不能为空\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-meta\">@AssertTrue(message = &quot;类型为1时必须填写邮箱&quot;)\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">private\u003C\u002Fspan> \u003Cspan class=\"hljs-type\">boolean\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">isEmailValid\u003C\u002Fspan>\u003Cspan class=\"hljs-params\">()\u003C\u002Fspan> {\n        \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> type != \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan> || email != \u003Cspan class=\"hljs-literal\">null\u003C\u002Fspan>;\n    }\n    \n    \u003Cspan class=\"hljs-keyword\">private\u003C\u002Fspan> Integer type;\n    \u003Cspan class=\"hljs-keyword\">private\u003C\u002Fspan> String email;\n}\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Ch3 data-line=\"566\" id=\"9.3 国际化消息支持\">9.3 国际化消息支持\u003C\u002Fh3>\n\n        \u003Cdetails  data-line=\"568\" class=\"md-editor-code\" open=\"\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">properties\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-properties\" language=properties>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"hljs-comment\"># messages.properties\u003C\u002Fspan>\n\u003Cspan class=\"hljs-attr\">user.id.notnull\u003C\u002Fspan>=用户ID不能为空\n\u003Cspan class=\"hljs-attr\">user.name.size\u003C\u002Fspan>=用户名长度必须在{min}到{max}之间\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \n        \u003Cdetails  data-line=\"574\" class=\"md-editor-code\" open=\"\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">java\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-java\" language=java>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">class\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">I18nUserDTO\u003C\u002Fspan> {\n    \u003Cspan class=\"hljs-meta\">@NotNull(message = &quot;{user.id.notnull}&quot;)\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">private\u003C\u002Fspan> Long id;\n    \n    \u003Cspan class=\"hljs-meta\">@Size(min = 2, max = 20, message = &quot;{user.name.size}&quot;)\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">private\u003C\u002Fspan> String username;\n}\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Ch2 data-line=\"584\" id=\"10. 性能优化与生产建议\">10. 性能优化与生产建议\u003C\u002Fh2>\n\u003Ch3 data-line=\"586\" id=\"10.1 校验性能优化\">10.1 校验性能优化\u003C\u002Fh3>\n\n        \u003Cdetails  data-line=\"588\" class=\"md-editor-code\" open=\"\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">java\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-java\" language=java>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"hljs-meta\">@Configuration\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">class\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">ValidationConfig\u003C\u002Fspan> {\n    \n    \u003Cspan class=\"hljs-comment\">\u002F**\n     * 自定义验证器，避免重复创建\n     *\u002F\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-meta\">@Bean\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> Validator \u003Cspan class=\"hljs-title function_\">validator\u003C\u002Fspan>\u003Cspan class=\"hljs-params\">()\u003C\u002Fspan> {\n        \u003Cspan class=\"hljs-type\">ValidatorFactory\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">factory\u003C\u002Fspan> \u003Cspan class=\"hljs-operator\">=\u003C\u002Fspan> Validation.buildDefaultValidatorFactory();\n        \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> factory.getValidator();\n    }\n}\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Ch3 data-line=\"603\" id=\"10.2 生产环境最佳实践\">10.2 生产环境最佳实践\u003C\u002Fh3>\n\u003Col data-line=\"605\">\n\u003Cli data-line=\"605\">\n\u003Cp data-line=\"605\">\u003Cstrong>分层校验策略\u003C\u002Fstrong>：\u003C\u002Fp>\n\u003Cul data-line=\"606\">\n\u003Cli data-line=\"606\">Controller层：基础格式校验\u003C\u002Fli>\n\u003Cli data-line=\"607\">Service层：业务规则校验\u003C\u002Fli>\n\u003Cli data-line=\"608\">数据库层：数据完整性校验\u003C\u002Fli>\n\u003C\u002Ful>\n\u003C\u002Fli>\n\u003Cli data-line=\"610\">\n\u003Cp data-line=\"610\">\u003Cstrong>错误信息设计\u003C\u002Fstrong>：\u003C\u002Fp>\n\u003Cul data-line=\"611\">\n\u003Cli data-line=\"611\">前端友好：避免技术性描述\u003C\u002Fli>\n\u003Cli data-line=\"612\">安全考虑：不暴露系统信息\u003C\u002Fli>\n\u003Cli data-line=\"613\">一致性：统一错误格式\u003C\u002Fli>\n\u003C\u002Ful>\n\u003C\u002Fli>\n\u003Cli data-line=\"615\">\n\u003Cp data-line=\"615\">\u003Cstrong>校验粒度控制\u003C\u002Fstrong>：\u003C\u002Fp>\n\u003Cul data-line=\"616\">\n\u003Cli data-line=\"616\">创建操作：严格校验\u003C\u002Fli>\n\u003Cli data-line=\"617\">更新操作：选择性校验\u003C\u002Fli>\n\u003Cli data-line=\"618\">查询操作：基础校验\u003C\u002Fli>\n\u003C\u002Ful>\n\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2 data-line=\"620\" id=\"11. 总结与选择指南\">11. 总结与选择指南\u003C\u002Fh2>\n\u003Cp data-line=\"622\">通过本文的详细讲解，我们可以看到 Spring Boot 校验体系的强大之处。在实际项目中：\u003C\u002Fp>\n\u003Cp data-line=\"624\">\u003Cstrong>选择 @Valid 的情况\u003C\u002Fstrong>：\u003C\u002Fp>\n\u003Cul data-line=\"625\">\n\u003Cli data-line=\"625\">简单的对象图校验\u003C\u002Fli>\n\u003Cli data-line=\"626\">与 JPA 实体校验保持一致性\u003C\u002Fli>\n\u003Cli data-line=\"627\">不需要分组校验的简单场景\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp data-line=\"629\">\u003Cstrong>选择 @Validated 的情况\u003C\u002Fstrong>：\u003C\u002Fp>\n\u003Cul data-line=\"630\">\n\u003Cli data-line=\"630\">需要分组校验（如按业务场景区分）\u003C\u002Fli>\n\u003Cli data-line=\"631\">方法参数校验（如Service层方法）\u003C\u002Fli>\n\u003Cli data-line=\"632\">需要更细粒度控制的复杂场景\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp data-line=\"634\">\u003Cstrong>核心价值\u003C\u002Fstrong>：\u003C\u002Fp>\n\u003Cul data-line=\"635\">\n\u003Cli data-line=\"635\">提高代码健壮性，减少异常处理代码\u003C\u002Fli>\n\u003Cli data-line=\"636\">统一校验逻辑，提高代码可维护性\u003C\u002Fli>\n\u003Cli data-line=\"637\">提升开发效率，减少重复校验代码\u003C\u002Fli>\n\u003Cli data-line=\"638\">改善用户体验，提供即时反馈\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp data-line=\"640\">掌握 \u003Ccode>@Valid\u003C\u002Fcode> 和 \u003Ccode>@Validated\u003C\u002Fcode> 的正确使用，能够让你的 Spring Boot 应用在数据校验方面更加专业和可靠。根据具体业务场景灵活选择，才能发挥最大的效能。\u003C\u002Fp>\n",14622,48,"0","",2,0,7,"&quot,校验,public,private,message","1. 参数校验概述\n在现代化的 Web 应用开发中，参数校验是保障系统健壮性的第一道防线。Spring Boot 通过集成 Bean Validation 规范，为我们提供了强大而灵活的校验机制。其中，@Valid 和 @Validated 是两个核心注解，它们虽然功能相似，但在不同场景下各有优势。","2025-12-20 23:49:19"]