Java表单校验流程如何规范

wen java案例 31

本文目录导读:

Java表单校验流程如何规范

  1. 校验分层架构
  2. 标准校验流程
  3. 校验规范建议
  4. 最佳实践总结

我来为你详细介绍Java表单校验的规范化流程。

校验分层架构

三层校验体系

Controller层 -> Service层 -> Persistence层
(参数校验)    (业务校验)   (数据库约束)

标准校验流程

基础校验框架搭建

Maven依赖配置

<!-- Spring Boot Validation -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- 或使用 Hibernate Validator -->
<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
</dependency>

Controller层校验(参数校验)

DTO对象定义

@Data
public class UserCreateRequest {
    @NotBlank(message = "用户名不能为空")
    @Size(min = 2, max = 20, message = "用户名长度需要在2-20个字符之间")
    private String username;
    @NotBlank(message = "密码不能为空")
    @Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).{8,20}$", 
             message = "密码需包含大小写字母和数字,长度8-20位")
    private String password;
    @NotBlank(message = "邮箱不能为空")
    @Email(message = "邮箱格式不正确")
    private String email;
    @NotNull(message = "年龄不能为空")
    @Min(value = 0, message = "年龄不能小于0")
    @Max(value = 150, message = "年龄不能大于150")
    private Integer age;
    @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
    private String phone;
    @Future(message = "过期时间必须是未来时间")
    private LocalDateTime expireTime;
}

Controller处理器

@RestController
@Validated
@RequestMapping("/api/users")
public class UserController {
    @PostMapping
    public Result<Void> createUser(@Valid @RequestBody UserCreateRequest request) {
        // 如果参数校验失败,会自动抛出异常
        userService.createUser(request);
        return Result.success();
    }
    @GetMapping("/{id}")
    public Result<UserVO> getUser(@PathVariable @Min(1) Long id) {
        return Result.success(userService.getUser(id));
    }
}

分组校验

@Data
public class UserRequest {
    @Null(groups = CreateGroup.class)
    @NotNull(groups = UpdateGroup.class)
    private Long id;
    @NotBlank(groups = CreateGroup.class)
    private String username;
    // 更新时不需要修改密码
    @NotBlank(groups = CreateGroup.class)
    private String password;
    private String email;
    // 分组接口
    public interface CreateGroup {}
    public interface UpdateGroup {}
}
// Controller使用
@PostMapping
public Result<Void> create(@Validated(UserRequest.CreateGroup.class) 
                           @RequestBody UserRequest request) {
    userService.create(request);
    return Result.success();
}
@PutMapping
public Result<Void> update(@Validated(UserRequest.UpdateGroup.class) 
                           @RequestBody UserRequest request) {
    userService.update(request);
    return Result.success();
}

Service层校验(业务校验)

@Service
@Validated
public class UserServiceImpl implements UserService {
    @Autowired
    private UserRepository userRepository;
    @Override
    public void createUser(UserCreateRequest request) {
        // 1. 参数校验(由Controller层保证)
        // 2. 业务校验
        validateBusinessRules(request);
        // 3. 转换为实体
        User user = new User();
        BeanUtils.copyProperties(request, user);
        // 4. 持久化
        userRepository.save(user);
    }
    private void validateBusinessRules(UserCreateRequest request) {
        // 用户名唯一性校验
        if (userRepository.existsByUsername(request.getUsername())) {
            throw new BusinessException(ErrorCode.USERNAME_EXISTS, "用户名已存在");
        }
        // 邮箱唯一性校验
        if (userRepository.existsByEmail(request.getEmail())) {
            throw new BusinessException(ErrorCode.EMAIL_EXISTS, "邮箱已被注册");
        }
        // 复杂度校验
        if (isPasswordWeak(request.getPassword())) {
            throw new BusinessException(ErrorCode.PASSWORD_WEAK, "密码强度不足");
        }
    }
    private boolean isPasswordWeak(String password) {
        // 实现密码强度检查逻辑
        return false;
    }
}

自定义校验注解

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PhoneValidator.class)
public @interface ValidPhone {
    String message() default "手机号格式不正确";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}
public class PhoneValidator implements ConstraintValidator<ValidPhone, String> {
    private static final Pattern PHONE_PATTERN = 
        Pattern.compile("^1[3-9]\\d{9}$");
    @Override
    public boolean isValid(String phone, ConstraintValidatorContext context) {
        if (phone == null) {
            return true; // 由@NotBlank处理空值
        }
        return PHONE_PATTERN.matcher(phone).matches();
    }
}
// 使用方式
@ValidPhone
private String phone;

