diff --git "a/docs/framework/fit/java/user-guide-book/12. \351\231\204\345\212\240\345\212\237\350\203\275\347\273\204\344\273\266.md" "b/docs/framework/fit/java/user-guide-book/12. \351\231\204\345\212\240\345\212\237\350\203\275\347\273\204\344\273\266.md" index b61170e4b..27b0ccd07 100644 --- "a/docs/framework/fit/java/user-guide-book/12. \351\231\204\345\212\240\345\212\237\350\203\275\347\273\204\344\273\266.md" +++ "b/docs/framework/fit/java/user-guide-book/12. \351\231\204\345\212\240\345\212\237\350\203\275\347\273\204\344\273\266.md" @@ -210,255 +210,314 @@ public class MyEventHandler implements EventHandler { } ``` -# 12.3 Validation +# 12.3 Validation 插件 +FIT 框架提供了基于 Hibernate Validator 的数据校验功能,支持对方法参数进行自动校验。 + +- `fit-hibernate-validation`:基于 javax.validation 规范,适用于传统 Java EE 环境 +- `fit-hibernate-validation-jakarta`:基于 jakarta.validation 规范,适用于 Jakarta EE 环境 约束由约束注解和约束验证实现的组合定义。约束注解可以应用于类、方法、字段或其他约束注解(在组合的情况下)。 ## 12.3.1 依赖 +当前 FIT 实现中同时存在两套 Validation 插件,分别对应 javax.validation 和 jakarta.validation 标准。 +两套校验插件同时运行,通过责任链的方式校验 FIT 程序中需要被校验的参数。 + +用户可根据自己的开发需要,自行选择对应的标准依赖: + +**传统 Java EE 环境(javax):** -``` xml - - org.fitframework.extension - fit-validation - ${fit.version} - +```xml + + javax.validation + validation-api + 2.0.2.Final + +``` + +**Jakarta EE 环境:** + +```xml + + jakarta.validation + jakarta.validation-api + 3.1.1 + ``` ## 12.3.2 注解用法 -1. 方法的某个入参是基本数据类型,可直接在该参数前使用注解添加约束规则。 - - ``` java - public void test1(@NotEmpty String name) {} - ``` -2. 方法的某个入参是非基本数据类型,需要在该参数前添加 @Validated 注解,否则添加的约束规则不生效,并在该类内对需要校验的字段添加所需的校验注解。 - - ``` java - public class CreateProjectReq { - /** - * 请求序列号 - */ - @Range(min = 0, max = 10, message = "最多只能请求10个值!") - private Integer requestNum; - - /** - * 项目名称 - */ - @NotBlank(message = "项目名称不可为空") - private String projectName; - ... - } - - public CreateProjectReq createProject(@Validated CreateProjectReq reqVO) { - ... - } - ``` -3. 方法的某个入参是泛型类型,如`Collection`、`Map`的参数类型,需要在该参数前添加 @Validated 注解,否则添加的约束规则不生效,并在该类内对需要校验的字段添加所需的校验注解。 - - ``` java - public void test(@Validated List obj) {} - ``` -4. `FIT` 支持对嵌套校验,即当泛型类型中内嵌泛型类型、非基本数据类型的字段中包含非基本数据类型。 - - ``` java - public class Company { - @Range(min = 0, max = 1, message = "经理只能有0-1个!") - private int manager; - - @Validated - private Product product; - - @Validated - private List cars; - - ... - } - - public void test(@Validated Company) {} - public void test(@Validated Map) {} - ... - ``` -5. 方法被调用时,如果传入的实际参数与约束规则不符,会直接抛出 ConstraintViolationException ,表明参数校验失败。 +### 基本用法 -## 12.3.3 校验原理 +1. **类级别校验**:在类上添加 `@Validated` 注解,框架会自动对该类的所有方法参数进行校验。 + +```java +@Component +@Validated +public class UserController { -`modelengine.fitframework.validation`目前提供了部分额外的自定义校验注解,如`@NotBlank` 、`@NotEmpty`、`@Range`等,其逻辑可以参考`spring`对应注解的逻辑。 + public void createUser(@NotBlank(message = "用户名不能为空") String username, + @Min(value = 18, message = "年龄必须大于等于 18") Integer age) { + // 业务逻辑 + } +} +``` -### 校验器接口 +2. **复杂对象校验**:对于复杂对象,需要在参数前添加 `@Valid` 注解,并在对象内部字段上添加校验注解。 -* 主要的检验逻辑由`modelengine.fitframework.validation.ConstraintValidator`接口定义。通过实现该接口,可以自定义校验器,并编写具体的校验逻辑。 -* 校验器接口定义了一个`isValid`方法,用于在给定的对象上进行校验。校验器将根据对象的类型和注解信息,逐一校验对象的属性。 +```java +public class UserDto { + @NotBlank(message = "名称不能为空") + private String name; -### 校验过程 + @Min(value = 0, message = "年龄必须大于等于 0") + @Max(value = 150, message = "年龄必须小于等于 150") + private Integer age; -* 当接收到请求时,可以使用 `@Validated` 注解来标记要校验的对象。 -* 框架会查找对应的校验器,并在处理请求时自动调用校验器的 `validate` 方法进行校验。 -* 校验器会根据对象的注解信息,逐个校验对象的属性。如果发现校验失败,则构造一个 `modelengine.fitframework.validation.exception.ConstraintViolationException` 对象,并将校验失败的信息存入其中。 + @Email(message = "邮箱格式不正确") + private String email; -### 异常处理 + @Pattern(regexp = "^[a-zA-Z]+$", message = "代码只能包含字母") + private String code; -* 在异常处理方法中,可以将异常中的校验错误信息提取出来,并按照需要进行处理,如包装成一个响应实体类返回给客户端。 + // getter/setter... +} +``` -总的来说,Validation 的源码逻辑主要涉及注解的定义、校验器的实现以及校验过程的触发和异常处理。校验器会根据注解信息逐一校验对象的属性,当遇到错误时,将错误信息存入 `ConstraintViolationException` 中,并通过异常处理机制将错误信息返回给客户端。这样可以实现灵活、便捷的校验功能,并提高开发效率和错误处理的友好性。 +```java +@PostMapping("/user") +public void createUser(@RequestBody @Valid UserDto userDto) { + // 业务逻辑 +} +``` -## 12.3.4 自定义校验注解 +### 泛型类型校验 -上述提供注解只是最通用的验证规则,对于千奇百怪的业务需求,显然是不满足的。因此用户可以自定义注解,实现符合自身业务逻辑的校验逻辑,只需要两步即可拥有自定义注解。 +3. **泛型类型参数校验**:方法的某个入参是泛型类型,如 `Collection`、`Map` 的参数类型,需要在校验对象前添加 `@Valid` + 注解(按照 Hibernate Validator reference 推荐,但您仍然可以以 @Valid List 这样的方式添加注解,但不能以 @Valid List> 的方式添加注解),否则添加的约束规则不生效,并在泛型内部类型对需要校验的字段添加所需的校验注解。 -### 创建一个约束注解 +```java +// List 类型参数校验 +public void createUsers(List<@Valid UserDto> users) { + // 会对 List 中每个 UserDto 对象进行校验 +} -示例如下: +// Map 类型参数校验 +public void processUserData(Map userMap) { + // 会对 Map 中每个 UserDto 值进行校验 +} -``` java -@Retention(RetentionPolicy.RUNTIME) -@Constraint(MaxLengthValidator.class) -@Validated -public @interface MaxLength { - /** - * 表示校验失败的信息。 - * - * @return 表示校验失败的信息的 {@link String}。 - */ - String message() default ""; - - /** - * 表示校验的分组。 - * - * @return 表示校验分组的 {@link Class}{@code []}。 - */ - Class[] groups() default {}; - - /** - * 表示校验的最大值。 - */ - long max(); +// Set 类型参数校验 +public void updateUsers(Set<@Valid UserDto> users) { + // 会对 Set 中每个 UserDto 对象进行校验 } ``` -上述逻辑为自定义一个名为@MaxLength的注解,下面分别介绍每一部分的逻辑含义: +### 嵌套对象校验 -1. @Retention(RetentionPolicy.RUNTIME) 表示 Java 注解中的元注解(即注解的注解),用于指定注解的保留策略。RUNTIME表示在运行时保留此注解,程序可以通过反射在运行期间获取该注解的信息。 -2. Constraint(MaxLengthValidator.class) `@Constraint` 是一个元注解,用于自定义验证约束注解时标注在自定义的验证约束注解上。该注解的意义是指定该注解所表示的验证约束注解需要使用哪些验证器对被注解的元素进行验证。示例中的`MaxLengthValidator`即为`@MaxLength`的验证器。 -3. `@Validated` 注解的含义是用于标记一个自定义注解 `@MaxLength`,指示该注解需要进行验证。`@Validated` 注解通常与校验框架结合使用,用于触发对被注解元素的校验。 -4. 定义注解`@MaxLength` -5. 表示校验失败的信息,默认值为空。用户可以在使用注解时,定义校验失败时的信息。 -6. 表示校验分组,其逻辑将会在下一节**分组校验**中详细介绍。 -7. 表示校验的最大值,由于其没有定义默认值,因此在使用注解时,需要将`max`值添加到注解中。 +4. **嵌套校验支持**:FIT 支持对嵌套校验,即当泛型类型中内嵌泛型类型、非基本数据类型的字段中包含非基本数据类型时,需要在嵌套字段上添加 `@Valid` + 注解。 -### 实现一个validator +```java +public class CompanyDto { + @NotBlank(message = "公司名称不能为空") + private String name; -MaxLengthValidator验证器定义: + @Min(value = 0, message = "员工数量不能为负数") + private Integer employeeCount; -``` java -@Component -public class MaxLengthValidator implements ConstraintValidator { - private long max; + // 嵌套单个对象校验 + @Valid + @NotNull(message = "经理信息不能为空") + private UserDto manager; - @Override - public void initialize(MaxLength constraintAnnotation) { - this.max = constraintAnnotation.max(); - } + // 嵌套集合对象校验 + @Valid + @Size(min = 1, message = "至少需要一个员工") + private List employees; - @Override - public boolean isValid(String value) { - return value != null && value.length() <= this.max; - } + // 嵌套 Map 对象校验 + @Valid + private Map departments; + + // getter/setter... } -``` -validatior的实现非常简单,`ConstraintValidator`接口。 +public class DepartmentDto { + @NotBlank(message = "部门名称不能为空") + private String departmentName; -1. initialize方法,顾名思义是做一些初始化工作,获取约束的元数据并将它们存储在validator的实例中。 -2. isValid,实际的验证逻辑。 + @Valid + private List members; -使用自定义约束@MaxLength注解: + // getter/setter... +} -``` java -@MaxLength(message = "用户名(from User2)不能大于10", max = 10) -private final String name; +// 使用示例 +public void createCompany(@Valid CompanyDto company) { + // 会递归校验 company 及其所有嵌套对象 +} + +// 复杂嵌套泛型校验 +public void processComplexData(Map> companyGroups) { + // 会校验 Map 中每个 List,以及 List 中每个 CompanyDto 及其嵌套对象 +} ``` -## 12.3.5 分组校验 +5. **校验失败处理**:方法被调用时,如果传入的实际参数与约束规则不符,会直接抛出 `ConstraintViolationException`,表明参数校验失败。 -每个约束注解都必须定义一个 `groups` 元素,用于指定约束声明所关联的处理组。`groups` 允许在验证过程中限制应用的约束集。所定位的组将作为参数传递给 `isValid`方法。所有属于目标组的约束都将应用于验证过程中。如果没有传递组,则默认为空数组。 +### 支持的校验注解 -``` java -Class[] groups() default {}; -``` +框架支持完整的 Bean Validation 注解集合: -如果在一个元素上声明约束时未指定组,则默认组为 Default 组。组通常用于控制约束的评估顺序,或者用于验证 JavaBean 的部分状态。当在验证方法中传递了多个组时,顺序并不被限制。 +#### 空值校验 -``` java -public interface GroupA {} +- `@NotNull`:值不能为 null +- `@NotEmpty`:值不能为 null 且不能为空(适用于字符串、集合、数组、键值对) +- `@NotBlank`:字符串不能为 null、空字符串或只包含空白字符 +- `@Null`:值必须为 null -public interface GroupB {} +#### 布尔值校验 -public class Address { - @NotEmpty(groups = GroupA.class) - @MaxLength(max=50) - private String street; +- `@AssertTrue`:值必须为 true +- `@AssertFalse`:值必须为 false - @NotEmpty - private String city; +#### 数值校验 - @NotEmpty(groups = {GroupA.class, GroupB.class}) - private String zipCode; - ... -} -``` +- `@Min(value)`:数值必须大于等于指定值 +- `@Max(value)`:数值必须小于等于指定值 +- `@DecimalMin(value)`:小数值必须大于等于指定值 +- `@DecimalMax(value)`:小数值必须小于等于指定值 +- `@Positive`:数值必须为正数(大于 0) +- `@PositiveOrZero`:数值必须为正数或零(大于等于 0) +- `@Negative`:数值必须为负数(小于 0) +- `@NegativeOrZero`:数值必须为负数或零(小于等于 0) +- `@Digits(integer, fraction)`:数字格式校验,integer 指定整数位数,fraction 指定小数位数 -在上述示例中,Address 类中`@NotEmpty`(及其组成的约束)适用于`GroupA`,`@MaxLength`在 `street`上适用于默认组,`Empty`(及其组成的约束)在`zipCode`上适用于`GroupA`和`GroupB`组。 +#### 大小校验 -当运行以下代码: +- `@Size(min, max)`:字符串、集合、数组、键值对的大小必须在指定范围内 -``` java -validator.isValid(address) -``` +#### 时间校验 -`street`与`zipCode`字段上的`@NotEmpty`约束不会生效,生效的仅为`street`字段的`@MaxLength`注解以及`city`字段的`NotEmpty`注解。 +- `@Past`:日期必须是过去的时间 +- `@PastOrPresent`:日期必须是过去或现在的时间 +- `@Future`:日期必须是未来的时间 +- `@FutureOrPresent`:日期必须是未来或现在的时间 -当运行以下代码时: +#### 格式校验 -```` -validator.isValid(address, GroupA.class, GroupB.class) -```` +- `@Pattern(regexp)`:字符串必须匹配指定的正则表达式 +- `@Email`:字符串必须是有效的邮箱格式 -`street`与`zipCode`字段上的`@NotEmpty`约束会生效,`street`字段的`@MaxLength`注解以及`city`字段的`NotEmpty`注解则不会生效。 +#### 使用示例 -当运行以下代码时: +```java +public class CompleteValidationDto { + @NotNull(message = "ID 不能为空") + private Long id; -```` -validator.isValid(address, GroupB.class) -```` + @NotBlank(message = "用户名不能为空") + @Size(min = 2, max = 20, message = "用户名长度必须在 2-20 之间") + @Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "用户名只能包含字母、数字和下划线") + private String username; -`zipCode`字段上的`@NotEmpty`约束会生效,其他字段的注解都不会生效。 + @Email(message = "邮箱格式不正确") + private String email; -## 12.3.6 校验的国际化 + @Min(value = 18, message = "年龄必须大于等于 18") + @Max(value = 100, message = "年龄必须小于等于 100") + private Integer age; -`FIT`提供`@Notmpty, @NotBlank, @Range, @Postive, @Min`支持当校验失败时抛出自定义信息,示例用法如下, -同时,如2.6.3.4所述,支持用户自定义注解及处理器,当实现message()方法,可实现同样功能。 + @DecimalMin(value = "0.0", message = "薪资不能为负数") + @DecimalMax(value = "999999.99", message = "薪资不能超过 999999.99") + @Digits(integer = 6, fraction = 2, message = "薪资格式不正确") + private BigDecimal salary; -``` java -@NotBlank(message = "姓名不能为空") -private String name; + @Past(message = "生日必须是过去的日期") + private LocalDate birthday; + + @Future(message = "合同到期日必须是未来的日期") + private LocalDate contractEndDate; + + @AssertTrue(message = "必须同意用户协议") + private Boolean agreeTerms; + + @Size(min = 1, max = 5, message = "技能数量必须在 1-5 之间") + @Valid + private List skills; + + // getter/setter... +} ``` -当校验失败后,自定义的异常信息会被`FIT`整合后抛出`ConstraintViolationException`,在国际化场景下需要对该异常进行国际化处理,`FIT`支持简单的实现方式: +### 分组校验 -1. 定义国际化信息的资源文件; -2. 在校验注解的`message`中使用`{xxxxx}`来表明国际化,其中xxxx是1所定义的国际化资源文件中的key; -3. 按照 2.6.1 的介绍进行国际化处理。 +支持校验分组功能,可以在不同场景下应用不同的校验规则: -示例: +```java +public class UserDto { + // 分组接口定义 + public interface BasicGroup {} -``` java -@NotBlank(message ="{name.notnull}") -private String name; + public interface AdvancedGroup {} + + public interface StudentGroup {} + + public interface TeacherGroup {} + + @NotBlank(message = "名称不能为空") + private String name; + + @Max(value = 200, message = "高级组年龄必须小于等于 200", groups = AdvancedGroup.class) + private Integer age; + + // getter/setter... +} ``` -``` yml -// resources/i18n/default_en.properties -name.notnull=The name cant be null. -// resources/i18n/default_zh.properties -name.notnull=姓名不能为空。 +```java +@Component +@Validated(UserDto.AdvancedGroup.class) +public class AdvancedUserService { + public void processAdvancedUser(@Valid UserDto user) { + // 只会应用 AdvancedGroup 分组的校验规则 + } +} ``` + +## 12.3.3 校验原理 + +### 核心组件 + +1. **ValidationHandler**:校验入口类,通过 AOP 方式拦截带有 `@Validated` 注解的类或方法,自动触发校验逻辑。 +2. **Hibernate Validator**:底层使用 Hibernate Validator 作为校验引擎。 +3. **ConstraintViolationException**:校验失败时抛出的异常,包含详细的错误信息。 + +### 校验流程 + +1. 当调用带有 `@Validated` 注解的类的方法时,`ValidationHandler` 会拦截该调用 +2. 检查方法参数是否包含校验注解 +3. 使用 Hibernate Validator 对参数进行校验 +4. 如果校验失败,抛出 `ConstraintViolationException` 异常 +5. 可通过全局异常处理器捕获并处理校验异常 + +## 12.3.4 版本差异 + +两个插件的主要差异: + +| 特性 | fit-hibernate-validation | fit-hibernate-validation-jakarta | +|-------------|--------------------------|----------------------------------| +| 校验规范 | javax.validation | jakarta.validation | +| Hibernate 版本 | 6.2.5.Final | 9.0.1.Final | +| 适用环境 | 传统 Java EE | Jakarta EE | +| 包名空间 | javax.validation.* | jakarta.validation.* | + +选择建议: + +- 新项目推荐使用 Jakarta 版本 +- 现有项目根据依赖情况选择对应版本 +- 两个版本功能完全一致,仅包名空间不同 + +## 12.3.5 校验的国际化 + +与校验扩展一样,需要手动处理报错信息中的内容,以完成国际化功能 \ No newline at end of file diff --git a/framework/dependency/pom.xml b/framework/dependency/pom.xml index ed91e09d7..d9d50fb79 100644 --- a/framework/dependency/pom.xml +++ b/framework/dependency/pom.xml @@ -203,16 +203,6 @@ fit-transaction ${fit.version} - - org.fitframework.extension - fit-validation - ${fit.version} - - - org.fitframework.extension - fit-validation-hibernate - ${fit.version} - @@ -360,11 +350,21 @@ fit-service-coordination-locator ${fit.version} + + org.fitframework.plugin + fit-service-coordination-nacos + ${fit.version} + org.fitframework.plugin fit-service-coordination-simple ${fit.version} + + org.fitframework.plugin + fit-service-discovery + ${fit.version} + org.fitframework.plugin fit-service-registry @@ -372,7 +372,12 @@ org.fitframework.plugin - fit-service-discovery + fit-validation-hibernate-jakarta + ${fit.version} + + + org.fitframework.plugin + fit-validation-hibernate-javax ${fit.version} diff --git a/framework/fit/java/fit-api/src/main/java/modelengine/fitframework/validation/ConstraintValidator.java b/framework/fit/java/fit-api/src/main/java/modelengine/fitframework/validation/ConstraintValidator.java deleted file mode 100644 index 759034e7b..000000000 --- a/framework/fit/java/fit-api/src/main/java/modelengine/fitframework/validation/ConstraintValidator.java +++ /dev/null @@ -1,41 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -package modelengine.fitframework.validation; - -import java.lang.annotation.Annotation; - -/** - * 表示约束的校验器类。 - * - * @author 邬涨财 - * @since 2023-03-08 - */ -public interface ConstraintValidator { - /** - * 校验器类的初始方法。每一个校验器对象都只会调用一次。 - * - * @param constraintAnnotation 表示约束注解的 {@link A}。 - */ - default void initialize(A constraintAnnotation) {} - - /** - * 校验对象是否合法。 - * - * @param value 表示需要校验的目标对象的 {@link T}。 - * @return 表示校验对象是否合法的 {@code boolean}。 - */ - boolean isValid(T value); - - /** - * 获取校验器的参数。 - * - * @return 表示校验器参数的 {@link Object}{@code []}。 - */ - default Object[] args() { - return new Object[] {}; - } -} diff --git a/framework/fit/java/fit-api/src/main/java/modelengine/fitframework/validation/Validated.java b/framework/fit/java/fit-api/src/main/java/modelengine/fitframework/validation/Validated.java index a3a56c85b..7f9c22612 100644 --- a/framework/fit/java/fit-api/src/main/java/modelengine/fitframework/validation/Validated.java +++ b/framework/fit/java/fit-api/src/main/java/modelengine/fitframework/validation/Validated.java @@ -1,12 +1,11 @@ /*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. + * Copyright (c) 2024-2025 Huawei Technologies Co., Ltd. All rights reserved. * This file is a part of the ModelEngine Project. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ package modelengine.fitframework.validation; -import modelengine.fitframework.validation.constraints.Constraint; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -15,11 +14,12 @@ /** * 表示校验的注解。 - *

用来标识所要校验的类和参数。若校验的是类,则该类方法的所有带有 {@link Constraint} - * 参数会被校验;若校验的是参数,则校验该参数对象所有的带有 {@link Constraint} 字段。 + *

+ * 用来标识所要校验的类,该类的所有公共方法中,所有被 javax 或 jakarta 校验注解标注的参数都会被校验。 *

* * @author 邬涨财 + * @author 阮睿 * @since 2023-03-14 */ @Retention(RetentionPolicy.RUNTIME) diff --git a/framework/fit/java/fit-api/src/main/java/modelengine/fitframework/validation/constraints/Constraint.java b/framework/fit/java/fit-api/src/main/java/modelengine/fitframework/validation/constraints/Constraint.java deleted file mode 100644 index 580179711..000000000 --- a/framework/fit/java/fit-api/src/main/java/modelengine/fitframework/validation/constraints/Constraint.java +++ /dev/null @@ -1,31 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -package modelengine.fitframework.validation.constraints; - -import modelengine.fitframework.validation.ConstraintValidator; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * 表示约束的注解。 - * - * @author 邬涨财 - * @since 2023-03-08 - */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.ANNOTATION_TYPE}) -public @interface Constraint { - /** - * 表示约束所对应的校验器的类型。 - * - * @return 表示校验器的类型的 {@link Class}{@code >[]}。 - */ - Class>[] value(); -} diff --git a/framework/fit/java/fit-api/src/main/java/modelengine/fitframework/validation/constraints/Min.java b/framework/fit/java/fit-api/src/main/java/modelengine/fitframework/validation/constraints/Min.java deleted file mode 100644 index aeea6faa3..000000000 --- a/framework/fit/java/fit-api/src/main/java/modelengine/fitframework/validation/constraints/Min.java +++ /dev/null @@ -1,45 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -package modelengine.fitframework.validation.constraints; - -import modelengine.fitframework.validation.Validated; -import modelengine.fitframework.validation.validators.MinValidator; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * 表示校验元素是否满足下限值约束的注解。 - * - * @author 兰宇晨 - * @since 2024-08-28 - */ -@Retention(RetentionPolicy.RUNTIME) -@Constraint(MinValidator.class) -@Validated -public @interface Min { - /** - * 表示校验元素的下限值。 - * - * @return 表示校验元素的下限值的 {@code long}。 - */ - long min(); - - /** - * 表示校验失败的信息。 - * - * @return 表示校验失败的信息的 {@link String}。 - */ - String message() default "must be greater than the minimum value."; - - /** - * 表示校验的分组。 - * - * @return 表示校验分组的 {@link Class}{@code []}。 - */ - Class[] groups() default {}; -} diff --git a/framework/fit/java/fit-api/src/main/java/modelengine/fitframework/validation/constraints/NotBlank.java b/framework/fit/java/fit-api/src/main/java/modelengine/fitframework/validation/constraints/NotBlank.java deleted file mode 100644 index ec92f9801..000000000 --- a/framework/fit/java/fit-api/src/main/java/modelengine/fitframework/validation/constraints/NotBlank.java +++ /dev/null @@ -1,38 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -package modelengine.fitframework.validation.constraints; - -import modelengine.fitframework.validation.Validated; -import modelengine.fitframework.validation.validators.NotBlankValidator; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * 表示校验元素是否为空白的注解。 - * - * @author 邬涨财 - * @since 2023-03-08 - */ -@Retention(RetentionPolicy.RUNTIME) -@Constraint(NotBlankValidator.class) -@Validated -public @interface NotBlank { - /** - * 表示校验失败的信息。 - * - * @return 表示校验失败的信息的 {@link String}。 - */ - String message() default "must not be blank"; - - /** - * 表示校验的分组。 - * - * @return 表示校验分组的 {@link Class}{@code []}。 - */ - Class[] groups() default {}; -} diff --git a/framework/fit/java/fit-api/src/main/java/modelengine/fitframework/validation/constraints/NotEmpty.java b/framework/fit/java/fit-api/src/main/java/modelengine/fitframework/validation/constraints/NotEmpty.java deleted file mode 100644 index f3cfbceff..000000000 --- a/framework/fit/java/fit-api/src/main/java/modelengine/fitframework/validation/constraints/NotEmpty.java +++ /dev/null @@ -1,38 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -package modelengine.fitframework.validation.constraints; - -import modelengine.fitframework.validation.Validated; -import modelengine.fitframework.validation.validators.NotEmptyValidator; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * 表示校验元素是否为空的注解。 - * - * @author 邬涨财 - * @since 2023-03-08 - */ -@Retention(RetentionPolicy.RUNTIME) -@Constraint(NotEmptyValidator.class) -@Validated -public @interface NotEmpty { - /** - * 表示校验失败的信息。 - * - * @return 表示校验失败的信息的 {@link String}。 - */ - String message() default "must not be empty"; - - /** - * 表示校验的分组。 - * - * @return 表示校验分组的 {@link Class}{@code []}。 - */ - Class[] groups() default {}; -} diff --git a/framework/fit/java/fit-api/src/main/java/modelengine/fitframework/validation/constraints/Positive.java b/framework/fit/java/fit-api/src/main/java/modelengine/fitframework/validation/constraints/Positive.java deleted file mode 100644 index 9c82f190d..000000000 --- a/framework/fit/java/fit-api/src/main/java/modelengine/fitframework/validation/constraints/Positive.java +++ /dev/null @@ -1,38 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -package modelengine.fitframework.validation.constraints; - -import modelengine.fitframework.validation.Validated; -import modelengine.fitframework.validation.validators.PositiveValidator; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * 表示判断元素值是否为正的注解。 - * - * @author 吕博文 - * @since 2024-08-01 - */ -@Retention(RetentionPolicy.RUNTIME) -@Constraint(PositiveValidator.class) -@Validated -public @interface Positive { - /** - * 表示校验失败的信息。 - * - * @return 表示校验失败的信息的 {@link String}。 - */ - String message() default "must be positive"; - - /** - * 表示校验的分组。 - * - * @return 表示校验分组的 {@link Class}{@code []}。 - */ - Class[] groups() default {}; -} diff --git a/framework/fit/java/fit-api/src/main/java/modelengine/fitframework/validation/constraints/Range.java b/framework/fit/java/fit-api/src/main/java/modelengine/fitframework/validation/constraints/Range.java deleted file mode 100644 index a5d64104a..000000000 --- a/framework/fit/java/fit-api/src/main/java/modelengine/fitframework/validation/constraints/Range.java +++ /dev/null @@ -1,52 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -package modelengine.fitframework.validation.constraints; - -import modelengine.fitframework.validation.Validated; -import modelengine.fitframework.validation.validators.RangeValidator; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * 表示校验元素是否为空的注解。 - * - * @author 邬涨财 - * @since 2023-03-08 - */ -@Retention(RetentionPolicy.RUNTIME) -@Constraint(RangeValidator.class) -@Validated -public @interface Range { - /** - * 表示校验元素的下限值。 - * - * @return 表示校验元素的下限值的 {@code long}。 - */ - long min(); - - /** - * 表示校验元素的上限值。 - * - * @return 表示校验元素的上限值的 {@code long}。 - */ - long max(); - - /** - * 表示校验失败的信息。 - * - * @return 表示校验失败的信息的 {@link String}。 - */ - String message() default "must be in range"; - - /** - * 表示校验的分组。 - * - * @return 表示校验分组的 {@link Class}{@code []}。 - */ - Class[] groups() default {}; -} diff --git a/framework/fit/java/fit-api/src/main/java/modelengine/fitframework/validation/validators/MinValidator.java b/framework/fit/java/fit-api/src/main/java/modelengine/fitframework/validation/validators/MinValidator.java deleted file mode 100644 index a6dedac92..000000000 --- a/framework/fit/java/fit-api/src/main/java/modelengine/fitframework/validation/validators/MinValidator.java +++ /dev/null @@ -1,52 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -package modelengine.fitframework.validation.validators; - -import modelengine.fitframework.util.ObjectUtils; -import modelengine.fitframework.validation.ConstraintValidator; -import modelengine.fitframework.validation.constraints.Min; - -/** - * 表示 {@link Min} 约束的校验器。 - * - * @author 兰宇晨 - * @since 2024-08-28 - */ -public class MinValidator implements ConstraintValidator { - private long min; - - @Override - public void initialize(Min constraintAnnotation) { - this.min = constraintAnnotation.min(); - } - - @Override - public boolean isValid(Object value) { - if (value == null) { - return false; - } else if (value instanceof Integer) { - int convertedValue = ObjectUtils.cast(value); - return convertedValue >= this.min; - } else if (value instanceof Long) { - long convertedValue = ObjectUtils.cast(value); - return convertedValue >= this.min; - } else if (value instanceof Float) { - float convertedValue = ObjectUtils.cast(value); - return convertedValue >= this.min; - } else if (value instanceof Double) { - double convertedValue = ObjectUtils.cast(value); - return convertedValue >= this.min; - } else { - throw new UnsupportedOperationException("Failed to validate value: invalid value."); - } - } - - @Override - public Object[] args() { - return new Object[] {this.min}; - } -} \ No newline at end of file diff --git a/framework/fit/java/fit-api/src/main/java/modelengine/fitframework/validation/validators/NotBlankValidator.java b/framework/fit/java/fit-api/src/main/java/modelengine/fitframework/validation/validators/NotBlankValidator.java deleted file mode 100644 index 5ec02ee3c..000000000 --- a/framework/fit/java/fit-api/src/main/java/modelengine/fitframework/validation/validators/NotBlankValidator.java +++ /dev/null @@ -1,24 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -package modelengine.fitframework.validation.validators; - -import modelengine.fitframework.util.StringUtils; -import modelengine.fitframework.validation.ConstraintValidator; -import modelengine.fitframework.validation.constraints.NotBlank; - -/** - * 表示 {@link NotBlank} 约束的校验器。 - * - * @author 邬涨财 - * @since 2023-03-08 - */ -public class NotBlankValidator implements ConstraintValidator { - @Override - public boolean isValid(String value) { - return StringUtils.isNotBlank(value); - } -} diff --git a/framework/fit/java/fit-api/src/main/java/modelengine/fitframework/validation/validators/NotEmptyValidator.java b/framework/fit/java/fit-api/src/main/java/modelengine/fitframework/validation/validators/NotEmptyValidator.java deleted file mode 100644 index 5c5d46e95..000000000 --- a/framework/fit/java/fit-api/src/main/java/modelengine/fitframework/validation/validators/NotEmptyValidator.java +++ /dev/null @@ -1,43 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -package modelengine.fitframework.validation.validators; - -import modelengine.fitframework.util.ArrayUtils; -import modelengine.fitframework.util.CollectionUtils; -import modelengine.fitframework.util.MapUtils; -import modelengine.fitframework.util.ObjectUtils; -import modelengine.fitframework.util.StringUtils; -import modelengine.fitframework.validation.ConstraintValidator; -import modelengine.fitframework.validation.constraints.NotEmpty; - -import java.util.List; -import java.util.Map; - -/** - * 表示 {@link NotEmpty} 约束的校验器。 - * - * @author 邬涨财 - * @since 2023-03-08 - */ -public class NotEmptyValidator implements ConstraintValidator { - @Override - public boolean isValid(Object value) { - if (value == null) { - return false; - } else if (value instanceof List) { - return CollectionUtils.isNotEmpty(ObjectUtils.cast(value)); - } else if (value instanceof Map) { - return MapUtils.isNotEmpty(ObjectUtils.cast(value)); - } else if (value instanceof String) { - return StringUtils.isNotEmpty(ObjectUtils.cast(value)); - } else if (value instanceof Object[]) { - return ArrayUtils.isNotEmpty(ObjectUtils.cast(value)); - } else { - throw new UnsupportedOperationException("Failed to validate value: invalid value."); - } - } -} diff --git a/framework/fit/java/fit-api/src/main/java/modelengine/fitframework/validation/validators/PositiveValidator.java b/framework/fit/java/fit-api/src/main/java/modelengine/fitframework/validation/validators/PositiveValidator.java deleted file mode 100644 index d6a5e9999..000000000 --- a/framework/fit/java/fit-api/src/main/java/modelengine/fitframework/validation/validators/PositiveValidator.java +++ /dev/null @@ -1,26 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -package modelengine.fitframework.validation.validators; - -import modelengine.fitframework.validation.ConstraintValidator; -import modelengine.fitframework.validation.constraints.Positive; - -/** - * 表示 {@link Positive} 约束的校验器。 - * - * @author 吕博文 - * @since 2024-07-29 - */ -public class PositiveValidator implements ConstraintValidator { - @Override - public boolean isValid(Number value) { - if (value == null) { - return false; - } - return value.doubleValue() > 0; - } -} diff --git a/framework/fit/java/fit-api/src/main/java/modelengine/fitframework/validation/validators/RangeValidator.java b/framework/fit/java/fit-api/src/main/java/modelengine/fitframework/validation/validators/RangeValidator.java deleted file mode 100644 index 3f80592df..000000000 --- a/framework/fit/java/fit-api/src/main/java/modelengine/fitframework/validation/validators/RangeValidator.java +++ /dev/null @@ -1,54 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -package modelengine.fitframework.validation.validators; - -import modelengine.fitframework.util.ObjectUtils; -import modelengine.fitframework.validation.ConstraintValidator; -import modelengine.fitframework.validation.constraints.Range; - -/** - * 表示 {@link Range} 约束的校验器。 - * - * @author 邬涨财 - * @since 2023-03-08 - */ -public class RangeValidator implements ConstraintValidator { - private long min; - private long max; - - @Override - public void initialize(Range constraintAnnotation) { - this.min = constraintAnnotation.min(); - this.max = constraintAnnotation.max(); - } - - @Override - public boolean isValid(Object value) { - if (value == null) { - return false; - } else if (value instanceof Integer) { - int convertedValue = ObjectUtils.cast(value); - return convertedValue >= this.min && convertedValue <= this.max; - } else if (value instanceof Long) { - long convertedValue = ObjectUtils.cast(value); - return convertedValue >= this.min && convertedValue <= this.max; - } else if (value instanceof Float) { - float convertedValue = ObjectUtils.cast(value); - return convertedValue >= this.min && convertedValue <= this.max; - } else if (value instanceof Double) { - double convertedValue = ObjectUtils.cast(value); - return convertedValue >= this.min && convertedValue <= this.max; - } else { - throw new UnsupportedOperationException("Failed to validate value: invalid value."); - } - } - - @Override - public Object[] args() { - return new Object[] {this.min, this.max}; - } -} diff --git a/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-jakarta/pom.xml b/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-jakarta/pom.xml new file mode 100644 index 000000000..41d45c5c1 --- /dev/null +++ b/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-jakarta/pom.xml @@ -0,0 +1,112 @@ + + + 4.0.0 + + + org.fitframework.plugin + fit-plugin-parent + 3.6.0-SNAPSHOT + + + fit-validation-hibernate-jakarta + + FIT Hibernate Validation Jakarta + FIT Framework Hibernate Validation module provides Validation for args. + + https://github.com/ModelEngine-Group/fit-framework + + + + 9.0.1.Final + + + + + + org.fitframework + fit-api + + + + + org.hibernate.validator + hibernate-validator + ${hibernate.version} + + + + + org.junit.jupiter + junit-jupiter + test + + + org.mockito + mockito-core + test + + + org.assertj + assertj-core + test + + + org.fitframework + fit-test-framework + test + + + com.h2database + h2 + test + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-dependencies + + copy-dependencies + + + ../../../../../../build/shared/ + jakarta.validation + + + + + + org.fitframework + fit-build-maven-plugin + + system + 4 + + + + org.apache.maven.plugins + maven-antrun-plugin + + + package + + + + + + + run + + + + + + + diff --git a/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-jakarta/src/main/java/modelengine/fitframework/validation/LocaleMessageInterpolator.java b/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-jakarta/src/main/java/modelengine/fitframework/validation/LocaleMessageInterpolator.java new file mode 100644 index 000000000..fc22cc98a --- /dev/null +++ b/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-jakarta/src/main/java/modelengine/fitframework/validation/LocaleMessageInterpolator.java @@ -0,0 +1,86 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package modelengine.fitframework.validation; + +import jakarta.validation.MessageInterpolator; + +import org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator; + +import java.util.Locale; + +/** + * 地区消息插值器。 + *

