@Configuration
public class JacksonConfig {
/**
* 日期时间格式:yyyy-MM-dd HH:mm:ss
*/
private static final String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
/**
* 日期格式:yyyy-MM-dd
*/
private static final String DATE_FORMAT = "yyyy-MM-dd";
/**
* 自定义Jackson ObjectMapper配置
* 配置LocalDateTime和LocalDate的序列化格式
*/
@Bean
public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() {
return builder -> {
var dateTimeFormatter = DateTimeFormatter.ofPattern(DATE_TIME_FORMAT);
var dateFormatter = DateTimeFormatter.ofPattern(DATE_FORMAT);
// 配置LocalDateTime序列化和反序列化
builder.serializers(new LocalDateTimeSerializer(dateTimeFormatter));
builder.deserializers(new LocalDateTimeDeserializer(dateTimeFormatter));
// 配置LocalDate序列化和反序列化
builder.serializers(new LocalDateSerializer(dateFormatter));
builder.deserializers(new LocalDateDeserializer(dateFormatter));
};
}
}
@Configuration
public class WebConfig implements WebMvcConfigurer {
/**
* 跨域配置
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}
public record PageRequestDTO(
@NotNull(message = "页码不能为空")
@Min(value = 1, message = "页码必须大于0")
Integer page,
@NotNull(message = "每页数量不能为空")
@Min(value = 1, message = "每页数量必须大于0")
@Max(value = 100, message = "每页数量不能超过100")
Integer size
) {
public PageRequestDTO() {
this(1, 10);
}
/**
* 获取JPA分页的页码(从0开始)
*/
public Integer getPage() {
return (page != null ? page : 1) - 1;
}
}
public record PageResultDTO<T>(
List<T> records,
Long total,
Integer pageNum,
Integer pageSize,
Integer totalPages
) {
public PageResultDTO(List<T> records, Long total, Integer pageNum, Integer pageSize) {
this(records, total, pageNum, pageSize, (int) Math.ceil((double) total / pageSize));
}
}
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 业务异常处理
*/
@ExceptionHandler(BusinessException.class)
@ResponseStatus(HttpStatus.OK)
public Result<?> handleBusinessException(BusinessException e) {
log.error("业务异常: {}", e.getMessage(), e);
return Result.error(e.getCode(), e.getMessage());
}
/**
* 参数校验异常处理
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Result<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
String message = e.getBindingResult().getFieldErrors().stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining(", "));
log.error("参数校验异常: {}", message);
return Result.error(ResultCode.PARAM_ERROR.code(), message);
}
/**
* 参数绑定异常处理
*/
@ExceptionHandler(BindException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Result<?> handleBindException(BindException e) {
String message = e.getBindingResult().getFieldErrors().stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining(", "));
log.error("参数绑定异常: {}", message);
return Result.error(ResultCode.PARAM_ERROR.code(), message);
}
/**
* 非法参数异常处理
*/
@ExceptionHandler(IllegalArgumentException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Result<?> handleIllegalArgumentException(IllegalArgumentException e) {
log.error("非法参数异常: {}", e.getMessage(), e);
return Result.error(ResultCode.PARAM_ERROR.code(), e.getMessage());
}
/**
* 运行时异常处理
*/
@ExceptionHandler(RuntimeException.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public Result<?> handleRuntimeException(RuntimeException e) {
log.error("运行时异常: {}", e.getMessage(), e);
return Result.error(ResultCode.INTERNAL_ERROR);
}
@ExceptionHandler(NoResourceFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public void handleNoResourceFoundException(NoResourceFoundException e) {
}
/**
* 其他异常处理
*/
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public Result<?> handleException(Exception e) {
log.error("系统异常: {}", e.getMessage(), e);
return Result.error(ResultCode.INTERNAL_ERROR);
}
}
@Getter
public class BusinessException extends RuntimeException {
private final Integer code;
public BusinessException(String message) {
super(message);
this.code = ResultCode.BUSINESS_ERROR.code();
}
public BusinessException(Integer code, String message) {
super(message);
this.code = code;
}
public BusinessException(ResultCode resultCode) {
super(resultCode.message());
this.code = resultCode.code();
}
public BusinessException(ResultCode resultCode, String message) {
super(message);
this.code = resultCode.code();
}
}
public record Result<T>(
Integer code,
String message,
T data,
Long timestamp,
Boolean isSuccess
) {
public static <T> Result<T> success() {
return success(null);
}
public static <T> Result<T> success(T data) {
return success("操作成功", data);
}
public static <T> Result<T> success(String message, T data) {
return new Result<>(
ResultCode.SUCCESS.code(),
message,
data,
System.currentTimeMillis(),
Boolean.TRUE
);
}
public static <T> Result<T> error() {
return error("操作失败");
}
public static <T> Result<T> error(String message) {
return error(ResultCode.ERROR.code(), message);
}
public static <T> Result<T> error(Integer code, String message) {
return new Result<>(
code,
message,
null,
System.currentTimeMillis(),
Boolean.FALSE
);
}
public static <T> Result<T> error(ResultCode resultCode) {
return error(resultCode.code(), resultCode.message());
}
}
public enum ResultCode {
/**
* 操作成功
*/
SUCCESS(0, "操作成功"),
/**
* 操作失败(通用)
*/
ERROR(5001, "操作失败"),
/**
* 参数错误
*/
PARAM_ERROR(2000, "参数错误"),
/**
* 参数校验失败
*/
VALIDATION_ERROR(2001, "参数校验失败"),
/**
* 资源未找到
*/
NOT_FOUND(4000, "资源未找到"),
/**
* 服务器内部错误
*/
INTERNAL_ERROR(5000, "服务器内部错误"),
/**
* 业务处理异常
*/
BUSINESS_ERROR(3000, "业务处理异常");
private final Integer code;
private final String message;
ResultCode(Integer code, String message) {
this.code = code;
this.message = message;
}
public Integer code() {
return code;
}
public String message() {
return message;
}
}
import java.util.Map;
import java.util.Set;
@FunctionalInterface
public interface DataLoader<K, E> {
// 根据一批Key,从数据源批量获取数据实体。
Map<K, E> load(Set<K> keys);
}
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.stream.Collectors;
public class EnrichmentService {
private final Map<Class<? extends DataLoader>, DataLoader<?, ?>> loaderMap;
private final Executor defaultExecutor;
// 构造函数
public EnrichmentService(List<DataLoader<?, ?>> loaders, Executor defaultExecutor) {
this.loaderMap = loaders.stream()
.collect(Collectors.toMap(DataLoader::getClass, l -> l, (l1, l2) -> l1));
this.defaultExecutor = Objects.requireNonNull(defaultExecutor, "默认的执行器不能为空");
}
// 创建包含平台线程池的服务实例
public static EnrichmentService createStandalone(List<DataLoader<?, ?>> loaders) {
return new EnrichmentService(loaders, Executors.newCachedThreadPool());
}
// 创建包含虚拟线程池的服务实例
public static EnrichmentService createStandaloneWithVirtualThreads(List<DataLoader<?, ?>> loaders) {
return new EnrichmentService(loaders, Executors.newVirtualThreadPerTaskExecutor());
}
// 创建执行器
public <T> EnrichmentExecutor<T> target(Collection<T> targets) {
return new EnrichmentExecutor<>(targets, this);
}
@SuppressWarnings("unchecked")
protected <K, E> DataLoader<K, E> getLoader(Class<? extends DataLoader<K, E>> loaderClass) {
DataLoader<K, E> loader = (DataLoader<K, E>) loaderMap.get(loaderClass);
if (loader == null) {
throw new IllegalArgumentException("未找到类型为 " + loaderClass.getName() + " 的DataLoader实例或Bean");
}
return loader;
}
// 流式执行器,支持多种模式
public static class EnrichmentExecutor<T> {
private final Collection<T> targets;
private final EnrichmentService service;
private final List<EnrichmentTask<T, ?, ?>> tasks = new ArrayList<>();
private Executor overrideExecutor = null;
private ExecutionMode mode = ExecutionMode.SEQUENTIAL;
private enum ExecutionMode {SEQUENTIAL, PARALLEL}
public EnrichmentExecutor(Collection<T> targets, EnrichmentService service) {
this.targets = targets;
this.service = service;
}
// 注册任务
public <K, E> EnrichmentExecutor<T> enrich(Class<? extends DataLoader<K, E>> loaderClass, Function<T, K> keyExtractor, BiConsumer<T, E> dataSetter) {
this.tasks.add(new EnrichmentTask<>(loaderClass, keyExtractor, dataSetter));
return this;
}
// 串行
public EnrichmentExecutor<T> sequentially() {
this.mode = ExecutionMode.SEQUENTIAL;
return this;
}
// 并行
public EnrichmentExecutor<T> parallel() {
this.mode = ExecutionMode.PARALLEL;
return this;
}
// 使用虚拟线程
public EnrichmentExecutor<T> withVirtualThreads() {
this.mode = ExecutionMode.PARALLEL;
this.overrideExecutor = Executors.newVirtualThreadPerTaskExecutor();
return this;
}
// 使用自定义线程池
public EnrichmentExecutor<T> usingExecutor(Executor executor) {
this.mode = ExecutionMode.PARALLEL;
this.overrideExecutor = Objects.requireNonNull(executor, "自定义执行器不能为空");
return this;
}
// 执行任务
public void execute() {
if (targets == null || targets.isEmpty() || tasks.isEmpty()) {
return;
}
switch (this.mode) {
case SEQUENTIAL -> executeSequentially();
case PARALLEL -> executeInParallel();
}
}
private void executeSequentially() {
for (var task : tasks) {
var loadedData = task.loadData(targets, service);
loadedData.applySetter(targets);
}
}
private void executeInParallel() {
final Executor activeExecutor = this.overrideExecutor != null ? this.overrideExecutor : this.service.defaultExecutor;
var futures = tasks.stream()
.map(task -> CompletableFuture.supplyAsync(() -> task.loadData(targets, service), activeExecutor))
.toList();
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
futures.stream()
.map(CompletableFuture::join)
.forEach(loadedData -> loadedData.applySetter(targets));
}
}
// 内部辅助记录类
private record EnrichmentTask<T, K, E>(
Class<? extends DataLoader<K, E>> loaderClass,
Function<T, K> keyExtractor,
BiConsumer<T, E> dataSetter
) {
LoadedData<T, K, E> loadData(Collection<T> targets, EnrichmentService service) {
Set<K> keys = targets.stream()
.map(keyExtractor)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
Map<K, E> dataMap = Map.of();
if (!keys.isEmpty()) {
DataLoader<K, E> loader = service.getLoader(loaderClass);
dataMap = loader.load(keys);
}
return new LoadedData<>(this, dataMap);
}
}
private record LoadedData<T, K, E>(EnrichmentTask<T, K, E> task, Map<K, E> dataMap) {
void applySetter(Collection<T> targets) {
if (dataMap == null || dataMap.isEmpty()) return;
for (T target : targets) {
K key = task.keyExtractor().apply(target);
if (key != null) {
E entity = dataMap.get(key);
if (entity != null) {
task.dataSetter().accept(target, entity);
}
}
}
}
}
}
@Data
@NoArgsConstructor
public class EnumDict {
/**
* List结构,适用于前端下拉框。
* 示例: [{ "name": "MALE", "desc": "男" }, { "name": "FEMALE", "desc": "女" }]
*/
private List<Option> list;
/**
* Map结构,适用于前端表格渲染等快速查找场景。
* 示例: { "MALE": "男", "FEMALE": "女" }
*/
private Map<String, String> map;
/**
* 枚举选项
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Option {
/**
* 字段名从value改为name,更贴合实际
*/
private String name;
private String desc;
}
}
/**
* 通用枚举接口,所有需要暴露给前端的枚举都应实现此接口。
* 它定义了枚举最核心的两个要素:值和描述。
*
* @param <T> 枚举值的类型(通常是Integer或String)
*/
public interface BaseEnum<T> {
/**
* 获取枚举的实际值(Code),这个值通常用于存储到数据库。
*/
@JsonValue
T getValue();
/**
* 获取枚举的描述文本(Label),用于在前端界面上展示给用户。
*/
String getDesc();
}
@Slf4j
@Service
public class EnumDictService {
@SuppressWarnings("rawtypes")
private final Map<String, Class<? extends BaseEnum>> enumRegistry = new LinkedHashMap<>();
private final Map<String, EnumDict> dtoCache = new ConcurrentHashMap<>();
@PostConstruct
public void init() {
scanEnumClasses();
log.info("枚举字典初始化完成,共扫描到 {} 个枚举类", enumRegistry.size());
}
private void scanEnumClasses() {
ClassPathScanningCandidateComponentProvider scanner =
new ClassPathScanningCandidateComponentProvider(false);
scanner.addIncludeFilter(new AssignableTypeFilter(BaseEnum.class));
scanner.addIncludeFilter((metadataReader, _) -> {
try {
Class<?> clazz = Class.forName(metadataReader.getClassMetadata().getClassName());
return clazz.isEnum();
} catch (ClassNotFoundException e) {
return false;
}
});
Set<BeanDefinition> candidates = scanner.findCandidateComponents(VlJavaApplication.class.getPackage().getName());
for (BeanDefinition candidate : candidates) {
try {
Class<?> rawClass = Class.forName(candidate.getBeanClassName());
if (rawClass.isEnum() && BaseEnum.class.isAssignableFrom(rawClass)) {
@SuppressWarnings({"unchecked", "rawtypes"})
Class<? extends BaseEnum> enumClass = (Class<? extends BaseEnum>) rawClass;
String alias = generateAlias(enumClass.getSimpleName());
enumRegistry.putIfAbsent(alias, enumClass);
}
} catch (Exception e) {
log.warn("无法加载枚举类: {}", candidate.getBeanClassName(), e);
}
}
}
private String generateAlias(String className) {
return Character.toLowerCase(className.charAt(0)) + className.substring(1);
}
public Map<String, EnumDict> getAllEnumDicts() {
return enumRegistry.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
entry -> getCachedEnumDict(entry.getValue()),
(v1, v2) -> v1,
LinkedHashMap::new
));
}
public EnumDict getEnumDict(String alias) {
@SuppressWarnings("rawtypes")
Class<? extends BaseEnum> enumClass = enumRegistry.get(alias);
return enumClass != null ? getCachedEnumDict(enumClass) : null;
}
@SuppressWarnings("rawtypes")
private EnumDict getCachedEnumDict(Class<? extends BaseEnum> enumClass) {
return dtoCache.computeIfAbsent(enumClass.getName(), key -> parseEnumToDict(enumClass));
}
@SuppressWarnings("rawtypes")
private EnumDict parseEnumToDict(Class<? extends BaseEnum> enumClass) {
Class<? extends Enum> enumType = (Class<? extends Enum>) enumClass;
Enum<?>[] enumConstants = enumType.getEnumConstants();
if (enumConstants == null || enumConstants.length == 0) {
EnumDict dict = new EnumDict();
dict.setList(Collections.emptyList());
dict.setMap(Collections.emptyMap());
return dict;
}
EnumDict dict = new EnumDict();
dict.setList(Arrays.stream(enumConstants)
.map(e -> (BaseEnum) e) // Cast to BaseEnum since we know it implements BaseEnum
.map(e -> new EnumDict.Option(String.valueOf(e.getValue()), e.getDesc()))
.collect(Collectors.toList()));
dict.setMap(Arrays.stream(enumConstants)
.map(e -> (BaseEnum) e) // Cast to BaseEnum
.collect(Collectors.toMap(
e -> String.valueOf(e.getValue()),
BaseEnum::getDesc,
(v1, v2) -> v2,
LinkedHashMap::new
)));
return dict;
}
}
/**
* 通用校验规则接口(策略接口)
*
* @param <T> 待校验对象的类型
*/
@FunctionalInterface
public interface ValidationRule<T> {
/**
* 执行校验逻辑
*
* @param target 待校验的对象
* @param errors 用于收集错误的容器
*/
void validate(T target, Errors errors);
}
/**
* 封装单个校验错误信息的DTO
*/
public record ValidationError(
String field,
String message
) {
public ValidationError {
if (field == null || field.isBlank()) {
throw new IllegalArgumentException("字段名称不能为空");
}
if (message == null || message.isBlank()) {
throw new IllegalArgumentException("错误信息不能为空");
}
}
}
import java.util.ArrayList;
import java.util.List;
/**
* 通用校验引擎,提供流式API来编排和执行校验规则
*
* @param <T> 待校验对象的类型
*/
public class GenericValidator<T> {
private final T target;
private final List<ValidationRule<T>> rules = new ArrayList<>();
private GenericValidator(T target) {
this.target = target;
}
/**
* 工厂方法,启动一个校验流程
*/
public static <T> GenericValidator<T> of(T target) {
return switch (target) {
case null -> throw new IllegalArgumentException("Target object for validation cannot be null.");
default -> new GenericValidator<>(target);
};
}
/**
* 添加一个校验规则到执行队列
*
* @param rule 校验规则
* @return 校验器实例,以支持链式调用
*/
public GenericValidator<T> withRule(ValidationRule<T> rule) {
if (rule != null) {
rules.add(rule);
}
return this;
}
/**
* 批量添加校验规则
*
* @param rules 校验规则列表
* @return 校验器实例,以支持链式调用
*/
@SafeVarargs
public final GenericValidator<T> withRules(ValidationRule<T>... rules) {
for (var rule : rules) {
if (rule != null) {
this.rules.add(rule);
}
}
return this;
}
/**
* 最终执行所有已添加的校验规则
*
* @throws GenericValidationException 如果有任何校验失败
*/
public void validate() throws GenericValidationException {
var errors = new Errors();
rules.forEach(rule -> rule.validate(target, errors));
if (errors.hasErrors()) {
throw new GenericValidationException(errors.getErrors());
}
}
}
import java.util.List;
/**
* 当通用校验失败时抛出的自定义运行时异常
*/
public class GenericValidationException extends RuntimeException {
private final List<ValidationError> errors;
public GenericValidationException(List<ValidationError> errors) {
super("Validation failed with " + errors.size() + " error(s).");
this.errors = List.copyOf(errors);
}
public List<ValidationError> getErrors() {
return errors;
}
public int getErrorCount() {
return errors.size();
}
}
import java.util.function.Function;
/**
* 封装了类型安全的字段Getter和其名称的记录。
* 这是连接Lambda世界和错误报告世界的桥梁。
*
* @param <T> 目标对象类型 (e.g., ContentDTO)
* @param <R> 字段的返回类型 (e.g., LocalDate)
*/
public record FieldAccessor<T, R>(
Function<T, R> getter, // 类型安全的Getter (e.g., ContentDTO::title)
String name // 对应的字段名 (e.g., "title")
) {
public FieldAccessor {
if (getter == null) {
throw new IllegalArgumentException("Getter不能为空");
}
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("字段名称不能为空");
}
}
/**
* 静态工厂方法,让调用更简洁
*/
public static <T, R> FieldAccessor<T, R> of(Function<T, R> getter, String name) {
return new FieldAccessor<>(getter, name);
}
}
import java.util.ArrayList;
import java.util.List;
/**
* 在校验过程中用于收集所有错误的容器
*/
public class Errors {
private final List<ValidationError> errors = new ArrayList<>();
/**
* 添加错误
*/
public void addError(String field, String message) {
errors.add(new ValidationError(field, message));
}
/**
* 检查是否有错误
*/
public boolean hasErrors() {
return !errors.isEmpty();
}
/**
* 获取所有错误(返回不可变列表)
*/
public List<ValidationError> getErrors() {
return List.copyOf(errors);
}
/**
* 获取错误数量
*/
public int getErrorCount() {
return errors.size();
}
/**
* 清空所有错误
*/
public void clear() {
errors.clear();
}
}
import com.ximad.validation.Errors;
import com.ximad.validation.FieldAccessor;
import com.ximad.validation.ValidationRule;
/**
* 字符串非空校验规则(独立实现类)
*/
public record NotBlankRule<T>(
FieldAccessor<T, String> accessor,
String message
) implements ValidationRule<T> {
public NotBlankRule {
if (accessor == null) {
throw new IllegalArgumentException("字段访问器不能为空");
}
if (message == null || message.isBlank()) {
throw new IllegalArgumentException("错误信息不能为空");
}
}
public static <T> NotBlankRule<T> of(FieldAccessor<T, String> accessor, String message) {
return new NotBlankRule<>(accessor, message);
}
@Override
public void validate(T target, Errors errors) {
var value = accessor.getter().apply(target);
if (value == null || value.isBlank()) {
errors.addError(accessor.name(), message);
}
}
}
import com.ximad.validation.Errors;
import com.ximad.validation.FieldAccessor;
import com.ximad.validation.ValidationRule;
/**
* 字符串最大长度校验规则(Record实现)
*/
public record MaxLengthRule<T>(
FieldAccessor<T, String> accessor,
int maxLength,
String message
) implements ValidationRule<T> {
public MaxLengthRule {
if (accessor == null) {
throw new IllegalArgumentException("字段访问器不能为空");
}
if (maxLength < 0) {
throw new IllegalArgumentException("最大长度不能小于0");
}
if (message == null || message.isBlank()) {
throw new IllegalArgumentException("错误信息不能为空");
}
}
public static <T> MaxLengthRule<T> of(FieldAccessor<T, String> accessor, int maxLength, String message) {
return new MaxLengthRule<>(accessor, maxLength, message);
}
@Override
public void validate(T target, Errors errors) {
var value = accessor.getter().apply(target);
if (value != null && value.length() > maxLength) {
errors.addError(accessor.name(), message);
}
}
}
import com.ximad.validation.Errors;
import com.ximad.validation.FieldAccessor;
import com.ximad.validation.ValidationRule;
import java.time.LocalDate;
/**
* 日期范围校验规则(Record实现)
* 校验开始日期必须在结束日期之前
*/
public record DateRangeRule<T>(
FieldAccessor<T, LocalDate> startDateAccessor,
FieldAccessor<T, LocalDate> endDateAccessor,
String message
) implements ValidationRule<T> {
public DateRangeRule {
if (startDateAccessor == null || endDateAccessor == null) {
throw new IllegalArgumentException("字段访问器不能为空");
}
if (message == null || message.isBlank()) {
throw new IllegalArgumentException("错误信息不能为空");
}
}
public static <T> DateRangeRule<T> of(
FieldAccessor<T, LocalDate> startDateAccessor,
FieldAccessor<T, LocalDate> endDateAccessor,
String message
) {
return new DateRangeRule<>(startDateAccessor, endDateAccessor, message);
}
@Override
public void validate(T target, Errors errors) {
var startDate = startDateAccessor.getter().apply(target);
var endDate = endDateAccessor.getter().apply(target);
if (startDate != null && endDate != null && startDate.isAfter(endDate)) {
errors.addError(endDateAccessor.name(), message);
}
}
}
import com.ximad.validation.Errors;
import com.ximad.validation.FieldAccessor;
import com.ximad.validation.ValidationRule;
import java.util.Objects;
/**
* 条件必填校验规则(Record实现)
* 当某个字段的值等于指定值时,另一个字段必填
*/
public record ConditionalRequiredRule<T, C>(
FieldAccessor<T, C> conditionalFieldAccessor,
Object expectedValue,
FieldAccessor<T, ?> requiredFieldAccessor,
String message
) implements ValidationRule<T> {
public ConditionalRequiredRule {
if (conditionalFieldAccessor == null || requiredFieldAccessor == null) {
throw new IllegalArgumentException("字段访问器不能为空");
}
if (message == null || message.isBlank()) {
throw new IllegalArgumentException("错误信息不能为空");
}
}
public static <T, C> ConditionalRequiredRule<T, C> of(
FieldAccessor<T, C> conditionalFieldAccessor,
Object expectedValue,
FieldAccessor<T, ?> requiredFieldAccessor,
String message
) {
return new ConditionalRequiredRule<>(conditionalFieldAccessor, expectedValue, requiredFieldAccessor, message);
}
@Override
public void validate(T target, Errors errors) {
var conditionalValue = conditionalFieldAccessor.getter().apply(target);
if (Objects.equals(expectedValue, conditionalValue)) {
var requiredValue = requiredFieldAccessor.getter().apply(target);
// 使用Pattern Matching for instanceof
if (requiredValue == null ||
requiredValue instanceof String str && str.isBlank()) {
errors.addError(requiredFieldAccessor.name(), message);
}
}
}
}
import com.ximad.validation.FieldAccessor;
import com.ximad.validation.ValidationRule;
import java.time.LocalDate;
import java.util.Objects;
/**
* 常用校验规则工具类
*/
public class CommonRules {
/**
* 非空校验规则
*/
public static <T> ValidationRule<T> notNull(FieldAccessor<T, ?> accessor, String message) {
return (target, errors) -> {
var value = accessor.getter().apply(target);
if (value == null) {
errors.addError(accessor.name(), message);
}
};
}
/**
* 非空校验规则(使用默认消息)
*/
public static <T> ValidationRule<T> notNull(FieldAccessor<T, ?> accessor) {
return notNull(accessor, accessor.name() + "不能为空");
}
/**
* 字符串非空校验规则
*/
public static <T> ValidationRule<T> notBlank(FieldAccessor<T, String> accessor, String message) {
return (target, errors) -> {
var value = accessor.getter().apply(target);
if (value == null || value.isBlank()) {
errors.addError(accessor.name(), message);
}
};
}
/**
* 字符串非空校验规则(使用默认消息)
*/
public static <T> ValidationRule<T> notBlank(FieldAccessor<T, String> accessor) {
return notBlank(accessor, accessor.name() + "不能为空");
}
/**
* 字符串最大长度校验规则
*/
public static <T> ValidationRule<T> maxLength(FieldAccessor<T, String> accessor, int maxLength, String message) {
return (target, errors) -> {
var value = accessor.getter().apply(target);
if (value != null && value.length() > maxLength) {
errors.addError(accessor.name(), message);
}
};
}
/**
* 字符串长度范围校验规则
*/
public static <T> ValidationRule<T> length(FieldAccessor<T, String> accessor, int min, int max, String message) {
return (target, errors) -> {
var value = accessor.getter().apply(target);
if (value != null) {
int len = value.length();
if (len < min || len > max) {
errors.addError(accessor.name(), message);
}
}
};
}
/**
* 数值范围校验规则
*/
public static <T, N extends Number & Comparable<N>> ValidationRule<T> range(
FieldAccessor<T, N> accessor, N min, N max, String message) {
return (target, errors) -> {
var value = accessor.getter().apply(target);
if (value != null) {
if (value.compareTo(min) < 0 || value.compareTo(max) > 0) {
errors.addError(accessor.name(), message);
}
}
};
}
/**
* 日期比较校验规则(date1必须在date2之前)
*/
public static <T> ValidationRule<T> before(
FieldAccessor<T, LocalDate> accessor, LocalDate compareDate, String message) {
return (target, errors) -> {
var value = accessor.getter().apply(target);
if (value != null && compareDate != null && !value.isBefore(compareDate)) {
errors.addError(accessor.name(), message);
}
};
}
/**
* 日期比较校验规则(date1必须在date2之后)
*/
public static <T> ValidationRule<T> after(
FieldAccessor<T, LocalDate> accessor, LocalDate compareDate, String message) {
return (target, errors) -> {
var value = accessor.getter().apply(target);
if (value != null && compareDate != null && !value.isAfter(compareDate)) {
errors.addError(accessor.name(), message);
}
};
}
/**
* 相等性校验规则
*/
public static <T, V> ValidationRule<T> equals(
FieldAccessor<T, V> accessor, V expectedValue, String message) {
return (target, errors) -> {
var value = accessor.getter().apply(target);
if (!Objects.equals(value, expectedValue)) {
errors.addError(accessor.name(), message);
}
};
}
/**
* 自定义校验规则
*/
public static <T, V> ValidationRule<T> custom(
FieldAccessor<T, V> accessor,
Predicate<V> predicate,
String message) {
return (target, errors) -> {
var value = accessor.getter().apply(target);
if (value != null && !predicate.test(value)) {
errors.addError(accessor.name(), message);
}
};
}
}