全局异常处理

@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<Void> handleValidationException(MethodArgumentNotValidException ex) {
        // 获取第一个错误信息
        String message = ex.getBindingResult()
                          .getAllErrors()
                          .stream()
                          .findFirst()
                          .map(error -> error.getDefaultMessage())
                          .orElse("参数校验失败");
        return Result.error(400, message);
    }
    @ExceptionHandler(ConstraintViolationException.class)
    public Result<Void> handleConstraintViolation(ConstraintViolationException ex) {
        String message = ex.getConstraintViolations()
                          .stream()
                          .findFirst()
                          .map(violation -> violation.getMessage())
                          .orElse("参数校验失败");
        return Result.error(400, message);
    }
    @ExceptionHandler(BusinessException.class)
    public Result<Void> handleBusinessException(BusinessException ex) {
        return Result.error(ex.getCode(), ex.getMessage());
    }
    @ExceptionHandler(Exception.class)
    public Result<Void> handleException(Exception ex) {
        log.error("系统异常", ex);
        return Result.error(500, "系统繁忙,请稍后重试");
    }
}

校验规范建议

校验粒度控制

// ✅ 好的实践:明确的校验级别
public class OrderCreateRequest {
    @NotNull
    private Long productId;  // 必须校验
    private String couponCode;  // 可选字段
    public boolean hasCoupon() {
        return couponCode != null && !couponCode.trim().isEmpty();
    }
}

统一返回格式

@Data
public class Result<T> {
    private int code;
    private String message;
    private T data;
    private long timestamp;
    public static <T> Result<T> success() {
        return success(null);
    }
    public static <T> Result<T> success(T data) {
        Result<T> result = new Result<>();
        result.code = 200;
        result.message = "success";
        result.data = data;
        result.timestamp = System.currentTimeMillis();
        return result;
    }
    public static <T> Result<T> error(int code, String message) {
        Result<T> result = new Result<>();
        result.code = code;
        result.message = message;
        result.timestamp = System.currentTimeMillis();
        return result;
    }
}

校验优先级示例

// 优先使用框架提供的注解
@NotEmpty(message = "订单号不能为空")
@Size(max = 32, message = "订单号长度不能超过32位")
private String orderNo;
// 复杂的业务逻辑使用自定义注解或方法校验
@ValidOrderStatus
private Integer status;
// 跨字段校验(如密码确认)
public class PasswordChangeRequest {
    @NotBlank
    private String newPassword;
    @NotBlank
    private String confirmPassword;
    @AssertTrue(message = "两次输入的密码不一致")
    public boolean isPasswordMatch() {
        return newPassword != null && newPassword.equals(confirmPassword);
    }
}

性能优化建议

// 使用正则表达式预编译
private static final Pattern EMAIL_PATTERN = 
    Pattern.compile("^[A-Za-z0-9+_.-]+@(.+)$");
// 批量校验优化
public void batchValidate(List<UserCreateRequest> requests) {
    List<String> errors = new ArrayList<>();
    requests.parallelStream().forEach(request -> {
        try {
            validateSingle(request);
        } catch (ValidationException e) {
            synchronized (errors) {
                errors.add("第" + index + "条数据: " + e.getMessage());
            }
        }
    });
    if (!errors.isEmpty()) {
        throw new ValidationException(String.join("; ", errors));
    }
}

测试用例示例

@SpringBootTest
class UserControllerTest {
    @Autowired
    private MockMvc mockMvc;
    @Test
    void testCreateUserValidation() throws Exception {
        String requestBody = """
            {
                "username": "",
                "email": "invalid-email",
                "password": "123"
            }
        """;
        mockMvc.perform(post("/api/users")
                .contentType(MediaType.APPLICATION_JSON)
                .content(requestBody))
                .andExpect(status().isBadRequest())
                .andExpect(jsonPath("$.message").value("用户名不能为空"));
    }
}

最佳实践总结

  1. 分层校验:Controller负责参数格式,Service负责业务逻辑
  2. 分组校验:根据不同场景使用不同校验规则
  3. 自定义注解:封装复杂校验逻辑,提高复用性
  4. 统一异常处理:减少重复代码,统一错误格式
  5. 清晰的错误信息:提供具体的错误描述,便于定位问题
  6. 性能考虑:避免在循环中重复创建对象,预编译正则表达式
  7. 安全性:避免暴露敏感信息(如SQL细节、系统路径等)

这样的校验流程能保证代码的可维护性、可读性和健壮性,同时提供良好的用户体验。

抱歉,评论功能暂时关闭!