参数验证
更新: 2/10/2025 字数: 0 字 时长: 0 分钟
参数注解
java
import jakarta.validation.constraints.*;
import lombok.Data;
import org.hibernate.validator.constraints.CreditCardNumber;
import org.hibernate.validator.constraints.EAN;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.URL;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Date;
import java.util.Map;
@Data
static class Entity {
/**
* NotNull 注解用于确保被注解的字段不为 null
*/
@NotNull(message = "用户名不能为空")
private String username;
/**
* NotBlank 注解除了确保字符串不为 null 外,还检查字符串至少有一个非空白字符。
*/
@NotBlank(message = "评论内容不能为空")
private String content;
/**
* NotEmpty 注解用于验证字符串、集合或数组不为 null 且至少有一个元素(对于集合和数组)或至少有一个非空白字符(对于字符串)。
*/
@NotEmpty(message = "消息内容不能为空")
private String text;
/**
* Size 注解用于验证字符串、集合、数组或Map的大小是否在指定的范围内。
*/
@Size(min = 1, max = 100, message = "书名长度必须在1到100个字符之间")
private String title;
/**
* Past 注解用于验证日期类型的字段是否表示过去的日期。
*/
@Past(message = "事件日期必须是过去的日期")
private LocalDate eventDate;
/**
* Future 注解用于验证日期类型的字段是否表示未来的日期。
*/
@Future(message = "预计送达日期必须是未来的日期")
private LocalDate expectedDeliveryDate;
/**
* PastOrPresent 注解用于验证日期类型的字段是否表示现在或过去的日期。
*/
@PastOrPresent(message = "保险生效日期必须为当前或过去的日期")
private LocalDate effectiveDate;
/**
* FutureOrPresent 注解用于验证日期类型的字段是否表示现在或未来的日期。
*/
@FutureOrPresent(message = "预约日期必须是当前或未来的日期")
private LocalDate appointmentDate;
/**
* Pattern 注解用于验证字符串是否与指定的正则表达式匹配。flags: 正则表达式的匹配标志。
*/
@Pattern(message = "手机号格式错误", regexp = "^1(3\\d|4[5-9]|5[0-35-9]|6[567]|7[0-8]|8\\d|9[0-35-9])\\d{8}$", flags = {Pattern.Flag.CASE_INSENSITIVE})
private String phone;
@Pattern(message = "电子邮箱格式不正确", regexp = "^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$", flags = {Pattern.Flag.CASE_INSENSITIVE})
private String email2;
@Email(message = "电子邮件地址无效", regexp = "^.+@.+\\..+$")
private String email;
@Email(message = "邮箱格式错误")
private String email3;
/**
* Min 注解用于验证数值类型的字段是否大于或等于指定的最小值。
*/
@Min(value = 1, message = "购买数量不能少于1")
private int quantity;
/**
* Max 注解用于验证数值类型的字段是否小于或等于指定的最大值。
*/
@Max(value = 100, message = "库存数量不能超过100")
private int stock;
/**
* DecimalMin 注解用于验证 BigDecimal 或包装的数值类型字段是否大于或等于指定的十进制最小值。inclusive: 布尔值,指示是否包含指定的边界值(默认为 true)。
*/
@DecimalMin(value = "0.00", message = "价格必须非负")
private BigDecimal value;
/**
* DecimalMax 注解用于验证 BigDecimal 或包装的数值类型字段是否小于或等于指定的十进制最大值。inclusive: 布尔值,指示是否包含指定的边界值(默认为 true)。
*/
@DecimalMax(value = "1000.00", message = "价格不能超过1000")
private BigDecimal value2;
/**
* Positive 注解用于验证数值类型的字段是否严格大于 0。
*/
@Positive(message = "账户余额必须为正数")
private BigDecimal balance;
/**
* PositiveOrZero 注解用于验证数值类型的字段是否大于或等于 0。
*/
@PositiveOrZero(message = "账户余额不能为负数")
private BigDecimal balance2;
/**
* Negative 注解用于验证数值类型的字段是否严格小于 0。
*/
@Negative(message = "债务金额必须为负数")
private BigDecimal amount;
/**
* NegativeOrZero 注解用于验证数值类型的字段是否小于或等于 0。
*/
@NegativeOrZero(message = "债务金额不能为正数")
private BigDecimal amount2;
/**
* Digits 注解用于验证数值类型的字段是否符合指定的整数和小数位数。
*/
@Digits(integer = 10, fraction = 2, message = "交易金额格式不正确")
private BigDecimal amount3;
/**
* EAN 注解用于验证国际标准书号(ISBN)或欧洲商品编号(EAN-13)。type: 指定要验证的EAN类型
*/
@EAN(message = "无效的EAN或ISBN编号")
private String ean;
/**
* 虽然 @URL注解在JSR 380规范中没有定义,但Hibernate Validator提供了类似的功能,用于验证字符串是否是有效的URL格式。
* protocol: 指定要验证的协议(默认为http或https)。
* host: 指定要验证的主机名。
* port: 指定要验证的端口号。
* regexp: 自定义的URL匹配正则表达式。
*/
@URL(message = "无效的URL", protocol = "https", host = "example.com", port = 443)
private String url;
}
//@AssertFalse 被注释的元素只能为false
//@AssertTrue 被注释的元素只能为true
//@DecimalMax 被注释的元素必须小于或等于{value}
//@DecimalMin 被注释的元素必须大于或等于{value}
//@Digits 被注释的元素数字的值超出了允许范围(只允许在{integer}位整数和{fraction}位小数范围内)
//@Email 被注释的元素不是一个合法的电子邮件地址
//@Future 被注释的元素需要是一个将来的时间
//@FutureOrPresent 被注释的元素需要是一个将来或现在的时间
//@Max 被注释的元素最大不能超过{value}
//@Min 被注释的元素最小不能小于{value}
//@Negative 被注释的元素必须是负数
//@NegativeOrZero 被注释的元素必须是负数或零
//@NotBlank 被注释的元素不能为空
//@NotEmpty 被注释的元素不能为空
//@NotNull 被注释的元素不能为null
//@Null 被注释的元素必须为null
//@Past 被注释的元素需要是一个过去的时间
//@PastOrPresent 被注释的元素需要是一个过去或现在的时间
//@Pattern 被注释的元素需要匹配正则表达式"{regexp}"
//@Positive 被注释的元素必须是正数
//@PositiveOrZero 被注释的元素必须是正数或零
//@Size 被注释的元素个数必须在{min}和{max}之间
//@CreditCardNumber 被注释的元素不合法的信用卡号码
//@Currency 被注释的元素不合法的货币 (必须是{value}其中之一)
//@EAN 被注释的元素不合法的{type}条形码
//@Email 被注释的元素不是一个合法的电子邮件地址 (已过期)
//@Length 被注释的元素长度需要在{min}和{max}之间
//@CodePointLength 被注释的元素长度需要在{min}和{max}之间
//@LuhnCheck 被注释的元素${validatedValue}的校验码不合法, Luhn模10校验和不匹配
//@Mod10Check 被注释的元素${validatedValue}的校验码不合法, 模10校验和不匹配
//@Mod11Check 被注释的元素${validatedValue}的校验码不合法, 模11校验和不匹配
//@ModCheck 被注释的元素${validatedValue}的校验码不合法, ${modType}校验和不匹配 (已过期)
//@NotBlank 被注释的元素不能为空 (已过期)
//@NotEmpty 被注释的元素不能为空 (已过期)
//@ParametersScriptAssert 被注释的元素执行脚本表达式"{script}"没有返回期望结果
//@Range 被注释的元素需要在{min}和{max}之间
//@SafeHtml 被注释的元素可能有不安全的HTML内容
//@ScriptAssert 被注释的元素执行脚本表达式"{script}"没有返回期望结果
//@URL 被注释的元素需要是一个合法的URL
//@DurationMax 被注释的元素必须小于${inclusive == true ? '或等于' : ''}${days == 0 ? '' : days += '天'}${hours == 0 ? '' : hours += '小时'}${minutes == 0 ? '' : minutes += '分钟'}${seconds == 0 ? '' : seconds += '秒'}${millis == 0 ? '' : millis += '毫秒'}${nanos == 0 ? '' : nanos += '纳秒'}
//@DurationMin 被注释的元素必须大于${inclusive == true ? '或等于' : ''}${days == 0 ? '' : days += '天'}${hours == 0 ? '' : hours += '小时'}${minutes == 0 ? '' : minutes += '分钟'}${seconds == 0 ? '' : seconds += '秒'}${millis == 0 ? '' : millis += '毫秒'}${nanos == 0 ? '' : nanos += '纳秒'}
工具类
java
import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import jakarta.validation.ValidatorFactory;
import lombok.experimental.UtilityClass;
import org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
/**
* @author zhanghaijun
* @date 2024/3/14 19:27
* @description validator工具类
*/
@UtilityClass
public class BeanValidator {
private static final ValidatorFactory validatorFactory = Validation.byDefaultProvider().configure().messageInterpolator(new ParameterMessageInterpolator()).buildValidatorFactory();
private static <T> Map<String, String> validate(T t, Class<?>... groups) {
Validator validator = validatorFactory.getValidator();
Set<ConstraintViolation<T>> validate = validator.validate(t, groups);
if (validate.isEmpty()) {
return Collections.emptyMap();
} else {
LinkedHashMap<String, String> errors = new LinkedHashMap<>();
for (ConstraintViolation<T> violation : validate) {
errors.put(violation.getPropertyPath().toString(), violation.getMessage());
}
return errors;
}
}
public static <T> String validateResult(T t, Class<?>... groups) {
Map<String, String> validate = validate(t, groups);
if (validate.isEmpty()) {
return "";
}
StringBuilder messages = new StringBuilder("参数验证失败: ");
validate.forEach((k, v) -> messages.append(k).append(": ").append(v).append("; "));
return messages.toString();
}
}
示例代码
电商平台
- 用户信息(User):必须包含有效的电子邮件和非空的用户名。
- 商品详情(Product):需要有有效的库存数量和价格范围。
- 订单(Order):必须包含用户信息、商品列表,并且总金额必须为正数。
- 订单项(OrderItem):每个订单项需要验证购买数量和商品ID。
- 支付信息(PaymentInfo):需要验证支付金额是否正确,并且支付方式是否被接受。
java
class ECommercePlatform {
// 用户信息
public class User {
@Email(message = "电子邮件地址无效", groups = UserChecks.class)
private String email;
@NotBlank(message = "用户名不能为空", groups = UserChecks.class)
private String username;
// 用户相关方法...
}
// 商品详情
public class Product {
@NotNull(message = "商品ID不能为空", groups = ProductChecks.class)
private String productId;
@PositiveOrZero(message = "库存数量不能为负数", groups = ProductChecks.class)
private int stock;
@DecimalMin(value = "0.01", message = "商品价格必须至少为0.01", groups = ProductChecks.class)
private BigDecimal price;
// 商品相关方法...
}
// 订单
public class Order {
@Valid
private User user;
@Valid
@Size(min = 1, message = "订单至少需要一个商品项", groups = OrderChecks.class)
private List<OrderItem> items;
@Positive(message = "订单总金额必须为正数", groups = OrderChecks.class)
private BigDecimal totalAmount;
// 订单相关方法...
}
// 订单项
public class OrderItem {
@NotNull(message = "商品ID不能为空", groups = ItemChecks.class)
private String productId;
@Positive(message = "购买数量必须为正数", groups = ItemChecks.class)
private int quantity;
// 订单项相关方法...
}
// 支付信息
public class PaymentInfo {
@Positive(message = "支付金额必须为正数", groups = PaymentChecks.class)
private BigDecimal amount;
@NotBlank(message = "支付方式不能为空", groups = PaymentChecks.class)
private String method;
// 支付信息相关方法...
}
//验证组定义
public interface UserChecks {
}
public interface ProductChecks {
}
public interface OrderChecks {
}
public interface ItemChecks {
}
public interface PaymentChecks {
}
//错误元数据载荷
public class ErrorPayloadsimplementsPayload implements Payload {
}
}
在线预订系统分组验证
在线预订系统允许用户根据其类型(个人或公司)进行预订。系统需要对不同类型的预订应用不同的验证规则。
java
import javax.validation.*;
import javax.validation.constraints.*;
import java.time.LocalDate;
import java.util.Set;
// 验证组接口
public interface PersonalBookingChecks {
}
public interface CorporateBookingChecks {
}
// 预订类型枚举
public enum BookingType {
PERSONAL, CORPORATE
}
// 预订实体类
@Data
public class Booking {
private BookingType type;
// 个人预订字段
@NotBlank(message = "个人预订的姓名必须填写", groups = PersonalBookingChecks.class)
private String personalName;
@Email(message = "个人预订的电子邮箱格式必须正确", groups = PersonalBookingChecks.class)
private String personalEmail;
// 公司预订字段
@NotBlank(message = "公司预订的公司名称必须填写", groups = CorporateBookingChecks.class)
private String corporateName;
@PositiveOrZero(message = "公司预订的税务编号必须是非负数", groups = CorporateBookingChecks.class)
private Long corporateTaxId;
// 所有预订类型共有的字段
@NotNull(message = "入住日期不能为空")
private LocalDate checkInDate;
@NotNull(message = "退房日期不能为空")
private LocalDate checkOutDate;
}
// 服务层验证逻辑
public class BookingService {
private final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
public void processBooking(Booking booking) {
Set<ConstraintViolation<Booking>> violations = switch (booking.getType()) {
case PERSONAL -> validator.validate(booking, PersonalBookingChecks.class);
case CORPORATE -> validator.validate(booking, CorporateBookingChecks.class);
default -> throw new IllegalStateException("未知的预订类型");
};
if (!violations.isEmpty()) {
// 将验证错误转换为用户友好的字符串信息
throw new IllegalArgumentException("预订验证失败: " + formatValidationErrors(violations));
}
// 如果验证通过,继续处理预订逻辑,例如保存到数据库
// 省略处理预订的代码...
}
private String formatValidationErrors(Set<ConstraintViolation<Booking>> violations) {
StringBuilder sb = new StringBuilder();
for (ConstraintViolation<Booking> violation : violations) {
sb.append(violation.getPropertyPath()).append(": ").append(violation.getMessage()).append("\n");
}
return sb.toString();
}
// 省略其他服务方法...
}
// 测试类
public class BookingApplication {
public static void main(String[] args) {
BookingService service = new BookingService();
Booking booking = new Booking();
// 这里设置了booking的属性和类型...
// 处理预订
service.processBooking(booking);
}
}