Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

140 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Plusone Validator

简介

Plusone Validator 是一个受 FluentValidation 启发的 Java 校验库,使用 Lambda 表达式(方法引用)和流式 API 构建声明式校验规则。

特性:

  • 链式调用
  • 支持 POJOMap 两种校验模式
  • 每种校验规则支持四种错误信息提供方式(默认/自定义字符串/自定义异常/含属性值的自定义异常)
  • 基于 JDK 1.8

环境要求

  • JDK:1.8 及以上
  • 编码:UTF-8

安装

Plusone Validator 暂未发布至 Maven 中央仓库,需 clone 代码仓库后通过 mvn install 安装到本地仓库,然后在项目中引入:

<dependency>
  <groupId>xyz.zhouxy.plusone</groupId>
  <artifactId>plusone-validator</artifactId>
  <version>${plusone-validator.version}</version>
</dependency>

该项目依赖 plusone-commons,它本身依赖 Guava。

示例

以下示例基于一个包含姓名、邮箱、会员等级、客户编号、生日、地址等字段的 Customer 对象。

校验 POJO 对象

继承 BaseValidator<T>,在构造器中通过 ruleFor 添加规则链:

class CustomerValidator extends BaseValidator<Customer> {

    private static final CustomerValidator INSTANCE = new CustomerValidator();

    private CustomerValidator() {
        ruleFor(Customer::getName).notBlank("姓名不能为空");
        ruleFor(Customer::getEmailAddress).notBlank().emailAddress();
        ruleFor(Customer::getVipLevel).notNull().inRange(Range.closed(0, 10), "会员等级必须在0-10之间");
        ruleFor(Customer::getCustomerId).notBlank("客户编号不能为空");
        ruleFor(Customer::getBirthday)
            .notNull("生日不能为空")
            .must(LocalDate.now().minusYears(16)::isAfter, "用户必须大于16周岁");
        ruleFor(Customer::getAddress).length(20, 250, "地址长度必须在20-250之间");
        ruleFor(Customer::getVipLevel, Customer::getBirthday)
            .must((vipLevel, birthday) -> vipLevel <= 5 || LocalDate.now().minusYears(18).isAfter(birthday),
                "5级以上会员必须满18周岁");
    }

    public static CustomerValidator getInstance() {
        return INSTANCE;
    }
}

使用:

public void foo(Customer customer) {
    CustomerValidator.getInstance().validate(customer);
    // ...
}

校验 Map

继承 MapValidator<K,V>,在构造器中通过 ruleForStringruleForInt 等方法按 key 添加规则链。

validateAndCopy()先校验,然后仅保留白名单 key(构造时传入的 FIELD_NAMES),多余字段会被自动剥离,可防止 Map 注入攻击。

注意泛型类型见证语法 this.<LocalDate>ruleFor(...) — 当 Map 的 value 类型是 Object 时,需显式指定实际类型。

class CustomerMapValidator extends MapValidator<String, Object> {

    private static final CustomerMapValidator INSTANCE = new CustomerMapValidator();

    private static final String[] FIELD_NAMES = {
        "name", "emailAddress", "vipLevel", "customerId", "birthday", "address"
    };

    private CustomerMapValidator() {
        // validateAndCopy() 时默认保留的 key
        super(FIELD_NAMES);

        ruleForString("name").notBlank("姓名不能为空");
        ruleForString("emailAddress").notBlank().emailAddress();
        ruleForInt("vipLevel").notNull().inRange(Range.closed(0, 10), "会员等级必须在0-10之间");
        ruleForString("customerId").notBlank("客户编号不能为空");
        this.<LocalDate>ruleFor("birthday")
            .notNull("生日不能为空")
            .must(LocalDate.now().minusYears(16)::isAfter, "用户必须大于16周岁");
        ruleForString("address").length(20, 250, "地址长度必须在20-250之间");
        this.<Integer, LocalDate>ruleForPair("vipLevel", "birthday")
            .must((vipLevel, birthday) -> vipLevel <= 5 || LocalDate.now().minusYears(18).isAfter(birthday),
                "5级以上会员必须满18周岁");
    }

