@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;
import java.util.function.BiConsumer;
import java.util.function.Function;
/**
* 通用数据填充器接口(策略接口)
* 它定义了"一种"数据填充操作所需的所有步骤。
*
* @param <T> 待填充的目标对象类型 (e.g., ContentVO)
* @param <K> 用于批量查询的Key的类型 (e.g., Long for contentId)
* @param <E> 填充所用的数据实体类型 (e.g., Content)
*/
public interface DataEnricher<T, K, E> {
/**
* 定义如何从单个目标对象中提取出用于查询的Key。
*
* @return 一个Function,输入目标对象T,返回Key K。
*/
Function<T, K> getKeyExtractor();
/**
* 定义如何根据一批Key,从数据源(如数据库)批量获取数据实体。
* 这是性能优化的核心,它将N次查询合并为1次。
*
* @return 一个Function,输入一批Key (Set<K>),返回一个Key到数据实体的映射 (Map<K, E>)
*/
Function<Set<K>, Map<K, E>> getDataFetcher();
/**
* 定义如何将查询到的单个数据实体设置回目标对象中。
*
* @return 一个BiConsumer,输入目标对象T和数据实体E,执行设值操作。
*/
BiConsumer<T, E> getDataSetter();
}
import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
/**
* 数据填充服务(核心调度引擎)
* 负责统一调度和执行各种数据填充策略,解决N+1查询问题
*/
@Service
@SuppressWarnings({"rawtypes", "unchecked"}) // 泛型操作会产生警告,这里进行抑制
public class EnrichmentService {
private final Map<String, DataEnricher> enricherMap;
/**
* 构造函数:Spring会自动注入所有实现了DataEnricher接口的Bean。
* 我们将它们根据类名存入Map中,方便快速查找。
*
* @param enrichers 所有实现了DataEnricher接口的Bean列表
*/
public EnrichmentService(List<DataEnricher> enrichers) {
this.enricherMap = enrichers.stream()
.collect(Collectors.toMap(
e -> e.getClass().getSimpleName(),
e -> e,
(e1, e2) -> e1 // 防重复
));
}
/**
* 对外暴露的主方法,用于执行多种数据填充。
* 使用类对象作为标识,更类型安全。
*
* @param targets 需要被填充数据的对象列表
* @param enricherClasses 需要执行的填充器类
* @param <T> 目标对象的类型
*/
@SafeVarargs
public final <T> void enrich(Collection<T> targets, Class<? extends DataEnricher>... enricherClasses) {
if (targets == null || targets.isEmpty() || enricherClasses == null || enricherClasses.length == 0) {
return;
}
for (var enricherClass : enricherClasses) {
var enricher = enricherMap.get(enricherClass.getSimpleName());
if (enricher != null) {
// 调用内部核心方法执行单个填充
executeEnrichment(targets, enricher);
}
}
}
/**
* 更灵活的重载方法,允许传入自定义的Setter逻辑(高级用法)
*
* @param targets 需要被填充数据的对象列表
* @param enricherClass 填充器类
* @param customSetter 自定义的Setter逻辑
* @param <T> 目标对象的类型
* @param <E> 填充数据的类型
*/
@SuppressWarnings("unchecked")
public <T, E> void enrich(Collection<T> targets, Class<? extends DataEnricher> enricherClass, BiConsumer<T, E> customSetter) {
if (targets == null || targets.isEmpty() || enricherClass == null) {
return;
}
var enricher = (DataEnricher<T, ?, E>) enricherMap.get(enricherClass.getSimpleName());
if (enricher == null) {
return;
}
// 使用自定义的Setter执行填充
executeEnrichmentWithCustomSetter(targets, enricher, customSetter);
}
/**
* 执行单个填充任务的核心逻辑
*/
private <T, K, E> void executeEnrichment(Collection<T> targets, DataEnricher<T, K, E> enricher) {
// 1. 提取所有不为null的Key
var keys = targets.stream()
.map(enricher.getKeyExtractor())
.filter(Objects::nonNull)
.collect(Collectors.toSet());
if (keys.isEmpty()) {
return;
}
// 2. 批量获取数据(一次数据库查询)
var dataMap = enricher.getDataFetcher().apply(keys);
if (dataMap.isEmpty()) {
return;
}
// 3. 遍历目标列表,在内存中进行数据组装
targets.forEach(target -> {
var key = enricher.getKeyExtractor().apply(target);
if (key != null) {
var entity = dataMap.get(key);
if (entity != null) {
// 调用setter将数据填充回去
enricher.getDataSetter().accept(target, entity);
}
}
});
}
/**
* 使用自定义Setter执行填充
*/
private <T, K, E> void executeEnrichmentWithCustomSetter(
Collection<T> targets,
DataEnricher<T, K, E> enricher,
BiConsumer<T, E> customSetter) {
// 1. 提取所有不为null的Key
var keys = targets.stream()
.map(enricher.getKeyExtractor())
.filter(Objects::nonNull)
.collect(Collectors.toSet());
if (keys.isEmpty()) {
return;
}
// 2. 批量获取数据(一次数据库查询)
var dataMap = enricher.getDataFetcher().apply(keys);
if (dataMap.isEmpty()) {
return;
}
// 3. 遍历目标列表,使用自定义Setter进行数据组装
targets.forEach(target -> {
var key = enricher.getKeyExtractor().apply(target);
if (key != null) {
var entity = dataMap.get(key);
if (entity != null) {
// 使用自定义的Setter
customSetter.accept(target, entity);
}
}
});
}
}
@Data
@NoArgsConstructor
public class EnumDictDTO {
/**
* 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 {
private EnumDictConfig config;
@SuppressWarnings("rawtypes")
private final Map<String, Class<? extends BaseEnum>> enumRegistry = new LinkedHashMap<>();
private final Map<String, EnumDictDTO> dtoCache = new ConcurrentHashMap<>();
@PostConstruct
public void init() {
config = new EnumDictConfig(Arrays.asList(""));
scanEnumClasses();
log.info("枚举字典初始化完成,共扫描到 {} 个枚举类", enumRegistry.size());
}
private void scanEnumClasses() {
ClassPathScanningCandidateComponentProvider scanner =
new ClassPathScanningCandidateComponentProvider(false);
scanner.addIncludeFilter(new AssignableTypeFilter(BaseEnum.class));
scanner.addIncludeFilter((metadataReader, metadataReaderFactory) -> {
try {
return Class.forName(metadataReader.getClassMetadata().getClassName()).isEnum();
} catch (ClassNotFoundException e) {
return false;
}
});
for (String scanPackage : config.scanPackages) {
Set<BeanDefinition> candidates = scanner.findCandidateComponents(scanPackage);
for (BeanDefinition candidate : candidates) {
try {
@SuppressWarnings({"unchecked", "rawtypes"})
Class<? extends BaseEnum> enumClass =
(Class<? extends BaseEnum>) Class.forName(candidate.getBeanClassName());
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, EnumDictDTO> getAllEnumDicts() {
return enumRegistry.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
entry -> getCachedEnumDict(entry.getValue()),
(v1, v2) -> v1,
LinkedHashMap::new
));
}
public EnumDictDTO getEnumDict(String alias) {
@SuppressWarnings("rawtypes")
Class<? extends BaseEnum> enumClass = enumRegistry.get(alias);
return enumClass != null ? getCachedEnumDict(enumClass) : null;
}
@SuppressWarnings("rawtypes")
private EnumDictDTO getCachedEnumDict(Class<? extends BaseEnum> enumClass) {
return dtoCache.computeIfAbsent(enumClass.getName(), key -> parseEnumToDict(enumClass));
}
@SuppressWarnings("rawtypes")
private EnumDictDTO parseEnumToDict(Class<? extends BaseEnum> enumClass) {
BaseEnum[] enumConstants = enumClass.getEnumConstants();
if (enumConstants == null || enumConstants.length == 0) {
EnumDictDTO dict = new EnumDictDTO();
dict.setList(Collections.emptyList());
dict.setMap(Collections.emptyMap());
return dict;
}
EnumDictDTO dict = new EnumDictDTO();
dict.setList(Arrays.stream(enumConstants)
.map(e -> new EnumDictDTO.Option(String.valueOf(e.getValue()), e.getDesc()))
.collect(Collectors.toList()));
dict.setMap(Arrays.stream(enumConstants)
.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);
}
};
}
}