feat(validation): 新增校验模块并引入 SpEL Validator 用于复杂校验场景

This commit is contained in:
2025-06-18 20:19:36 +08:00
parent 5a53d953da
commit 5ae5b2602a
31 changed files with 91 additions and 34 deletions

View File

@@ -1,67 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.core.autoconfigure;
import jakarta.annotation.PostConstruct;
import jakarta.validation.Validator;
import org.hibernate.validator.HibernateValidator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import java.util.Properties;
/**
* JSR 303 校验器自动配置
*
* @author Charles7c
* @since 2.3.0
*/
@AutoConfiguration
public class ValidatorAutoConfiguration {
private static final Logger log = LoggerFactory.getLogger(ValidatorAutoConfiguration.class);
/**
* Validator 失败立即返回模式配置
*
* <p>
* 默认情况下会校验完所有字段,然后才抛出异常。
* </p>
*/
@Bean
public Validator validator(MessageSource messageSource) {
try (LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean()) {
// 国际化
factoryBean.setValidationMessageSource(messageSource);
factoryBean.setProviderClass(HibernateValidator.class);
Properties properties = new Properties();
properties.setProperty("hibernate.validator.fail_fast", "true");
factoryBean.setValidationProperties(properties);
factoryBean.afterPropertiesSet();
return factoryBean.getValidator();
}
}
@PostConstruct
public void postConstruct() {
log.debug("[ContiNew Starter] - Auto Configuration 'Validator' completed initialization.");
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package top.continew.starter.core.validation;
package top.continew.starter.core.util;
import cn.hutool.core.text.CharSequenceUtil;
import top.continew.starter.core.constant.StringConstants;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package top.continew.starter.core.validation;
package top.continew.starter.core.util;
import cn.hutool.core.text.CharSequenceUtil;
import top.continew.starter.core.exception.BadRequestException;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package top.continew.starter.core.validation;
package top.continew.starter.core.util;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;

View File

@@ -1,88 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.core.validation.constraints;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
/**
* 枚举校验注解
*
* <p>
* {@code @EnumValue(value = XxxEnum.class, message = "参数值无效")} <br />
* {@code @EnumValue(enumValues = {"F", "M"} ,message = "性别只允许为F或M")}
* </p>
*
* @author Jasmine
* @author Charles7c
* @since 2.7.3
*/
@Documented
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = EnumValueValidator.class)
public @interface EnumValue {
/**
* 枚举类
*
* @return 枚举类
*/
Class<? extends Enum> value() default Enum.class;
/**
* 枚举值
*
* @return 枚举值
*/
String[] enumValues() default {};
/**
* 获取枚举值的方法名
*
* @return 获取枚举值的方法名
*/
String method() default "";
/**
* 提示消息
*
* @return 提示消息
*/
String message() default "参数值无效";
/**
* 分组
*
* @return 分组
*/
Class<?>[] groups() default {};
/**
* 负载
*
* @return 负载
*/
Class<? extends Payload>[] payload() default {};
}

View File

@@ -1,97 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.core.validation.constraints;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.text.CharSequenceUtil;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.function.Function;
/**
* 枚举校验器
*
* @author Charles7c
* @author Jasmine
* @since 2.7.3
*/
public class EnumValueValidator implements ConstraintValidator<EnumValue, Object> {
private static final Logger log = LoggerFactory.getLogger(EnumValueValidator.class);
private Class<? extends Enum> enumClass;
private String[] enumValues;
private String enumMethod;
@Override
public void initialize(EnumValue enumValue) {
this.enumClass = enumValue.value();
this.enumValues = enumValue.enumValues();
this.enumMethod = enumValue.method();
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
// 优先校验 enumValues
if (enumValues.length > 0) {
return Arrays.asList(enumValues).contains(Convert.toStr(value));
}
Enum[] enumConstants = enumClass.getEnumConstants();
if (enumConstants.length == 0) {
return false;
}
if (CharSequenceUtil.isBlank(enumMethod)) {
return findEnumValue(enumConstants, Enum::toString, Convert.toStr(value));
}
try {
// 枚举类指定了方法名,则调用指定方法获取枚举值
Method method = enumClass.getMethod(enumMethod);
for (Enum enumConstant : enumConstants) {
if (Convert.toStr(method.invoke(enumConstant)).equals(Convert.toStr(value))) {
return true;
}
}
} catch (Exception e) {
log.error("An error occurred while validating the enum value, please check the @EnumValue parameter configuration.", e);
}
return false;
}
/**
* 遍历枚举类,判断是否包含指定值
*
* @param enumConstants 枚举类数组
* @param function 获取枚举值的函数
* @param value 待校验的值
* @return 是否包含指定值
*/
private boolean findEnumValue(Enum[] enumConstants, Function<Enum, Object> function, Object value) {
for (Enum enumConstant : enumConstants) {
if (function.apply(enumConstant).equals(value)) {
return true;
}
}
return false;
}
}

View File

@@ -1,66 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.core.validation.constraints;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.ElementType.TYPE_USE;
/**
* JSON 格式字符串校验注解
*
* <p>
* 校验字符串是否为 JSON 格式字符串
* {@code @JsonString(message = "必须为有效的 JSON 格式")} <br />
* </p>
*
* @author Charles7c
* @since 2.12.0
*/
@Documented
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = JsonStringValidator.class)
public @interface JsonString {
/**
* 提示消息
*
* @return 提示消息
*/
String message() default "必须为有效的 JSON 格式";
/**
* 分组
*
* @return 分组
*/
Class<?>[] groups() default {};
/**
* 负载
*
* @return 负载
*/
Class<? extends Payload>[] payload() default {};
}

View File

@@ -1,38 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.core.validation.constraints;
import cn.hutool.json.JSONUtil;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
/**
* JSON 格式字符串校验器
*
* @author Charles7c
* @since 2.12.0
*/
public class JsonStringValidator implements ConstraintValidator<JsonString, String> {
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
return JSONUtil.isTypeJSON(value);
}
}

View File

@@ -1,66 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.core.validation.constraints;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
/**
* 手机号校验注解
*
* <p>
* 校验中国大陆手机号码
* {@code @Mobile(message = "手机号格式不正确")} <br />
* </p>
*
* @author Charles7c
* @since 2.10.0
*/
@Documented
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = MobileValidator.class)
public @interface Mobile {
/**
* 提示消息
*
* @return 提示消息
*/
String message() default "手机号格式不正确";
/**
* 分组
*
* @return 分组
*/
Class<?>[] groups() default {};
/**
* 负载
*
* @return 负载
*/
Class<? extends Payload>[] payload() default {};
}

View File

@@ -1,38 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.core.validation.constraints;
import cn.hutool.core.util.PhoneUtil;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
/**
* 手机号校验器
*
* @author Charles7c
* @since 2.10.0
*/
public class MobileValidator implements ConstraintValidator<Mobile, String> {
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
return PhoneUtil.isMobile(value);
}
}

View File

@@ -1,4 +1,3 @@
top.continew.starter.core.autoconfigure.project.ProjectAutoConfiguration
top.continew.starter.core.autoconfigure.ValidatorAutoConfiguration
top.continew.starter.core.autoconfigure.threadpool.ThreadPoolAutoConfiguration
top.continew.starter.core.autoconfigure.threadpool.AsyncAutoConfiguration