+ * 作为 Jakarta 消息插值器的代理类,提供地区设置能力。 + *

+ * + * @author 阮睿 + * @since 2025-08-18 + */ +public class LocaleMessageInterpolator implements MessageInterpolator { + private final MessageInterpolator target; + + private Locale locale; + + /** + * 构造函数,使用指定的目标消息插值器初始化实例。 + * + * @param target 表示目标消息插值器的 {@link MessageInterpolator}。 + */ + public LocaleMessageInterpolator(MessageInterpolator target) { + this.target = target; + this.locale = Locale.getDefault(); + } + + /** + * 构造函数,使用指定的地区初始化实例。 + * + * @param locale 表示指定地区的 {@link Locale}。 + */ + public LocaleMessageInterpolator(Locale locale) { + this.locale = locale; + this.target = new ParameterMessageInterpolator(); + } + + /** + * 构造函数,使用指定的目标消息插值器和地区初始化实例。 + * + * @param target 表示被代理的目标消息插值器的 {@link MessageInterpolator}。 + * @param locale 表示当前消息插值器要使用语言的相关地区的 {@link Locale}。 + */ + public LocaleMessageInterpolator(MessageInterpolator target, Locale locale) { + this.target = target; + this.locale = locale; + } + + /** + * 构造函数,使用默认地区初始化实例。 + */ + public LocaleMessageInterpolator() { + this.locale = Locale.getDefault(); + this.target = new ParameterMessageInterpolator(); + } + + @Override + public String interpolate(String messageTemplate, Context context) { + return this.target.interpolate(messageTemplate, context, this.locale); + } + + @Override + public String interpolate(String messageTemplate, Context context, Locale locale) { + return this.target.interpolate(messageTemplate, context, locale); + } + + /** + * 设置地区。 + * + * @param locale 表示当前消息插值器要使用语言的相关地区的 {@link Locale}。 + */ + public void setLocale(Locale locale) { + this.locale = locale; + } +} diff --git a/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-jakarta/src/main/java/modelengine/fitframework/validation/ValidationHandler.java b/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-jakarta/src/main/java/modelengine/fitframework/validation/ValidationHandler.java new file mode 100644 index 000000000..ab4805c70 --- /dev/null +++ b/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-jakarta/src/main/java/modelengine/fitframework/validation/ValidationHandler.java @@ -0,0 +1,166 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package modelengine.fitframework.validation; + +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; +import jakarta.validation.Validation; +import jakarta.validation.Validator; +import jakarta.validation.ValidatorFactory; +import jakarta.validation.executable.ExecutableValidator; +import modelengine.fitframework.annotation.Component; +import modelengine.fitframework.annotation.Scope; +import modelengine.fitframework.aop.JoinPoint; +import modelengine.fitframework.aop.annotation.Aspect; +import modelengine.fitframework.aop.annotation.Before; +import modelengine.fitframework.ioc.annotation.PreDestroy; + +import org.hibernate.validator.HibernateValidator; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedParameterizedType; +import java.lang.reflect.AnnotatedType; +import java.lang.reflect.Parameter; +import java.util.Arrays; +import java.util.Locale; +import java.util.Set; + +/** + * 校验入口类。 + *

+ * 当调用的类包含 {@link Validated} 注解时,会对该类公共方法参数进行校验处理。 + *

+ * + * @author 阮睿 + * @since 2025-07-18 + */ +@Aspect(scope = Scope.GLOBAL) +@Component +public class ValidationHandler implements AutoCloseable { + private final ValidatorFactory validatorFactory; + private final Validator validator; + private final LocaleMessageInterpolator messageInterpolator; + + public ValidationHandler() { + this.messageInterpolator = new LocaleMessageInterpolator(); + this.validatorFactory = Validation.byProvider(HibernateValidator.class) + .configure() + .messageInterpolator(this.messageInterpolator) + .failFast(false) + .buildValidatorFactory(); + this.validator = this.validatorFactory.getValidator(); + } + + /** + * 设置校验信息语言。 + * + * @param locale 校验语言 {@link Locale}。 + */ + public void setLocale(Locale locale) { + this.messageInterpolator.setLocale(locale); + } + + /** + * 方法参数校验处理。 + * + * @param joinPoint 表示切面的切点 {@link JoinPoint}。 + * @param validated 切点方法所属类的校验注解 {@link Validated}。 + */ + @Before(value = "@target(validated) && execution(public * *(..))", argNames = "joinPoint, validated") + private void handle(JoinPoint joinPoint, Validated validated) { + // 检查方法参数是否包含被 jakarta.validation.Constraint 标注的校验注解。 + if (hasJakartaConstraintAnnotations(joinPoint.getMethod().getParameters())) { + ExecutableValidator execVal = this.validator.forExecutables(); + Set> result = execVal.validateParameters(joinPoint.getTarget(), + joinPoint.getMethod(), + joinPoint.getArgs(), + validated.value()); + if (!result.isEmpty()) { + throw new ConstraintViolationException(result); + } + } + } + + @PreDestroy + @Override + public void close() { + this.validatorFactory.close(); + } + + /** + * 检查方法参数是否包含 {@code jakarta.validation} 校验注解。 + * + * @param parameters 表示可能携带校验注解的方法参数数组 {@link Parameter}{@code []}。 + * @return 如果包含 {@code jakarta.validation} 标注的校验注解则返回 {@code true},否则返回 {@code false}。 + */ + private boolean hasJakartaConstraintAnnotations(Parameter[] parameters) { + return Arrays.stream(parameters).anyMatch(this::hasConstraintAnnotationsInParameter); + } + + /** + * 检查参数及其泛型类型参数是否包含校验注解。 + * + * @param parameter 表示可能携带校验注解的方法参数 {@link Parameter}。 + * @return 如果包含 {@code jakarta.validation} 标注的校验注解则返回 {@code true},否则返回 {@code false}。 + */ + private boolean hasConstraintAnnotationsInParameter(Parameter parameter) { + return hasConstraintAnnotationsInType(parameter.getAnnotatedType()); + } + + /** + * 判断参数类型,解析参数本身注解或其泛型类型参数注解。 + * + * @param annotatedType 表示待检查的参数类型 {@link AnnotatedType}。 + * @return 如果包含 {@code jakarta.validation} 标注的校验注解则返回 {@code true},否则返回 {@code false}。 + */ + private boolean hasConstraintAnnotationsInType(AnnotatedType annotatedType) { + // 检查当前类型上的注解。 + if (Arrays.stream(annotatedType.getAnnotations()).anyMatch(this::isJakartaConstraintAnnotation)) { + return true; + } + // 如果是参数化类型,递归检查类型参数。 + if (annotatedType instanceof AnnotatedParameterizedType parameterizedType) { + return Arrays.stream(parameterizedType.getAnnotatedActualTypeArguments()) + .anyMatch(this::hasConstraintAnnotationsInType); + } + return false; + } + + /** + * 检查注解是否属于 {@code jakarta.validation} 注解。 + *

+ * 由于存在嵌套校验的情况,{@code @Valid} 与其他校验注解都可以标注参数需要进行校验,但两者的实现与语义上存在差异,处理逻辑不能合并,因此分情况讨论: + *

+ *
    + *
  1. + * {@code @Valid} 注解检查。用于标记需要级联校验的对象,例如:{@code void validateCompany(@Valid Company company)}。 + *
  2. + *
  3. + * 其他携带 {@code @Constraint} 元注解的校验注解检查。例如:{@code void validateEmployee(@NotBlank String name, @Positive + * int)}。 + *
  4. + *
+ * + * @param annotation 要检查的注解 {@link java.lang.annotation.Annotation}。 + * @return 如果属于 {@code jakarta.validation} 注解(即 {@code @Valid} 或携带 {@code @Constraint}),则返回 {@code true},否则返回 + * {@code false}。 + */ + private boolean isJakartaConstraintAnnotation(Annotation annotation) { + // @Valid 注解检查。 + if ("jakarta.validation.Valid".equals(annotation.annotationType().getName())) { + return true; + } + // 检查 jakarta.validation.constraints,org.hibernate.validator.constraints 包下的注解或者用户根据 jakarta 标准自行实现的注解。 + // 通过 Constraint 注解检查当前注解是否为校验注解。 + Annotation[] metaAnnotations = annotation.annotationType().getAnnotations(); + return Arrays.stream(metaAnnotations).anyMatch(metaAnnotation -> { + String packageName = metaAnnotation.annotationType().getPackage().getName(); + String className = metaAnnotation.annotationType().getSimpleName(); + return "jakarta.validation".equals(packageName) && "Constraint".equals(className); + }); + } +} diff --git a/framework/fit/java/fit-extension/fit-validation/src/main/resources/application.yaml b/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-jakarta/src/main/resources/application.yaml similarity index 100% rename from framework/fit/java/fit-extension/fit-validation/src/main/resources/application.yaml rename to framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-jakarta/src/main/resources/application.yaml diff --git a/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-jakarta/src/test/java/modelengine/fitframework/validation/ValidationDataControllerTest.java b/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-jakarta/src/test/java/modelengine/fitframework/validation/ValidationDataControllerTest.java new file mode 100644 index 000000000..ce817a706 --- /dev/null +++ b/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-jakarta/src/test/java/modelengine/fitframework/validation/ValidationDataControllerTest.java @@ -0,0 +1,95 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package modelengine.fitframework.validation; + +import static org.assertj.core.api.Assertions.assertThat; + +import modelengine.fit.http.client.HttpClassicClientResponse; +import modelengine.fitframework.annotation.Fit; +import modelengine.fitframework.test.annotation.MvcTest; +import modelengine.fitframework.test.domain.mvc.MockMvc; +import modelengine.fitframework.test.domain.mvc.request.MockMvcRequestBuilders; +import modelengine.fitframework.test.domain.mvc.request.MockRequestBuilder; +import modelengine.fitframework.validation.data.Company; +import modelengine.fitframework.validation.data.Employee; +import modelengine.fitframework.validation.data.ValidationDataController; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.Collections; + +/** + * {@link ValidationDataController} 的测试集。 + * + * @author 阮睿 + * @since 2025-07-18 + */ +@MvcTest(classes = {ValidationDataController.class}) +@DisplayName("测试 EvalDataController") +public class ValidationDataControllerTest { + @Fit + private MockMvc mockMvc; + + private HttpClassicClientResponse response; + + @AfterEach + void teardown() throws IOException { + if (this.response != null) { + this.response.close(); + } + } + + @Test + @DisplayName("合法 Company 对象校验") + void shouldOKWhenCreateValidCompany() { + Employee validEmployee = new Employee("John", 25); + Company validCompany = new Company(Collections.singletonList(validEmployee)); + MockRequestBuilder requestBuilder = MockMvcRequestBuilders.post("/validation/company/default") + .jsonEntity(validCompany) + .responseType(Void.class); + this.response = this.mockMvc.perform(requestBuilder); + assertThat(this.response.statusCode()).isEqualTo(200); + } + + @Test + @DisplayName("不合法 Company 对象校验") + void shouldFailedWhenCreateInvalidCompany() { + Company invalidCompany = new Company(null); + MockRequestBuilder requestBuilder = MockMvcRequestBuilders.post("/validation/company/default") + .jsonEntity(invalidCompany) + .responseType(Void.class); + this.response = this.mockMvc.perform(requestBuilder); + assertThat(this.response.statusCode()).isEqualTo(500); + } + + @Test + @DisplayName("自定义分组校验 Company 对象") + void shouldOKWhenCreateValidCompanyWithGroup() { + Employee validEmployee = new Employee("Jane", 30); + Company validCompany = new Company(Collections.singletonList(validEmployee)); + MockRequestBuilder requestBuilder = MockMvcRequestBuilders.post("/validation/company/companyGroup") + .jsonEntity(validCompany) + .responseType(Void.class); + this.response = this.mockMvc.perform(requestBuilder); + assertThat(this.response.statusCode()).isEqualTo(200); + } + + @Test + @DisplayName("自定义分组校验 Company 对象") + void shouldFailedWhenCreateInvalidCompanyWithGroup() { + Employee invalidEmployee = new Employee("", 15); + Company invalidCompany = new Company(Collections.singletonList(invalidEmployee)); + MockRequestBuilder requestBuilder = MockMvcRequestBuilders.post("/validation/company/companyGroup") + .jsonEntity(invalidCompany) + .responseType(Void.class); + this.response = this.mockMvc.perform(requestBuilder); + assertThat(this.response.statusCode()).isEqualTo(500); + } +} \ No newline at end of file diff --git a/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-jakarta/src/test/java/modelengine/fitframework/validation/ValidationHandlerTest.java b/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-jakarta/src/test/java/modelengine/fitframework/validation/ValidationHandlerTest.java new file mode 100644 index 000000000..2b5d9def0 --- /dev/null +++ b/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-jakarta/src/test/java/modelengine/fitframework/validation/ValidationHandlerTest.java @@ -0,0 +1,758 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package modelengine.fitframework.validation; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowableOfType; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import jakarta.validation.ConstraintViolationException; +import modelengine.fitframework.aop.JoinPoint; +import modelengine.fitframework.ioc.BeanContainer; +import modelengine.fitframework.ioc.annotation.AnnotationMetadataResolver; +import modelengine.fitframework.ioc.annotation.support.DefaultAnnotationMetadataResolver; +import modelengine.fitframework.runtime.FitRuntime; +import modelengine.fitframework.util.ObjectUtils; +import modelengine.fitframework.util.ReflectionUtils; +import modelengine.fitframework.validation.data.Company; +import modelengine.fitframework.validation.data.Employee; +import modelengine.fitframework.validation.data.GroupValidateService; +import modelengine.fitframework.validation.data.ValidateService; +import modelengine.fitframework.validation.data.ValidationTestData; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +/** + * {@link ValidationHandler} 的单元测试。 + * + * @author 阮睿 + * @since 2025-07-18 + */ +public class ValidationHandlerTest { + private final ValidateService validateService = mock(ValidateService.class); + private final BeanContainer beanContainer = mock(BeanContainer.class); + private final FitRuntime fitRuntime = mock(FitRuntime.class); + private final AnnotationMetadataResolver annotationMetadataResolver = new DefaultAnnotationMetadataResolver(); + private final Validated validated = Mockito.mock(Validated.class); + private final ValidationHandler handler = new ValidationHandler(); + + @BeforeEach + void setUp() { + this.handler.setLocale(Locale.CHINA); + when(this.validated.value()).thenReturn(new Class[0]); + when(this.fitRuntime.resolverOfAnnotations()).thenReturn(this.annotationMetadataResolver); + when(this.beanContainer.runtime()).thenReturn(this.fitRuntime); + } + + private ConstraintViolationException invokeHandleMethod(Method targetMethod, Object[] args) { + Method handleValidatedMethod = + ReflectionUtils.getDeclaredMethod(ValidationHandler.class, "handle", JoinPoint.class, Validated.class); + handleValidatedMethod.setAccessible(true); + JoinPoint joinPoint = mock(JoinPoint.class); + when(joinPoint.getMethod()).thenReturn(targetMethod); + when(joinPoint.getArgs()).thenReturn(args); + when(joinPoint.getTarget()).thenReturn(this.validateService); + + InvocationTargetException invocationTargetException = catchThrowableOfType(InvocationTargetException.class, + () -> handleValidatedMethod.invoke(this.handler, joinPoint, this.validated)); + + return ObjectUtils.cast(invocationTargetException.getTargetException()); + } + + @Test + @DisplayName("测试校验原始类型成功") + void givePrimitiveThenValidateOk() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "foo0", int.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {-1}); + assertThat(exception.getMessage()).contains("必须是正数"); + } + + @Test + @DisplayName("测试校验结构体成功") + void giveClassThenValidateOk() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "foo1", Employee.class); + Employee employee = new Employee("sky", 17); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {employee}); + assertThat(exception.getMessage()).contains("年龄必须大于等于18"); + } + + @Test + @DisplayName("测试嵌套结构体成功") + void giveNestedClassThenValidateOk() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "foo2", Company.class); + Employee employee = new Employee("sky", 17); + Company company = new Company(Collections.singletonList(employee)); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {company}); + assertThat(exception.getMessage()).contains("年龄必须大于等于18"); + } + + @Test + @DisplayName("测试 @NotNull 注解") + void testNotNullValidation() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "testNotNull", String.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {null}); + assertThat(exception.getMessage()).contains("不能为null"); + } + + @Test + @DisplayName("测试 @NotEmpty 注解") + void testNotEmptyValidation() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "testNotEmpty", String.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {""}); + assertThat(exception.getMessage()).contains("不能为空"); + } + + @Test + @DisplayName("测试 @NotBlank 注解") + void testNotBlankValidation() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "testNotBlank", String.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {" "}); + assertThat(exception.getMessage()).contains("不能为空"); + } + + @Test + @DisplayName("测试 @Null 注解") + void testNullValidation() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "testNull", String.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {"not null"}); + assertThat(exception.getMessage()).contains("必须为null"); + } + + @Test + @DisplayName("测试 @Size 注解 - 字符串") + void testSizeStringValidation() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "testSize", String.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {"a"}); + assertThat(exception.getMessage()).contains("个数必须在2和10之间"); + } + + @Test + @DisplayName("测试 @Size 注解 - 集合") + void testSizeListValidation() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "testSizeList", List.class); + ConstraintViolationException exception = + invokeHandleMethod(method, new Object[] {Arrays.asList("1", "2", "3", "4")}); + assertThat(exception.getMessage()).contains("个数必须在1和3之间"); + } + + @Test + @DisplayName("测试 @Min 注解") + void testMinValidation() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "testMin", int.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {5}); + assertThat(exception.getMessage()).contains("最小不能小于10"); + } + + @Test + @DisplayName("测试 @Max 注解") + void testMaxValidation() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "testMax", int.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {150}); + assertThat(exception.getMessage()).contains("最大不能超过100"); + } + + @Test + @DisplayName("测试 @DecimalMin 注解") + void testDecimalMinValidation() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "testDecimalMin", BigDecimal.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {new BigDecimal("5.0")}); + assertThat(exception.getMessage()).contains("必须大于"); + } + + @Test + @DisplayName("测试 @DecimalMax 注解") + void testDecimalMaxValidation() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "testDecimalMax", BigDecimal.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {new BigDecimal("150.0")}); + assertThat(exception.getMessage()).contains("必须小于"); + } + + @Test + @DisplayName("测试 @Positive 注解") + void testPositiveValidation() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "testPositive", int.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {0}); + assertThat(exception.getMessage()).contains("必须是正数"); + } + + @Test + @DisplayName("测试 @PositiveOrZero 注解") + void testPositiveOrZeroValidation() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "testPositiveOrZero", int.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {-1}); + assertThat(exception.getMessage()).contains("必须是正数或零"); + } + + @Test + @DisplayName("测试 @Negative 注解") + void testNegativeValidation() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "testNegative", int.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {1}); + assertThat(exception.getMessage()).contains("必须是负数"); + } + + @Test + @DisplayName("测试 @NegativeOrZero 注解") + void testNegativeOrZeroValidation() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "testNegativeOrZero", int.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {1}); + assertThat(exception.getMessage()).contains("必须是负数或零"); + } + + @Test + @DisplayName("测试 @Digits 注解") + void testDigitsValidation() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "testDigits", BigDecimal.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {new BigDecimal("1234.567")}); + assertThat(exception.getMessage()).contains("数字的值超出了允许范围"); + } + + @Test + @DisplayName("测试 @Past 注解") + void testPastValidation() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "testPast", LocalDate.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {LocalDate.now().plusDays(1)}); + assertThat(exception.getMessage()).contains("需要是一个过去的时间"); + } + + @Test + @DisplayName("测试 @PastOrPresent 注解") + void testPastOrPresentValidation() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "testPastOrPresent", LocalDate.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {LocalDate.now().plusDays(1)}); + assertThat(exception.getMessage()).contains("需要是一个过去或现在的时间"); + } + + @Test + @DisplayName("测试 @Future 注解") + void testFutureValidation() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "testFuture", LocalDate.class); + ConstraintViolationException exception = + invokeHandleMethod(method, new Object[] {LocalDate.now().minusDays(1)}); + assertThat(exception.getMessage()).contains("需要是一个将来的时间"); + } + + @Test + @DisplayName("测试 @FutureOrPresent 注解") + void testFutureOrPresentValidation() { + Method method = + ReflectionUtils.getDeclaredMethod(ValidateService.class, "testFutureOrPresent", LocalDate.class); + ConstraintViolationException exception = + invokeHandleMethod(method, new Object[] {LocalDate.now().minusDays(1)}); + assertThat(exception.getMessage()).contains("需要是一个将来或现在的时间"); + } + + @Test + @DisplayName("测试 @Pattern 注解") + void testPatternValidation() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "testPattern", String.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {"123"}); + assertThat(exception.getMessage()).contains("需要匹配正则表达式"); + } + + @Test + @DisplayName("测试 @Email 注解") + void testEmailValidation() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "testEmail", String.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {"invalid-email"}); + assertThat(exception.getMessage()).contains("不是一个合法的电子邮件地址"); + } + + @Test + @DisplayName("测试 @AssertTrue 注解") + void testAssertTrueValidation() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "testAssertTrue", boolean.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {false}); + assertThat(exception.getMessage()).contains("只能为true"); + } + + @Test + @DisplayName("测试 @AssertFalse 注解") + void testAssertFalseValidation() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "testAssertFalse", boolean.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {true}); + assertThat(exception.getMessage()).contains("只能为false"); + } + + @Test + @DisplayName("测试复杂对象校验") + void testComplexObjectValidation() { + Method method = + ReflectionUtils.getDeclaredMethod(ValidateService.class, "testValidObject", ValidationTestData.class); + ValidationTestData invalidData = new ValidationTestData(); + invalidData.setName(null); // 违反@NotNull + invalidData.setAge(200); // 违反@Max(150) + + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {invalidData}); + assertThat(exception.getMessage()).contains("名称不能为空"); + } + + @Test + @DisplayName("校验数据类,该数据类的 Constraint 字段会被校验,其余字段不会被校验") + public void givenFieldsWithConstraintAnnotationThenValidateHappened() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "validateEmployee", Employee.class); + Employee invalidEmployee = new Employee("", 150); // 空名字,年龄超限 + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {invalidEmployee}); + assertThat(exception.getMessage()).contains("不能为空"); + } + + @Test + @DisplayName("校验方法参数,该方法的 Constraint 参数会被校验,其余参数不会被校验") + public void givenParametersWithConstraintAnnotationThenValidateHappened() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "validateAge", int.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {-1}); + assertThat(exception.getMessage()).contains("必须是正数"); + } + + @Test + @DisplayName("校验多个参数") + public void givenMultipleParametersThenValidateHappened() { + Method method = + ReflectionUtils.getDeclaredMethod(ValidateService.class, "validateNameAndAge", String.class, int.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {"", -1}); + assertThat(exception).isNotNull(); + } + + @Nested + @DisplayName("Student Group Validation Tests") + class StudentGroupValidationTests { + @Test + @DisplayName("校验方法参数,分组约束测试") + public void givenParametersThenGroupValidateHappened() { + // 测试学生年龄验证 - 现在会抛出异常,因为使用了学生分组 + Method method = ReflectionUtils.getDeclaredMethod(GroupValidateService.StudentValidateService.class, + "validateStudentAge", int.class); + Method handleValidatedMethod = ReflectionUtils.getDeclaredMethod(ValidationHandler.class, + "handle", + JoinPoint.class, + Validated.class); + handleValidatedMethod.setAccessible(true); + JoinPoint joinPoint = mock(JoinPoint.class); + when(joinPoint.getMethod()).thenReturn(method); + when(joinPoint.getArgs()).thenReturn(new Object[] {25}); + when(joinPoint.getTarget()).thenReturn(new GroupValidateService.StudentValidateService()); + when(ValidationHandlerTest.this.validated.value()).thenReturn(new Class[] { + ValidationTestData.StudentGroup.class + }); + + InvocationTargetException invocationTargetException = catchThrowableOfType(InvocationTargetException.class, + () -> handleValidatedMethod.invoke(ValidationHandlerTest.this.handler, + joinPoint, + ValidationHandlerTest.this.validated)); + + ConstraintViolationException exception = ObjectUtils.cast(invocationTargetException.getTargetException()); + assertThat(exception.getMessage()).contains("范围要在7~20之内"); + } + } + + @Nested + @DisplayName("Teacher Group Validation Tests") + class TeacherGroupValidationTests { + @Test + @DisplayName("校验方法参数,教师年龄验证测试") + public void givenParametersThenTeacherGroupValidateNotHappened() { + // 测试教师年龄验证 - 现在会抛出异常,因为使用了教师分组 + Method method = ReflectionUtils.getDeclaredMethod(GroupValidateService.TeacherValidateService.class, + "validateTeacherAge", + int.class); + Method handleValidatedMethod = ReflectionUtils.getDeclaredMethod(ValidationHandler.class, + "handle", + JoinPoint.class, + Validated.class); + handleValidatedMethod.setAccessible(true); + JoinPoint joinPoint = mock(JoinPoint.class); + when(joinPoint.getMethod()).thenReturn(method); + when(joinPoint.getArgs()).thenReturn(new Object[] {15}); + when(joinPoint.getTarget()).thenReturn(new GroupValidateService.TeacherValidateService()); + when(ValidationHandlerTest.this.validated.value()).thenReturn(new Class[] { + ValidationTestData.TeacherGroup.class + }); + + InvocationTargetException invocationTargetException = catchThrowableOfType(InvocationTargetException.class, + () -> handleValidatedMethod.invoke(ValidationHandlerTest.this.handler, + joinPoint, + ValidationHandlerTest.this.validated)); + + ConstraintViolationException exception = ObjectUtils.cast(invocationTargetException.getTargetException()); + assertThat(exception.getMessage()).contains("范围要在22~65之内"); + } + } + + @Nested + @DisplayName("Advanced Group Validation Tests") + class AdvancedGroupValidationTests { + @Test + @DisplayName("测试验证高级分组数据") + void testValidateAdvancedGroup() { + // 测试高级分组验证 - 现在会抛出异常,因为使用了高级分组 + Method method = ReflectionUtils.getDeclaredMethod(GroupValidateService.AdvancedValidateService.class, + "validateAdvancedGroup", + ValidationTestData.class); + Method handleValidatedMethod = ReflectionUtils.getDeclaredMethod(ValidationHandler.class, + "handle", + JoinPoint.class, + Validated.class); + handleValidatedMethod.setAccessible(true); + JoinPoint joinPoint = mock(JoinPoint.class); + when(joinPoint.getMethod()).thenReturn(method); + + ValidationTestData data = new ValidationTestData(); + data.setAge(300); // 违反高级分组的约束 + data.setName(""); // 违反默认分组约束 + + when(joinPoint.getArgs()).thenReturn(new Object[] {data}); + when(joinPoint.getTarget()).thenReturn(new GroupValidateService.AdvancedValidateService()); + when(ValidationHandlerTest.this.validated.value()).thenReturn(new Class[] { + ValidationTestData.AdvancedGroup.class + }); + + InvocationTargetException invocationTargetException = catchThrowableOfType(InvocationTargetException.class, + () -> handleValidatedMethod.invoke(ValidationHandlerTest.this.handler, + joinPoint, + ValidationHandlerTest.this.validated)); + + ConstraintViolationException exception = ObjectUtils.cast(invocationTargetException.getTargetException()); + assertThat(exception.getMessage()).contains("高级组年龄必须小于等于200"); + } + } + + @Test + @DisplayName("测试嵌套校验类 Company") + void shouldReturnMsgWhenValidateCompany() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "validateCompany", Company.class); + Employee invalidEmployee1 = new Employee("", 17); // 空名字,年龄不足 + Employee invalidEmployee2 = new Employee("John", 150); // 年龄超限 + Company company = new Company(Arrays.asList(invalidEmployee1, invalidEmployee2)); + + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {company}); + assertThat(exception).isNotNull(); + } + + @Test + @DisplayName("测试 @Valid List") + void shouldReturnMsgWhenValidateList() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "validateEmployeeList", List.class); + Employee validEmployee = new Employee("John", 25); + Employee invalidEmployee1 = new Employee("", 17); + Employee invalidEmployee2 = new Employee("Jane", 150); + List employees = Arrays.asList(validEmployee, invalidEmployee1, invalidEmployee2); + + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {employees}); + assertThat(exception).isNotNull(); + } + + @Test + @DisplayName("测试 @Valid List>") + void shouldReturnMsgWhenValidateListInList() { + Method method = + ReflectionUtils.getDeclaredMethod(ValidateService.class, "validateNestedEmployeeList", List.class); + Employee validEmployee1 = new Employee("John", 25); + Employee validEmployee2 = new Employee("John", 25); + Employee invalidEmployee1 = new Employee("", 17); + Employee invalidEmployee2 = new Employee("Jane", 150); + + List employeeList1 = Arrays.asList(validEmployee1, invalidEmployee1); + List employeeList2 = Arrays.asList(validEmployee2, invalidEmployee2); + List> nestedList = Arrays.asList(employeeList1, employeeList2); + + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {nestedList}); + assertThat(exception).isNotNull(); + } + + @Test + @DisplayName("测试 @Valid Map") + void shouldReturnMsgWhenValidateMapSimple() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "validateEmployeeMap", Map.class); + Employee validEmployee = new Employee("John", 25); + Employee invalidEmployee1 = new Employee("", 17); + Employee invalidEmployee2 = new Employee("Jane", 150); + + Map employeeMap = new HashMap<>(); + employeeMap.put("1", validEmployee); + employeeMap.put("2", invalidEmployee1); + employeeMap.put("3", invalidEmployee2); + + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {employeeMap}); + assertThat(exception).isNotNull(); + } + + @Test + @DisplayName("测试 @Valid Map") + void shouldReturnMsgWhenValidateMapObj() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "validateEmployeeDataMap", Map.class); + Employee validEmployee = new Employee("John", 25); + Employee invalidEmployee = new Employee("", 17); + + ValidationTestData validData = new ValidationTestData(); + validData.setName("Test"); + validData.setAge(25); + + ValidationTestData invalidData = new ValidationTestData(); + invalidData.setName(""); + invalidData.setAge(-1); + + Map map = new HashMap<>(); + map.put(validEmployee, validData); + map.put(invalidEmployee, invalidData); + + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {map}); + assertThat(exception).isNotNull(); + } + + @Test + @DisplayName("测试 @Valid Map>") + void shouldReturnMsgWhenValidateMapInMap() { + Method method = + ReflectionUtils.getDeclaredMethod(ValidateService.class, "validateNestedEmployeeDataMap", Map.class); + Employee validEmployee = new Employee("John", 25); + Employee invalidEmployee = new Employee("", 17); + + ValidationTestData validData = new ValidationTestData(); + validData.setName("Test"); + validData.setAge(25); + + ValidationTestData invalidData = new ValidationTestData(); + invalidData.setName(""); + invalidData.setAge(-1); + + Map innerMap = new HashMap<>(); + innerMap.put("valid", validData); + innerMap.put("invalid", invalidData); + + Map> nestedMap = new HashMap<>(); + nestedMap.put(validEmployee, innerMap); + nestedMap.put(invalidEmployee, innerMap); + + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {nestedMap}); + assertThat(exception).isNotNull(); + } + + @Test + @DisplayName("测试 @Valid List>") + void shouldReturnMsgWhenValidateMapInList() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "validateEmployeeMapList", List.class); + Employee validEmployee = new Employee("John", 25); + Employee invalidEmployee1 = new Employee("", 17); + Employee invalidEmployee2 = new Employee("Jane", 150); + + Map map1 = new HashMap<>(); + map1.put("valid", validEmployee); + map1.put("invalid1", invalidEmployee1); + + Map map2 = new HashMap<>(); + map2.put("valid", validEmployee); + map2.put("invalid2", invalidEmployee2); + + List> listOfMaps = Arrays.asList(map1, map2); + + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {listOfMaps}); + assertThat(exception).isNotNull(); + } + + @Test + @DisplayName("测试 @Valid Map>") + void shouldReturnMsgWhenValidateListInMap() { + Method method = + ReflectionUtils.getDeclaredMethod(ValidateService.class, "validateEmployeeDataListMap", Map.class); + Employee validEmployee = new Employee("John", 25); + Employee invalidEmployee = new Employee("", 17); + + ValidationTestData validData = new ValidationTestData(); + validData.setName("Test"); + validData.setAge(25); + + ValidationTestData invalidData = new ValidationTestData(); + invalidData.setName(""); + invalidData.setAge(-1); + + List dataList1 = Arrays.asList(validData, invalidData); + List dataList2 = List.of(validData); + + Map> map = new HashMap<>(); + map.put(validEmployee, dataList1); + map.put(invalidEmployee, dataList2); + + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {map}); + assertThat(exception).isNotNull(); + } + + @Test + @DisplayName("测试 @Valid List") + void shouldReturnMsgWhenValidateListComplex() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "validateCompanyList", List.class); + Employee invalidEmployee = new Employee("", 17); + Company invalidCompany = new Company(List.of(invalidEmployee)); + List companies = List.of(invalidCompany); + + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {companies}); + assertThat(exception).isNotNull(); + } + + @Test + @DisplayName("测试 @NotNull 注解 - 成功场景") + void testNotNullValidationSuccess() { + this.validateService.testNotNull("valid value"); + } + + @Test + @DisplayName("测试 @Size 注解 - 最小边界值") + void testSizeStringValidationMinBoundary() { + this.validateService.testSize("ab"); // 2个字符,最小边界 + } + + @Test + @DisplayName("测试 @Size 注解 - 最大边界值") + void testSizeStringValidationMaxBoundary() { + this.validateService.testSize("abcdefghij"); // 10个字符,最大边界 + } + + @Test + @DisplayName("测试 @Min 注解 - 边界值") + void testMinValidationBoundary() { + this.validateService.testMin(10); // 最小值边界 + } + + @Test + @DisplayName("测试 @Max 注解 - 边界值") + void testMaxValidationBoundary() { + this.validateService.testMax(100); // 最大值边界 + } + + @Test + @DisplayName("测试 @Positive 注解 - 边界值") + void testPositiveValidationBoundary() { + this.validateService.testPositive(1); // 最小正数 + } + + @Test + @DisplayName("测试 @PositiveOrZero 注解 - 零值") + void testPositiveOrZeroValidationZero() { + this.validateService.testPositiveOrZero(0); // 零值 + } + + @Test + @DisplayName("测试 @Past 注解 - 边界值") + void testPastValidationBoundary() { + this.validateService.testPast(LocalDate.now().minusDays(1)); // 昨天 + } + + @Test + @DisplayName("测试 @Future 注解 - 边界值") + void testFutureValidationBoundary() { + this.validateService.testFuture(LocalDate.now().plusDays(1)); // 明天 + } + + @Test + @DisplayName("测试有效的复杂对象") + void testValidComplexObject() { + ValidationTestData validData = new ValidationTestData(); + validData.setName("Test Name"); + validData.setAge(25); + validData.setDescription("Test Description"); + validData.setContent("Test Content"); + validData.setQuantity(10); + validData.setDiscount(new BigDecimal("-5.0")); + validData.setAgreed(true); + + this.validateService.testValidObject(validData); + } + + @Test + @DisplayName("测试多个验证失败") + void testMultipleValidationFailures() { + Method method = + ReflectionUtils.getDeclaredMethod(ValidateService.class, "testValidObject", ValidationTestData.class); + ValidationTestData invalidData = new ValidationTestData(); + invalidData.setName(""); // 违反@NotBlank + invalidData.setAge(-1); // 违反@Min(0) + invalidData.setQuantity(-10); // 违反@Positive + + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {invalidData}); + assertThat(exception).isNotNull(); + } + + @Test + @DisplayName("测试混合原始类型和对象校验") + void testMixedPrimitiveAndObjectValidation() { + Method method = + ReflectionUtils.getDeclaredMethod(ValidateService.class, "validateMixed", int.class, Employee.class); + Employee invalidEmployee = new Employee("", 17); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {-1, invalidEmployee}); + assertThat(exception).isNotNull(); + } + + @Test + @DisplayName("测试空集合和 null 值混合") + void testEmptyCollectionAndNullMixed() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, + "validateMixedCollections", + List.class, + List.class); + ConstraintViolationException exception = + invokeHandleMethod(method, new Object[] {null, Collections.emptyList()}); + assertThat(exception).isNotNull(); + } + + @Test + @DisplayName("测试 @Range 注解 - 最小值验证") + void testRangeMinValidation() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "testRange", int.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {5}); + assertThat(exception.getMessage()).contains("需要在10和100之间"); + } + + @Test + @DisplayName("测试 @Range 注解 - 最大值验证") + void testRangeMaxValidation() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "testRange", int.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {150}); + assertThat(exception.getMessage()).contains("需要在10和100之间"); + } + + @Test + @DisplayName("测试 @Range 注解 - 最小边界值") + void testRangeMinBoundary() { + this.validateService.testRange(10); // 最小边界值,应该通过 + } + + @Test + @DisplayName("测试 @Range 注解 - 最大边界值") + void testRangeMaxBoundary() { + this.validateService.testRange(100); // 最大边界值,应该通过 + } + + @Test + @DisplayName("测试 @Range 注解 - 中间值") + void testRangeValidValue() { + this.validateService.testRange(50); // 中间值,应该通过 + } + + @Test + @DisplayName("测试 @Range 注解 - BigDecimal 类型") + void testRangeBigDecimalValidation() { + Method method = + ReflectionUtils.getDeclaredMethod(ValidateService.class, "testRangeBigDecimal", BigDecimal.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {new BigDecimal("5.5")}); + assertThat(exception.getMessage()).contains("需要在10和100之间"); + } +} diff --git a/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-jakarta/src/test/java/modelengine/fitframework/validation/data/Company.java b/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-jakarta/src/test/java/modelengine/fitframework/validation/data/Company.java new file mode 100644 index 000000000..d78be0a0a --- /dev/null +++ b/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-jakarta/src/test/java/modelengine/fitframework/validation/data/Company.java @@ -0,0 +1,39 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2024-2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package modelengine.fitframework.validation.data; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; + +import java.util.List; + +/** + * 公司实体类。 + * + * @author 易文渊 + * @author 阮睿 + * @since 2024-09-27 + */ +public class Company { + @NotNull + @Valid + private List employees; + + /** + * 默认构造函数。 + */ + public Company() {} + + /** + * 构造函数。 + * + * @param employees 表示雇员列表的 {@link List}{@code <}{@link Employee}{@code >}。 + */ + public Company(List employees) { + this.employees = employees; + } +} \ No newline at end of file diff --git a/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-jakarta/src/test/java/modelengine/fitframework/validation/data/Employee.java b/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-jakarta/src/test/java/modelengine/fitframework/validation/data/Employee.java new file mode 100644 index 000000000..892f9a994 --- /dev/null +++ b/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-jakarta/src/test/java/modelengine/fitframework/validation/data/Employee.java @@ -0,0 +1,50 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2024-2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package modelengine.fitframework.validation.data; + +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; + +/** + * 员工实体类。 + * + * @author 易文渊 + * @author 阮睿 + * @since 2024-09-27 + */ +public class Employee { + @NotBlank(message = "姓名不能为空") + private String name; + + @Min(value = 18, message = "年龄必须大于等于18") + private int age; + + /** + * 默认构造函数。 + */ + public Employee() {} + + /** + * 构造函数。 + * + * @param name 表示姓名的 {@link String}。 + * @param age 表示年龄的 {@code int}。 + */ + public Employee(String name, int age) { + this.name = name; + this.age = age; + } + + /** + * 获取员工姓名。 + * + * @return 表示员工姓名的 {@link String}。 + */ + public String getName() { + return this.name; + } +} \ No newline at end of file diff --git a/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-jakarta/src/test/java/modelengine/fitframework/validation/data/GroupValidateService.java b/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-jakarta/src/test/java/modelengine/fitframework/validation/data/GroupValidateService.java new file mode 100644 index 000000000..bba16b88b --- /dev/null +++ b/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-jakarta/src/test/java/modelengine/fitframework/validation/data/GroupValidateService.java @@ -0,0 +1,71 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package modelengine.fitframework.validation.data; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import modelengine.fitframework.annotation.Component; +import modelengine.fitframework.log.Logger; +import modelengine.fitframework.validation.Validated; + +/** + * 表示测试用分组校验服务。 + * + * @author 阮睿 + * @since 2025-07-18 + */ +@Component +public class GroupValidateService { + private static final Logger LOG = Logger.get(GroupValidateService.class); + + @Component + @Validated(ValidationTestData.StudentGroup.class) + public static class StudentValidateService { + /** + * 验证学生年龄。 + * + * @param age 表示年龄的 {@code int}。 + */ + public void validateStudentAge( + @Min(value = 7, message = "范围要在7~20之内", groups = ValidationTestData.StudentGroup.class) @Max( + value = 20, message = "范围要在7~20之内", + groups = ValidationTestData.StudentGroup.class) int age) { + LOG.debug("Validating student age: {}", age); + } + } + + @Component + @Validated(ValidationTestData.TeacherGroup.class) + public static class TeacherValidateService { + /** + * 验证教师年龄。 + * + * @param age 表示年龄的 {@code int}。 + */ + public void validateTeacherAge( + @Min(value = 22, message = "范围要在22~65之内", groups = ValidationTestData.TeacherGroup.class) @Max( + value = 65, message = "范围要在22~65之内", + groups = ValidationTestData.TeacherGroup.class) int age) { + LOG.debug("Validating teacher age: {}", age); + } + } + + // 高级分组验证服务。 + @Component + @Validated(ValidationTestData.AdvancedGroup.class) + public static class AdvancedValidateService { + /** + * 验证高级分组数据。 + * + * @param data 用于测试复杂对象验证的数据 {@link ValidationTestData}。 + */ + public void validateAdvancedGroup(@Valid ValidationTestData data) { + LOG.debug("Validating advanced group data: {}", data); + } + } +} \ No newline at end of file diff --git a/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-jakarta/src/test/java/modelengine/fitframework/validation/data/ValidateService.java b/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-jakarta/src/test/java/modelengine/fitframework/validation/data/ValidateService.java new file mode 100644 index 000000000..89ace7ae5 --- /dev/null +++ b/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-jakarta/src/test/java/modelengine/fitframework/validation/data/ValidateService.java @@ -0,0 +1,438 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package modelengine.fitframework.validation.data; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.AssertFalse; +import jakarta.validation.constraints.AssertTrue; +import jakarta.validation.constraints.DecimalMax; +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.Digits; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.Future; +import jakarta.validation.constraints.FutureOrPresent; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.Negative; +import jakarta.validation.constraints.NegativeOrZero; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Null; +import jakarta.validation.constraints.Past; +import jakarta.validation.constraints.PastOrPresent; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; +import jakarta.validation.constraints.Size; +import modelengine.fitframework.annotation.Component; +import modelengine.fitframework.annotation.Fit; +import modelengine.fitframework.log.Logger; +import modelengine.fitframework.validation.Validated; + +import org.hibernate.validator.constraints.Range; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.List; +import java.util.Map; + +/** + * 表示测试用校验服务。 + * + * @author 阮睿 + * @since 2025-07-18 + */ +@Component +@Validated +public class ValidateService { + private static final Logger LOG = Logger.get(ValidateService.class); + + @Fit + private GroupValidateService.StudentValidateService studentValidateService; + + @Fit + private GroupValidateService.TeacherValidateService teacherValidateService; + + @Fit + private GroupValidateService.AdvancedValidateService advancedValidateService; + + /** + * 测试原始类型。 + * + * @param num 表示输入的 {@code int}。 + */ + public void foo0(@Positive(message = "必须是正数") int num) { + LOG.debug("{}", num); + } + + /** + * 测试结构体类型。 + * + * @param employee 表示输入的 {@link Employee}。 + */ + public void foo1(@Valid Employee employee) { + LOG.debug("{}", employee); + } + + /** + * 测试嵌套类型。 + * + * @param company 表示输入的 {@link Company}。 + */ + public void foo2(@Valid Company company) { + LOG.debug("{}", company); + } + + /** + * 测试 NotNull 约束注解。 + * + * @param value 表示输入的 {@link String}。 + */ + public void testNotNull(@NotNull String value) {} + + /** + * 测试 NotEmpty 约束注解。 + * + * @param value 表示输入的 {@link String}。 + */ + public void testNotEmpty(@NotEmpty String value) {} + + /** + * 测试 NotBlank 约束注解。 + * + * @param value 表示输入的 {@link String}。 + */ + public void testNotBlank(@NotBlank String value) {} + + /** + * 测试 Null 约束注解。 + * + * @param value 表示输入的 {@link String}。 + */ + public void testNull(@Null String value) {} + + /** + * 测试 Size 约束注解。 + * + * @param value 表示输入的 {@link String}。 + */ + public void testSize(@Size(min = 2, max = 10) String value) {} + + /** + * 测试 List 的 Size 约束注解。 + * + * @param value 表示输入的 {@link List}{@code <}{@link String}{@code >}。 + */ + public void testSizeList(@Size(min = 1, max = 3) List value) {} + + /** + * 测试 Min 约束注解。 + * + * @param value 表示输入的 {@code int}。 + */ + public void testMin(@Min(10) int value) {} + + /** + * 测试 Max 约束注解。 + * + * @param value 表示输入的 {@code int}。 + */ + public void testMax(@Max(100) int value) {} + + /** + * 测试 DecimalMin 约束注解。 + * + * @param value 表示输入的 {@link BigDecimal}。 + */ + public void testDecimalMin(@DecimalMin("10.5") BigDecimal value) {} + + /** + * 测试 DecimalMax 约束注解。 + * + * @param value 表示输入的 {@link BigDecimal}。 + */ + public void testDecimalMax(@DecimalMax("100.5") BigDecimal value) {} + + /** + * 测试 Positive 约束注解。 + * + * @param value 表示输入的 {@code int}。 + */ + public void testPositive(@Positive int value) {} + + /** + * 测试 PositiveOrZero 约束注解。 + * + * @param value 表示输入的 {@code int}。 + */ + public void testPositiveOrZero(@PositiveOrZero int value) {} + + /** + * 测试 Negative 约束注解。 + * + * @param value 表示输入的 {@code int}。 + */ + public void testNegative(@Negative int value) {} + + /** + * 测试 NegativeOrZero 约束注解。 + * + * @param value 表示输入的 {@code int}。 + */ + public void testNegativeOrZero(@NegativeOrZero int value) {} + + /** + * 测试 Digits 约束注解。 + * + * @param value 表示输入的 {@link BigDecimal}。 + */ + public void testDigits(@Digits(integer = 3, fraction = 2) BigDecimal value) {} + + /** + * 测试 Past 约束注解。 + * + * @param value 表示输入的 {@link LocalDate}。 + */ + public void testPast(@Past LocalDate value) {} + + /** + * 测试 PastOrPresent 约束注解。 + * + * @param value 表示输入的 {@link LocalDate}。 + */ + public void testPastOrPresent(@PastOrPresent LocalDate value) {} + + /** + * 测试 Future 约束注解。 + * + * @param value 表示输入的 {@link LocalDate}。 + */ + public void testFuture(@Future LocalDate value) {} + + /** + * 测试 FutureOrPresent 约束注解。 + * + * @param value 表示输入的 {@link LocalDate}。 + */ + public void testFutureOrPresent(@FutureOrPresent LocalDate value) {} + + /** + * 测试 Pattern 约束注解。 + * + * @param value 表示输入的 {@link String}。 + */ + public void testPattern(@Pattern(regexp = "^[a-zA-Z]+$") String value) {} + + /** + * 测试 Email 约束注解。 + * + * @param value 表示输入的 {@link String}。 + */ + public void testEmail(@Email String value) {} + + /** + * 测试 AssertTrue 约束注解。 + * + * @param value 表示输入的 {@code boolean}。 + */ + public void testAssertTrue(@AssertTrue boolean value) {} + + /** + * 测试 AssertFalse 约束注解。 + * + * @param value 表示输入的 {@code boolean}。 + */ + public void testAssertFalse(@AssertFalse boolean value) {} + + /** + * 测试 Valid 对象验证。 + * + * @param data 表示输入的 {@link ValidationTestData}。 + */ + public void testValidObject(@Valid ValidationTestData data) {} + + /** + * 测试 Range 约束注解。 + * + * @param value 表示输入的 {@code int}。 + */ + public void testRange(@Range(min = 10, max = 100, message = "需要在10和100之间") int value) {} + + /** + * 测试 BigDecimal 类型的Range约束注解。 + * + * @param value 表示输入的 {@link BigDecimal}。 + */ + public void testRangeBigDecimal(@Range(min = 10, max = 100, message = "需要在10和100之间") BigDecimal value) {} + + /** + * 验证 Employee 对象。 + * + * @param employee 表示输入的 {@link Employee}。 + */ + public void validateEmployee(@Valid Employee employee) { + LOG.debug("Validating employee: {}", employee); + } + + /** + * 验证年龄是否为正数。 + * + * @param age 表示输入的 {@code int}。 + */ + public void validateAge(@Positive(message = "必须是正数") int age) { + LOG.debug("Validating age: {}", age); + } + + /** + * 验证姓名和年龄。 + * + * @param name 表示输入的 {@link String}。 + * @param age 表示输入的 {@code int}。 + */ + public void validateNameAndAge(@NotBlank String name, @Positive int age) { + LOG.debug("Validating name: {} and age: {}", name, age); + } + + /** + * 验证高级分组数据。 + * + * @param data 表示输入的 {@link ValidationTestData}。 + */ + public void validateAdvancedGroup(ValidationTestData data) { + if (this.advancedValidateService != null) { + this.advancedValidateService.validateAdvancedGroup(data); + } + } + + /** + * 验证学生年龄。 + * + * @param age 表示输入的 {@code int}。 + */ + public void validateStudentAge(int age) { + if (this.studentValidateService != null) { + this.studentValidateService.validateStudentAge(age); + } + } + + /** + * 验证教师年龄。 + * + * @param age 表示输入的 {@code int}。 + */ + public void validateTeacherAge(int age) { + if (this.teacherValidateService != null) { + this.teacherValidateService.validateTeacherAge(age); + } + } + + /** + * 验证公司对象。 + * + * @param company 表示输入的 {@link Company}。 + */ + public void validateCompany(@Valid Company company) { + LOG.debug("Validating company: {}", company); + } + + /** + * 验证员工列表。 + * + * @param employees 表示输入的 {@link List}{@code <}{@link Employee}{@code >}。 + */ + public void validateEmployeeList(List<@Valid Employee> employees) { + LOG.debug("Validating employee list: {}", employees); + } + + /** + * 验证嵌套员工列表。 + * + * @param nestedList 表示输入的 {@link List}{@code <}{@link List}{@code <}{@link Employee}{@code >}{@code >}}。 + */ + public void validateNestedEmployeeList(List> nestedList) { + LOG.debug("Validating nested employee list: {}", nestedList); + } + + /** + * 验证员工映射。 + * + * @param employeeMap 表示输入的 {@link Map}{@code <}{@link String}{@code , }{@link Employee}{@code >}。 + */ + public void validateEmployeeMap(@Valid Map employeeMap) { + LOG.debug("Validating employee map: {}", employeeMap); + } + + /** + * 验证员工数据映射。 + * + * @param map 表示输入的 {@link Map}{@code <}{@link Employee}{@code , }{@link ValidationTestData}>。 + */ + public void validateEmployeeDataMap(@Valid Map map) { + LOG.debug("Validating employee data map: {}", map); + } + + /** + * 验证嵌套员工数据映射。 + * + * @param nestedMap 表示输入的 + * {@link Map}{@code <}{@link Employee}{@code , }{@link Map}{@code <}{@link String}{@code , }{@link + * ValidationTestData}{@code >}{@code >}。 + */ + public void validateNestedEmployeeDataMap(Map<@Valid Employee, Map> nestedMap) { + LOG.debug("Validating nested employee data map: {}", nestedMap); + } + + /** + * 验证员工映射列表。 + * + * @param listOfMaps 表示输入的 {@link List}{@code <}{@link Map}{@code <}{@link String}{@code , + * }{@link Employee}{@code >}{@code >}。 + */ + public void validateEmployeeMapList(List> listOfMaps) { + LOG.debug("Validating employee map list: {}", listOfMaps); + } + + /** + * 验证员工数据列表映射。 + * + * @param map 表示输入的 + * {@link Map}{@code <}{@link Employee}{@code , }{@link List}{@code <}{@link ValidationTestData}{@code >}。 + */ + public void validateEmployeeDataListMap(Map<@Valid Employee, List<@Valid ValidationTestData>> map) { + LOG.debug("Validating employee data list map: {}", map); + } + + /** + * 验证公司列表。 + * + * @param companies 表示输入的 {@link List}{@code <}{@link Company}{@code >}。 + */ + public void validateCompanyList(@Valid List companies) { + LOG.debug("Validating company list: {}", companies); + } + + /** + * 验证混合类型数据。 + * + * @param value 表示输入的 {@code int}。 + * @param employee 表示输入的 {@link Employee}。 + */ + public void validateMixed(@Positive int value, @Valid Employee employee) { + LOG.debug("Validating mixed primitive {} and object {}", value, employee); + } + + /** + * 验证混合集合数据。 + * + * @param list1 表示输入的 {@link List}{@code <}{@link String}{@code >}。 + * @param list2 表示输入的 {@link List}{@code <}{@link String}{@code >}。 + */ + public void validateMixedCollections(@NotNull List list1, @NotEmpty List list2) { + LOG.debug("Validating mixed collections: {} and {}", list1, list2); + } +} \ No newline at end of file diff --git a/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-jakarta/src/test/java/modelengine/fitframework/validation/data/ValidationDataController.java b/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-jakarta/src/test/java/modelengine/fitframework/validation/data/ValidationDataController.java new file mode 100644 index 000000000..631451c3f --- /dev/null +++ b/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-jakarta/src/test/java/modelengine/fitframework/validation/data/ValidationDataController.java @@ -0,0 +1,41 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package modelengine.fitframework.validation.data; + +import jakarta.validation.Valid; +import modelengine.fit.http.annotation.PostMapping; +import modelengine.fit.http.annotation.RequestBody; +import modelengine.fit.http.annotation.RequestMapping; +import modelengine.fitframework.annotation.Component; +import modelengine.fitframework.validation.Validated; + +/** + * 表示评估注解验证数据接口集。 + * + * @author 阮睿 + * @since 2025-07-18 + */ +@Component +@Validated +@RequestMapping(path = "/validation", group = "评估注解验证数据接口") +public class ValidationDataController { + /** + * Company 类默认分组注解验证。 + * + * @param company 表示注解验证类 {@link Company}。 + */ + @PostMapping(path = "/company/default", description = "验证 Company 类默认分组注解") + public void validateCompanyDefaultGroup(@RequestBody @Valid Company company) {} + + /** + * Company 类特定分组注解验证。 + * + * @param company 表示注解验证类 {@link Company}。 + */ + @PostMapping(path = "/company/companyGroup", description = "验证 Company 类特定分组注解") + public void validateCompanyGroup(@RequestBody @Valid Company company) {} +} \ No newline at end of file diff --git a/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-jakarta/src/test/java/modelengine/fitframework/validation/data/ValidationTestData.java b/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-jakarta/src/test/java/modelengine/fitframework/validation/data/ValidationTestData.java new file mode 100644 index 000000000..49a72914b --- /dev/null +++ b/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-jakarta/src/test/java/modelengine/fitframework/validation/data/ValidationTestData.java @@ -0,0 +1,122 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package modelengine.fitframework.validation.data; + +import jakarta.validation.constraints.AssertTrue; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.Negative; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Positive; + +import java.math.BigDecimal; + +/** + * 测试用的复杂验证数据类。 + * + * @author 阮睿 + * @since 2025-07-18 + */ +public class ValidationTestData { + // 分组接口定义。 + public interface AdvancedGroup {} + + public interface StudentGroup {} + + public interface TeacherGroup {} + + @NotBlank(message = "名称不能为空") + private String name; + + @Min(value = 0, message = "年龄必须大于等于0") + @Max(value = 150, message = "年龄必须小于等于150") + @Max(value = 200, message = "高级组年龄必须小于等于200", groups = AdvancedGroup.class) + private Integer age; + + @NotBlank(message = "描述不能为空") + private String description; + + @NotBlank(message = "内容不能为空白") + private String content; + + @Positive(message = "数量必须是正数") + private Integer quantity; + + @Negative(message = "折扣必须是负数") + private BigDecimal discount; + + @AssertTrue(message = "必须同意条款") + private Boolean agreed; + + /** + * 构造函数。 + */ + public ValidationTestData() {} + + /** + * 设置名称。 + * + * @param name 表示名称的 {@link String}。 + */ + public void setName(String name) { + this.name = name; + } + + /** + * 设置年龄。 + * + * @param age 表示年龄的 {@link Integer}。 + */ + public void setAge(Integer age) { + this.age = age; + } + + /** + * 设置描述。 + * + * @param description 表示描述的 {@link String}。 + */ + public void setDescription(String description) { + this.description = description; + } + + /** + * 设置内容。 + * + * @param content 表示内容的 {@link String}。 + */ + public void setContent(String content) { + this.content = content; + } + + /** + * 设置数量。 + * + * @param quantity 表示数量的 {@link Integer}。 + */ + public void setQuantity(Integer quantity) { + this.quantity = quantity; + } + + /** + * 设置折扣。 + * + * @param discount 表示折扣的 {@link BigDecimal}。 + */ + public void setDiscount(BigDecimal discount) { + this.discount = discount; + } + + /** + * 设置是否同意条款。 + * + * @param agreed 表示是否同意条款的 {@link Boolean}。 + */ + public void setAgreed(Boolean agreed) { + this.agreed = agreed; + } +} diff --git a/framework/fit/java/fit-extension/fit-validation-hibernate/pom.xml b/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-javax/pom.xml similarity index 63% rename from framework/fit/java/fit-extension/fit-validation-hibernate/pom.xml rename to framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-javax/pom.xml index 07394cfaf..0d0e43be2 100644 --- a/framework/fit/java/fit-extension/fit-validation-hibernate/pom.xml +++ b/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-javax/pom.xml @@ -4,16 +4,15 @@ 4.0.0 - org.fitframework.extension - fit-extension-parent + org.fitframework.plugin + fit-plugin-parent 3.6.0-SNAPSHOT - fit-validation-hibernate + fit-validation-hibernate-javax - FIT Validation Hibernate - FIT Framework Validation Hibernate module provides validation functionality based on Hibernate - Validator implementation. + FIT Hibernate Validation Javax + FIT Framework Hibernate Validation module provides Validation for args. https://github.com/ModelEngine-Group/fit-framework @@ -76,12 +75,38 @@ copy-dependencies - ../../../../../build/shared/ + ../../../../../../build/shared/ jakarta.validation + + org.fitframework + fit-build-maven-plugin + + system + 4 + + + + org.apache.maven.plugins + maven-antrun-plugin + + + package + + + + + + + run + + + + diff --git a/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-javax/src/main/java/modelengine/fitframework/validation/LocaleMessageInterpolator.java b/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-javax/src/main/java/modelengine/fitframework/validation/LocaleMessageInterpolator.java new file mode 100644 index 000000000..74438d75c --- /dev/null +++ b/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-javax/src/main/java/modelengine/fitframework/validation/LocaleMessageInterpolator.java @@ -0,0 +1,86 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package modelengine.fitframework.validation; + +import org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator; + +import java.util.Locale; + +import javax.validation.MessageInterpolator; + +/** + * 地区消息插值器。 + *

+ * 作为 Jakarta 消息插值器的代理类,提供地区设置能力。 + *

+ * + * @author 阮睿 + * @since 2025-08-18 + */ +public class LocaleMessageInterpolator implements MessageInterpolator { + private final MessageInterpolator target; + + private Locale locale; + + /** + * 构造函数,使用指定的目标消息插值器初始化实例。 + * + * @param target 表示目标消息插值器的 {@link MessageInterpolator}。 + */ + public LocaleMessageInterpolator(MessageInterpolator target) { + this.target = target; + this.locale = Locale.getDefault(); + } + + /** + * 构造函数,使用指定的地区初始化实例。 + * + * @param locale 表示指定地区的 {@link Locale}。 + */ + public LocaleMessageInterpolator(Locale locale) { + this.locale = locale; + this.target = new ParameterMessageInterpolator(); + } + + /** + * 构造函数,使用指定的目标消息插值器和地区初始化实例。 + * + * @param target 表示被代理的目标消息插值器的 {@link MessageInterpolator}。 + * @param locale 表示当前消息插值器要使用语言的相关地区的 {@link Locale}。 + */ + public LocaleMessageInterpolator(MessageInterpolator target, Locale locale) { + this.target = target; + this.locale = locale; + } + + /** + * 构造函数,使用默认地区初始化实例。 + */ + public LocaleMessageInterpolator() { + this.locale = Locale.getDefault(); + this.target = new ParameterMessageInterpolator(); + } + + @Override + public String interpolate(String messageTemplate, Context context) { + return this.target.interpolate(messageTemplate, context, this.locale); + } + + @Override + public String interpolate(String messageTemplate, Context context, Locale locale) { + return this.target.interpolate(messageTemplate, context, locale); + } + + /** + * 设置地区。 + * + * @param locale 表示当前消息插值器要使用语言的相关地区的 {@link Locale}。 + */ + public void setLocale(Locale locale) { + this.locale = locale; + } +} diff --git a/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-javax/src/main/java/modelengine/fitframework/validation/ValidationHandler.java b/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-javax/src/main/java/modelengine/fitframework/validation/ValidationHandler.java new file mode 100644 index 000000000..40e9086df --- /dev/null +++ b/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-javax/src/main/java/modelengine/fitframework/validation/ValidationHandler.java @@ -0,0 +1,167 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package modelengine.fitframework.validation; + +import modelengine.fitframework.annotation.Component; +import modelengine.fitframework.annotation.Scope; +import modelengine.fitframework.aop.JoinPoint; +import modelengine.fitframework.aop.annotation.Aspect; +import modelengine.fitframework.aop.annotation.Before; +import modelengine.fitframework.ioc.annotation.PreDestroy; + +import org.hibernate.validator.HibernateValidator; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedParameterizedType; +import java.lang.reflect.AnnotatedType; +import java.lang.reflect.Parameter; +import java.util.Arrays; +import java.util.Locale; +import java.util.Set; + +import javax.validation.ConstraintViolation; +import javax.validation.ConstraintViolationException; +import javax.validation.Validation; +import javax.validation.Validator; +import javax.validation.ValidatorFactory; +import javax.validation.executable.ExecutableValidator; + +/** + * 校验入口类。 + *

+ * 当调用的类包含 {@link Validated} 注解时,会对该类公共方法参数进行校验处理。 + *

+ * + * @author 阮睿 + * @since 2025-07-18 + */ +@Aspect(scope = Scope.GLOBAL) +@Component +public class ValidationHandler implements AutoCloseable { + private final ValidatorFactory validatorFactory; + private final Validator validator; + private final LocaleMessageInterpolator messageInterpolator; + + public ValidationHandler() { + this.messageInterpolator = new LocaleMessageInterpolator(); + this.validatorFactory = Validation.byProvider(HibernateValidator.class) + .configure() + .messageInterpolator(this.messageInterpolator) + .failFast(false) + .buildValidatorFactory(); + this.validator = this.validatorFactory.getValidator(); + } + + /** + * 设置校验信息语言。 + * + * @param locale 表示校验语言的 {@link Locale}。 + */ + public void setLocale(Locale locale) { + this.messageInterpolator.setLocale(locale); + } + + /** + * 方法参数校验处理。 + * + * @param joinPoint 表示切面切点的 {@link JoinPoint}。 + * @param validated 表示切点方法所属类的校验注解的 {@link Validated}。 + */ + @Before(value = "@target(validated) && execution(public * *(..))", argNames = "joinPoint, validated") + private void handle(JoinPoint joinPoint, Validated validated) { + // 检查方法参数是否包含被 javax.validation.Constraint 标注的校验注解。 + if (hasJavaxConstraintAnnotations(joinPoint.getMethod().getParameters())) { + ExecutableValidator execVal = this.validator.forExecutables(); + Set> result = execVal.validateParameters(joinPoint.getTarget(), + joinPoint.getMethod(), + joinPoint.getArgs(), + validated.value()); + if (!result.isEmpty()) { + throw new ConstraintViolationException(result); + } + } + } + + @PreDestroy + @Override + public void close() { + this.validatorFactory.close(); + } + + /** + * 检查方法参数是否包含 {@code javax.validation} 校验注解。 + * + * @param parameters 表示可能携带校验注解的方法参数数组 {@link Parameter}{@code []}。 + * @return 如果包含 {@code javax.validation} 标注的校验注解则返回 {@code true},否则返回 {@code false}。 + */ + private boolean hasJavaxConstraintAnnotations(Parameter[] parameters) { + return Arrays.stream(parameters).anyMatch(this::hasConstraintAnnotationsInParameter); + } + + /** + * 检查参数及其泛型类型参数是否包含校验注解。 + * + * @param parameter 表示可能携带校验注解的方法参数 {@link Parameter}。 + * @return 如果包含 {@code javax.validation} 标注的校验注解则返回 {@code true},否则返回 {@code false}。 + */ + private boolean hasConstraintAnnotationsInParameter(Parameter parameter) { + return hasConstraintAnnotationsInType(parameter.getAnnotatedType()); + } + + /** + * 判断参数类型,解析参数本身注解或其泛型类型参数注解。 + * + * @param annotatedType 表示待检查的参数类型 {@link AnnotatedType}。 + * @return 如果包含 {@code javax.validation} 标注的校验注解则返回 {@code true},否则返回 {@code false}。 + */ + private boolean hasConstraintAnnotationsInType(AnnotatedType annotatedType) { + // 检查当前类型上的注解。 + if (annotatedType.getAnnotations().length > 0) { + return Arrays.stream(annotatedType.getAnnotations()).anyMatch(this::isJavaxConstraintAnnotation); + } + // 如果是参数化类型,递归检查类型参数。 + if (annotatedType instanceof AnnotatedParameterizedType parameterizedType) { + return Arrays.stream(parameterizedType.getAnnotatedActualTypeArguments()) + .anyMatch(this::hasConstraintAnnotationsInType); + } + return false; + } + + /** + * 检查注解是否属于 {@code javax.validation} 注解。 + *

+ * 由于存在嵌套校验的情况,{@code @Valid} 与其他校验注解都可以标注参数需要进行校验,但两者的实现与语义上存在差异,处理逻辑不能合并,因此分情况讨论: + *

+ *
    + *
  1. + * {@code @Valid} 注解检查。用于标记需要级联校验的对象,例如:{@code void validateCompany(@Valid Company company)}。 + *
  2. + *
  3. + * 其他携带 {@code @Constraint} 元注解的校验注解检查。例如:{@code void validateEmployee(@NotBlank String name, @Positive + * int)}。 + *
  4. + *
+ * + * @param annotation 要检查的注解 {@link java.lang.annotation.Annotation}。 + * @return 如果属于 {@code javax.validation} 注解(即 {@code @Valid} 或携带 {@code @Constraint}),则返回 {@code true},否则返回 + * {@code false}。 + */ + private boolean isJavaxConstraintAnnotation(Annotation annotation) { + // @Valid 注解检查。 + if (annotation.annotationType().getName().equals("javax.validation.Valid")) { + return true; + } + // 检查 javax.validation.constraints, org.hibernate.validator.constraints 包下的注解或者用户根据 javax 标准自行实现的注解检查。 + // 通过 Constraint 注解检查当前注解是否为校验注解。 + Annotation[] metaAnnotations = annotation.annotationType().getAnnotations(); + return Arrays.stream(metaAnnotations).anyMatch(metaAnnotation -> { + String packageName = metaAnnotation.annotationType().getPackage().getName(); + String className = metaAnnotation.annotationType().getSimpleName(); + return "javax.validation".equals(packageName) && "Constraint".equals(className); + }); + } +} diff --git a/framework/fit/java/fit-extension/fit-validation-hibernate/src/main/resources/application.yml b/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-javax/src/main/resources/application.yaml similarity index 100% rename from framework/fit/java/fit-extension/fit-validation-hibernate/src/main/resources/application.yml rename to framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-javax/src/main/resources/application.yaml diff --git a/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-javax/src/test/java/modelengine/fitframework/validation/ValidationDataControllerTest.java b/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-javax/src/test/java/modelengine/fitframework/validation/ValidationDataControllerTest.java new file mode 100644 index 000000000..ce817a706 --- /dev/null +++ b/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-javax/src/test/java/modelengine/fitframework/validation/ValidationDataControllerTest.java @@ -0,0 +1,95 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package modelengine.fitframework.validation; + +import static org.assertj.core.api.Assertions.assertThat; + +import modelengine.fit.http.client.HttpClassicClientResponse; +import modelengine.fitframework.annotation.Fit; +import modelengine.fitframework.test.annotation.MvcTest; +import modelengine.fitframework.test.domain.mvc.MockMvc; +import modelengine.fitframework.test.domain.mvc.request.MockMvcRequestBuilders; +import modelengine.fitframework.test.domain.mvc.request.MockRequestBuilder; +import modelengine.fitframework.validation.data.Company; +import modelengine.fitframework.validation.data.Employee; +import modelengine.fitframework.validation.data.ValidationDataController; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.Collections; + +/** + * {@link ValidationDataController} 的测试集。 + * + * @author 阮睿 + * @since 2025-07-18 + */ +@MvcTest(classes = {ValidationDataController.class}) +@DisplayName("测试 EvalDataController") +public class ValidationDataControllerTest { + @Fit + private MockMvc mockMvc; + + private HttpClassicClientResponse response; + + @AfterEach + void teardown() throws IOException { + if (this.response != null) { + this.response.close(); + } + } + + @Test + @DisplayName("合法 Company 对象校验") + void shouldOKWhenCreateValidCompany() { + Employee validEmployee = new Employee("John", 25); + Company validCompany = new Company(Collections.singletonList(validEmployee)); + MockRequestBuilder requestBuilder = MockMvcRequestBuilders.post("/validation/company/default") + .jsonEntity(validCompany) + .responseType(Void.class); + this.response = this.mockMvc.perform(requestBuilder); + assertThat(this.response.statusCode()).isEqualTo(200); + } + + @Test + @DisplayName("不合法 Company 对象校验") + void shouldFailedWhenCreateInvalidCompany() { + Company invalidCompany = new Company(null); + MockRequestBuilder requestBuilder = MockMvcRequestBuilders.post("/validation/company/default") + .jsonEntity(invalidCompany) + .responseType(Void.class); + this.response = this.mockMvc.perform(requestBuilder); + assertThat(this.response.statusCode()).isEqualTo(500); + } + + @Test + @DisplayName("自定义分组校验 Company 对象") + void shouldOKWhenCreateValidCompanyWithGroup() { + Employee validEmployee = new Employee("Jane", 30); + Company validCompany = new Company(Collections.singletonList(validEmployee)); + MockRequestBuilder requestBuilder = MockMvcRequestBuilders.post("/validation/company/companyGroup") + .jsonEntity(validCompany) + .responseType(Void.class); + this.response = this.mockMvc.perform(requestBuilder); + assertThat(this.response.statusCode()).isEqualTo(200); + } + + @Test + @DisplayName("自定义分组校验 Company 对象") + void shouldFailedWhenCreateInvalidCompanyWithGroup() { + Employee invalidEmployee = new Employee("", 15); + Company invalidCompany = new Company(Collections.singletonList(invalidEmployee)); + MockRequestBuilder requestBuilder = MockMvcRequestBuilders.post("/validation/company/companyGroup") + .jsonEntity(invalidCompany) + .responseType(Void.class); + this.response = this.mockMvc.perform(requestBuilder); + assertThat(this.response.statusCode()).isEqualTo(500); + } +} \ No newline at end of file diff --git a/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-javax/src/test/java/modelengine/fitframework/validation/ValidationHandlerTest.java b/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-javax/src/test/java/modelengine/fitframework/validation/ValidationHandlerTest.java new file mode 100644 index 000000000..b23b8dd09 --- /dev/null +++ b/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-javax/src/test/java/modelengine/fitframework/validation/ValidationHandlerTest.java @@ -0,0 +1,760 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package modelengine.fitframework.validation; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowableOfType; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import modelengine.fitframework.aop.JoinPoint; +import modelengine.fitframework.ioc.BeanContainer; +import modelengine.fitframework.ioc.annotation.AnnotationMetadataResolver; +import modelengine.fitframework.ioc.annotation.support.DefaultAnnotationMetadataResolver; +import modelengine.fitframework.runtime.FitRuntime; +import modelengine.fitframework.util.ObjectUtils; +import modelengine.fitframework.util.ReflectionUtils; +import modelengine.fitframework.validation.data.Company; +import modelengine.fitframework.validation.data.Employee; +import modelengine.fitframework.validation.data.GroupValidateService; +import modelengine.fitframework.validation.data.ValidateService; +import modelengine.fitframework.validation.data.ValidationTestData; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import javax.validation.ConstraintViolationException; + +/** + * {@link ValidationHandler} 的单元测试。 + * + * @author 阮睿 + * @since 2025-07-18 + */ +public class ValidationHandlerTest { + private final ValidateService validateService = mock(ValidateService.class); + private final BeanContainer beanContainer = mock(BeanContainer.class); + private final FitRuntime fitRuntime = mock(FitRuntime.class); + private final AnnotationMetadataResolver annotationMetadataResolver = new DefaultAnnotationMetadataResolver(); + private final Validated validated = Mockito.mock(Validated.class); + private final ValidationHandler handler = new ValidationHandler(); + + @BeforeEach + void setUp() { + handler.setLocale(Locale.CHINA); + when(validated.value()).thenReturn(new Class[0]); + when(fitRuntime.resolverOfAnnotations()).thenReturn(annotationMetadataResolver); + when(beanContainer.runtime()).thenReturn(fitRuntime); + } + + private ConstraintViolationException invokeHandleMethod(Method targetMethod, Object[] args) { + Method handleValidatedMethod = + ReflectionUtils.getDeclaredMethod(ValidationHandler.class, "handle", JoinPoint.class, Validated.class); + handleValidatedMethod.setAccessible(true); + JoinPoint joinPoint = mock(JoinPoint.class); + when(joinPoint.getMethod()).thenReturn(targetMethod); + when(joinPoint.getArgs()).thenReturn(args); + when(joinPoint.getTarget()).thenReturn(validateService); + + InvocationTargetException invocationTargetException = catchThrowableOfType(InvocationTargetException.class, + () -> handleValidatedMethod.invoke(handler, joinPoint, validated)); + + return ObjectUtils.cast(invocationTargetException.getTargetException()); + } + + @Test + @DisplayName("测试校验原始类型成功") + void givePrimitiveThenValidateOk() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "foo0", int.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {-1}); + assertThat(exception.getMessage()).contains("必须是正数"); + } + + @Test + @DisplayName("测试校验结构体成功") + void giveClassThenValidateOk() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "foo1", Employee.class); + Employee employee = new Employee("sky", 17); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {employee}); + assertThat(exception.getMessage()).contains("年龄必须大于等于18"); + } + + @Test + @DisplayName("测试嵌套结构体成功") + void giveNestedClassThenValidateOk() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "foo2", Company.class); + Employee employee = new Employee("sky", 17); + Company company = new Company(Collections.singletonList(employee)); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {company}); + assertThat(exception.getMessage()).contains("年龄必须大于等于18"); + } + + @Test + @DisplayName("测试@NotNull注解") + void testNotNullValidation() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "testNotNull", String.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {null}); + assertThat(exception.getMessage()).contains("不能为null"); + } + + @Test + @DisplayName("测试@NotEmpty注解") + void testNotEmptyValidation() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "testNotEmpty", String.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {""}); + assertThat(exception.getMessage()).contains("不能为空"); + } + + @Test + @DisplayName("测试@NotBlank注解") + void testNotBlankValidation() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "testNotBlank", String.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {" "}); + assertThat(exception.getMessage()).contains("不能为空"); + } + + @Test + @DisplayName("测试@Null注解") + void testNullValidation() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "testNull", String.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {"not null"}); + assertThat(exception.getMessage()).contains("必须为null"); + } + + @Test + @DisplayName("测试@Size注解-字符串") + void testSizeStringValidation() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "testSize", String.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {"a"}); + assertThat(exception.getMessage()).contains("个数必须在2和10之间"); + } + + @Test + @DisplayName("测试@Size注解-集合") + void testSizeListValidation() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "testSizeList", List.class); + ConstraintViolationException exception = + invokeHandleMethod(method, new Object[] {Arrays.asList("1", "2", "3", "4")}); + assertThat(exception.getMessage()).contains("个数必须在1和3之间"); + } + + @Test + @DisplayName("测试@Min注解") + void testMinValidation() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "testMin", int.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {5}); + assertThat(exception.getMessage()).contains("最小不能小于10"); + } + + @Test + @DisplayName("测试@Max注解") + void testMaxValidation() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "testMax", int.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {150}); + assertThat(exception.getMessage()).contains("最大不能超过100"); + } + + @Test + @DisplayName("测试@DecimalMin注解") + void testDecimalMinValidation() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "testDecimalMin", BigDecimal.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {new BigDecimal("5.0")}); + assertThat(exception.getMessage()).contains("必须大于"); + } + + @Test + @DisplayName("测试@DecimalMax注解") + void testDecimalMaxValidation() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "testDecimalMax", BigDecimal.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {new BigDecimal("150.0")}); + assertThat(exception.getMessage()).contains("必须小于"); + } + + @Test + @DisplayName("测试@Positive注解") + void testPositiveValidation() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "testPositive", int.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {0}); + assertThat(exception.getMessage()).contains("必须是正数"); + } + + @Test + @DisplayName("测试@PositiveOrZero注解") + void testPositiveOrZeroValidation() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "testPositiveOrZero", int.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {-1}); + assertThat(exception.getMessage()).contains("必须是正数或零"); + } + + @Test + @DisplayName("测试@Negative注解") + void testNegativeValidation() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "testNegative", int.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {1}); + assertThat(exception.getMessage()).contains("必须是负数"); + } + + @Test + @DisplayName("测试@NegativeOrZero注解") + void testNegativeOrZeroValidation() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "testNegativeOrZero", int.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {1}); + assertThat(exception.getMessage()).contains("必须是负数或零"); + } + + @Test + @DisplayName("测试@Digits注解") + void testDigitsValidation() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "testDigits", BigDecimal.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {new BigDecimal("1234.567")}); + assertThat(exception.getMessage()).contains("数字的值超出了允许范围"); + } + + @Test + @DisplayName("测试@Past注解") + void testPastValidation() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "testPast", LocalDate.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {LocalDate.now().plusDays(1)}); + assertThat(exception.getMessage()).contains("需要是一个过去的时间"); + } + + @Test + @DisplayName("测试@PastOrPresent注解") + void testPastOrPresentValidation() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "testPastOrPresent", LocalDate.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {LocalDate.now().plusDays(1)}); + assertThat(exception.getMessage()).contains("需要是一个过去或现在的时间"); + } + + @Test + @DisplayName("测试@Future注解") + void testFutureValidation() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "testFuture", LocalDate.class); + ConstraintViolationException exception = + invokeHandleMethod(method, new Object[] {LocalDate.now().minusDays(1)}); + assertThat(exception.getMessage()).contains("需要是一个将来的时间"); + } + + @Test + @DisplayName("测试@FutureOrPresent注解") + void testFutureOrPresentValidation() { + Method method = + ReflectionUtils.getDeclaredMethod(ValidateService.class, "testFutureOrPresent", LocalDate.class); + ConstraintViolationException exception = + invokeHandleMethod(method, new Object[] {LocalDate.now().minusDays(1)}); + assertThat(exception.getMessage()).contains("需要是一个将来或现在的时间"); + } + + @Test + @DisplayName("测试@Pattern注解") + void testPatternValidation() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "testPattern", String.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {"123"}); + assertThat(exception.getMessage()).contains("需要匹配正则表达式"); + } + + @Test + @DisplayName("测试@Email注解") + void testEmailValidation() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "testEmail", String.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {"invalid-email"}); + assertThat(exception.getMessage()).contains("不是一个合法的电子邮件地址"); + } + + @Test + @DisplayName("测试@AssertTrue注解") + void testAssertTrueValidation() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "testAssertTrue", boolean.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {false}); + assertThat(exception.getMessage()).contains("只能为true"); + } + + @Test + @DisplayName("测试@AssertFalse注解") + void testAssertFalseValidation() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "testAssertFalse", boolean.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {true}); + assertThat(exception.getMessage()).contains("只能为false"); + } + + @Test + @DisplayName("测试复杂对象校验") + void testComplexObjectValidation() { + Method method = + ReflectionUtils.getDeclaredMethod(ValidateService.class, "testValidObject", ValidationTestData.class); + ValidationTestData invalidData = new ValidationTestData(); + invalidData.setName(null); // 违反@NotNull + invalidData.setAge(200); // 违反@Max(150) + + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {invalidData}); + assertThat(exception.getMessage()).contains("名称不能为空"); + } + + @Test + @DisplayName("校验数据类,该数据类的 Constraint 字段会被校验,其余字段不会被校验") + public void givenFieldsWithConstraintAnnotationThenValidateHappened() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "validateEmployee", Employee.class); + Employee invalidEmployee = new Employee("", 150); // 空名字,年龄超限 + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {invalidEmployee}); + assertThat(exception.getMessage()).contains("不能为空"); + } + + @Test + @DisplayName("校验方法参数,该方法的 Constraint 参数会被校验,其余参数不会被校验") + public void givenParametersWithConstraintAnnotationThenValidateHappened() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "validateAge", int.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {-1}); + assertThat(exception.getMessage()).contains("必须是正数"); + } + + @Test + @DisplayName("校验多个参数") + public void givenMultipleParametersThenValidateHappened() { + Method method = + ReflectionUtils.getDeclaredMethod(ValidateService.class, "validateNameAndAge", String.class, int.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {"", -1}); + assertThat(exception).isNotNull(); + } + + @Nested + @DisplayName("Student Group Validation Tests") + class StudentGroupValidationTests { + @Test + @DisplayName("校验方法参数,分组约束测试") + public void givenParametersThenGroupValidateHappened() { + // 测试学生年龄验证 - 现在会抛出异常,因为使用了学生分组 + Method method = ReflectionUtils.getDeclaredMethod(GroupValidateService.StudentValidateService.class, + "validateStudentAge", + int.class); + Method handleValidatedMethod = ReflectionUtils.getDeclaredMethod(ValidationHandler.class, + "handle", + JoinPoint.class, + Validated.class); + handleValidatedMethod.setAccessible(true); + JoinPoint joinPoint = mock(JoinPoint.class); + when(joinPoint.getMethod()).thenReturn(method); + when(joinPoint.getArgs()).thenReturn(new Object[] {25}); + when(joinPoint.getTarget()).thenReturn(new GroupValidateService.StudentValidateService()); + when(ValidationHandlerTest.this.validated.value()).thenReturn(new Class[] { + ValidationTestData.StudentGroup.class + }); + + InvocationTargetException invocationTargetException = catchThrowableOfType(InvocationTargetException.class, + () -> handleValidatedMethod.invoke(ValidationHandlerTest.this.handler, + joinPoint, + ValidationHandlerTest.this.validated)); + + ConstraintViolationException exception = ObjectUtils.cast(invocationTargetException.getTargetException()); + assertThat(exception.getMessage()).contains("范围要在7~20之内"); + } + } + + @Nested + @DisplayName("Teacher Group Validation Tests") + class TeacherGroupValidationTests { + @Test + @DisplayName("校验方法参数,教师年龄验证测试") + public void givenParametersThenTeacherGroupValidateNotHappened() { + // 测试教师年龄验证 - 现在会抛出异常,因为使用了教师分组 + Method method = ReflectionUtils.getDeclaredMethod(GroupValidateService.TeacherValidateService.class, + "validateTeacherAge", + int.class); + Method handleValidatedMethod = ReflectionUtils.getDeclaredMethod(ValidationHandler.class, + "handle", + JoinPoint.class, + Validated.class); + handleValidatedMethod.setAccessible(true); + JoinPoint joinPoint = mock(JoinPoint.class); + when(joinPoint.getMethod()).thenReturn(method); + when(joinPoint.getArgs()).thenReturn(new Object[] {15}); + when(joinPoint.getTarget()).thenReturn(new GroupValidateService.TeacherValidateService()); + when(ValidationHandlerTest.this.validated.value()).thenReturn(new Class[] { + ValidationTestData.TeacherGroup.class + }); + + InvocationTargetException invocationTargetException = catchThrowableOfType(InvocationTargetException.class, + () -> handleValidatedMethod.invoke(ValidationHandlerTest.this.handler, + joinPoint, + ValidationHandlerTest.this.validated)); + + ConstraintViolationException exception = ObjectUtils.cast(invocationTargetException.getTargetException()); + assertThat(exception.getMessage()).contains("范围要在22~65之内"); + } + } + + @Nested + @DisplayName("Advanced Group Validation Tests") + class AdvancedGroupValidationTests { + @Test + @DisplayName("测试验证高级分组数据") + void testValidateAdvancedGroup() { + // 测试高级分组验证 - 现在会抛出异常,因为使用了高级分组 + Method method = ReflectionUtils.getDeclaredMethod(GroupValidateService.AdvancedValidateService.class, + "validateAdvancedGroup", + ValidationTestData.class); + Method handleValidatedMethod = ReflectionUtils.getDeclaredMethod(ValidationHandler.class, + "handle", + JoinPoint.class, + Validated.class); + handleValidatedMethod.setAccessible(true); + JoinPoint joinPoint = mock(JoinPoint.class); + when(joinPoint.getMethod()).thenReturn(method); + + ValidationTestData data = new ValidationTestData(); + data.setAge(300); // 违反高级分组的约束 + data.setName(""); // 违反默认分组约束 + + when(joinPoint.getArgs()).thenReturn(new Object[] {data}); + when(joinPoint.getTarget()).thenReturn(new GroupValidateService.AdvancedValidateService()); + when(ValidationHandlerTest.this.validated.value()).thenReturn(new Class[] { + ValidationTestData.AdvancedGroup.class + }); + + InvocationTargetException invocationTargetException = catchThrowableOfType(InvocationTargetException.class, + () -> handleValidatedMethod.invoke(ValidationHandlerTest.this.handler, + joinPoint, + ValidationHandlerTest.this.validated)); + + ConstraintViolationException exception = ObjectUtils.cast(invocationTargetException.getTargetException()); + assertThat(exception.getMessage()).contains("高级组年龄必须小于等于200"); + } + } + + @Test + @DisplayName("测试嵌套校验类 Company") + void shouldReturnMsgWhenValidateCompany() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "validateCompany", Company.class); + Employee invalidEmployee1 = new Employee("", 17); // 空名字,年龄不足 + Employee invalidEmployee2 = new Employee("John", 150); // 年龄超限 + Company company = new Company(Arrays.asList(invalidEmployee1, invalidEmployee2)); + + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {company}); + assertThat(exception).isNotNull(); + } + + @Test + @DisplayName("测试 @Valid List") + void shouldReturnMsgWhenValidateList() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "validateEmployeeList", List.class); + Employee validEmployee = new Employee("John", 25); + Employee invalidEmployee1 = new Employee("", 17); + Employee invalidEmployee2 = new Employee("Jane", 150); + List employees = Arrays.asList(validEmployee, invalidEmployee1, invalidEmployee2); + + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {employees}); + assertThat(exception).isNotNull(); + } + + @Test + @DisplayName("测试 @Valid List>") + void shouldReturnMsgWhenValidateListInList() { + Method method = + ReflectionUtils.getDeclaredMethod(ValidateService.class, "validateNestedEmployeeList", List.class); + Employee validEmployee1 = new Employee("John", 25); + Employee validEmployee2 = new Employee("John", 25); + Employee invalidEmployee1 = new Employee("", 17); + Employee invalidEmployee2 = new Employee("Jane", 150); + + List employeeList1 = Arrays.asList(validEmployee1, invalidEmployee1); + List employeeList2 = Arrays.asList(validEmployee2, invalidEmployee2); + List> nestedList = Arrays.asList(employeeList1, employeeList2); + + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {nestedList}); + assertThat(exception).isNotNull(); + } + + @Test + @DisplayName("测试 @Valid Map") + void shouldReturnMsgWhenValidateMapSimple() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "validateEmployeeMap", Map.class); + Employee validEmployee = new Employee("John", 25); + Employee invalidEmployee1 = new Employee("", 17); + Employee invalidEmployee2 = new Employee("Jane", 150); + + Map employeeMap = new HashMap<>(); + employeeMap.put("1", validEmployee); + employeeMap.put("2", invalidEmployee1); + employeeMap.put("3", invalidEmployee2); + + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {employeeMap}); + assertThat(exception).isNotNull(); + } + + @Test + @DisplayName("测试 @Valid Map") + void shouldReturnMsgWhenValidateMapObj() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "validateEmployeeDataMap", Map.class); + Employee validEmployee = new Employee("John", 25); + Employee invalidEmployee = new Employee("", 17); + + ValidationTestData validData = new ValidationTestData(); + validData.setName("Test"); + validData.setAge(25); + + ValidationTestData invalidData = new ValidationTestData(); + invalidData.setName(""); + invalidData.setAge(-1); + + Map map = new HashMap<>(); + map.put(validEmployee, validData); + map.put(invalidEmployee, invalidData); + + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {map}); + assertThat(exception).isNotNull(); + } + + @Test + @DisplayName("测试 @Valid Map>") + void shouldReturnMsgWhenValidateMapInMap() { + Method method = + ReflectionUtils.getDeclaredMethod(ValidateService.class, "validateNestedEmployeeDataMap", Map.class); + Employee validEmployee = new Employee("John", 25); + Employee invalidEmployee = new Employee("", 17); + + ValidationTestData validData = new ValidationTestData(); + validData.setName("Test"); + validData.setAge(25); + + ValidationTestData invalidData = new ValidationTestData(); + invalidData.setName(""); + invalidData.setAge(-1); + + Map innerMap = new HashMap<>(); + innerMap.put("valid", validData); + innerMap.put("invalid", invalidData); + + Map> nestedMap = new HashMap<>(); + nestedMap.put(validEmployee, innerMap); + nestedMap.put(invalidEmployee, innerMap); + + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {nestedMap}); + assertThat(exception).isNotNull(); + } + + @Test + @DisplayName("测试 @Valid List>") + void shouldReturnMsgWhenValidateMapInList() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "validateEmployeeMapList", List.class); + Employee validEmployee = new Employee("John", 25); + Employee invalidEmployee1 = new Employee("", 17); + Employee invalidEmployee2 = new Employee("Jane", 150); + + Map map1 = new HashMap<>(); + map1.put("valid", validEmployee); + map1.put("invalid1", invalidEmployee1); + + Map map2 = new HashMap<>(); + map2.put("valid", validEmployee); + map2.put("invalid2", invalidEmployee2); + + List> listOfMaps = Arrays.asList(map1, map2); + + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {listOfMaps}); + assertThat(exception).isNotNull(); + } + + @Test + @DisplayName("测试 @Valid Map>") + void shouldReturnMsgWhenValidateListInMap() { + Method method = + ReflectionUtils.getDeclaredMethod(ValidateService.class, "validateEmployeeDataListMap", Map.class); + Employee validEmployee = new Employee("John", 25); + Employee invalidEmployee = new Employee("", 17); + + ValidationTestData validData = new ValidationTestData(); + validData.setName("Test"); + validData.setAge(25); + + ValidationTestData invalidData = new ValidationTestData(); + invalidData.setName(""); + invalidData.setAge(-1); + + List dataList1 = Arrays.asList(validData, invalidData); + List dataList2 = List.of(validData); + + Map> map = new HashMap<>(); + map.put(validEmployee, dataList1); + map.put(invalidEmployee, dataList2); + + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {map}); + assertThat(exception).isNotNull(); + } + + @Test + @DisplayName("测试 @Valid List") + void shouldReturnMsgWhenValidateListComplex() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "validateCompanyList", List.class); + Employee invalidEmployee = new Employee("", 17); + Company invalidCompany = new Company(List.of(invalidEmployee)); + List companies = List.of(invalidCompany); + + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {companies}); + assertThat(exception).isNotNull(); + } + + @Test + @DisplayName("测试 @NotNull 注解 - 成功场景") + void testNotNullValidationSuccess() { + this.validateService.testNotNull("valid value"); + } + + @Test + @DisplayName("测试 @Size 注解 - 最小边界值") + void testSizeStringValidationMinBoundary() { + this.validateService.testSize("ab"); // 2个字符,最小边界 + } + + @Test + @DisplayName("测试 @Size 注解 - 最大边界值") + void testSizeStringValidationMaxBoundary() { + this.validateService.testSize("abcdefghij"); // 10个字符,最大边界 + } + + @Test + @DisplayName("测试 @Min 注解 - 边界值") + void testMinValidationBoundary() { + this.validateService.testMin(10); // 最小值边界 + } + + @Test + @DisplayName("测试 @Max 注解 - 边界值") + void testMaxValidationBoundary() { + this.validateService.testMax(100); // 最大值边界 + } + + @Test + @DisplayName("测试 @Positive 注解 - 边界值") + void testPositiveValidationBoundary() { + this.validateService.testPositive(1); // 最小正数 + } + + @Test + @DisplayName("测试 @PositiveOrZero 注解 - 零值") + void testPositiveOrZeroValidationZero() { + this.validateService.testPositiveOrZero(0); // 零值 + } + + @Test + @DisplayName("测试 @Past 注解 - 边界值") + void testPastValidationBoundary() { + this.validateService.testPast(LocalDate.now().minusDays(1)); // 昨天 + } + + @Test + @DisplayName("测试 @Future 注解 - 边界值") + void testFutureValidationBoundary() { + this.validateService.testFuture(LocalDate.now().plusDays(1)); // 明天 + } + + @Test + @DisplayName("测试有效的复杂对象") + void testValidComplexObject() { + ValidationTestData validData = new ValidationTestData(); + validData.setName("Test Name"); + validData.setAge(25); + validData.setDescription("Test Description"); + validData.setContent("Test Content"); + validData.setQuantity(10); + validData.setDiscount(new BigDecimal("-5.0")); + validData.setAgreed(true); + + this.validateService.testValidObject(validData); + } + + @Test + @DisplayName("测试多个验证失败") + void testMultipleValidationFailures() { + Method method = + ReflectionUtils.getDeclaredMethod(ValidateService.class, "testValidObject", ValidationTestData.class); + ValidationTestData invalidData = new ValidationTestData(); + invalidData.setName(""); // 违反@NotBlank + invalidData.setAge(-1); // 违反@Min(0) + invalidData.setQuantity(-10); // 违反@Positive + + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {invalidData}); + assertThat(exception).isNotNull(); + } + + @Test + @DisplayName("测试混合原始类型和对象校验") + void testMixedPrimitiveAndObjectValidation() { + Method method = + ReflectionUtils.getDeclaredMethod(ValidateService.class, "validateMixed", int.class, Employee.class); + Employee invalidEmployee = new Employee("", 17); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {-1, invalidEmployee}); + assertThat(exception).isNotNull(); + } + + @Test + @DisplayName("测试空集合和 null 值混合") + void testEmptyCollectionAndNullMixed() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, + "validateMixedCollections", + List.class, + List.class); + ConstraintViolationException exception = + invokeHandleMethod(method, new Object[] {null, Collections.emptyList()}); + assertThat(exception).isNotNull(); + } + + @Test + @DisplayName("测试 @Range 注解 - 最小值验证") + void testRangeMinValidation() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "testRange", int.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {5}); + assertThat(exception.getMessage()).contains("需要在10和100之间"); + } + + @Test + @DisplayName("测试 @Range 注解 - 最大值验证") + void testRangeMaxValidation() { + Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "testRange", int.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {150}); + assertThat(exception.getMessage()).contains("需要在10和100之间"); + } + + @Test + @DisplayName("测试 @Range 注解 - 最小边界值") + void testRangeMinBoundary() { + this.validateService.testRange(10); // 最小边界值,应该通过 + } + + @Test + @DisplayName("测试 @Range 注解 - 最大边界值") + void testRangeMaxBoundary() { + this.validateService.testRange(100); // 最大边界值,应该通过 + } + + @Test + @DisplayName("测试 @Range 注解 - 中间值") + void testRangeValidValue() { + this.validateService.testRange(50); // 中间值,应该通过 + } + + @Test + @DisplayName("测试 @Range 注解 - BigDecimal 类型") + void testRangeBigDecimalValidation() { + Method method = + ReflectionUtils.getDeclaredMethod(ValidateService.class, "testRangeBigDecimal", BigDecimal.class); + ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {new BigDecimal("5.5")}); + assertThat(exception.getMessage()).contains("需要在10和100之间"); + } +} diff --git a/framework/fit/java/fit-extension/fit-validation-hibernate/src/test/java/modelengine/fitframework/validation/data/Company.java b/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-javax/src/test/java/modelengine/fitframework/validation/data/Company.java similarity index 75% rename from framework/fit/java/fit-extension/fit-validation-hibernate/src/test/java/modelengine/fitframework/validation/data/Company.java rename to framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-javax/src/test/java/modelengine/fitframework/validation/data/Company.java index 0cc9dfe39..8836fa01a 100644 --- a/framework/fit/java/fit-extension/fit-validation-hibernate/src/test/java/modelengine/fitframework/validation/data/Company.java +++ b/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-javax/src/test/java/modelengine/fitframework/validation/data/Company.java @@ -1,5 +1,5 @@ /*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. + * Copyright (c) 2024-2025 Huawei Technologies Co., Ltd. All rights reserved. * This file is a part of the ModelEngine Project. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ @@ -12,18 +12,24 @@ import javax.validation.constraints.NotNull; /** - * 公司 pojo。 + * 公司实体类。 * * @author 易文渊 + * @author 阮睿 * @since 2024-09-27 */ public class Company { + @NotNull @Valid - @NotNull(message = "雇员列表不能为空") - List<@Valid @NotNull Employee> employees; + private List employees; /** - * 构建函数。 + * 默认构造函数。 + */ + public Company() {} + + /** + * 构造函数。 * * @param employees 表示雇员列表的 {@link List}{@code <}{@link Employee}{@code >}。 */ diff --git a/framework/fit/java/fit-extension/fit-validation-hibernate/src/test/java/modelengine/fitframework/validation/data/Employee.java b/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-javax/src/test/java/modelengine/fitframework/validation/data/Employee.java similarity index 61% rename from framework/fit/java/fit-extension/fit-validation-hibernate/src/test/java/modelengine/fitframework/validation/data/Employee.java rename to framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-javax/src/test/java/modelengine/fitframework/validation/data/Employee.java index 4217505fb..969feb4c8 100644 --- a/framework/fit/java/fit-extension/fit-validation-hibernate/src/test/java/modelengine/fitframework/validation/data/Employee.java +++ b/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-javax/src/test/java/modelengine/fitframework/validation/data/Employee.java @@ -1,5 +1,5 @@ /*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. + * Copyright (c) 2024-2025 Huawei Technologies Co., Ltd. All rights reserved. * This file is a part of the ModelEngine Project. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ @@ -10,26 +10,41 @@ import javax.validation.constraints.NotBlank; /** - * 雇员 pojo。 + * 员工实体类。 * * @author 易文渊 + * @author 阮睿 * @since 2024-09-27 */ public class Employee { - @NotBlank + @NotBlank(message = "姓名不能为空") private String name; @Min(value = 18, message = "年龄必须大于等于18") private int age; + /** + * 默认构造函数。 + */ + public Employee() {} + /** * 构造函数。 * - * @param name 表示用户名字的 {@link String}。 - * @param age 表示用户年龄的 {@code int}。 + * @param name 表示姓名的 {@link String}。 + * @param age 表示年龄的 {@code int}。 */ public Employee(String name, int age) { this.name = name; this.age = age; } + + /** + * 获取员工姓名。 + * + * @return 返回员工姓名的 {@link String}。 + */ + public String getName() { + return name; + } } \ No newline at end of file diff --git a/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-javax/src/test/java/modelengine/fitframework/validation/data/GroupValidateService.java b/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-javax/src/test/java/modelengine/fitframework/validation/data/GroupValidateService.java new file mode 100644 index 000000000..d299bdfd5 --- /dev/null +++ b/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-javax/src/test/java/modelengine/fitframework/validation/data/GroupValidateService.java @@ -0,0 +1,72 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package modelengine.fitframework.validation.data; + +import modelengine.fitframework.annotation.Component; +import modelengine.fitframework.log.Logger; +import modelengine.fitframework.validation.Validated; + +import javax.validation.Valid; +import javax.validation.constraints.Max; +import javax.validation.constraints.Min; + +/** + * 表示测试用分组校验服务。 + * + * @author 阮睿 + * @since 2025-07-18 + */ +@Component +public class GroupValidateService { + private static final Logger LOG = Logger.get(GroupValidateService.class); + + @Component + @Validated(ValidationTestData.StudentGroup.class) + public static class StudentValidateService { + /** + * 验证学生年龄。 + * + * @param age 表示年龄的 {@code int}。 + */ + public void validateStudentAge( + @Min(value = 7, message = "范围要在7~20之内", groups = ValidationTestData.StudentGroup.class) @Max( + value = 20, message = "范围要在7~20之内", + groups = ValidationTestData.StudentGroup.class) int age) { + LOG.debug("Validating student age: {}", age); + } + } + + @Component + @Validated(ValidationTestData.TeacherGroup.class) + public static class TeacherValidateService { + /** + * 验证教师年龄。 + * + * @param age 表示年龄的 {@code int}。 + */ + public void validateTeacherAge( + @Min(value = 22, message = "范围要在22~65之内", groups = ValidationTestData.TeacherGroup.class) @Max( + value = 65, message = "范围要在22~65之内", + groups = ValidationTestData.TeacherGroup.class) int age) { + LOG.debug("Validating teacher age: {}", age); + } + } + + // 高级分组验证服务。 + @Component + @Validated(ValidationTestData.AdvancedGroup.class) + public static class AdvancedValidateService { + /** + * 验证高级分组数据。 + * + * @param data 用于测试复杂对象验证的数据 {@link ValidationTestData}。 + */ + public void validateAdvancedGroup(@Valid ValidationTestData data) { + LOG.debug("Validating advanced group data: {}", data); + } + } +} \ No newline at end of file diff --git a/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-javax/src/test/java/modelengine/fitframework/validation/data/ValidateService.java b/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-javax/src/test/java/modelengine/fitframework/validation/data/ValidateService.java new file mode 100644 index 000000000..56c837133 --- /dev/null +++ b/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-javax/src/test/java/modelengine/fitframework/validation/data/ValidateService.java @@ -0,0 +1,439 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package modelengine.fitframework.validation.data; + +import modelengine.fitframework.annotation.Component; +import modelengine.fitframework.annotation.Fit; +import modelengine.fitframework.log.Logger; +import modelengine.fitframework.validation.Validated; + +import org.hibernate.validator.constraints.Range; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.List; +import java.util.Map; + +import javax.validation.Valid; +import javax.validation.constraints.AssertFalse; +import javax.validation.constraints.AssertTrue; +import javax.validation.constraints.DecimalMax; +import javax.validation.constraints.DecimalMin; +import javax.validation.constraints.Digits; +import javax.validation.constraints.Email; +import javax.validation.constraints.Future; +import javax.validation.constraints.FutureOrPresent; +import javax.validation.constraints.Max; +import javax.validation.constraints.Min; +import javax.validation.constraints.Negative; +import javax.validation.constraints.NegativeOrZero; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Null; +import javax.validation.constraints.Past; +import javax.validation.constraints.PastOrPresent; +import javax.validation.constraints.Pattern; +import javax.validation.constraints.Positive; +import javax.validation.constraints.PositiveOrZero; +import javax.validation.constraints.Size; + +/** + * 表示测试用校验服务。 + * + * @author 阮睿 + * @since 2025-07-18 + */ +@Component +@Validated +public class ValidateService { + private static final Logger LOG = Logger.get(ValidateService.class); + + @Fit + private GroupValidateService.StudentValidateService studentValidateService; + + @Fit + private GroupValidateService.TeacherValidateService teacherValidateService; + + @Fit + private GroupValidateService.AdvancedValidateService advancedValidateService; + + /** + * 测试原始类型。 + * + * @param num 表示输入的 {@code int}。 + */ + public void foo0(@Positive(message = "必须是正数") int num) { + LOG.debug("{}", num); + } + + /** + * 测试结构体类型。 + * + * @param employee 表示输入的 {@link Employee}。 + */ + public void foo1(@Valid Employee employee) { + LOG.debug("{}", employee); + } + + /** + * 测试嵌套类型。 + * + * @param company 表示输入的 {@link Company}。 + */ + public void foo2(@Valid Company company) { + LOG.debug("{}", company); + } + + /** + * 测试 NotNull 约束注解。 + * + * @param value 表示输入的 {@link String}。 + */ + public void testNotNull(@NotNull String value) {} + + /** + * 测试 NotEmpty 约束注解。 + * + * @param value 表示输入的 {@link String}。 + */ + public void testNotEmpty(@NotEmpty String value) {} + + /** + * 测试 NotBlank 约束注解。 + * + * @param value 表示输入的 {@link String}。 + */ + public void testNotBlank(@NotBlank String value) {} + + /** + * 测试 Null 约束注解。 + * + * @param value 表示输入的 {@link String}。 + */ + public void testNull(@Null String value) {} + + /** + * 测试 Size 约束注解。 + * + * @param value 表示输入的 {@link String}。 + */ + public void testSize(@Size(min = 2, max = 10) String value) {} + + /** + * 测试 List 的 Size 约束注解。 + * + * @param value 表示输入的 {@link List}{@code <}{@link String}{@code >}。 + */ + public void testSizeList(@Size(min = 1, max = 3) List value) {} + + /** + * 测试 Min 约束注解。 + * + * @param value 表示输入的 {@code int}。 + */ + public void testMin(@Min(10) int value) {} + + /** + * 测试 Max 约束注解。 + * + * @param value 表示输入的 {@code int}。 + */ + public void testMax(@Max(100) int value) {} + + /** + * 测试 DecimalMin 约束注解。 + * + * @param value 表示输入的 {@link BigDecimal}。 + */ + public void testDecimalMin(@DecimalMin("10.5") BigDecimal value) {} + + /** + * 测试 DecimalMax 约束注解。 + * + * @param value 表示输入的 {@link BigDecimal}。 + */ + public void testDecimalMax(@DecimalMax("100.5") BigDecimal value) {} + + /** + * 测试 Positive 约束注解。 + * + * @param value 表示输入的 {@code int}。 + */ + public void testPositive(@Positive int value) {} + + /** + * 测试 PositiveOrZero 约束注解。 + * + * @param value 表示输入的 {@code int}。 + */ + public void testPositiveOrZero(@PositiveOrZero int value) {} + + /** + * 测试 Negative 约束注解。 + * + * @param value 表示输入的 {@code int}。 + */ + public void testNegative(@Negative int value) {} + + /** + * 测试 NegativeOrZero 约束注解。 + * + * @param value 表示输入的 {@code int}。 + */ + public void testNegativeOrZero(@NegativeOrZero int value) {} + + /** + * 测试 Digits 约束注解。 + * + * @param value 表示输入的 {@link BigDecimal}。 + */ + public void testDigits(@Digits(integer = 3, fraction = 2) BigDecimal value) {} + + /** + * 测试 Past 约束注解。 + * + * @param value 表示输入的 {@link LocalDate}。 + */ + public void testPast(@Past LocalDate value) {} + + /** + * 测试 PastOrPresent 约束注解。 + * + * @param value 表示输入的 {@link LocalDate}。 + */ + public void testPastOrPresent(@PastOrPresent LocalDate value) {} + + /** + * 测试 Future 约束注解。 + * + * @param value 表示输入的 {@link LocalDate}。 + */ + public void testFuture(@Future LocalDate value) {} + + /** + * 测试 FutureOrPresent 约束注解。 + * + * @param value 表示输入的 {@link LocalDate}。 + */ + public void testFutureOrPresent(@FutureOrPresent LocalDate value) {} + + /** + * 测试 Pattern 约束注解。 + * + * @param value 表示输入的 {@link String}。 + */ + public void testPattern(@Pattern(regexp = "^[a-zA-Z]+$") String value) {} + + /** + * 测试 Email 约束注解。 + * + * @param value 表示输入的 {@link String}。 + */ + public void testEmail(@Email String value) {} + + /** + * 测试 AssertTrue 约束注解。 + * + * @param value 表示输入的 {@code boolean}。 + */ + public void testAssertTrue(@AssertTrue boolean value) {} + + /** + * 测试 AssertFalse 约束注解。 + * + * @param value 表示输入的 {@code boolean}。 + */ + public void testAssertFalse(@AssertFalse boolean value) {} + + /** + * 测试 Valid 对象验证。 + * + * @param data 表示输入的 {@link ValidationTestData}。 + */ + public void testValidObject(@Valid ValidationTestData data) {} + + /** + * 测试 Range 约束注解。 + * + * @param value 表示输入的 {@code int}。 + */ + public void testRange(@Range(min = 10, max = 100, message = "需要在10和100之间") int value) {} + + /** + * 测试 BigDecimal 类型的Range约束注解。 + * + * @param value 表示输入的 {@link BigDecimal}。 + */ + public void testRangeBigDecimal(@Range(min = 10, max = 100, message = "需要在10和100之间") BigDecimal value) {} + + /** + * 验证 Employee 对象。 + * + * @param employee 表示输入的 {@link Employee}。 + */ + public void validateEmployee(@Valid Employee employee) { + LOG.debug("Validating employee: {}", employee); + } + + /** + * 验证年龄是否为正数。 + * + * @param age 表示输入的 {@code int}。 + */ + public void validateAge(@Positive(message = "必须是正数") int age) { + LOG.debug("Validating age: {}", age); + } + + /** + * 验证姓名和年龄。 + * + * @param name 表示输入的 {@link String}。 + * @param age 表示输入的 {@code int}。 + */ + public void validateNameAndAge(@NotBlank String name, @Positive int age) { + LOG.debug("Validating name: {} and age: {}", name, age); + } + + /** + * 验证高级分组数据。 + * + * @param data 表示输入的 {@link ValidationTestData}。 + */ + public void validateAdvancedGroup(ValidationTestData data) { + if (this.advancedValidateService != null) { + this.advancedValidateService.validateAdvancedGroup(data); + } + } + + /** + * 验证学生年龄。 + * + * @param age 表示输入的 {@code int}。 + */ + public void validateStudentAge(int age) { + if (this.studentValidateService != null) { + this.studentValidateService.validateStudentAge(age); + } + } + + /** + * 验证教师年龄。 + * + * @param age 表示输入的 {@code int}。 + */ + public void validateTeacherAge(int age) { + if (this.teacherValidateService != null) { + this.teacherValidateService.validateTeacherAge(age); + } + } + + /** + * 验证公司对象。 + * + * @param company 表示输入的 {@link Company}。 + */ + public void validateCompany(@Valid Company company) { + LOG.debug("Validating company: {}", company); + } + + /** + * 验证员工列表。 + * + * @param employees 表示输入的 {@link List}{@code <}{@link Employee}{@code >}。 + */ + public void validateEmployeeList(List<@Valid Employee> employees) { + LOG.debug("Validating employee list: {}", employees); + } + + /** + * 验证嵌套员工列表。 + * + * @param nestedList 表示输入的 {@link List}{@code <}{@link List}{@code <}{@link Employee}{@code >}{@code >}}。 + */ + public void validateNestedEmployeeList(List> nestedList) { + LOG.debug("Validating nested employee list: {}", nestedList); + } + + /** + * 验证员工映射。 + * + * @param employeeMap 表示输入的 {@link Map}{@code <}{@link String}{@code , }{@link Employee}{@code >}。 + */ + public void validateEmployeeMap(@Valid Map employeeMap) { + LOG.debug("Validating employee map: {}", employeeMap); + } + + /** + * 验证员工数据映射。 + * + * @param map 表示输入的 {@link Map}{@code <}{@link Employee}{@code , }{@link ValidationTestData}>。 + */ + public void validateEmployeeDataMap(@Valid Map map) { + LOG.debug("Validating employee data map: {}", map); + } + + /** + * 验证嵌套员工数据映射。 + * + * @param nestedMap 表示输入的 + * {@link Map}{@code <}{@link Employee}{@code , }{@link Map}{@code <}{@link String}{@code , }{@link + * ValidationTestData}{@code >}{@code >}。 + */ + public void validateNestedEmployeeDataMap(Map<@Valid Employee, Map> nestedMap) { + LOG.debug("Validating nested employee data map: {}", nestedMap); + } + + /** + * 验证员工映射列表。 + * + * @param listOfMaps 表示输入的 {@link List}{@code <}{@link Map}{@code <}{@link String}{@code , + * }{@link Employee}{@code >}{@code >}。 + */ + public void validateEmployeeMapList(List> listOfMaps) { + LOG.debug("Validating employee map list: {}", listOfMaps); + } + + /** + * 验证员工数据列表映射。 + * + * @param map 表示输入的 + * {@link Map}{@code <}{@link Employee}{@code , }{@link List}{@code <}{@link ValidationTestData}{@code >}。 + */ + public void validateEmployeeDataListMap(Map<@Valid Employee, List<@Valid ValidationTestData>> map) { + LOG.debug("Validating employee data list map: {}", map); + } + + /** + * 验证公司列表。 + * + * @param companies 表示输入的 {@link List}{@code <}{@link Company}{@code >}。 + */ + public void validateCompanyList(@Valid List companies) { + LOG.debug("Validating company list: {}", companies); + } + + /** + * 验证混合类型数据。 + * + * @param value 表示输入的 {@code int}。 + * @param employee 表示输入的 {@link Employee}。 + */ + public void validateMixed(@Positive int value, @Valid Employee employee) { + LOG.debug("Validating mixed primitive {} and object {}", value, employee); + } + + /** + * 验证混合集合数据。 + * + * @param list1 表示输入的 {@link List}{@code <}{@link String}{@code >}。 + * @param list2 表示输入的 {@link List}{@code <}{@link String}{@code >}。 + */ + public void validateMixedCollections(@NotNull List list1, @NotEmpty List list2) { + LOG.debug("Validating mixed collections: {} and {}", list1, list2); + } +} \ No newline at end of file diff --git a/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-javax/src/test/java/modelengine/fitframework/validation/data/ValidationDataController.java b/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-javax/src/test/java/modelengine/fitframework/validation/data/ValidationDataController.java new file mode 100644 index 000000000..5ce41bf3e --- /dev/null +++ b/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-javax/src/test/java/modelengine/fitframework/validation/data/ValidationDataController.java @@ -0,0 +1,42 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package modelengine.fitframework.validation.data; + +import modelengine.fit.http.annotation.PostMapping; +import modelengine.fit.http.annotation.RequestBody; +import modelengine.fit.http.annotation.RequestMapping; +import modelengine.fitframework.annotation.Component; +import modelengine.fitframework.validation.Validated; + +import javax.validation.Valid; + +/** + * 表示评估注解验证数据接口集。 + * + * @author 阮睿 + * @since 2025-07-18 + */ +@Component +@Validated +@RequestMapping(path = "/validation", group = "评估注解验证数据接口") +public class ValidationDataController { + /** + * Company 类默认分组注解验证。 + * + * @param company 表示注解验证类 {@link Company}。 + */ + @PostMapping(path = "/company/default", description = "验证 Company 类默认分组注解") + public void validateCompanyDefaultGroup(@RequestBody @Valid Company company) {} + + /** + * Company 类特定分组注解验证。 + * + * @param company 表示注解验证类 {@link Company}。 + */ + @PostMapping(path = "/company/companyGroup", description = "验证 Company 类特定分组注解") + public void validateCompanyGroup(@RequestBody @Valid Company company) {} +} \ No newline at end of file diff --git a/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-javax/src/test/java/modelengine/fitframework/validation/data/ValidationTestData.java b/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-javax/src/test/java/modelengine/fitframework/validation/data/ValidationTestData.java new file mode 100644 index 000000000..59234a991 --- /dev/null +++ b/framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-javax/src/test/java/modelengine/fitframework/validation/data/ValidationTestData.java @@ -0,0 +1,122 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package modelengine.fitframework.validation.data; + +import java.math.BigDecimal; + +import javax.validation.constraints.AssertTrue; +import javax.validation.constraints.Max; +import javax.validation.constraints.Min; +import javax.validation.constraints.Negative; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Positive; + +/** + * 测试用的复杂验证数据类。 + * + * @author 阮睿 + * @since 2025-07-18 + */ +public class ValidationTestData { + // 分组接口定义。 + public interface AdvancedGroup {} + + public interface StudentGroup {} + + public interface TeacherGroup {} + + @NotBlank(message = "名称不能为空") + private String name; + + @Min(value = 0, message = "年龄必须大于等于0") + @Max(value = 150, message = "年龄必须小于等于150") + @Max(value = 200, message = "高级组年龄必须小于等于200", groups = AdvancedGroup.class) + private Integer age; + + @NotBlank(message = "描述不能为空") + private String description; + + @NotBlank(message = "内容不能为空白") + private String content; + + @Positive(message = "数量必须是正数") + private Integer quantity; + + @Negative(message = "折扣必须是负数") + private BigDecimal discount; + + @AssertTrue(message = "必须同意条款") + private Boolean agreed; + + /** + * 构造函数。 + */ + public ValidationTestData() {} + + /** + * 设置名称。 + * + * @param name 表示名称的 {@link String}。 + */ + public void setName(String name) { + this.name = name; + } + + /** + * 设置年龄。 + * + * @param age 表示年龄的 {@link Integer}。 + */ + public void setAge(Integer age) { + this.age = age; + } + + /** + * 设置描述。 + * + * @param description 表示描述的 {@link String}。 + */ + public void setDescription(String description) { + this.description = description; + } + + /** + * 设置内容。 + * + * @param content 表示内容的 {@link String}。 + */ + public void setContent(String content) { + this.content = content; + } + + /** + * 设置数量。 + * + * @param quantity 表示数量的 {@link Integer}。 + */ + public void setQuantity(Integer quantity) { + this.quantity = quantity; + } + + /** + * 设置折扣。 + * + * @param discount 表示折扣的 {@link BigDecimal}。 + */ + public void setDiscount(BigDecimal discount) { + this.discount = discount; + } + + /** + * 确认是否同意。 + * + * @param agreed 表示是否同意的 {@link Boolean}。 + */ + public void setAgreed(Boolean agreed) { + this.agreed = agreed; + } +} diff --git a/framework/fit/java/fit-builtin/plugins/pom.xml b/framework/fit/java/fit-builtin/plugins/pom.xml index b4e52efd9..0260ff321 100644 --- a/framework/fit/java/fit-builtin/plugins/pom.xml +++ b/framework/fit/java/fit-builtin/plugins/pom.xml @@ -43,6 +43,8 @@ fit-service-discovery fit-service-registry fit-value-fastjson + fit-validation-hibernate-jakarta + fit-validation-hibernate-javax diff --git a/framework/fit/java/fit-extension/fit-validation-hibernate/src/main/java/modelengine/fitframework/validation/ValidationHandler.java b/framework/fit/java/fit-extension/fit-validation-hibernate/src/main/java/modelengine/fitframework/validation/ValidationHandler.java deleted file mode 100644 index 0aeb50301..000000000 --- a/framework/fit/java/fit-extension/fit-validation-hibernate/src/main/java/modelengine/fitframework/validation/ValidationHandler.java +++ /dev/null @@ -1,68 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -package modelengine.fitframework.validation; - -import modelengine.fitframework.annotation.Component; -import modelengine.fitframework.aop.JoinPoint; -import modelengine.fitframework.aop.annotation.Aspect; -import modelengine.fitframework.aop.annotation.Before; -import modelengine.fitframework.ioc.annotation.PreDestroy; - -import org.hibernate.validator.HibernateValidator; -import org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator; - -import java.util.Set; - -import javax.validation.ConstraintViolation; -import javax.validation.ConstraintViolationException; -import javax.validation.Validation; -import javax.validation.Validator; -import javax.validation.ValidatorFactory; -import javax.validation.executable.ExecutableValidator; - -/** - * 校验入口类。 - *

- * 当调用的类或方法参数包含 {@link Validated} 注解时,会对该方法进行校验处理。 - *

- * - * @author 易文渊 - * @since 2024-09-27 - */ -@Aspect -@Component -public class ValidationHandler implements AutoCloseable { - private final ValidatorFactory validatorFactory; - private final Validator validator; - - ValidationHandler() { - this.validatorFactory = Validation.byProvider(HibernateValidator.class) - .configure() - .messageInterpolator(new ParameterMessageInterpolator()) - .failFast(false) - .buildValidatorFactory(); - this.validator = validatorFactory.getValidator(); - } - - @Before(value = "@target(validated) && execution(public * *(..))", argNames = "joinPoint, validated") - private void handle(JoinPoint joinPoint, Validated validated) { - ExecutableValidator execVal = this.validator.forExecutables(); - Set> result = execVal.validateParameters(joinPoint.getTarget(), - joinPoint.getMethod(), - joinPoint.getArgs(), - validated.value()); - if (!result.isEmpty()) { - throw new ConstraintViolationException(result); - } - } - - @PreDestroy - @Override - public void close() { - this.validatorFactory.close(); - } -} \ No newline at end of file diff --git a/framework/fit/java/fit-extension/fit-validation-hibernate/src/test/java/modelengine/fitframework/validation/ValidationHandlerTest.java b/framework/fit/java/fit-extension/fit-validation-hibernate/src/test/java/modelengine/fitframework/validation/ValidationHandlerTest.java deleted file mode 100644 index e7dd155d2..000000000 --- a/framework/fit/java/fit-extension/fit-validation-hibernate/src/test/java/modelengine/fitframework/validation/ValidationHandlerTest.java +++ /dev/null @@ -1,58 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -package modelengine.fitframework.validation; - -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import modelengine.fitframework.annotation.Fit; -import modelengine.fitframework.test.annotation.FitTestWithJunit; -import modelengine.fitframework.validation.data.Company; -import modelengine.fitframework.validation.data.Employee; -import modelengine.fitframework.validation.data.ValidateService; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import java.util.Collections; - -import javax.validation.ConstraintViolationException; - -/** - * {@link ValidationHandler} 的单元测试。 - * - * @author 易文渊 - * @since 2024-09-27 - */ -@DisplayName("测试注解校验功能") -@FitTestWithJunit(includeClasses = {ValidateService.class, ValidationHandler.class}) -public class ValidationHandlerTest { - @Fit - private ValidateService validateService; - - @Test - @DisplayName("测试校验原始类型成功") - void givePrimitiveThenValidateOk() { - assertThatThrownBy(() -> this.validateService.foo0(-1)).isInstanceOf(ConstraintViolationException.class) - .hasMessageContaining("必须是正数"); - } - - @Test - @DisplayName("测试校验结构体成功") - void giveClassThenValidateOk() { - assertThatThrownBy(() -> this.validateService.foo1(new Employee("sky", 17))).isInstanceOf( - ConstraintViolationException.class).hasMessageContaining("年龄必须大于等于18"); - } - - @Test - @DisplayName("测试嵌套结构体成功") - void giveNestedClassThenValidateOk() { - assertThatThrownBy(() -> { - Employee employee = new Employee("sky", 17); - this.validateService.foo2(new Company(Collections.singletonList(employee))); - }).isInstanceOf(ConstraintViolationException.class).hasMessageContaining("年龄必须大于等于18"); - } -} diff --git a/framework/fit/java/fit-extension/fit-validation-hibernate/src/test/java/modelengine/fitframework/validation/data/ValidateService.java b/framework/fit/java/fit-extension/fit-validation-hibernate/src/test/java/modelengine/fitframework/validation/data/ValidateService.java deleted file mode 100644 index e47915261..000000000 --- a/framework/fit/java/fit-extension/fit-validation-hibernate/src/test/java/modelengine/fitframework/validation/data/ValidateService.java +++ /dev/null @@ -1,53 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -package modelengine.fitframework.validation.data; - -import modelengine.fitframework.annotation.Component; -import modelengine.fitframework.log.Logger; -import modelengine.fitframework.validation.Validated; - -import javax.validation.Valid; -import javax.validation.constraints.Positive; - -/** - * 表示测试用校验服务。 - * - * @author 易文渊 - * @since 2024-09-27 - */ -@Component -@Validated -public class ValidateService { - private static final Logger LOG = Logger.get(ValidateService.class); - - /** - * 测试原始类型。 - * - * @param num 表示输入的 {@code int}。 - */ - public void foo0(@Positive(message = "必须是正数") int num) { - LOG.debug("{}", num); - } - - /** - * 测试结构体类型。 - * - * @param employee 表示输入的 {@code Employee}。 - */ - public void foo1(@Valid Employee employee) { - LOG.debug("{}", employee); - } - - /** - * 测试嵌套类型。 - * - * @param company 表示输入的 {@code Company}。 - */ - public void foo2(@Valid Company company) { - LOG.debug("{}", company); - } -} \ No newline at end of file diff --git a/framework/fit/java/fit-extension/fit-validation/README.md b/framework/fit/java/fit-extension/fit-validation/README.md deleted file mode 100644 index e7fc016f6..000000000 --- a/framework/fit/java/fit-extension/fit-validation/README.md +++ /dev/null @@ -1,179 +0,0 @@ -
Validation Architecture
- -# 核心类图 - -``` plantuml - -@startuml -hide empty members - -skinparam backgroundColor #EEEBDC -skinparam roundcorner 20 -skinparam sequenceArrowThickness 2 -skinparam ClassFontName Consolas - -skinparam class { -BackgroundColor<> LightGray -BackgroundColor<> Pink -} - -class ValidationHandler { - - validator : Validator - -- - - handle(ProceedingJoinPoint joinPoint) : Object - === - This class as the entry of the validation capability and automatically scans the validator annotation during startup. - Therefore, the handle method is private and is not provided externally. -} - -interface ConstraintValidator { - + {abstract} initialize(Annotation constraintAnnotation) : void - + {abstract} isValid(T value) : void - === - Represents a constraint validator that validates a specific constraint. - The specific constraint is represented by an annotation. -} -annotation Constraint -Constraint .left.> ConstraintValidator - -interface ConstraintViolation { - + {abstract} message() : String - + {abstract} message(String value) : void - + {abstract} propertyName() : String - + {abstract} propertyName(String value) : void - + {abstract} propertyValue() : Object - + {abstract} propertyValue(Object value) : void - === - This class is used to save data verification error information. -} -interface Validator { - + {abstract} validate(Object object, Class... groups) : Set<ConstraintViolation> - === - Provides the ability to verify that an object complies with a specific constraint. such as non-null, non-empty so on. -} -Validator ..> ConstraintViolation -Validator .right.> ConstraintValidator -ValidationHandler o-- Validator -@enduml -``` - -``` plantuml -@startuml -hide empty members - -skinparam backgroundColor #EEEBDC -skinparam roundcorner 20 -skinparam sequenceArrowThickness 2 -skinparam ClassFontName Consolas - -skinparam class { -BackgroundColor<> LightGray -BackgroundColor<> Pink -} - -annotation Constraint - -interface ConstraintValidator { - + {abstract} initialize(Annotation constraintAnnotation) : void - + {abstract} isValid(T value) : void - === - Represents a constraint validator that validates a specific constraint. - The specific constraint is represented by an annotation. -} - -Constraint .left.> ConstraintValidator - -class NotBlankValidator { - + initialize(NotBlank constraintAnnotation) : void - + isValid(Object value) : boolean -} -NotBlankValidator .up.|> ConstraintValidator -class NotEmptyValidator { - + initialize(NotEmpty constraintAnnotation) : void - + isValid(Object value) : boolean -} -NotEmptyValidator .up.|> ConstraintValidator -class RangeValidator { - + initialize(Range constraintAnnotation) : void - + isValid(Long value) : boolean -} -RangeValidator .up.|> ConstraintValidator - -annotation NotBlank -NotBlank .up.> NotBlankValidator -annotation NotEmpty -NotEmpty .up.> NotEmptyValidator -annotation Range -Range .up.> RangeValidator - -@enduml -``` - -# 核心类 - -### ValidationHandler - -`ValidationHandler`类作为校验能力的入口,通过`AOP`的方式来解析。框架启动时,ValidationHandler类的handle方法会扫描@Validated注解,对有该注解的类或者参数进行解析:如果是类上含有该注解, -则对这个类的所有方法进行判断,判断方法参数是否含有约束注解,如果有,对该方法进行AOP 注入;如果是参数上含有该注解,则对该参数所属的方法进行注入。 - -### ConstraintValidator - -表示约束验证器,对某个特定的约束进行验证。当前实现的`ConstraintValidator`的有: - -- NotBlankValidator: 判断该字符串是否不为`null`,且不是空白字符。 -- NotEmptyValidator:判断该实体是否不为`null`,且不是空对象。 -- RangeValidator:判断该数字是否在所属区间范围内。 - -### ConstraintViolation - -`ConstraintViolation`主要用于保存数据校验的错误信息。当用户使用约束注解进行校验时,如果校验出错,`ConstraintViolation` -里就保存了校验出错的字段、出错原因等信息。通过处理这个异常,可以将错误信息返回给客户端,让其得知请求传递的数据有误,从而进行相应的修正。 - -### 约束注解 - -每个`ConstraintValidator`都有其对应的约束注解供用户使用,如`NotBlankValidator`对应`NotBlank`注解。注解除了特定属性外,都含有`message`、`group`属性 - -- message: 当约束条件不满足时,打印出的异常信息 -- group: - 用于分组约束。通过分组约束可以对校验进行更精确化控制。如果在类级别和方法级别同时使用分组,那么方法级别的分组将覆盖类级别的分组。 - -考虑一下例子: 首先,需要定义一个分组接口: - -``` java -public interface GroupA {} - -public interface GroupB {} -``` - -然后,在需要进行校验的类中,使用`@Validated`注解指定需要校验的分组: - -``` java -public class User { - @NotBlank(message = "用户名不能为空", groups = {GroupA.class}) - private String username; - - @NotBlank(message = "密码不能为空", groups = {GroupB.class}) - private String password; - - // getter and setter -} -``` - -在Controller中,使用`@Validated`注解指定需要校验的分组: - -``` java -public class UserController { - @PostMapping("/login") - public String login(@Validated(GroupA.class) User user) { - // ... - } - - @PostMapping("/register") - public String register(@Validated(GroupB.class) User user) { - // ... - } -} -``` - -这样,在`login`方法中,只会校验`username`字段,而在`register`方法中,只会校验`password`字段。 - diff --git a/framework/fit/java/fit-extension/fit-validation/pom.xml b/framework/fit/java/fit-extension/fit-validation/pom.xml deleted file mode 100644 index 16a8a61ea..000000000 --- a/framework/fit/java/fit-extension/fit-validation/pom.xml +++ /dev/null @@ -1,60 +0,0 @@ - - - 4.0.0 - - - org.fitframework.extension - fit-extension-parent - 3.6.0-SNAPSHOT - - - fit-validation - - FIT Validation - FIT Framework Validation module provides built-in validation functionality. - https://github.com/ModelEngine-Group/fit-framework - - - - - org.fitframework - fit-api - - - org.fitframework - fit-util - - - - - org.fitframework - fit-ioc - test - - - org.junit.jupiter - junit-jupiter - test - - - org.mockito - mockito-core - test - - - org.assertj - assertj-core - test - - - org.fitframework.service - fit-http-classic - test - - - org.fitframework - fit-test-framework - - - diff --git a/framework/fit/java/fit-extension/fit-validation/src/main/java/modelengine/fitframework/validation/ConstraintViolation.java b/framework/fit/java/fit-extension/fit-validation/src/main/java/modelengine/fitframework/validation/ConstraintViolation.java deleted file mode 100644 index 6d14d8adf..000000000 --- a/framework/fit/java/fit-extension/fit-validation/src/main/java/modelengine/fitframework/validation/ConstraintViolation.java +++ /dev/null @@ -1,125 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -package modelengine.fitframework.validation; - -import modelengine.fitframework.pattern.builder.BuilderFactory; - -import java.lang.reflect.Method; - -/** - * 表示约束校验失败的数据类。 - * - * @author 邬涨财 - * @since 2023-03-08 - */ -public interface ConstraintViolation { - /** - * 获取校验失败的信息。 - * - * @return 表示校验失败的信息的 {@link String}。 - */ - String message(); - - /** - * 获取校验的属性名。 - * - * @return 表示校验的属性名的 {@link String}。 - */ - String propertyName(); - - /** - * 获取校验的属性值。 - * - * @return 表示校验的属性值的 {@link Object}。 - */ - Object propertyValue(); - - /** - * 获取校验的方法。 - * - * @return 表示校验的方法的 {@link Method}。 - */ - Method validationMethod(); - - /** - * 获取校验注解的参数。 - * - * @return 表示校验注解的参数的 {@link Object}{@code []}。 - */ - Object[] args(); - - /** - * {@link ConstraintViolation} 的构建器。 - */ - interface Builder { - /** - * 向构建器设置校验失败的信息。 - * - * @param message 表示校验失败的信息的 {@link String}。 - * @return 表示构建器的 {@link Builder}。 - */ - Builder message(String message); - - /** - * 向构建器设置校验的属性名。 - * - * @param propertyName 表示校验的属性名的 {@link String}。 - * @return 表示构建器的 {@link Builder}。 - */ - Builder propertyName(String propertyName); - - /** - * 向构建器设置校验的属性值。 - * - * @param propertyValue 表示校验的属性值的 {@link String}。 - * @return 表示构建器的 {@link Builder}。 - */ - Builder propertyValue(Object propertyValue); - - /** - * 向构建器设置校验的方法。 - * - * @param validationMethod 表示校验的方法的 {@link String}。 - * @return 表示构建器的 {@link Builder}。 - */ - Builder validationMethod(Method validationMethod); - - /** - * 向构建器设置校验注解的参数。 - * - * @param args 表示校验注解的参数的 {@link Object}{@code []}。 - * @return 表示构建器的 {@link Builder}。 - */ - Builder args(Object args); - - /** - * 构建对象。 - * - * @return 表示构建出来的对象的 {@link ConstraintViolation}。 - */ - ConstraintViolation build(); - } - - /** - * 获取 {@link ConstraintViolation} 的构建器。 - * - * @return 表示 {@link ConstraintViolation} 的构建器的 {@link Builder}。 - */ - static Builder builder() { - return builder(null); - } - - /** - * 获取 {@link ConstraintViolation} 的构建器,同时将指定对象的值进行填充。 - * - * @param value 表示指定对象的 {@link ConstraintViolation}。 - * @return 表示 {@link ConstraintViolation} 的构建器的 {@link Builder}。 - */ - static Builder builder(ConstraintViolation value) { - return BuilderFactory.get(ConstraintViolation.class, Builder.class).create(value); - } -} diff --git a/framework/fit/java/fit-extension/fit-validation/src/main/java/modelengine/fitframework/validation/ValidationHandler.java b/framework/fit/java/fit-extension/fit-validation/src/main/java/modelengine/fitframework/validation/ValidationHandler.java deleted file mode 100644 index 9b9e1cba8..000000000 --- a/framework/fit/java/fit-extension/fit-validation/src/main/java/modelengine/fitframework/validation/ValidationHandler.java +++ /dev/null @@ -1,381 +0,0 @@ -/* - * Copyright (c) 2024-2025 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - */ - -package modelengine.fitframework.validation; - -import modelengine.fitframework.annotation.Component; -import modelengine.fitframework.aop.JoinPoint; -import modelengine.fitframework.aop.annotation.Aspect; -import modelengine.fitframework.aop.annotation.Before; -import modelengine.fitframework.inspection.Validation; -import modelengine.fitframework.ioc.BeanContainer; -import modelengine.fitframework.pattern.builder.BuilderFactory; -import modelengine.fitframework.util.AnnotationUtils; -import modelengine.fitframework.util.ArrayUtils; -import modelengine.fitframework.util.CollectionUtils; -import modelengine.fitframework.util.MapUtils; -import modelengine.fitframework.util.ObjectUtils; -import modelengine.fitframework.util.ReflectionUtils; -import modelengine.fitframework.util.StringUtils; -import modelengine.fitframework.validation.constraints.Constraint; -import modelengine.fitframework.validation.exception.ConstraintViolationException; -import modelengine.fitframework.validation.group.DefaultGroup; -import modelengine.fitframework.value.PropertyValue; - -import java.lang.annotation.Annotation; -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.lang.reflect.Parameter; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -/** - * 校验入口类。 - *

当调用的方法参数包含 {@link Validated} 注解时,会对该方法进行校验处理。当前存在两种场景包含该场景:

- *
    - *
  1. 方法参数直接包含 {@link Validated} 注解,此时校验的是该参数对象的字段。
  2. - *
  3. 方法参数包含约束注解,如 {@link modelengine.fitframework.validation.constraints.NotEmpty},此时校验的是该参数对象。
  4. - *
- * - * @author 邬涨财 - * @since 2023-03-14 - */ -@Aspect -@Component -public class ValidationHandler { - private final BeanContainer container; - private final Map>> validatorMap = - new ConcurrentHashMap<>(); - - /** - * 使用指定的 Bean 容器初始化 {@link ValidationHandler} 的新实例。 - * - * @param container 表示 Bean 容器的 {@link BeanContainer}。 - * @throws IllegalArgumentException 当 {@code container} 为 {@code null} 时。 - */ - public ValidationHandler(BeanContainer container) { - this.container = - Validation.notNull(container, "The bean container cannot be null when construct validation handle."); - } - - @Before(value = "@params(validated)", argNames = "joinPoint, validated") - private void handle(JoinPoint joinPoint, Validated validated) { - Method method = joinPoint.getMethod(); - Object[] args = joinPoint.getArgs(); - List violations = this.handleValidationMethod(method, args) - .stream() - .flatMap(validationMetadata -> this.validate(validationMetadata).stream()) - .collect(Collectors.toList()); - Validation.isTrue(violations.isEmpty(), () -> new ConstraintViolationException(violations)); - } - - private List handleValidationMethod(Method method, Object[] args) { - Class[] classGroups = this.getClassGroups(method); - Parameter[] parameters = ReflectionUtils.getParameters(method); - List validationMetadataList = new ArrayList<>(); - for (int index = 0; index < parameters.length; index++) { - if (this.hasConstraintAnnotation(parameters[index])) { - ValidationMetadata validationMetadata = ValidationMetadata.createValidationParameter(parameters[index], - classGroups, - args[index], - method); - validationMetadataList.add(validationMetadata); - continue; - } - if (this.hasValidatedAnnotation(parameters[index])) { - Class[] validationGroups = this.getValidationGroups(parameters[index], classGroups); - PropertyValue parameterValue = PropertyValue.createParameterValue(parameters[index]); - validationMetadataList.addAll(this.getValidationFields(parameterValue.getParameterizedType(), - args[index], - method, - validationGroups)); - } - } - return validationMetadataList; - } - - private Class[] getClassGroups(Method method) { - return AnnotationUtils.getAnnotation(this.container, method.getDeclaringClass(), Validated.class) - .map(Validated::value) - .filter(ArrayUtils::isNotEmpty) - .orElseGet(() -> new Class[] {DefaultGroup.class}); - } - - private Class[] getValidationGroups(Parameter parameters, Class[] classGroups) { - return AnnotationUtils.getAnnotation(this.container, parameters, Validated.class) - .map(Validated::value) - .filter(ArrayUtils::isNotEmpty) - .orElse(classGroups); - } - - private boolean hasValidatedAnnotation(AnnotatedElement element) { - return AnnotationUtils.getAnnotation(this.container, element, Validated.class).isPresent(); - } - - private boolean hasConstraintAnnotation(AnnotatedElement element) { - return AnnotationUtils.getAnnotation(this.container, element, Constraint.class).isPresent(); - } - - private List getValidationFields(Type validationObject, Object validationValue, Method method, - Class[] validationGroups) { - if (validationObject instanceof ParameterizedType) { - ParameterizedType parameterizedType = ObjectUtils.cast(validationObject); - if (Collection.class.isAssignableFrom(ObjectUtils.cast(parameterizedType.getRawType()))) { - return this.handleCollection(parameterizedType.getActualTypeArguments(), - ObjectUtils.cast(validationValue), - method, - validationGroups); - } - if (Map.class.isAssignableFrom(ObjectUtils.cast(parameterizedType.getRawType()))) { - return this.handleMap(parameterizedType.getActualTypeArguments(), - ObjectUtils.cast(validationValue), - method, - validationGroups); - } - } - if (validationObject instanceof Class) { - Field[] fields = new Field[0]; - Map fieldNameValues = Collections.emptyMap(); - if (!isSimpleClass(ObjectUtils.cast(validationObject))) { - fields = ReflectionUtils.getDeclaredFields(ObjectUtils.cast(validationObject), true); - fieldNameValues = Arrays.stream(fields) - .collect(HashMap::new, - (map, field) -> map.put(field.getName(), - validationValue == null - ? null - : ReflectionUtils.getField(validationValue, field)), - HashMap::putAll); - } - List constraintFieldMetadata = - this.getConstraintFieldMetadata(method, validationGroups, fields, fieldNameValues); - List validatedFieldMetadata = - this.getValidatedFieldMetadata(method, validationGroups, fields, fieldNameValues); - return CollectionUtils.merge(constraintFieldMetadata, validatedFieldMetadata); - } - return Collections.emptyList(); - } - - private List handleCollection(Type[] actualTypeArgs, Collection validationValueCollection, - Method method, Class[] validationGroups) { - Validation.equals(actualTypeArgs.length, 1, "The collection must have exactly 1 parameterized type."); - if (CollectionUtils.isEmpty(validationValueCollection)) { - return Collections.emptyList(); - } - return validationValueCollection.stream() - .flatMap(element -> this.getValidationFields(actualTypeArgs[0], element, method, validationGroups) - .stream()) - .collect(Collectors.toList()); - } - - private List handleMap(Type[] actualTypeArgs, Map validationValueMap, Method method, - Class[] validationGroups) { - Validation.equals(actualTypeArgs.length, 2, "The map must have exactly 2 parameterized types."); - if (MapUtils.isEmpty(validationValueMap)) { - return Collections.emptyList(); - } - return validationValueMap.entrySet() - .stream() - .flatMap(entry -> Stream.concat( - this.getValidationFields(actualTypeArgs[0], entry.getKey(), method, validationGroups).stream(), - this.getValidationFields(actualTypeArgs[1], entry.getValue(), method, validationGroups).stream() - )) - .collect(Collectors.toList()); - } - - private List getConstraintFieldMetadata(Method method, Class[] validationGroups, - Field[] fields, Map fieldNameValues) { - return Arrays.stream(fields) - .filter(this::hasConstraintAnnotation) - .map(field -> ValidationMetadata.createValidationField(field, - validationGroups, - fieldNameValues.get(field.getName()), - method)) - .collect(Collectors.toList()); - } - - private List getValidatedFieldMetadata(Method method, Class[] validationGroups, - Field[] fields, Map fieldNameValues) { - return Arrays.stream(fields) - .filter(this::hasValidatedAnnotation) - .flatMap(field -> this.getValidationFields(PropertyValue.createFieldValue(field).getParameterizedType(), - fieldNameValues.get(field.getName()), - method, - validationGroups).stream()) - .collect(Collectors.toList()); - } - - private List validate(ValidationMetadata validationMetadata) { - List violations = new ArrayList<>(); - for (Annotation annotation : validationMetadata.annotations()) { - if (!this.needValidate(annotation, validationMetadata)) { - continue; - } - this.getConstraintValidators(annotation, validationMetadata) - .stream() - .filter(validator -> !validator.isValid(validationMetadata.value())) - .forEach((validator) -> violations.add(this.buildConstraintViolation(validationMetadata, - annotation, - validator.args()))); - } - return violations; - } - - private List> getConstraintValidators(Annotation annotation, - ValidationMetadata validationMetadata) { - ValidatorKey validatorKey = - ValidatorKey.builder().annotation(annotation).annotatedElement(validationMetadata.element()).build(); - return this.validatorMap.computeIfAbsent(validatorKey, (key) -> this.buildConstraintValidators(annotation)); - } - - private List> buildConstraintValidators(Annotation annotation) { - Class>[] validatorClasses = - AnnotationUtils.getAnnotation(this.container, annotation.annotationType(), Constraint.class) - .orElseThrow(IllegalStateException::new) - .value(); - return Arrays.stream(validatorClasses) - .map(validatorClass -> this.buildConstraintValidator(annotation, validatorClass)) - .collect(Collectors.toList()); - } - - private ConstraintValidator buildConstraintValidator(Annotation annotation, - Class> validatorClass) { - ConstraintValidator validator = - ObjectUtils.cast(ReflectionUtils.instantiate(validatorClass)); - validator.initialize(annotation); - return validator; - } - - private ConstraintViolation buildConstraintViolation(ValidationMetadata validationMetadata, Annotation annotation, - Object[] args) { - String message = - String.valueOf(this.getAnnotationPropertyValue(annotation, "message").orElse(StringUtils.EMPTY)); - return ConstraintViolation.builder() - .message(message) - .propertyName(validationMetadata.name()) - .propertyValue(validationMetadata.value()) - .validationMethod(validationMetadata.getValidationMethod()) - .args(args) - .build(); - } - - /** - * 判断该校验对象是否需要校验,通过比较校验对象的 {@link Validated} 注解和 {@link Constraint} 注解是否有相同的分组。 - *

其中通过校验元数据 {@link ValidationMetadata} 可以获得 {@link Validated} 注解的分组;通过约束注解可以获得 {@link Constraint} - * 上的分组值。

- * - * @param constraintAnnotation 表示约束注解的 {@link Annotation}。 - * @param validationMetadata 表示需要校验的元数据的 {@link ValidationMetadata}。 - * @return 表示是否需要校验的 {@code boolean}。 - */ - private boolean needValidate(Annotation constraintAnnotation, ValidationMetadata validationMetadata) { - if (constraintAnnotation.annotationType().getAnnotation(Constraint.class) == null) { - return false; - } - List> validationClasses = Arrays.asList(validationMetadata.groups()); - Optional optionGroups = this.getAnnotationPropertyValue(constraintAnnotation, "groups"); - if (!optionGroups.isPresent()) { - return validationClasses.contains(DefaultGroup.class); - } - Class[] groups = ObjectUtils.cast(optionGroups.get()); - if (groups.length == 0) { - return validationClasses.contains(DefaultGroup.class); - } - return !CollectionUtils.intersect(validationClasses, Arrays.asList(groups)).isEmpty(); - } - - private Optional getAnnotationPropertyValue(Annotation annotation, String propertyKey) { - Class annotationType = annotation.annotationType(); - try { - Method method = annotationType.getDeclaredMethod(propertyKey); - return Optional.ofNullable(ReflectionUtils.invoke(annotation, method)); - } catch (NoSuchMethodException e) { - return Optional.empty(); - } - } - - private boolean isSimpleClass(Class validationClass) { - return validationClass.isPrimitive() || ReflectionUtils.isPrimitiveWrapper(validationClass) - || validationClass == String.class; - } - - /** - * 表示校验器缓存 {@link #validatorMap} 的键。 - */ - public interface ValidatorKey { - /** - * 获取校验元素的 {@link AnnotatedElement}。 - * - * @return 表示校验元素的 {@link AnnotatedElement}。 - */ - AnnotatedElement annotatedElement(); - - /** - * 获取校验的约束注解的 {@link Annotation}。 - * - * @return 表示校验的约束注解的 {@link Annotation}。 - */ - Annotation annotation(); - - /** - * {@link ValidatorKey} 的构建器。 - */ - interface Builder { - /** - * 向构建器设置校验元素的唯一标识。 - * - * @param annotatedElement 表示设置的校验元素的唯一标识的 {@link String}。 - * @return 表示构建器的 {@link Builder}。 - */ - Builder annotatedElement(AnnotatedElement annotatedElement); - - /** - * 向构建器设置校验的约束注解。 - * - * @param annotation 表示设置的校验约束注解的 {@link Annotation}。 - * @return 表示构建器的 {@link Builder}。 - */ - Builder annotation(Annotation annotation); - - /** - * 构建对象。 - * - * @return 表示构建出来的对象的 {@link }。 - */ - ValidatorKey build(); - } - - /** - * 获取 {@link ValidatorKey} 的构建器。 - * - * @return 表示 {@link ValidatorKey} 的构建器的 {@link Builder}。 - */ - static Builder builder() { - return builder(null); - } - - /** - * 获取 {@link ValidatorKey} 的构建器,同时将指定对象的值进行填充。 - * - * @param value 表示指定对象的 {@link ValidatorKey}。 - * @return 表示 {@link ValidatorKey} 的构建器的 {@link Builder}。 - */ - static Builder builder(ValidatorKey value) { - return BuilderFactory.get(ValidatorKey.class, Builder.class).create(value); - } - } -} \ No newline at end of file diff --git a/framework/fit/java/fit-extension/fit-validation/src/main/java/modelengine/fitframework/validation/ValidationMetadata.java b/framework/fit/java/fit-extension/fit-validation/src/main/java/modelengine/fitframework/validation/ValidationMetadata.java deleted file mode 100644 index 19968db70..000000000 --- a/framework/fit/java/fit-extension/fit-validation/src/main/java/modelengine/fitframework/validation/ValidationMetadata.java +++ /dev/null @@ -1,95 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -package modelengine.fitframework.validation; - -import modelengine.fitframework.validation.domain.ValidationField; -import modelengine.fitframework.validation.domain.ValidationParameter; - -import java.lang.annotation.Annotation; -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.lang.reflect.Parameter; - -/** - * 表示校验的元数据。 - * - * @author 白鹏坤 - * @author 邬涨财 - * @since 2023-04-23 - */ -public interface ValidationMetadata { - /** - * 获取分组信息。 - * - * @return 表示分组信息集合的 {@link Class}{@code []}。 - */ - Class[] groups(); - - /** - * 获取校验元素的属性值。 - * - * @return 表示校验元素的属性值的 {@link Object}。 - */ - Object value(); - - /** - * 获取校验的方法。 - * - * @return 表示校验方法的 {@link Method}。 - */ - Method getValidationMethod(); - - /** - * 获取校验的元素。 - * - * @return 表示校验的元素 {@link AnnotatedElement}。 - */ - AnnotatedElement element(); - - /** - * 获取校验元素的属性名。 - * - * @return 表示校验的元素的属性名的 {@link String}。 - */ - String name(); - - /** - * 获取校验元素的所有注解。 - * - * @return 表示校验元素的所有注解的 {@link Annotation}{@code []}。 - */ - Annotation[] annotations(); - - /** - * 创建一个 {@link ValidationMetadata} 对象,表示需要校验的字段的元数据。 - * - * @param field 表示需要校验的字段的 {@link Field}。 - * @param groups 表示需要校验的分组的 {@link Class}{@code []}。 - * @param value 表示字段值的 {@link Object}。 - * @param validationMethod 表示校验的方法的 {@link Method}. - * @return 表示创建后的校验元数据的 {@link ValidationMetadata}。 - */ - static ValidationMetadata createValidationField(Field field, Class[] groups, Object value, - Method validationMethod) { - return new ValidationField(field, groups, value, validationMethod); - } - - /** - * 创建一个 {@link ValidationMetadata} 对象,表示需要校验的参数的元数据。 - * - * @param parameter 表示需要校验的参数的 {@link Parameter}。 - * @param groups 表示需要校验的分组的 {@link Class}{@code []}。 - * @param value 表示参数值的 {@link Object}。 - * @param validationMethod 表示校验的方法的 {@link Method}. - * @return 表示创建后的校验元数据的 {@link ValidationMetadata}。 - */ - static ValidationMetadata createValidationParameter(Parameter parameter, Class[] groups, Object value, - Method validationMethod) { - return new ValidationParameter(parameter, groups, value, validationMethod); - } -} diff --git a/framework/fit/java/fit-extension/fit-validation/src/main/java/modelengine/fitframework/validation/domain/AbstractValidationMetadata.java b/framework/fit/java/fit-extension/fit-validation/src/main/java/modelengine/fitframework/validation/domain/AbstractValidationMetadata.java deleted file mode 100644 index bc9647278..000000000 --- a/framework/fit/java/fit-extension/fit-validation/src/main/java/modelengine/fitframework/validation/domain/AbstractValidationMetadata.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2024-2025 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - */ - -package modelengine.fitframework.validation.domain; - -import modelengine.fitframework.inspection.Validation; -import modelengine.fitframework.validation.ValidationMetadata; - -import java.lang.reflect.Method; - -/** - * 表示 {@link ValidationMetadata} 的抽象实现。 - * - * @author 白鹏坤 - * @author 邬涨财 - * @since 2023-04-23 - */ -public abstract class AbstractValidationMetadata implements ValidationMetadata { - private final Class[] groups; - private final Object value; - private final Method validationMethod; - - /** - * 使用指定的分组、值和验证方法初始化 {@link AbstractValidationMetadata} 的新实例。 - * - * @param groups 表示分组的 {@link Class}{@code []}。 - * @param value 表示值的 {@link Object}。 - * @param validationMethod 表示验证方法的 {@link Method}。 - * @throws IllegalArgumentException 当 {@code groups} 或 {@code validationMethod} 为 {@code null} 时。 - */ - public AbstractValidationMetadata(Class[] groups, Object value, Method validationMethod) { - this.groups = Validation.notNull(groups, "The groups cannot be null when construct validation metadata."); - this.value = value; - this.validationMethod = Validation.notNull(validationMethod, - "The validation method cannot be null when construct validation metadata."); - } - - @Override - public Class[] groups() { - return this.groups; - } - - @Override - public Object value() { - return this.value; - } - - @Override - public Method getValidationMethod() { - return this.validationMethod; - } -} diff --git a/framework/fit/java/fit-extension/fit-validation/src/main/java/modelengine/fitframework/validation/domain/ValidationField.java b/framework/fit/java/fit-extension/fit-validation/src/main/java/modelengine/fitframework/validation/domain/ValidationField.java deleted file mode 100644 index 4dbf933f7..000000000 --- a/framework/fit/java/fit-extension/fit-validation/src/main/java/modelengine/fitframework/validation/domain/ValidationField.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2024-2025 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - */ - -package modelengine.fitframework.validation.domain; - -import modelengine.fitframework.inspection.Validation; - -import java.lang.annotation.Annotation; -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Field; -import java.lang.reflect.Method; - -/** - * 校验类型为字段的元数据类。 - * - * @author 邬涨财 - * @since 2023-05-18 - */ -public class ValidationField extends AbstractValidationMetadata { - private final Field field; - - /** - * 使用指定的字段、分组、值和验证方法初始化 {@link ValidationField} 的新实例。 - * - * @param field 表示字段的 {@link Field}。 - * @param groups 表示分组的 {@link Class}{@code []}。 - * @param value 表示值的 {@link Object}。 - * @param validationMethod 表示验证方法的 {@link Method}。 - * @throws IllegalArgumentException 当 {@code field}、{@code groups} 或 {@code validationMethod} 为 {@code null} 时。 - */ - public ValidationField(Field field, Class[] groups, Object value, Method validationMethod) { - super(groups, value, validationMethod); - this.field = Validation.notNull(field, "The field cannot be null when construct validation filed."); - } - - @Override - public AnnotatedElement element() { - return this.field; - } - - @Override - public String name() { - return this.field.getName(); - } - - @Override - public Annotation[] annotations() { - return this.field.getAnnotations(); - } -} diff --git a/framework/fit/java/fit-extension/fit-validation/src/main/java/modelengine/fitframework/validation/domain/ValidationParameter.java b/framework/fit/java/fit-extension/fit-validation/src/main/java/modelengine/fitframework/validation/domain/ValidationParameter.java deleted file mode 100644 index 6982e06d7..000000000 --- a/framework/fit/java/fit-extension/fit-validation/src/main/java/modelengine/fitframework/validation/domain/ValidationParameter.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2024-2025 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - */ - -package modelengine.fitframework.validation.domain; - -import modelengine.fitframework.inspection.Validation; - -import java.lang.annotation.Annotation; -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Method; -import java.lang.reflect.Parameter; - -/** - * 校验类型为参数的元数据类。 - * - * @author 邬涨财 - * @since 2023-05-19 - */ -public class ValidationParameter extends AbstractValidationMetadata { - private final Parameter parameter; - - /** - * 使用指定的参数、分组、值和验证方法初始化 {@link ValidationParameter} 的新实例。 - * - * @param parameter 表示参数的 {@link Parameter}。 - * @param groups 表示分组的 {@link Class}{@code []}。 - * @param value 表示值的 {@link Object}。 - * @param validationMethod 表示校验方法的 {@link Method}。 - * @throws IllegalArgumentException 当 {@code parameter}、{@code groups} 或 {@code validationMethod} 为 {@code null} 时。 - */ - public ValidationParameter(Parameter parameter, Class[] groups, Object value, Method validationMethod) { - super(groups, value, validationMethod); - this.parameter = - Validation.notNull(parameter, "The parameter cannot be null when construct validation parameter."); - } - - @Override - public AnnotatedElement element() { - return this.parameter; - } - - @Override - public String name() { - return this.parameter.getName(); - } - - @Override - public Annotation[] annotations() { - return this.parameter.getAnnotations(); - } -} diff --git a/framework/fit/java/fit-extension/fit-validation/src/main/java/modelengine/fitframework/validation/exception/ConstraintViolationException.java b/framework/fit/java/fit-extension/fit-validation/src/main/java/modelengine/fitframework/validation/exception/ConstraintViolationException.java deleted file mode 100644 index 0c1b19f48..000000000 --- a/framework/fit/java/fit-extension/fit-validation/src/main/java/modelengine/fitframework/validation/exception/ConstraintViolationException.java +++ /dev/null @@ -1,67 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -package modelengine.fitframework.validation.exception; - -import static modelengine.fitframework.util.ObjectUtils.nullIf; - -import modelengine.fitframework.util.CollectionUtils; -import modelengine.fitframework.util.StringUtils; -import modelengine.fitframework.validation.ConstraintViolation; - -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; - -/** - * 表示校验失败的异常。 - * - * @author 邬涨财 - * @since 2023-05-18 - */ -public class ConstraintViolationException extends RuntimeException { - private final List violations; - - /** - * 表示创建一个 {@link ConstraintViolationException} 的新实例。 - * - * @param message 表示异常消息的 {@link String}。 - * @param violations 表示约束校验失败的数据类列表的 {@link List}{@code <}{@link ConstraintViolation}{@code >}。 - */ - public ConstraintViolationException(String message, List violations) { - super(message); - this.violations = nullIf(violations, Collections.emptyList()); - } - - /** - * 表示创建一个 {@link ConstraintViolationException} 的新实例。 - * - * @param violations 表示约束校验失败的数据类列表的 {@link List}{@code <}{@link ConstraintViolation}{@code >}。 - */ - public ConstraintViolationException(List violations) { - this(buildMessage(violations), violations); - } - - private static String buildMessage(List violations) { - if (CollectionUtils.isEmpty(violations)) { - return StringUtils.EMPTY; - } - return violations.stream() - .filter(Objects::nonNull) - .map(ConstraintViolation::message) - .collect(Collectors.joining(", ")); - } - - /** - * 获取约束校验失败的数据类列表信息。 - * - * @return 表示约束校验失败的数据类列表信息的 {@link List}{@code <}{@link ConstraintViolation}{@code >}。 - */ - public List getViolations() { - return this.violations; - } -} \ No newline at end of file diff --git a/framework/fit/java/fit-extension/fit-validation/src/main/java/modelengine/fitframework/validation/group/DefaultGroup.java b/framework/fit/java/fit-extension/fit-validation/src/main/java/modelengine/fitframework/validation/group/DefaultGroup.java deleted file mode 100644 index 4bfe62c9c..000000000 --- a/framework/fit/java/fit-extension/fit-validation/src/main/java/modelengine/fitframework/validation/group/DefaultGroup.java +++ /dev/null @@ -1,15 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -package modelengine.fitframework.validation.group; - -/** - * 默认分组。group 为空时,使用默认分组。 - * - * @author 白鹏坤 - * @since 2023-04-23 - */ -public interface DefaultGroup {} diff --git a/framework/fit/java/fit-extension/fit-validation/src/test/java/modelengine/fitframework/validation/ValidationDataControllerTest.java b/framework/fit/java/fit-extension/fit-validation/src/test/java/modelengine/fitframework/validation/ValidationDataControllerTest.java deleted file mode 100644 index 8d70066e7..000000000 --- a/framework/fit/java/fit-extension/fit-validation/src/test/java/modelengine/fitframework/validation/ValidationDataControllerTest.java +++ /dev/null @@ -1,141 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024-2025 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -package modelengine.fitframework.validation; - -import static org.assertj.core.api.Assertions.assertThat; - -import modelengine.fit.http.client.HttpClassicClientResponse; -import modelengine.fitframework.annotation.Fit; -import modelengine.fitframework.test.annotation.MvcTest; -import modelengine.fitframework.test.domain.mvc.MockMvc; -import modelengine.fitframework.test.domain.mvc.request.MockMvcRequestBuilders; -import modelengine.fitframework.test.domain.mvc.request.MockRequestBuilder; -import modelengine.fitframework.validation.data.Car; -import modelengine.fitframework.validation.data.Product; -import modelengine.fitframework.validation.data.ValidationDataController; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import java.io.IOException; -import java.util.Arrays; -import java.util.Collections; - -/** - * {@link ValidationDataController} 的测试集。 - * - * @author 吕博文 - * @since 2024-08-15 - */ -@MvcTest(classes = {ValidationDataController.class}) -@DisplayName("测试 EvalDataController") -public class ValidationDataControllerTest { - @Fit - private MockMvc mockMvc; - - private HttpClassicClientResponse response; - - @AfterEach - void teardown() throws IOException { - if (this.response != null) { - this.response.close(); - } - } - - @Test - @DisplayName("合法 Car 对象校验") - void shouldOKWhenCreateValidCar() { - Car validCar = new Car(1, 1, "brand", "model", -1, -1); - MockRequestBuilder requestBuilder = - MockMvcRequestBuilders.post("/validation/car/default").jsonEntity(validCar).responseType(Void.class); - this.response = this.mockMvc.perform(requestBuilder); - assertThat(this.response.statusCode()).isEqualTo(200); - } - - @Test - @DisplayName("不合法 Car 对象校验") - void shouldFailedWhenCreateInvalidCar() { - Car invalidCar = new Car(0, 3, "", "abd", -1, -1); - MockRequestBuilder requestBuilder = - MockMvcRequestBuilders.post("/validation/car/default").jsonEntity(invalidCar).responseType(Void.class); - this.response = this.mockMvc.perform(requestBuilder); - assertThat(this.response.statusCode()).isEqualTo(500); - } - - @Test - @DisplayName("自定义分组校验 Car 对象") - void shouldOKWhenCreateValidCarWithCarGroup() { - Car invalidCar = new Car(0, 2, "", "abd", 2000, -1); - MockRequestBuilder requestBuilder = - MockMvcRequestBuilders.post("/validation/car/carGroup").jsonEntity(invalidCar).responseType(Void.class); - this.response = this.mockMvc.perform(requestBuilder); - assertThat(this.response.statusCode()).isEqualTo(200); - } - - @Test - @DisplayName("自定义分组校验 Car 对象") - void shouldFailedWhenCreateInvalidCarWithCarGroup() { - Car invalidCar = new Car(0, 3, "", "abd", -1, -1); - MockRequestBuilder requestBuilder = - MockMvcRequestBuilders.post("/validation/car/carGroup").jsonEntity(invalidCar).responseType(Void.class); - this.response = this.mockMvc.perform(requestBuilder); - assertThat(this.response.statusCode()).isEqualTo(500); - } - - @Test - @DisplayName("合法 Product 对象校验") - void shouldOKWhenCreateValidProduct() { - Product product = new Product("mac", 10499.0, 100, "computer"); - MockRequestBuilder requestBuilder = - MockMvcRequestBuilders.post("/validation/product/default").jsonEntity(product).responseType(Void.class); - this.response = this.mockMvc.perform(requestBuilder); - assertThat(this.response.statusCode()).isEqualTo(200); - } - - @Test - @DisplayName("不合法 Product 对象校验") - void shouldFailedWhenCreateInvalidProduct() { - Product product = new Product("", 10499.0, -1, "computer"); - MockRequestBuilder requestBuilder = - MockMvcRequestBuilders.post("/validation/product/default").jsonEntity(product).responseType(Void.class); - this.response = this.mockMvc.perform(requestBuilder); - assertThat(this.response.statusCode()).isEqualTo(500); - } - - @Test - @DisplayName("合法 Product-List 对象校验: cars == null") - void shouldOkWhenCreateInvalidProductWithNoCar() { - Product product = new Product("mac", 10499.0, 100, "computer", null); - MockRequestBuilder requestBuilder = - MockMvcRequestBuilders.post("/validation/product/default").jsonEntity(product).responseType(Void.class); - this.response = this.mockMvc.perform(requestBuilder); - assertThat(this.response.statusCode()).isEqualTo(200); - } - - @Test - @DisplayName("合法 Product-List 对象校验: cars.size() == 1") - void shouldOkWhenCreateInvalidProductWith1Car() { - Car validCar = new Car(1, 1, "brand", "model", 2000, 1999); - Product product = new Product("mac", 10499.0, 100, "computer", Collections.singletonList(validCar)); - MockRequestBuilder requestBuilder = - MockMvcRequestBuilders.post("/validation/product/default").jsonEntity(product).responseType(Void.class); - this.response = this.mockMvc.perform(requestBuilder); - assertThat(this.response.statusCode()).isEqualTo(200); - } - - @Test - @DisplayName("不合法 Product-List 对象校验: cars.size() == 2") - void shouldFailedWhenCreateInvalidProductWith2Cars() { - Car validCar = new Car(1, 1, "brand", "model", 2000, 1999); - Product product = new Product("mac", 10499.0, 100, "computer", Arrays.asList(validCar, validCar)); - MockRequestBuilder requestBuilder = - MockMvcRequestBuilders.post("/validation/product/default").jsonEntity(product).responseType(Void.class); - this.response = this.mockMvc.perform(requestBuilder); - assertThat(this.response.statusCode()).isEqualTo(500); - } -} \ No newline at end of file diff --git a/framework/fit/java/fit-extension/fit-validation/src/test/java/modelengine/fitframework/validation/ValidationHandlerTest.java b/framework/fit/java/fit-extension/fit-validation/src/test/java/modelengine/fitframework/validation/ValidationHandlerTest.java deleted file mode 100644 index 130e05803..000000000 --- a/framework/fit/java/fit-extension/fit-validation/src/test/java/modelengine/fitframework/validation/ValidationHandlerTest.java +++ /dev/null @@ -1,489 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -package modelengine.fitframework.validation; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.catchThrowableOfType; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import modelengine.fitframework.aop.JoinPoint; -import modelengine.fitframework.ioc.BeanContainer; -import modelengine.fitframework.ioc.annotation.AnnotationMetadataResolver; -import modelengine.fitframework.ioc.annotation.support.DefaultAnnotationMetadataResolver; -import modelengine.fitframework.runtime.FitRuntime; -import modelengine.fitframework.util.ObjectUtils; -import modelengine.fitframework.util.ReflectionUtils; -import modelengine.fitframework.validation.data.Car; -import modelengine.fitframework.validation.data.CarValidate; -import modelengine.fitframework.validation.data.Company; -import modelengine.fitframework.validation.data.NestedValidate; -import modelengine.fitframework.validation.data.Product; -import modelengine.fitframework.validation.data.ProductValidate; -import modelengine.fitframework.validation.data.StudentValidate; -import modelengine.fitframework.validation.exception.ConstraintViolationException; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.mockito.Mockito; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -/** - * {@link ValidationHandler} 的单元测试。 - * - * @author 邬涨财 - * @since 2023-05-23 - */ -public class ValidationHandlerTest { - private final BeanContainer beanContainer = mock(BeanContainer.class); - private final FitRuntime fitRuntime = mock(FitRuntime.class); - private final AnnotationMetadataResolver annotationMetadataResolver = new DefaultAnnotationMetadataResolver(); - private final Validated validated = Mockito.mock(Validated.class); - private final ValidationHandler handler = new ValidationHandler(beanContainer); - - @Nested - @DisplayName("使用默认的分组进行校验") - class UseDefaultGroupTest { - @BeforeEach - void setUp() { - when(validated.value()).thenReturn(new Class[0]); - when(fitRuntime.resolverOfAnnotations()).thenReturn(annotationMetadataResolver); - when(beanContainer.runtime()).thenReturn(fitRuntime); - } - - /** - * 调用 {@link CarValidate#validate1(Car)} 作为需要校验的方法。 - */ - @Test - @DisplayName("校验数据类,该数据类的 Constraint 字段会被校验,其余字段不会被校验") - public void givenFieldsWithConstraintAnnotationThenValidateHappened() { - // when - Method validateMethod = ReflectionUtils.getDeclaredMethod(CarValidate.class, "validate1", Car.class); - Method handleValidatedMethod = ReflectionUtils.getDeclaredMethod(ValidationHandler.class, - "handle", - JoinPoint.class, - Validated.class); - Car car = new Car(10, 10, "", "", -1, -1); - handleValidatedMethod.setAccessible(true); - JoinPoint joinPoint = mock(JoinPoint.class); - when(joinPoint.getMethod()).thenReturn(validateMethod); - when(joinPoint.getArgs()).thenReturn(new Object[] {car}); - InvocationTargetException invocationTargetException = catchThrowableOfType(InvocationTargetException.class, - () -> handleValidatedMethod.invoke(handler, joinPoint, validated)); - - // then - ConstraintViolationException expectedException = - ObjectUtils.cast(invocationTargetException.getTargetException()); - assertThat(expectedException.getMessage()).isEqualTo("座位数量范围只能在0和6!, 发动机数量范围只能在0和2!, 品牌不能为空!, 型号不能为空!"); - } - - /** - * 调用 {@link ProductValidate#validate1(Product)} 作为需要校验的方法。 - */ - @Test - @DisplayName("校验数据类,该数据类的 Constraint 字段会被校验,其余字段不会被校验") - void givenFieldsWithConstraintAnnotationThenValidateHappened2() { - // when - Method validateMethod = - ReflectionUtils.getDeclaredMethod(ProductValidate.class, "validate1", Product.class); - Method handleValidatedMethod = ReflectionUtils.getDeclaredMethod(ValidationHandler.class, - "handle", - JoinPoint.class, - Validated.class); - Product product = new Product("computer", -1.0, 100, " "); - handleValidatedMethod.setAccessible(true); - JoinPoint joinPoint = mock(JoinPoint.class); - when(joinPoint.getMethod()).thenReturn(validateMethod); - when(joinPoint.getArgs()).thenReturn(new Object[] {product}); - InvocationTargetException invocationTargetException = catchThrowableOfType(InvocationTargetException.class, - () -> handleValidatedMethod.invoke(handler, joinPoint, validated)); - - // then - ConstraintViolationException expectedException = - ObjectUtils.cast(invocationTargetException.getTargetException()); - assertThat(expectedException.getMessage()).isEqualTo("产品价格必须为正, 产品类别不能为空"); - } - - /** - * 调用 {@link ProductValidate#validate1(Product)} 作为需要校验的方法。 - */ - @Test - @DisplayName("校验数据类,该数据类的 Constraint 字段会被校验,其余字段不会被校验") - void givenFieldsWithConstraintAnnotationThenValidateHappened3() { - // when - Method validateMethod = - ReflectionUtils.getDeclaredMethod(ProductValidate.class, "validate1", Product.class); - Method handleValidatedMethod = ReflectionUtils.getDeclaredMethod(ValidationHandler.class, - "handle", - JoinPoint.class, - Validated.class); - Product product = new Product(null, 12999.0, null, "electronic devices"); - handleValidatedMethod.setAccessible(true); - JoinPoint joinPoint = mock(JoinPoint.class); - when(joinPoint.getMethod()).thenReturn(validateMethod); - when(joinPoint.getArgs()).thenReturn(new Object[] {product}); - InvocationTargetException invocationTargetException = catchThrowableOfType(InvocationTargetException.class, - () -> handleValidatedMethod.invoke(handler, joinPoint, validated)); - - // then - ConstraintViolationException expectedException = - ObjectUtils.cast(invocationTargetException.getTargetException()); - assertThat(expectedException.getMessage()).isEqualTo("产品名不能为空, 产品数量必须为正"); - } - - /** - * 调用 {@link CarValidate#validate2(int, int)} 作为需要校验的方法。 - */ - @Test - @DisplayName("校验方法参数,该方法的 Constraint 参数会被校验,其余参数不会被校验") - public void givenParametersWithConstraintAnnotationThenValidateHappened() { - // when - Method validateMethod = - ReflectionUtils.getDeclaredMethod(CarValidate.class, "validate2", int.class, int.class); - Method handleValidatedMethod = ReflectionUtils.getDeclaredMethod(ValidationHandler.class, - "handle", - JoinPoint.class, - Validated.class); - handleValidatedMethod.setAccessible(true); - JoinPoint joinPoint = mock(JoinPoint.class); - when(joinPoint.getMethod()).thenReturn(validateMethod); - when(joinPoint.getArgs()).thenReturn(new Object[] {-1, -1}); - InvocationTargetException invocationTargetException = catchThrowableOfType(InvocationTargetException.class, - () -> handleValidatedMethod.invoke(handler, joinPoint, validated)); - - // then - ConstraintViolationException expectedException = - ObjectUtils.cast(invocationTargetException.getTargetException()); - assertThat(expectedException.getMessage()).isEqualTo("座位数量范围只能在0和6!"); - } - } - - @Nested - @DisplayName("使用自定义的分组进行校验") - class UseCustomGroupTest { - @BeforeEach - void setUp() { - when(validated.value()).thenReturn(new Class[0]); - when(fitRuntime.resolverOfAnnotations()).thenReturn(annotationMetadataResolver); - when(beanContainer.runtime()).thenReturn(fitRuntime); - } - - /** - * 调用 {@link CarValidate#validate3(Car)} 作为需要校验的方法。 - */ - @Test - @DisplayName("校验数据类,只会校验与数据类的分组一致的字段分组。") - public void givenFieldsThenSameGroupValidateHappened() { - // when - Method validateMethod = ReflectionUtils.getDeclaredMethod(CarValidate.class, "validate3", Car.class); - Method handleValidatedMethod = ReflectionUtils.getDeclaredMethod(ValidationHandler.class, - "handle", - JoinPoint.class, - Validated.class); - Car car = new Car(10, 10, "", "", -1, -1); - handleValidatedMethod.setAccessible(true); - JoinPoint joinPoint = mock(JoinPoint.class); - when(joinPoint.getMethod()).thenReturn(validateMethod); - when(joinPoint.getArgs()).thenReturn(new Object[] {car}); - InvocationTargetException invocationTargetException = catchThrowableOfType(InvocationTargetException.class, - () -> handleValidatedMethod.invoke(handler, joinPoint, validated)); - - // then - ConstraintViolationException expectedException = - ObjectUtils.cast(invocationTargetException.getTargetException()); - assertThat(expectedException.getMessage()).isEqualTo("生产年份在2000-2030之内"); - } - - /** - * 调用 {@link StudentValidate#validateStudent(int)} 作为需要校验的方法。 - */ - @Test - @DisplayName("校验方法参数,只会校验与数据类的分组一致的方法参数。") - public void givenParametersThenSameGroupValidateHappened() { - // when - Method validateMethod = - ReflectionUtils.getDeclaredMethod(StudentValidate.class, "validateStudent", int.class); - Method handleValidatedMethod = ReflectionUtils.getDeclaredMethod(ValidationHandler.class, - "handle", - JoinPoint.class, - Validated.class); - handleValidatedMethod.setAccessible(true); - JoinPoint joinPoint = mock(JoinPoint.class); - when(joinPoint.getMethod()).thenReturn(validateMethod); - when(joinPoint.getArgs()).thenReturn(new Object[] {-1}); - InvocationTargetException invocationTargetException = catchThrowableOfType(InvocationTargetException.class, - () -> handleValidatedMethod.invoke(handler, joinPoint, validated)); - - // then - ConstraintViolationException expectedException = - ObjectUtils.cast(invocationTargetException.getTargetException()); - assertThat(expectedException.getMessage()).isEqualTo("范围要在7~20之内"); - } - - /** - * 调用 {@link StudentValidate#validateTeacher(int)} 作为需要校验的方法。 - */ - @Test - @DisplayName("校验方法参数,与数据类的分组不一致的方法参数不会校验。") - public void givenParametersThenDifferentGroupValidateNotHappened() { - // when - Method validateMethod = - ReflectionUtils.getDeclaredMethod(StudentValidate.class, "validateTeacher", int.class); - Method handleValidatedMethod = ReflectionUtils.getDeclaredMethod(ValidationHandler.class, - "handle", - JoinPoint.class, - Validated.class); - handleValidatedMethod.setAccessible(true); - JoinPoint joinPoint = mock(JoinPoint.class); - when(joinPoint.getMethod()).thenReturn(validateMethod); - when(joinPoint.getArgs()).thenReturn(new Object[] {-1}); - InvocationTargetException invocationTargetException = catchThrowableOfType(InvocationTargetException.class, - () -> handleValidatedMethod.invoke(handler, joinPoint, validated)); - - // then - assertThat(invocationTargetException == null).isTrue(); - } - } - - @Nested - @DisplayName("测试嵌套校验") - class NestedTest { - private static final String INVALID_CAR1_MSG = "座位数量范围只能在0和6!, 品牌不能为空!"; - private static final String INVALID_CAR2_MSG = "型号不能为空!"; - private static final String INVALID_PRODUCT1_MSG = "产品名不能为空, 产品数量必须为正"; - private static final String INVALID_PRODUCT2_MSG = "产品类别不能为空"; - - private final Car validCar = new Car(5, 1, "brand", "model", 2024, 1999); - private final Car invalidCar1 = new Car(-1, 1, "", "model", 2024, 1999); - private final Car invalidCar2 = new Car(5, 1, "brand", "", 2024, 1999); - private final Product validProduct = new Product("name", 1.0, 1, "category"); - private final Product invalidProduct1 = new Product("", 1.0, -1, "category"); - private final Product invalidProduct2 = new Product("name", 1.0, 1, ""); - private final Method handleValidatedMethod = - ReflectionUtils.getDeclaredMethod(ValidationHandler.class, "handle", JoinPoint.class, Validated.class); - private final JoinPoint joinPoint = mock(JoinPoint.class); - - @BeforeEach - void setUp() { - when(validated.value()).thenReturn(new Class[0]); - when(fitRuntime.resolverOfAnnotations()).thenReturn(annotationMetadataResolver); - when(beanContainer.runtime()).thenReturn(fitRuntime); - handleValidatedMethod.setAccessible(true); - } - - @Test - @DisplayName("测试嵌套校验类 Company") - void shouldReturnMsgWhenValidateCompany() { - Method validateMethod = ReflectionUtils.getDeclaredMethod(NestedValidate.class, "test1", Company.class); - when(this.joinPoint.getMethod()).thenReturn(validateMethod); - Company company = - new Company(-1, 100, this.invalidProduct2, Arrays.asList(this.invalidCar1, this.invalidCar2)); - when(this.joinPoint.getArgs()).thenReturn(new Object[] {company}); - InvocationTargetException invocationTargetException = catchThrowableOfType(InvocationTargetException.class, - () -> handleValidatedMethod.invoke(handler, this.joinPoint, validated)); - - // then - ConstraintViolationException expectedException = - ObjectUtils.cast(invocationTargetException.getTargetException()); - List msgList = - Arrays.asList("经理只能有0-1个!", INVALID_PRODUCT2_MSG, INVALID_CAR1_MSG, INVALID_CAR2_MSG); - assertThat(expectedException.getMessage()).isEqualTo(String.join(", ", msgList)); - } - - @Test - @DisplayName("测试 @Validated List") - void shouldReturnMsgWhenValidateList() { - Method validateMethod = ReflectionUtils.getDeclaredMethod(NestedValidate.class, "test2", List.class); - when(this.joinPoint.getMethod()).thenReturn(validateMethod); - when(this.joinPoint.getArgs()).thenReturn(new Object[] { - Arrays.asList(this.validCar, this.invalidCar1, this.invalidCar2) - }); - InvocationTargetException invocationTargetException = catchThrowableOfType(InvocationTargetException.class, - () -> handleValidatedMethod.invoke(handler, this.joinPoint, validated)); - - // then - ConstraintViolationException expectedException = - ObjectUtils.cast(invocationTargetException.getTargetException()); - List msgList = Arrays.asList(INVALID_CAR1_MSG, INVALID_CAR2_MSG); - assertThat(expectedException.getMessage()).isEqualTo(String.join(", ", msgList)); - } - - @Test - @DisplayName("测试 @Validated List>") - void shouldReturnMsgWhenValidateListInList() { - Method validateMethod = ReflectionUtils.getDeclaredMethod(NestedValidate.class, "test3", List.class); - when(this.joinPoint.getMethod()).thenReturn(validateMethod); - List personList1 = Arrays.asList(this.validCar, this.invalidCar1, this.invalidCar2); - List personList2 = Arrays.asList(this.validCar, this.invalidCar1); - - when(this.joinPoint.getArgs()).thenReturn(new Object[] {Arrays.asList(personList1, personList2)}); - InvocationTargetException invocationTargetException = catchThrowableOfType(InvocationTargetException.class, - () -> handleValidatedMethod.invoke(handler, this.joinPoint, validated)); - - // then - ConstraintViolationException expectedException = - ObjectUtils.cast(invocationTargetException.getTargetException()); - List msgList = Arrays.asList(INVALID_CAR1_MSG, INVALID_CAR2_MSG, INVALID_CAR1_MSG); - assertThat(expectedException.getMessage()).isEqualTo(String.join(", ", msgList)); - } - - @Test - @DisplayName("测试 @Validated Map") - void shouldReturnMsgWhenValidateMapSimple() { - Method validateMethod = ReflectionUtils.getDeclaredMethod(NestedValidate.class, "test4", Map.class); - when(this.joinPoint.getMethod()).thenReturn(validateMethod); - LinkedHashMap map = new LinkedHashMap<>(); - map.put("1", this.validCar); - map.put("2", this.invalidCar1); - map.put("3", this.invalidCar2); - - when(this.joinPoint.getArgs()).thenReturn(new Object[] {map}); - InvocationTargetException invocationTargetException = catchThrowableOfType(InvocationTargetException.class, - () -> handleValidatedMethod.invoke(handler, this.joinPoint, validated)); - - // then - ConstraintViolationException expectedException = - ObjectUtils.cast(invocationTargetException.getTargetException()); - List msgList = Arrays.asList(INVALID_CAR1_MSG, INVALID_CAR2_MSG); - assertThat(expectedException.getMessage()).isEqualTo(String.join(", ", msgList)); - } - - @Test - @DisplayName("测试 @Validated Map") - void shouldReturnMsgWhenValidateMapObj() { - Method validateMethod = ReflectionUtils.getDeclaredMethod(NestedValidate.class, "test5", Map.class); - when(this.joinPoint.getMethod()).thenReturn(validateMethod); - LinkedHashMap map = new LinkedHashMap<>(); - map.put(this.validCar, this.validProduct); - map.put(this.invalidCar1, this.invalidProduct1); - map.put(this.invalidCar2, this.invalidProduct2); - - when(this.joinPoint.getArgs()).thenReturn(new Object[] {map}); - InvocationTargetException invocationTargetException = catchThrowableOfType(InvocationTargetException.class, - () -> handleValidatedMethod.invoke(handler, this.joinPoint, validated)); - - // then - ConstraintViolationException expectedException = - ObjectUtils.cast(invocationTargetException.getTargetException()); - List msgList = - Arrays.asList(INVALID_CAR1_MSG, INVALID_PRODUCT1_MSG, INVALID_CAR2_MSG, INVALID_PRODUCT2_MSG); - assertThat(expectedException.getMessage()).isEqualTo(String.join(", ", msgList)); - } - - @Test - @DisplayName("测试 @Validated Map>") - void shouldReturnMsgWhenValidateMapInMap() { - Method validateMethod = ReflectionUtils.getDeclaredMethod(NestedValidate.class, "test6", Map.class); - - when(this.joinPoint.getMethod()).thenReturn(validateMethod); - LinkedHashMap map1 = new LinkedHashMap<>(); - map1.put(this.validCar, this.invalidProduct1); - map1.put(this.invalidCar1, this.validProduct); - map1.put(this.invalidCar2, this.invalidProduct2); - LinkedHashMap map2 = new LinkedHashMap<>(); - map2.put(this.invalidCar1, this.invalidProduct1); - map2.put(this.validCar, this.validProduct); - LinkedHashMap> nestMap = new LinkedHashMap<>(); - nestMap.put(this.validCar, map1); - nestMap.put(this.invalidCar1, map2); - - when(this.joinPoint.getArgs()).thenReturn(new Object[] {nestMap}); - InvocationTargetException invocationTargetException = catchThrowableOfType(InvocationTargetException.class, - () -> handleValidatedMethod.invoke(handler, this.joinPoint, validated)); - - // then - ConstraintViolationException expectedException = - ObjectUtils.cast(invocationTargetException.getTargetException()); - List msgList = Arrays.asList(INVALID_PRODUCT1_MSG, - INVALID_CAR1_MSG, - INVALID_CAR2_MSG, - INVALID_PRODUCT2_MSG, - INVALID_CAR1_MSG, - INVALID_CAR1_MSG, - INVALID_PRODUCT1_MSG); - assertThat(expectedException.getMessage()).isEqualTo(String.join(", ", msgList)); - } - - @Test - @DisplayName("测试 @Validated List>") - void shouldReturnMsgWhenValidateMapInCar() { - Method validateMethod = ReflectionUtils.getDeclaredMethod(NestedValidate.class, "test7", List.class); - when(this.joinPoint.getMethod()).thenReturn(validateMethod); - LinkedHashMap map1 = new LinkedHashMap<>(); - map1.put(this.validCar, this.invalidProduct1); - map1.put(this.invalidCar1, this.validProduct); - map1.put(this.invalidCar2, this.invalidProduct2); - LinkedHashMap map2 = new LinkedHashMap<>(); - map2.put(this.invalidCar1, this.invalidProduct1); - map2.put(this.validCar, this.validProduct); - - when(this.joinPoint.getArgs()).thenReturn(new Object[] {Arrays.asList(map1, map2)}); - InvocationTargetException invocationTargetException = catchThrowableOfType(InvocationTargetException.class, - () -> handleValidatedMethod.invoke(handler, this.joinPoint, validated)); - - // then - ConstraintViolationException expectedException = - ObjectUtils.cast(invocationTargetException.getTargetException()); - List msgList = Arrays.asList(INVALID_PRODUCT1_MSG, - INVALID_CAR1_MSG, - INVALID_CAR2_MSG, - INVALID_PRODUCT2_MSG, - INVALID_CAR1_MSG, - INVALID_PRODUCT1_MSG); - assertThat(expectedException.getMessage()).isEqualTo(String.join(", ", msgList)); - } - - @Test - @DisplayName("测试 @Validated Map>") - void shouldReturnMsgWhenValidateListInMap() { - Method validateMethod = ReflectionUtils.getDeclaredMethod(NestedValidate.class, "test8", Map.class); - when(this.joinPoint.getMethod()).thenReturn(validateMethod); - List productList1 = Arrays.asList(this.validProduct, this.invalidProduct1); - List productList2 = Arrays.asList(this.validProduct, this.invalidProduct2); - LinkedHashMap> map = new LinkedHashMap<>(); - map.put(this.invalidCar1, productList1); - map.put(this.invalidCar2, productList2); - - when(this.joinPoint.getArgs()).thenReturn(new Object[] {map}); - InvocationTargetException invocationTargetException = catchThrowableOfType(InvocationTargetException.class, - () -> handleValidatedMethod.invoke(handler, this.joinPoint, validated)); - - // then - ConstraintViolationException expectedException = - ObjectUtils.cast(invocationTargetException.getTargetException()); - List msgList = - Arrays.asList(INVALID_CAR1_MSG, INVALID_PRODUCT1_MSG, INVALID_CAR2_MSG, INVALID_PRODUCT2_MSG); - assertThat(expectedException.getMessage()).isEqualTo(String.join(", ", msgList)); - } - - @Test - @DisplayName("测试 @Validated List") - void shouldReturnMsgWhenValidateListComplex() { - Method validateMethod = ReflectionUtils.getDeclaredMethod(NestedValidate.class, "test9", List.class); - when(this.joinPoint.getMethod()).thenReturn(validateMethod); - Company company = - new Company(-1, 100, this.validProduct, Arrays.asList(this.invalidCar1, this.invalidCar2)); - when(this.joinPoint.getArgs()).thenReturn(new Object[] {List.of(company)}); - InvocationTargetException invocationTargetException = catchThrowableOfType(InvocationTargetException.class, - () -> handleValidatedMethod.invoke(handler, this.joinPoint, validated)); - - // then - ConstraintViolationException expectedException = - ObjectUtils.cast(invocationTargetException.getTargetException()); - List msgList = Arrays.asList("经理只能有0-1个!", INVALID_CAR1_MSG, INVALID_CAR2_MSG); - assertThat(expectedException.getMessage()).isEqualTo(String.join(", ", msgList)); - } - } -} diff --git a/framework/fit/java/fit-extension/fit-validation/src/test/java/modelengine/fitframework/validation/annotation/MaxSize.java b/framework/fit/java/fit-extension/fit-validation/src/test/java/modelengine/fitframework/validation/annotation/MaxSize.java deleted file mode 100644 index c0d97f0bb..000000000 --- a/framework/fit/java/fit-extension/fit-validation/src/test/java/modelengine/fitframework/validation/annotation/MaxSize.java +++ /dev/null @@ -1,46 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -package modelengine.fitframework.validation.annotation; - -import modelengine.fitframework.validation.Validated; -import modelengine.fitframework.validation.constraints.Constraint; -import modelengine.fitframework.validation.validator.MaxSizeValidator; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * 表示集合元素数量限制的测试注解类。 - * - * @author 李金绪 - * @since 2025-03-17 - */ -@Retention(RetentionPolicy.RUNTIME) -@Constraint({MaxSizeValidator.class}) -@Validated -public @interface MaxSize { - /** - * 表示集合元素的大小的上限值。 - * - * @return 表示集合元素的大小的上限值的 {@code long}。 - */ - long max(); - - /** - * 表示校验失败的信息。 - * - * @return 表示校验失败的信息的 {@link String}。 - */ - String message() default "must be lesser than the max value."; - - /** - * 表示校验的分组。 - * - * @return 表示校验分组的 {@link Class}{@code []}。 - */ - Class[] groups() default {}; -} \ No newline at end of file diff --git a/framework/fit/java/fit-extension/fit-validation/src/test/java/modelengine/fitframework/validation/data/Car.java b/framework/fit/java/fit-extension/fit-validation/src/test/java/modelengine/fitframework/validation/data/Car.java deleted file mode 100644 index 092ed1c7b..000000000 --- a/framework/fit/java/fit-extension/fit-validation/src/test/java/modelengine/fitframework/validation/data/Car.java +++ /dev/null @@ -1,63 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -package modelengine.fitframework.validation.data; - -import modelengine.fitframework.validation.constraints.NotBlank; -import modelengine.fitframework.validation.constraints.NotEmpty; -import modelengine.fitframework.validation.constraints.Range; -import modelengine.fitframework.validation.group.NormalCarGroup; -import modelengine.fitframework.validation.group.OldCarGroup; - -/** - * 表示汽车的数据类。 - * - * @author 李金绪 - * @since 2024-09-04 - */ -public class Car { - @Range(min = 0, max = 6, message = "座位数量范围只能在0和6!") - private int seats; - - @Range(min = 0, max = 2, message = "发动机数量范围只能在0和2!") - private int engines; - - @NotBlank(message = "品牌不能为空!") - private String brand; - - @NotEmpty(message = "型号不能为空!") - private String model; - - @Range(min = 2000, max = 2030, message = "生产年份在2000-2030之内", groups = {NormalCarGroup.class}) - private int normalManufactureYear; - - @Range(min = 1900, max = 1999, message = "生产年份在1900-1999之内", groups = {OldCarGroup.class}) - private int oldManufactureYear; - - /** - * 表示创建一个 {@link Car} 的新实例。 - */ - public Car() {} - - /** - * 表示创建一个 {@link Car} 的新实例。 - * - * @param seats 表示座位数量的 {@code int}。 - * @param engines 表示发动机数量的 {@code int}。 - * @param brand 表示品牌的 {@link String}。 - * @param model 表示型号的 {@link String}。 - * @param normalManufactureYear 表示新型汽车的生产年份的 {@code int}。 - * @param oldManufactureYear 表示老旧汽车的生产年份的 {@code int}。 - */ - public Car(int seats, int engines, String brand, String model, int normalManufactureYear, int oldManufactureYear) { - this.seats = seats; - this.engines = engines; - this.brand = brand; - this.model = model; - this.normalManufactureYear = normalManufactureYear; - this.oldManufactureYear = oldManufactureYear; - } -} diff --git a/framework/fit/java/fit-extension/fit-validation/src/test/java/modelengine/fitframework/validation/data/CarValidate.java b/framework/fit/java/fit-extension/fit-validation/src/test/java/modelengine/fitframework/validation/data/CarValidate.java deleted file mode 100644 index f0b389fe3..000000000 --- a/framework/fit/java/fit-extension/fit-validation/src/test/java/modelengine/fitframework/validation/data/CarValidate.java +++ /dev/null @@ -1,42 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -package modelengine.fitframework.validation.data; - -import modelengine.fitframework.validation.Validated; -import modelengine.fitframework.validation.constraints.Range; -import modelengine.fitframework.validation.group.NormalCarGroup; - -/** - * 表示汽车的校验器。 - * - * @author 李金绪 - * @since 2024-09-04 - */ -@Validated -public class CarValidate { - /** - * Car 类的校验方法一。 - * - * @param car 表示校验的 Car 对象 {@link Car}。 - */ - public void validate1(@Validated Car car) {} - - /** - * Car 类的校验方法二。 - * - * @param seats 表示输入的座位数量 {@link int}。 - * @param engines 表示输入的发动机数量 {@link int}。 - */ - public void validate2(@Range(min = 0, max = 6, message = "座位数量范围只能在0和6!") int seats, int engines) {} - - /** - * Car 类的校验方法三。 - * - * @param car 表示校验的 Car 对象 {@link Car}。 - */ - public void validate3(@Validated(NormalCarGroup.class) Car car) {} -} diff --git a/framework/fit/java/fit-extension/fit-validation/src/test/java/modelengine/fitframework/validation/data/Company.java b/framework/fit/java/fit-extension/fit-validation/src/test/java/modelengine/fitframework/validation/data/Company.java deleted file mode 100644 index 86041ae15..000000000 --- a/framework/fit/java/fit-extension/fit-validation/src/test/java/modelengine/fitframework/validation/data/Company.java +++ /dev/null @@ -1,52 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -package modelengine.fitframework.validation.data; - -import modelengine.fitframework.validation.Validated; -import modelengine.fitframework.validation.constraints.Range; - -import java.util.List; - -/** - * 表示测试嵌套校验的数据类。 - * - * @author 李金绪 - * @since 2024-09-06 - */ -public class Company { - @Range(min = 0, max = 1, message = "经理只能有0-1个!") - private int manager; - - @Range(min = 0, max = 100, message = "工人只能有0-100个!") - private int worker; - - @Validated - private Product product; - - @Validated - private List cars; - - /** - * 表示创建一个 {@link Company} 的新实例。 - */ - public Company() {} - - /** - * 表示创建一个 {@link Company} 的新实例。 - * - * @param manager 表示经理数量的 {@code int}。 - * @param worker 表示工人数量的 {@code int}。 - * @param product 表示产品的 {@link Product}。 - * @param cars 表示车辆的 {@link List}{@code <}{@link Car}{@code >}。 - */ - public Company(int manager, int worker, Product product, List cars) { - this.manager = manager; - this.worker = worker; - this.product = product; - this.cars = cars; - } -} diff --git a/framework/fit/java/fit-extension/fit-validation/src/test/java/modelengine/fitframework/validation/data/NestedValidate.java b/framework/fit/java/fit-extension/fit-validation/src/test/java/modelengine/fitframework/validation/data/NestedValidate.java deleted file mode 100644 index 4114949a4..000000000 --- a/framework/fit/java/fit-extension/fit-validation/src/test/java/modelengine/fitframework/validation/data/NestedValidate.java +++ /dev/null @@ -1,84 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -package modelengine.fitframework.validation.data; - -import modelengine.fitframework.validation.Validated; - -import java.util.List; -import java.util.Map; - -/** - * 表示公司的校验器。 - * - * @author 李金绪 - * @since 2024-09-06 - */ -public class NestedValidate { - /** - * 嵌套校验的测试方法一。 - * - * @param obj 需要被校验的对象的 {@link Company}。 - */ - public void test1(@Validated Company obj) {} - - /** - * 嵌套校验的测试方法二。 - * - * @param obj 需要被校验的对象的 {@link List}{@code <}{@link Car}{@code >}。 - */ - public void test2(@Validated List obj) {} - - /** - * 嵌套校验的测试方法三。 - * - * @param obj 需要被校验的对象的 {@link List}{@code <}{@link List}{@code <}{@link Car}{@code >>}。 - */ - public void test3(@Validated List> obj) {} - - /** - * 嵌套校验的测试方法四。 - * - * @param obj 需要被校验的对象的 {@link Map}{@code <}{@link String}{@code , }{@link Car}{@code >}。 - */ - public void test4(@Validated Map obj) {} - - /** - * 嵌套校验的测试方法五。 - * - * @param obj 需要被校验的对象的 {@link Map}{@code <}{@link Car}{@code , }{@link Product}{@code >}。 - */ - public void test5(@Validated Map obj) {} - - /** - * 嵌套校验的测试方法六。 - * - * @param obj 需要被校验的对象的 - * {@link Map}{@code <}{@link Car}{@code , }{@link Map}{@code <}{@link Car}{@code , }{@link Product}{@code >>}。 - */ - public void test6(@Validated Map> obj) {} - - /** - * 嵌套校验的测试方法七。 - * - * @param obj 需要被校验的对象的 {@link List}{@code <}{@link Map}{@code <}{@link Car}{@code , }{@link Product}{@code >>}。 - */ - public void test7(@Validated List> obj) {} - - /** - * 嵌套校验的测试方法八。 - * - * @param obj 需要被校验的对象的 {@link Map}{@code <}{@link Car}{@code , }{@link List}{@code <}{@link Product}{@code >>}。 - */ - public void test8(@Validated Map> obj) {} - - /** - * 嵌套校验的测试方法九。 - * - * @param obj 需要被校验的对象的 {@link List}{@code <}{@link Company}{@code >}。 - */ - public void test9(@Validated List obj) {} -} diff --git a/framework/fit/java/fit-extension/fit-validation/src/test/java/modelengine/fitframework/validation/data/Product.java b/framework/fit/java/fit-extension/fit-validation/src/test/java/modelengine/fitframework/validation/data/Product.java deleted file mode 100644 index debb17cd5..000000000 --- a/framework/fit/java/fit-extension/fit-validation/src/test/java/modelengine/fitframework/validation/data/Product.java +++ /dev/null @@ -1,73 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024-2025 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -package modelengine.fitframework.validation.data; - -import modelengine.fitframework.validation.annotation.MaxSize; -import modelengine.fitframework.validation.constraints.NotBlank; -import modelengine.fitframework.validation.constraints.Positive; - -import java.util.List; - -/** - * 表示产品的数据类。 - * - * @author 吕博文 - * @since 2024-08-02 - */ -public class Product { - @NotBlank(message = "产品名不能为空") - private String name; - - @Positive(message = "产品价格必须为正") - private Double price; - - @Positive(message = "产品数量必须为正") - private Integer quantity; - - @NotBlank(message = "产品类别不能为空") - private String category; - - @MaxSize(max = 2) - private List cars; - - /** - * Product 默认构造函数。 - */ - public Product() {} - - /** - * 构造函数。 - * - * @param name 表示名字的 {@link String}。 - * @param price 表示价格的 {@link Double}。 - * @param quantity 表示数量的 {@link Integer}。 - * @param category 表示类别的 {@link String}。 - */ - public Product(String name, Double price, Integer quantity, String category) { - this.name = name; - this.price = price; - this.quantity = quantity; - this.category = category; - } - - /** - * 构造函数。 - * - * @param name 表示名字的 {@link String}。 - * @param price 表示价格的 {@link Double}。 - * @param quantity 表示数量的 {@link Integer}。 - * @param category 表示类别的 {@link String}。 - * @param cars 表示汽车集合的 {@link List}{@code <}{@link Car}{@code >}。 - */ - public Product(String name, Double price, Integer quantity, String category, List cars) { - this.name = name; - this.price = price; - this.quantity = quantity; - this.category = category; - this.cars = cars; - } -} diff --git a/framework/fit/java/fit-extension/fit-validation/src/test/java/modelengine/fitframework/validation/data/ProductValidate.java b/framework/fit/java/fit-extension/fit-validation/src/test/java/modelengine/fitframework/validation/data/ProductValidate.java deleted file mode 100644 index ce15a8588..000000000 --- a/framework/fit/java/fit-extension/fit-validation/src/test/java/modelengine/fitframework/validation/data/ProductValidate.java +++ /dev/null @@ -1,24 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -package modelengine.fitframework.validation.data; - -import modelengine.fitframework.validation.Validated; - -/** - * 表示产品的校验器。 - * - * @author 吕博文 - * @since 2024-08-02 - */ -public class ProductValidate { - /** - * Product 类的校验方法一。 - * - * @param product 表示输入的产品 {@link Product}。 - */ - public void validate1(@Validated Product product) {} -} diff --git a/framework/fit/java/fit-extension/fit-validation/src/test/java/modelengine/fitframework/validation/data/StudentValidate.java b/framework/fit/java/fit-extension/fit-validation/src/test/java/modelengine/fitframework/validation/data/StudentValidate.java deleted file mode 100644 index 5b886ecb1..000000000 --- a/framework/fit/java/fit-extension/fit-validation/src/test/java/modelengine/fitframework/validation/data/StudentValidate.java +++ /dev/null @@ -1,29 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -package modelengine.fitframework.validation.data; - -import modelengine.fitframework.validation.Validated; -import modelengine.fitframework.validation.constraints.Range; -import modelengine.fitframework.validation.group.StudentGroup; -import modelengine.fitframework.validation.group.TeacherGroup; - -/** - * 学生的校验类。 - * - * @author 邬涨财 - * @since 2023-05-19 - */ -@Validated(StudentGroup.class) -public class StudentValidate { - public void validateStudent( - @Range(min = 7, max = 20, message = "范围要在7~20之内", groups = {StudentGroup.class}) int age) { - } - - public void validateTeacher( - @Range(min = 30, max = 70, message = "范围要在30~70之内", groups = {TeacherGroup.class}) int age) { - } -} diff --git a/framework/fit/java/fit-extension/fit-validation/src/test/java/modelengine/fitframework/validation/data/ValidationDataController.java b/framework/fit/java/fit-extension/fit-validation/src/test/java/modelengine/fitframework/validation/data/ValidationDataController.java deleted file mode 100644 index 2c630d81a..000000000 --- a/framework/fit/java/fit-extension/fit-validation/src/test/java/modelengine/fitframework/validation/data/ValidationDataController.java +++ /dev/null @@ -1,48 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -package modelengine.fitframework.validation.data; - -import modelengine.fit.http.annotation.PostMapping; -import modelengine.fit.http.annotation.RequestBody; -import modelengine.fit.http.annotation.RequestMapping; -import modelengine.fitframework.annotation.Component; -import modelengine.fitframework.validation.Validated; -import modelengine.fitframework.validation.group.NormalCarGroup; - -/** - * 表示评估注解验证数据接口集。 - * - * @author 吕博文 - * @since 2024-08-15 - */ -@Component -@RequestMapping(path = "/validation", group = "评估注解验证数据接口") -public class ValidationDataController { - /** - * Car 类默认分组注解验证。 - * - * @param car 表示注解验证类 {@link Car}。 - */ - @PostMapping(path = "/car/default", description = "验证 Car 类默认分组注解") - public void validateCarDefaultGroup(@RequestBody @Validated Car car) {} - - /** - * Car 类特定分组注解验证。 - * - * @param car 表示注解验证类 {@link Car}。 - */ - @PostMapping(path = "/car/carGroup", description = "验证 Car 类特定分组注解") - public void validateCarCarGroup(@RequestBody @Validated(NormalCarGroup.class) Car car) {} - - /** - * Product 类默认分组注解验证。 - * - * @param product 表示注解验证类 {@link Product}。 - */ - @PostMapping(path = "/product/default", description = "验证 Product 类默认分组注解") - public void validateProductDefaultGroup(@RequestBody @Validated Product product) {} -} \ No newline at end of file diff --git a/framework/fit/java/fit-extension/fit-validation/src/test/java/modelengine/fitframework/validation/group/NormalCarGroup.java b/framework/fit/java/fit-extension/fit-validation/src/test/java/modelengine/fitframework/validation/group/NormalCarGroup.java deleted file mode 100644 index 088243618..000000000 --- a/framework/fit/java/fit-extension/fit-validation/src/test/java/modelengine/fitframework/validation/group/NormalCarGroup.java +++ /dev/null @@ -1,15 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -package modelengine.fitframework.validation.group; - -/** - * 正常汽车的分组类。 - * - * @author 李金绪 - * @since 2024-09-04 - */ -public interface NormalCarGroup {} diff --git a/framework/fit/java/fit-extension/fit-validation/src/test/java/modelengine/fitframework/validation/group/OldCarGroup.java b/framework/fit/java/fit-extension/fit-validation/src/test/java/modelengine/fitframework/validation/group/OldCarGroup.java deleted file mode 100644 index 38ca8c43b..000000000 --- a/framework/fit/java/fit-extension/fit-validation/src/test/java/modelengine/fitframework/validation/group/OldCarGroup.java +++ /dev/null @@ -1,15 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -package modelengine.fitframework.validation.group; - -/** - * 老旧汽车的分组类。 - * - * @author 李金绪 - * @since 2024-09-04 - */ -public interface OldCarGroup {} diff --git a/framework/fit/java/fit-extension/fit-validation/src/test/java/modelengine/fitframework/validation/group/StudentGroup.java b/framework/fit/java/fit-extension/fit-validation/src/test/java/modelengine/fitframework/validation/group/StudentGroup.java deleted file mode 100644 index 322820820..000000000 --- a/framework/fit/java/fit-extension/fit-validation/src/test/java/modelengine/fitframework/validation/group/StudentGroup.java +++ /dev/null @@ -1,15 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -package modelengine.fitframework.validation.group; - -/** - * 学生的分组类。 - * - * @author 邬涨财 - * @since 2023-05-16 - */ -public interface StudentGroup {} diff --git a/framework/fit/java/fit-extension/fit-validation/src/test/java/modelengine/fitframework/validation/group/TeacherGroup.java b/framework/fit/java/fit-extension/fit-validation/src/test/java/modelengine/fitframework/validation/group/TeacherGroup.java deleted file mode 100644 index 4b73386ca..000000000 --- a/framework/fit/java/fit-extension/fit-validation/src/test/java/modelengine/fitframework/validation/group/TeacherGroup.java +++ /dev/null @@ -1,15 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -package modelengine.fitframework.validation.group; - -/** - * 老师的分组类。 - * - * @author 邬涨财 - * @since 2023-05-19 - */ -public interface TeacherGroup {} diff --git a/framework/fit/java/fit-extension/fit-validation/src/test/java/modelengine/fitframework/validation/validator/MaxSizeValidator.java b/framework/fit/java/fit-extension/fit-validation/src/test/java/modelengine/fitframework/validation/validator/MaxSizeValidator.java deleted file mode 100644 index c06f6cfb8..000000000 --- a/framework/fit/java/fit-extension/fit-validation/src/test/java/modelengine/fitframework/validation/validator/MaxSizeValidator.java +++ /dev/null @@ -1,41 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -package modelengine.fitframework.validation.validator; - -import static modelengine.fitframework.util.ObjectUtils.cast; - -import modelengine.fitframework.validation.ConstraintValidator; -import modelengine.fitframework.validation.annotation.MaxSize; - -import java.util.List; - -/** - * 表示单集合元素的测试校验类。 - * - * @author 李金绪 - * @since 2025-03-17 - */ -public class MaxSizeValidator implements ConstraintValidator { - private long max; - - @Override - public void initialize(MaxSize constraintAnnotation) { - this.max = constraintAnnotation.max(); - } - - @Override - public boolean isValid(Object value) { - if (value == null) { - return true; - } - if (!(value instanceof List)) { - return false; - } - List valueList = cast(value); - return valueList.size() < this.max; - } -} \ No newline at end of file diff --git a/framework/fit/java/fit-extension/pom.xml b/framework/fit/java/fit-extension/pom.xml index fa747a2fe..ae825bc0d 100644 --- a/framework/fit/java/fit-extension/pom.xml +++ b/framework/fit/java/fit-extension/pom.xml @@ -21,8 +21,6 @@ fit-retry fit-schedule fit-transaction - fit-validation - fit-validation-hibernate diff --git a/framework/fit/java/jacoco-aggregator/pom.xml b/framework/fit/java/jacoco-aggregator/pom.xml index 24357f0d2..42d0f62b6 100644 --- a/framework/fit/java/jacoco-aggregator/pom.xml +++ b/framework/fit/java/jacoco-aggregator/pom.xml @@ -98,10 +98,6 @@ org.fitframework.extension fit-transaction - - org.fitframework.extension - fit-validation - @@ -196,17 +192,29 @@ org.fitframework.plugin fit-service-coordination-locator + + org.fitframework.plugin + fit-service-coordination-nacos + org.fitframework.plugin fit-service-coordination-simple + + org.fitframework.plugin + fit-service-discovery + org.fitframework.plugin fit-service-registry org.fitframework.plugin - fit-service-discovery + fit-validation-hibernate-jakarta + + + org.fitframework.plugin + fit-validation-hibernate-javax org.fitframework.plugin