本文目录导读:

我来为你整理一个完善的Java表单校验案例,涵盖前后端校验的完整实现。
前端校验(JavaScript)
HTML表单结构
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">用户注册表单</title>
<style>
.error { color: red; font-size: 12px; }
.success { border-color: green; }
.error-input { border-color: red; }
</style>
</head>
<body>
<form id="registerForm" onsubmit="return validateForm()">
<div>
<label>用户名:</label>
<input type="text" id="username" name="username" required>
<span id="usernameError" class="error"></span>
</div>
<div>
<label>密码:</label>
<input type="password" id="password" name="password" required>
<span id="passwordError" class="error"></span>
</div>
<div>
<label>确认密码:</label>
<input type="password" id="confirmPassword" name="confirmPassword" required>
<span id="confirmPasswordError" class="error"></span>
</div>
<div>
<label>邮箱:</label>
<input type="email" id="email" name="email" required>
<span id="emailError" class="error"></span>
</div>
<div>
<label>手机号:</label>
<input type="tel" id="phone" name="phone">
<span id="phoneError" class="error"></span>
</div>
<div>
<label>年龄:</label>
<input type="number" id="age" name="age" min="1" max="120">
<span id="ageError" class="error"></span>
</div>
<button type="submit">注册</button>
<button type="reset">重置</button>
</form>
<script src="validator.js"></script>
</body>
</html>
JavaScript校验文件(validator.js)
class FormValidator {
constructor(config) {
this.config = {
validClass: 'success',
errorClass: 'error-input',
onInputChange: true,
...config
};
}
// 初始化校验
init(formId) {
const form = document.getElementById(formId);
if (!form) return false;
this.form = form;
this.fields = form.querySelectorAll('input, select, textarea');
// 实时校验
if (this.config.onInputChange) {
this.fields.forEach(field => {
field.addEventListener('input', () => {
this.validateField(field);
});
field.addEventListener('blur', () => {
this.validateField(field);
});
});
}
}
// 校验单个字段
validateField(field) {
const value = field.value.trim();
const fieldName = field.name;
let error = '';
// 必填校验
if (field.hasAttribute('required') && !value) {
error = `${this.getFieldLabel(field)}不能为空`;
}
// 具体字段校验
if (!error) {
switch(fieldName) {
case 'username':
error = this.validateUsername(value);
break;
case 'password':
error = this.validatePassword(value);
if (!error) {
const confirmPassword = document.getElementById('confirmPassword');
if (confirmPassword && confirmPassword.value) {
this.validateField(confirmPassword);
}
}
break;
case 'confirmPassword':
const password = document.getElementById('password').value;
error = this.validateConfirmPassword(value, password);
break;
case 'email':
error = this.validateEmail(value);
break;
case 'phone':
error = this.validatePhone(value);
break;
case 'age':
error = this.validateAge(value);
break;
}
}
// 显示错误信息
this.showFieldError(field, error);
return !error;
}
// 校验用户名
validateUsername(username) {
if (username.length < 3 || username.length > 20) {
return '用户名长度应在3-20个字符之间';
}
if (!/^[a-zA-Z0-9_]+$/.test(username)) {
return '用户名只能包含字母、数字和下划线';
}
return '';
}
// 校验密码
validatePassword(password) {
if (password.length < 6 || password.length > 20) {
return '密码长度应在6-20个字符之间';
}
if (!/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/.test(password)) {
return '密码必须包含大小写字母和数字';
}
return '';
}
// 校验确认密码
validateConfirmPassword(confirmPassword, password) {
if (confirmPassword !== password) {
return '两次输入的密码不一致';
}
return '';
}
// 校验邮箱
validateEmail(email) {
const emailRegex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$/;
if (!emailRegex.test(email)) {
return '请输入有效的邮箱地址';
}
return '';
}
// 校验手机号
validatePhone(phone) {
if (phone && !/^1[3-9]\d{9}$/.test(phone)) {
return '请输入有效的手机号码';
}
return '';
}
// 校验年龄
validateAge(age) {
if (age) {
const ageNum = parseInt(age);
if (isNaN(ageNum) || ageNum < 1 || ageNum > 120) {
return '年龄必须在1-120之间';
}
}
return '';
}
// 显示字段错误
showFieldError(field, error) {
const errorElement = document.getElementById(`${field.name}Error`);
if (errorElement) {
errorElement.textContent = error;
}
if (error) {
field.classList.add(this.config.errorClass);
field.classList.remove(this.config.validClass);
} else {
field.classList.remove(this.config.errorClass);
if (field.value.trim()) {
field.classList.add(this.config.validClass);
}
}
}
// 获取字段标签
getFieldLabel(field) {
const label = field.closest('div').querySelector('label');
return label ? label.textContent.replace(':', '') : field.name;
}
// 校验整个表单
validateForm() {
let isValid = true;
this.fields.forEach(field => {
if (!this.validateField(field)) {
isValid = false;
}
});
return isValid;
}
}
// 使用示例
const validator = new FormValidator({
onInputChange: true
});
validator.init('registerForm');
// 表单提交
function validateForm() {
return validator.validateForm();
}
后端校验(Spring Boot)
Maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
DTO对象(带校验注解)
import javax.validation.constraints.*;
import lombok.Data;
@Data
public class UserRegisterDTO {
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 20, message = "用户名长度应在3-20个字符之间")
@Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "用户名只能包含字母、数字和下划线")
private String username;
@NotBlank(message = "密码不能为空")
@Size(min = 6, max = 20, message = "密码长度应在6-20个字符之间")
@Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).+$",
message = "密码必须包含大小写字母和数字")
private String password;
@NotBlank(message = "确认密码不能为空")
private String confirmPassword;
@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
private String email;
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
@Min(value = 1, message = "年龄不能小于1")
@Max(value = 120, message = "年龄不能大于120")
private Integer age;
}
自定义校验注解
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PasswordMatchValidator.class)
@Documented
public @interface PasswordMatch {
String message() default "两次输入的密码不一致";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
自定义校验器
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class PasswordMatchValidator implements ConstraintValidator<PasswordMatch, UserRegisterDTO> {
@Override
public boolean isValid(UserRegisterDTO dto, ConstraintValidatorContext context) {
if (dto.getPassword() == null || dto.getConfirmPassword() == null) {
return true; // 其他校验会处理null情况
}
return dto.getPassword().equals(dto.getConfirmPassword());
}
}
Controller层
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/user")
@Validated
public class UserController {
@PostMapping("/register")
public ResponseEntity<?> register(@Valid @RequestBody UserRegisterDTO userDTO) {
// 业务逻辑处理
Map<String, Object> result = new HashMap<>();
result.put("success", true);
result.put("message", "注册成功");
return ResponseEntity.ok(result);
}
// 单个字段校验
@PostMapping("/check-username")
public ResponseEntity<?> checkUsername(
@RequestParam @NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 20, message = "用户名长度不符合要求") String username) {
// 检查用户名是否存在
Map<String, Object> result = new HashMap<>();
result.put("available", !"admin".equals(username));
return ResponseEntity.ok(result);
}
}
全局异常处理
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.validation.ConstraintViolationException;
import java.util.HashMap;
import java.util.Map;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<?> handleValidationExceptions(MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach(error -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
Map<String, Object> result = new HashMap<>();
result.put("success", false);
result.put("errors", errors);
return ResponseEntity.badRequest().body(result);
}
@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<?> handleConstraintViolation(ConstraintViolationException ex) {
Map<String, String> errors = new HashMap<>();
ex.getConstraintViolations().forEach(violation -> {
String propertyPath = violation.getPropertyPath().toString();
String message = violation.getMessage();
errors.put(propertyPath, message);
});
Map<String, Object> result = new HashMap<>();
result.put("success", false);
result.put("errors", errors);
return ResponseEntity.badRequest().body(result);
}
}
前端异步提交(增强版)
// 异步提交表单
async function submitFormAsync(event) {
event.preventDefault();
if (!validator.validateForm()) {
return false;
}
const formData = new FormData(document.getElementById('registerForm'));
const data = Object.fromEntries(formData.entries());
try {
const response = await fetch('/api/user/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data)
});
const result = await response.json();
if (result.success) {
alert('注册成功!');
// 跳转或清空表单
} else {
// 显示服务端错误
if (result.errors) {
Object.keys(result.errors).forEach(field => {
const errorElement = document.getElementById(`${field}Error`);
if (errorElement) {
errorElement.textContent = result.errors[field];
}
});
}
}
} catch (error) {
console.error('提交失败:', error);
alert('提交失败,请稍后重试');
}
}
// 绑定异步提交
document.getElementById('registerForm').onsubmit = submitFormAsync;
完善建议
安全增强
// XSS防护
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// 防重复提交
let isSubmitting = false;
async function preventDoubleSubmit(form) {
if (isSubmitting) return;
isSubmitting = true;
try {
// 提交逻辑
} finally {
isSubmitting = false;
}
}
用户体验优化
// 实时校验显示强度
function checkPasswordStrength(password) {
let strength = 0;
if (password.length >= 8) strength++;
if (password.match(/[a-z]+/)) strength++;
if (password.match(/[A-Z]+/)) strength++;
if (password.match(/[0-9]+/)) strength++;
if (password.match(/[$@#&!]+/)) strength++;
const levels = ['非常弱', '弱', '中等', '强', '非常强'];
return levels[Math.min(strength, 4)];
}
// 密码强度UI
passwordInput.addEventListener('input', function() {
const strength = checkPasswordStrength(this.value);
const strengthBar = document.getElementById('passwordStrength');
strengthBar.textContent = `密码强度: ${strength}`;
});
国际化支持
const messages = {
'zh-CN': {
required: '不能为空',
username: '用户名',
// ...
},
'en-US': {
required: 'is required',
username: 'Username',
// ...
}
};
这个完整的表单校验案例包含了:
- 前端校验:实时反馈、字段级校验、自定义规则
- 后端校验:注解校验、自定义校验器、全局异常处理
- 安全措施:XSS防护、防重复提交
- 用户体验:密码强度显示、实时验证
- 维护性:模块化设计、错误信息集中管理
建议根据实际需求选择合适的校验方式,前端主要提升用户体验,后端确保数据安全。