    public static CustomerMapValidator getInstance() {
        return INSTANCE;
    }
}

使用:

public void foo(Map<String, Object> customer) {
    Map<String, Object> validatedCustomer = CustomerMapValidator.getInstance().validateAndCopy(customer);
    // ...
}

快速参考

ruleFor 方法一览

方法 属性类型 适用场景
ruleFor(getter) 任意类型 通用对象属性校验
ruleForString(getter) String 字符串属性校验
ruleForInt(getter) Integer 整数属性校验
ruleForLong(getter) Long 长整数属性校验
ruleForDouble(getter) Double 浮点数属性校验
ruleForBool(getter) Boolean 布尔属性校验
ruleForComparable(getter) Comparable 可比较类型属性校验
ruleForCollection(getter) Collection<E> 集合属性校验
ruleForArray(getter) E[] 数组属性校验
ruleFor(g1, g2) 二元组 两个属性的联合校验

校验规则一览

规则 Object Comparable 数值 String Bool 集合/数组 说明
notNull() 不为 null
isNull() 必须为 null
equal(obj) 等于给定值
notEqual(obj) 不等于给定值
must(pred) 自定义条件
gt/ge/lt/le 大于/大于等于/小于/小于等于
inRange(range) 值在指定区间内
notBlank() 不为空白字符串
notEmpty() 不为空(长度 > 0)
isEmpty() 必须为空
length(min, max) 长度/大小在范围内
emailAddress() 满足邮箱格式
matches(pattern) 匹配正则表达式
matchesAny(patterns) 匹配任一正则
matchesAll(patterns) 匹配所有正则
allMatch(pred) 所有元素满足条件
isTrueValue() 必须为 true
isFalseValue() 必须为 false

自定义错误信息

每个校验规则都支持四种错误信息提供方式:

// 1. 默认消息
ruleFor(Person::getName).notNull();

// 2. 自定义字符串
ruleFor(Person::getName).notNull("姓名不能为空");

// 3. 自定义异常(Supplier)
ruleFor(Person::getName).notNull(() -> new BizException("姓名不能为空"));

// 4. 自定义异常 + 属性值注入(Function)
ruleFor(Person::getAge).gt(0, age -> new BizException("年龄必须 > 0,当前值:%d", age));

Null 值处理

多数校验规则对 null 宽松处理(视为通过),让用户通过 notNull() 显式控制 null 行为:

// ✅ null 视为通过 — 不会因 NPE 失败
ruleFor(Person::getEmail).emailAddress();

// ✅ 显式拒绝 null
ruleFor(Person::getEmail).notNull("邮箱不能为空").emailAddress();

例外:notBlank()notEmpty()null 视为不通过(null 本身就是 blank/empty)。

嵌套对象校验

使用 withRule(Consumer) 在子类构造器中调用嵌套校验器:

class OrderValidator extends BaseValidator<Order> {
    private static final OrderValidator INSTANCE = new OrderValidator();

    private OrderValidator() {
        ruleFor(Order::getOrderId).notBlank("订单号不能为空");

        // 嵌套校验 Customer
        withRule(order -> {
            Customer customer = order.getCustomer();
            CustomerValidator.getInstance().validate(customer);
        });

        // 需要自定义错误信息时,try-catch 重新包装
        withRule(order -> {
            try {
                AddressValidator.getInstance().validate(order.getAddress());
            } catch (ValidationException e) {
                throw ValidationException.withMessage("地址校验失败:%s", e.getMessage());
            }
        });
    }
}

对象整体校验

使用 withRule(Predicate) 对多个属性进行联合校验:

// 三个及以上字段的联合校验
withRule(order -> order.getTotalAmount().compareTo(order.getPaidAmount()) >= 0,
    "订单金额不能小于已付金额");

关于本项目

Plusone Validator 受 .NET 的 FluentValidation API 启发,使用 Apache License 2.0 开源。

About

Plusone Validator 是一个校验库,使用函数式接口(lambda 表达式和方法引用)和流式 API 构建校验规则,对对象进行校验。

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages