Skip to content

多线程的协调控制顺序

CountDownLatch T1 T2 T3 按照顺序执行

java
package com.me.test;


import java.util.concurrent.CountDownLatch;

/**
 *   T1  T2  T3  顺序执行
 *
 *   T1 执行完毕后 调用   countDownLatch.countDown();  通知 T2
 *   T2 一致处于阻塞态  countDownLatch.await(); 当T1完成后 自动执行 
 *     执行完毕后 调用 countDownLatch2.countDown(); 
 *     
 *     T3 一致处于阻塞态  countDownLatch2.await(); 当T2完成后 自动执行
 *
 *
 *
 */

public class Test1123 {
    public static void main(String[] args) {
        CountDownLatch countDownLatch = new CountDownLatch(1);
        CountDownLatch countDownLatch2 = new CountDownLatch(1);
        Thread thread = new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"hello world");
            countDownLatch.countDown();
        }, "thread1");
        Thread thread2 = new Thread(()->{
            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(Thread.currentThread().getName()+"hello world2");
            countDownLatch2.countDown();
        }, "thread2");
        Thread thread3 = new Thread(()->{
            try {
                countDownLatch2.await();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(Thread.currentThread().getName()+"hello world3");
        }, "thread3");
        thread.start();
        thread2.start();
        thread3.start();
    }
}

ReentrantLock Condition 接口实现

java
package com.me.test;


import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
 *   
 * 多线程同步 顺序  基于 ReentrantLock Condition
 *  有几个线程 就有几个Condition   再加一个标志位 来控制
 *   需要使用循环控制线程的等待与唤醒
 *
 **/
public class TestThread {

   static ReentrantLock lock = new ReentrantLock();
    static   Condition condition = lock.newCondition();
    static  Condition condition2 = lock.newCondition();
    static  Condition condition3 = lock.newCondition();
    static int flag = 0; // 0-1 1-2 2-3

    public static void main(String[] args) {
        Thread t1 = new Thread(()->{

            try {
                lock.lock();
                while (flag != 0){
                    condition.await();
                }
                System.out.println(Thread.currentThread().getName()+"hello world");
                flag = 1;
                condition2.signal();
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        }, "t1");
        Thread t2 = new Thread(()->{
           try {
               lock.lock();
               while (flag != 1){
                   condition2.await();
               }
               System.out.println(Thread.currentThread().getName()+"hello world");
               flag = 2;
               condition3.signal();
           } catch (Exception e) {
               throw new RuntimeException(e);
           }finally {
               lock.unlock();
           }
        }, "t2");
        Thread t3 = new Thread(()->{
           try {
               lock.lock();
               while (flag != 2){
                   condition3.await();
               }
               System.out.println(Thread.currentThread().getName()+"hello world");
               flag = 0;
               condition.signal();
           }catch ( Exception e){
               e.printStackTrace();
           }finally {
               lock.unlock();
           }
        }, "t3");
        t1.start();
        t2.start();
        t3.start();
    }
}

synchronized wait notifyAll

java
package com.me.test;
/*
*   基于 synchronized  wait   notifyAll
*
* */
public class TestT23 {


    private int flag = 0;
    public  synchronized  void test() throws InterruptedException {
            while (flag!=0){
                this.wait();
            }
            System.out.println("thread:"+Thread.currentThread().getName()+"count:"+flag);
            flag = 1;
            this.notifyAll();
    }
    public  synchronized  void test2() throws InterruptedException {
        while (flag!=1){
            this.wait();
        }
        System.out.println("thread:"+Thread.currentThread().getName()+"count:"+flag);
        flag = 2;
        this.notifyAll();
    }
    public  synchronized  void test3() throws InterruptedException {
        while (flag!=2){
            this.wait();
        }
        System.out.println("thread:"+Thread.currentThread().getName()+"count:"+flag);
        flag = 1;
        this.notifyAll();
    }
    public static void main(String[] args) {
        TestT23 testT23 = new TestT23();
        Thread t1 = new Thread(()->{

            try {
                testT23.test();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "t1");
        Thread t2 = new Thread(()->{

            try {
                testT23.test2();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "t2");
        Thread t3 = new Thread(()->{

            try {
                testT23.test3();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "t3");
        t1.start();
        t2.start();
        t3.start();
    }
}

配置加密

BeanPostProcessor 基于这个拓展点实现

java
package com.me.config;

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

@Configuration
@Data
public class IDMSProperties {

    @Value("${idms.data}")
    private String data;
}
java
package com.me.config;

import com.me.util.AES;
import lombok.Data;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Configuration;

@Configuration
@Data
public class IDMSConfig  implements BeanPostProcessor {


    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof IDMSProperties){
            IDMSProperties idmsConfig = (IDMSProperties) bean;
            System.out.println("原始数据:"+idmsConfig.getData());
            String decrypt = null;
            try {
                decrypt = AES.Decrypt(idmsConfig.getData());
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            idmsConfig.setData(decrypt);
            System.out.println("解密数据:"+decrypt);
        }

        return bean;
    }
}

InitializingBean 方式

java
package com.me.config;

import com.me.util.AESUtil;
import lombok.Data;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

@Configuration
@Data
public class SecuretData  implements InitializingBean {

    @Value("${sec.data}")
    private  String secData ;

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("SEC_DATA front :"+secData);
        secData = AESUtil.decrypt(secData);
        System.out.println("SEC_DATA:"+secData);
    }
}

多字段分组

java
package com.me.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;
import java.time.LocalDate;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class BillDTO {
    private Long id;
    private String billNo;       // 票据号码
    private String subBillRange; // 子票区间
    private BigDecimal amount;   // 票据金额
    private LocalDate dueDate;   // 到期日
}

分组键

java
package com.me.dto;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class BillKey {
    private String billNo;
    private String subBillRange;

    @Override
    public String toString() {
        return String.format("票据号码=%s, 子票区间=%s", billNo, subBillRange);
    }
}

分组方法实现 三种

java
package com.me;


import com.me.dto.BillDTO;
import com.me.dto.BillKey;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class Test {
    public static void main(String[] args) throws Exception  {
        // 构建测试数据
        List<BillDTO> billList = createTestData();

        // 方式1:按票据号码+子票区间分组(复合键使用List)
        Map<List<String>, List<BillDTO>> groupByBillNoAndSubRange = billList.stream()
                .collect(Collectors.groupingBy(
                        bill -> Arrays.asList(bill.getBillNo(), bill.getSubBillRange()),
                        Collectors.toList()
                ));

        // 方式2:按票据号码+子票区间分组(复合键使用自定义对象)
        Map<BillKey, List<BillDTO>> groupByCustomKey = billList.stream()
                .collect(Collectors.groupingBy(
                        bill -> new BillKey(bill.getBillNo(), bill.getSubBillRange()),
                        Collectors.toList()
                ));

        // 输出分组结果
        System.out.println("=== 按票据号码+子票区间分组(List复合键) ===");
        groupByBillNoAndSubRange.forEach((key, bills) -> {
            System.out.printf("分组键:票据号码=%s, 子票区间=%s\n", key.get(0), key.get(1));
            bills.forEach(bill -> System.out.printf("  - %s\n", bill));
        });

        System.out.println("\n=== 按票据号码+子票区间分组(自定义Key) ===");
        groupByCustomKey.forEach((key, bills) -> {
            System.out.printf("分组键:%s\n", key);
            bills.forEach(bill -> System.out.printf("  - %s\n", bill));
        });

    }

    // 构建测试数据
    private static List<BillDTO> createTestData() {
        return Arrays.asList(
                new BillDTO(1L, "BJ2025001", "001-010", new BigDecimal("10000.00"), LocalDate.of(2025, 12, 31)),
                new BillDTO(2L, "BJ2025001", "001-010", new BigDecimal("15000.00"), LocalDate.of(2025, 12, 31)),
                new BillDTO(3L, "BJ2025001", "011-020", new BigDecimal("20000.00"), LocalDate.of(2026, 1, 15)),
                new BillDTO(4L, "BJ2025002", "001-010", new BigDecimal("5000.00"), LocalDate.of(2025, 11, 30)),
                new BillDTO(5L, "BJ2025002", "001-010", new BigDecimal("8000.00"), LocalDate.of(2025, 11, 30)),
                new BillDTO(6L, "BJ2025003", "021-030", new BigDecimal("30000.00"), LocalDate.of(2026, 2, 28)),
                new BillDTO(7L, "BJ2025001", "001-010", new BigDecimal("12000.00"), LocalDate.of(2025, 12, 31)),
                new BillDTO(8L, "BJ2025003", "021-030", new BigDecimal("25000.00"), LocalDate.of(2026, 2, 28))
        );
    }
}
java
package com.me;

import com.me.dto.BillDTO;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class Test2 {
    public static void main(String[] args) {
        // 创建测试数据
        List<BillDTO> billList = createTestData();

        System.out.println("原始数据:");
        billList.forEach(System.out::println);
        System.out.println("=====================================");

        // 使用Stream按照票据号码和子票区间进行分组
        Map<String, List<BillDTO>> groupedBills = billList.stream()
                .collect(Collectors.groupingBy(bill ->
                        bill.getBillNo() + "_" + bill.getSubBillRange()
                ));

        // 打印分组结果
        System.out.println("分组结果:");
        groupedBills.forEach((key, value) -> {
            System.out.println("分组键: " + key);
            value.forEach(System.out::println);
            System.out.println("---");
        });

//        System.out.println("=====================================");
//
//        // 更复杂的分组:使用自定义分组键对象
//        Map<GroupKey, List<BillDTO>> groupedByKeyObject = billList.stream()
//                .collect(Collectors.groupingBy(bill ->
//                        new GroupKey(bill.getBillNumber(), bill.getSubTicketRange())
//                ));
//
//        System.out.println("使用自定义分组键对象的分组结果:");
//        groupedByKeyObject.forEach((key, value) -> {
//            System.out.println("分组键: " + key);
//            System.out.println("该组票据总金额: " +
//                    value.stream().mapToDouble(BillDTO::getBillAmount).sum());
//            value.forEach(System.out::println);
//            System.out.println("---");
//        });
//
//        System.out.println("=====================================");
//
//        // 分组并统计每个组的汇总信息
//        Map<String, Double> amountSummary = billList.stream()
//                .collect(Collectors.groupingBy(
//                        bill -> bill.getBillNo() + "_" + bill.getSubBillRange(),
//                        Collectors.summingDouble(BillDTO::getBillAmount)
//                ));
//
//        System.out.println("每个分组的金额汇总:");
//        amountSummary.forEach((key, amount) -> {
//            System.out.println("分组: " + key + ", 总金额: " + amount);
//        });
    }

    private static List<BillDTO> createTestData() {
        return Arrays.asList(
                new BillDTO(1L, "BJ2025001", "001-010", new BigDecimal("10000.00"), LocalDate.of(2025, 12, 31)),
                new BillDTO(2L, "BJ2025001", "001-010", new BigDecimal("15000.00"), LocalDate.of(2025, 12, 31)),
                new BillDTO(3L, "BJ2025001", "011-020", new BigDecimal("20000.00"), LocalDate.of(2026, 1, 15)),
                new BillDTO(4L, "BJ2025002", "001-010", new BigDecimal("5000.00"), LocalDate.of(2025, 11, 30)),
                new BillDTO(5L, "BJ2025002", "001-010", new BigDecimal("8000.00"), LocalDate.of(2025, 11, 30)),
                new BillDTO(6L, "BJ2025003", "021-030", new BigDecimal("30000.00"), LocalDate.of(2026, 2, 28)),
                new BillDTO(7L, "BJ2025001", "001-010", new BigDecimal("12000.00"), LocalDate.of(2025, 12, 31)),
                new BillDTO(8L, "BJ2025003", "021-030", new BigDecimal("25000.00"), LocalDate.of(2026, 2, 28))
        );
    }
}

校验票据是否重复

java
package com.me.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;
import java.time.LocalDate;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class BillDTO {
    /**
     * 票据号码
     */
    private String billNo;
    /**
     * 票据区间开始
     */
    private Long rangeStart;
    /**
     * 票据区间结束
     */
    private Long rangeEnd;
    /**
     * 票据金额
     */
    private BigDecimal amount;
    /**
     * 到期日
     */
    private LocalDate dueDate;
}
java
package com.me.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class BillValidationResult {
    /**
     * 校验通过的票据
     */
    private List<BillDTO> validBills;
    /**
     * 校验不通过的票据
     */
    private List<BillDTO> invalidBills;
}

校验工具 核心

java
package com.me;

import com.me.dto.BillDTO;
import com.me.dto.BillValidationResult;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class BillValidator {

    /**
     * 校验票据列表,返回通过和不通过的票据
     *
     * @param billList 待校验的票据列表
     * @return 校验结果
     */
    public BillValidationResult validateBills(List<BillDTO> billList) {
        if (billList == null || billList.isEmpty()) {
            return new BillValidationResult(Collections.emptyList(), Collections.emptyList());
        }

        // 按票据号码分组
        Map<String, List<BillDTO>> billMap = billList.stream()
                .collect(Collectors.groupingBy(BillDTO::getBillNo));

        List<BillDTO> validBills = new ArrayList<>();
        List<BillDTO> invalidBills = new ArrayList<>();

        // 遍历每个票据号码对应的票据列表
        for (Map.Entry<String, List<BillDTO>> entry : billMap.entrySet()) {
            List<BillDTO> sameNoBills = entry.getValue();
            // 按区间开始排序,方便检查交叉
            sameNoBills.sort(Comparator.comparing(BillDTO::getRangeStart));

            // 存储已通过校验的区间(用于后续交叉检查)
            List<BillDTO> passedBills = new ArrayList<>();

            for (BillDTO currentBill : sameNoBills) {
                // 检查当前票据区间是否与已通过的票据区间有交叉
                boolean isConflict = passedBills.stream()
                        .anyMatch(passedBill -> isRangeOverlap(passedBill.getRangeStart(), passedBill.getRangeEnd(),
                                currentBill.getRangeStart(), currentBill.getRangeEnd()));

                if (isConflict) {
                    invalidBills.add(currentBill);
                } else {
                    validBills.add(currentBill);
                    passedBills.add(currentBill);
                }
            }
        }

        return new BillValidationResult(validBills, invalidBills);
    }

    /**
     * 判断两个区间是否有交叉
     * 区间规则:[start1, end1] 和 [start2, end2]
     *
     * @param start1 第一个区间开始
     * @param end1   第一个区间结束
     * @param start2 第二个区间开始
     * @param end2   第二个区间结束
     * @return 是否交叉
     */
    private boolean isRangeOverlap(Long start1, Long end1, Long start2, Long end2) {
        // 区间不交叉的条件:第一个区间在第二个区间之后,或第一个区间在第二个区间之前
        return !(end1 < start2 || start1 > end2);
    }
}
java
package com.me.dto;

import com.me.BillValidator;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        // 构造测试数据
        BillDTO bill1 = new BillDTO("NO001", 1L, 10L, new BigDecimal("1000"), LocalDate.of(2025, 12, 31));
        BillDTO bill2 = new BillDTO("NO001", 11L, 20L, new BigDecimal("2000"), LocalDate.of(2025, 12, 31));
        BillDTO bill3 = new BillDTO("NO001", 15L, 25L, new BigDecimal("1500"), LocalDate.of(2025, 12, 31)); // 与bill2交叉
        BillDTO bill4 = new BillDTO("NO002", 1L, 5L, new BigDecimal("500"), LocalDate.of(2025, 11, 30));

        List<BillDTO> billList = Arrays.asList(bill1, bill2, bill3, bill4);

        // 执行校验
        BillValidator validator = new BillValidator();
        BillValidationResult result = validator.validateBills(billList);

        // 输出结果
        System.out.println("校验通过的票据:");
        result.getValidBills().forEach(System.out::println);

        System.out.println("\n校验不通过的票据:");
        result.getInvalidBills().forEach(System.out::println);
    }
}

参数校验

校验规则 参数名称不能重复 参数的值类型要匹配

自定义异常类

java
public class DtoValidationException extends RuntimeException {
    public DtoValidationException(String message) {
        super(message);
    }
}

DTO 类定义

java
import lombok.Data;

@Data
public class ParamDto {
    /** 参数编码 */
    private String code;
    
    /** 参数名称 */
    private String name;
    
    /** 参数默认值 */
    private String defaultValue;
    
    /** 参数类型(日期/数字/字符串/正则表达式) */
    private String type;
}

校验工具类

java

package com.me.param;

import java.time.LocalDate;
import java.time.format.DateTimeParseException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

public class DtoValidator {

    // 支持的参数类型
    private static final Set<String> SUPPORTED_TYPES = new HashSet<>();
    static {
        SUPPORTED_TYPES.add("字符串");
        SUPPORTED_TYPES.add("数字");
        SUPPORTED_TYPES.add("日期");
        SUPPORTED_TYPES.add("正则表达式");
    }

    // 数字正则表达式
    private static final String NUMBER_REGEX = "^-?\\d+(\\.\\d+)?$";
    // 简单日期正则表达式(yyyy-MM-dd)
    private static final String DATE_REGEX = "^\\d{4}-\\d{2}-\\d{2}$";

    /**
     * 校验DTO列表
     * @param dtoList DTO列表
     */
    public static void validateDtoList(List<ParamDto> dtoList) {
        if (dtoList == null || dtoList.isEmpty()) {
            throw new DtoValidationException("DTO列表不能为空");
        }

        // 校验参数code重复
        validateCodeDuplication(dtoList);

        // 逐个校验DTO
        for (ParamDto dto : dtoList) {
            validateSingleDto(dto);
        }
    }

    /**
     * 校验参数code是否重复
     */
    private static void validateCodeDuplication(List<ParamDto> dtoList) {
        Set<String> codeSet = new HashSet<>();
        for (ParamDto dto : dtoList) {
            if (dto.getCode() == null || dto.getCode().trim().isEmpty()) {
                throw new DtoValidationException("参数code不能为空");
            }
            String code = dto.getCode().trim();
            if (codeSet.contains(code)) {
                throw new DtoValidationException("参数code重复: " + code);
            }
            codeSet.add(code);
        }
    }

    /**
     * 校验单个DTO
     */
    private static void validateSingleDto(ParamDto dto) {
        // 校验参数类型
        validateParamType(dto);

        // 校验默认值的类型匹配
        validateDefaultValueType(dto);

        // 校验正则表达式(如果类型是正则表达式)
        if ("正则表达式".equals(dto.getType())) {
            validateRegex(dto);
        }
    }

    /**
     * 校验参数类型是否合法
     */
    private static void validateParamType(ParamDto dto) {
        String type = dto.getType();
        if (type == null || type.trim().isEmpty()) {
            throw new DtoValidationException("参数类型不能为空,code: " + dto.getCode());
        }
        type = type.trim();
        if (!SUPPORTED_TYPES.contains(type)) {
            throw new DtoValidationException("不支持的参数类型: " + type + ",code: " + dto.getCode());
        }
    }

    /**
     * 校验默认值与类型是否匹配
     */
    private static void validateDefaultValueType(ParamDto dto) {
        String type = dto.getType().trim();
        String defaultValue = dto.getDefaultValue();

        // 默认值为空的情况处理
        if (defaultValue == null || defaultValue.trim().isEmpty()) {
            throw new DtoValidationException("参数默认值不能为空,code: " + dto.getCode());
        }

        defaultValue = defaultValue.trim();

        switch (type) {
            case "数字":
                validateNumberValue(dto, defaultValue);
                break;
            case "日期":
                validateDateValue(dto, defaultValue);
                break;
            case "字符串":
                // 字符串类型默认值不需要特殊校验,非空即可
                break;
            case "正则表达式":
                // 正则表达式的格式校验在专门的方法中处理
                break;
            default:
                throw new DtoValidationException("未知的参数类型: " + type + ",code: " + dto.getCode());
        }
    }

    /**
     * 校验数字类型的默认值
     */
    private static void validateNumberValue(ParamDto dto, String value) {
        if (!value.matches(NUMBER_REGEX)) {
            throw new DtoValidationException("参数类型为数字,但默认值不是合法数字: " + value + ",code: " + dto.getCode());
        }
    }

    /**
     * 校验日期类型的默认值(yyyy-MM-dd格式)
     */
    private static void validateDateValue(ParamDto dto, String value) {
        if (!value.matches(DATE_REGEX)) {
            throw new DtoValidationException("参数类型为日期,但默认值格式不正确(应为yyyy-MM-dd): " + value + ",code: " + dto.getCode());
        }

        try {
            LocalDate.parse(value); // 进一步验证日期的合法性(如2月30日会失败)
        } catch (DateTimeParseException e) {
            throw new DtoValidationException("参数类型为日期,但默认值不是合法日期: " + value + ",code: " + dto.getCode());
        }
    }

    /**
     * 校验正则表达式是否正确
     */
    private static void validateRegex(ParamDto dto) {
        String regex = dto.getDefaultValue().trim();
        try {
            Pattern.compile(regex);
        } catch (PatternSyntaxException e) {
            throw new DtoValidationException("正则表达式格式错误,code: " + dto.getCode() + ",错误: " + e.getMessage());
        }
    }
}

使用示例

java
import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        // 创建测试数据
        List<ParamDto> dtoList = new ArrayList<>();
        
        ParamDto dto1 = new ParamDto();
        dto1.setCode("CODE001");
        dto1.setName("参数1");
        dto1.setType("字符串");
        dto1.setDefaultValue("默认值");
        dtoList.add(dto1);
        
        ParamDto dto2 = new ParamDto();
        dto2.setCode("CODE002");
        dto2.setName("参数2");
        dto2.setType("正则表达式");
        dto2.setDefaultValue("[a-z]+"); // 合法的正则表达式
        dtoList.add(dto2);
        
        ParamDto dto3 = new ParamDto();
        dto3.setCode("CODE001"); // 重复的code
        dto3.setName("参数3");
        dto3.setType("数字");
        dto3.setDefaultValue("123");
        dtoList.add(dto3);
        
        try {
            // 执行校验
            DtoValidator.validateDtoList(dtoList);
            System.out.println("校验通过");
        } catch (DtoValidationException e) {
            System.err.println("校验失败: " + e.getMessage());
        }
    }
}

参数校验2

DTO

java
// 无包结构,直接放在根目录

import java.io.Serializable;

/**
 * 参数DTO类,用于存储参数信息
 */
public class ParameterDTO implements Serializable {
    private static final long serialVersionUID = 1L;
    
    // 参数唯一标识
    private String code;
    
    // 参数名称
    private String name;
    
    // 参数默认值
    private String defaultValue;
    
    // 参数类型
    private String type;
    
    // 构造方法
    public ParameterDTO() {
    }
    
    public ParameterDTO(String code, String name, String defaultValue, String type) {
        this.code = code;
        this.name = name;
        this.defaultValue = defaultValue;
        this.type = type;
    }
    
    // Getter和Setter方法
    public String getCode() {
        return code;
    }
    
    public void setCode(String code) {
        this.code = code;
    }
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    public String getDefaultValue() {
        return defaultValue;
    }
    
    public void setDefaultValue(String defaultValue) {
        this.defaultValue = defaultValue;
    }
    
    public String getType() {
        return type;
    }
    
    public void setType(String type) {
        this.type = type;
    }
    
    @Override
    public String toString() {
        return "ParameterDTO{" +
                "code='" + code + '\'' +
                ", name='" + name + '\'' +
                ", defaultValue='" + defaultValue + '\'' +
                ", type='" + type + '\'' +
                '}';
    }
}

枚举

java
// 无包结构,直接放在根目录

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

/**
 * 参数类型枚举类,定义支持的参数类型
 */
public enum ParameterTypeEnum {
    // 日期类型
    DATE("date") {
        @Override
        public boolean validate(String value) {
            if (value == null || value.isEmpty()) {
                return true; // 允许空值
            }
            try {
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
                sdf.setLenient(false);
                sdf.parse(value);
                return true;
            } catch (ParseException e) {
                try {
                    // 尝试其他常见日期格式
                    SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                    sdf2.setLenient(false);
                    sdf2.parse(value);
                    return true;
                } catch (ParseException ex) {
                    return false;
                }
            }
        }
    },
    
    // 数字类型
    NUMBER("number") {
        @Override
        public boolean validate(String value) {
            if (value == null || value.isEmpty()) {
                return true; // 允许空值
            }
            try {
                Double.parseDouble(value);
                return true;
            } catch (NumberFormatException e) {
                return false;
            }
        }
    },
    
    // 字符串类型
    STRING("string") {
        @Override
        public boolean validate(String value) {
            // 字符串类型总是有效的,包括null和空字符串
            return true;
        }
    },
    
    // 正则表达式类型
    REGEX("regex") {
        @Override
        public boolean validate(String value) {
            if (value == null || value.isEmpty()) {
                return true; // 允许空值
            }
            try {
                Pattern.compile(value);
                return true;
            } catch (PatternSyntaxException e) {
                return false;
            }
        }
    },
    
    // 整数类型
    INTEGER("integer") {
        @Override
        public boolean validate(String value) {
            if (value == null || value.isEmpty()) {
                return true; // 允许空值
            }
            return value.matches("^-?\\d+$");
        }
    },
    
    // 布尔类型
    BOOLEAN("boolean") {
        @Override
        public boolean validate(String value) {
            if (value == null || value.isEmpty()) {
                return true; // 允许空值
            }
            return "true".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value);
        }
    };
    
    private final String type;
    
    ParameterTypeEnum(String type) {
        this.type = type;
    }
    
    public String getType() {
        return type;
    }
    
    /**
     * 验证值是否符合该类型
     * @param value 要验证的值
     * @return 如果值符合类型要求则返回true,否则返回false
     */
    public abstract boolean validate(String value);
    
    /**
     * 根据类型字符串获取枚举实例
     * @param type 类型字符串
     * @return 对应的枚举实例
     * @throws IllegalArgumentException 如果类型不支持
     */
    public static ParameterTypeEnum fromType(String type) {
        if (type == null || type.isEmpty()) {
            throw new IllegalArgumentException("类型不能为空");
        }
        
        for (ParameterTypeEnum enumType : values()) {
            if (enumType.type.equalsIgnoreCase(type)) {
                return enumType;
            }
        }
        
        throw new IllegalArgumentException("不支持的参数类型: " + type);
    }
}

参数验证工具类

java
// 无包结构,直接放在根目录

import ParameterDTO;
import ParameterTypeEnum;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * 参数验证工具类,用于验证参数DTO列表的有效性
 */
public class ParameterValidator {
    
    /**
     * 验证参数DTO列表
     * @param parameterDTOList 要验证的参数DTO列表
     * @throws IllegalArgumentException 如果验证失败
     */
    public static void validateParameterList(List<ParameterDTO> parameterDTOList) {
        if (parameterDTOList == null || parameterDTOList.isEmpty()) {
            throw new IllegalArgumentException("参数列表不能为空");
        }
        
        // 验证单个参数的基本信息
        for (ParameterDTO parameterDTO : parameterDTOList) {
            validateSingleParameter(parameterDTO);
        }
        
        // 检查code是否重复
        checkDuplicateCode(parameterDTOList);
        
        // 验证参数类型匹配
        validateParameterTypeMatch(parameterDTOList);
        
        // 特别验证正则表达式类型
        validateRegexTypeParameters(parameterDTOList);
    }
    
    /**
     * 验证单个参数的基本信息
     * @param parameterDTO 参数DTO
     * @throws IllegalArgumentException 如果参数基本信息不合法
     */
    private static void validateSingleParameter(ParameterDTO parameterDTO) {
        if (parameterDTO == null) {
            throw new IllegalArgumentException("参数DTO不能为null");
        }
        
        if (parameterDTO.getCode() == null || parameterDTO.getCode().trim().isEmpty()) {
            throw new IllegalArgumentException("参数code不能为空");
        }
        
        if (parameterDTO.getName() == null || parameterDTO.getName().trim().isEmpty()) {
            throw new IllegalArgumentException("参数名称不能为空,code: " + parameterDTO.getCode());
        }
        
        if (parameterDTO.getType() == null || parameterDTO.getType().trim().isEmpty()) {
            throw new IllegalArgumentException("参数类型不能为空,code: " + parameterDTO.getCode());
        }
    }
    
    /**
     * 检查参数code是否重复
     * @param parameterDTOList 参数DTO列表
     * @throws IllegalArgumentException 如果存在重复的code
     */
    private static void checkDuplicateCode(List<ParameterDTO> parameterDTOList) {
        Set<String> codeSet = new HashSet<>();
        
        for (ParameterDTO parameterDTO : parameterDTOList) {
            String code = parameterDTO.getCode();
            if (!codeSet.add(code)) {
                throw new IllegalArgumentException("参数code重复: " + code);
            }
        }
    }
    
    /**
     * 验证参数类型匹配
     * @param parameterDTOList 参数DTO列表
     * @throws IllegalArgumentException 如果参数类型不匹配
     */
    private static void validateParameterTypeMatch(List<ParameterDTO> parameterDTOList) {
        for (ParameterDTO parameterDTO : parameterDTOList) {
            String type = parameterDTO.getType();
            String defaultValue = parameterDTO.getDefaultValue();
            
            try {
                // 验证类型是否支持
                ParameterTypeEnum parameterType = ParameterTypeEnum.fromType(type);
                
                // 验证默认值是否符合类型要求
                if (!parameterType.validate(defaultValue)) {
                    throw new IllegalArgumentException(
                        "参数默认值与类型不匹配,code: " + parameterDTO.getCode() + 
                        ", type: " + type + ", defaultValue: " + defaultValue);
                }
            } catch (IllegalArgumentException e) {
                // 直接抛出异常
                throw e;
            } catch (Exception e) {
                throw new IllegalArgumentException(
                    "参数类型验证失败,code: " + parameterDTO.getCode() + ", type: " + type, e);
            }
        }
    }
    
    /**
     * 特别验证正则表达式类型的参数
     * @param parameterDTOList 参数DTO列表
     * @throws IllegalArgumentException 如果正则表达式无效
     */
    private static void validateRegexTypeParameters(List<ParameterDTO> parameterDTOList) {
        for (ParameterDTO parameterDTO : parameterDTOList) {
            if ("regex".equalsIgnoreCase(parameterDTO.getType())) {
                String defaultValue = parameterDTO.getDefaultValue();
                if (defaultValue != null && !defaultValue.isEmpty()) {
                    try {
                        ParameterTypeEnum.REGEX.validate(defaultValue);
                    } catch (Exception e) {
                        throw new IllegalArgumentException(
                            "正则表达式无效,code: " + parameterDTO.getCode() + 
                            ", regex: " + defaultValue, e);
                    }
                }
            }
        }
    }
}

参数验证器测试类

java
// 无包结构,直接放在根目录

import ParameterDTO;
import ParameterValidator;
import RegexValidator;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * 参数验证器测试类
 */
public class ParameterValidatorTest {
    
    public static void main(String[] args) {
        System.out.println("===== 参数验证器测试开始 =====");
        
        // 运行所有测试
        testNormalCase();
        testDuplicateCode();
        testTypeMismatch();
        testInvalidRegex();
        testMissingRequiredFields();
        testRegexValidator();
        
        System.out.println("===== 参数验证器测试结束 =====");
    }
    
    /**
     * 测试正常场景
     */
    private static void testNormalCase() {
        System.out.println("\n测试正常场景...");
        try {
            List<ParameterDTO> parameters = new ArrayList<>();
            
            // 添加不同类型的参数
            parameters.add(new ParameterDTO("param1", "日期参数", "2023-12-31", "date"));
            parameters.add(new ParameterDTO("param2", "数字参数", "123.45", "number"));
            parameters.add(new ParameterDTO("param3", "字符串参数", "hello", "string"));
            parameters.add(new ParameterDTO("param4", "正则表达式参数", "^[a-zA-Z0-9]+$", "regex"));
            parameters.add(new ParameterDTO("param5", "整数参数", "123", "integer"));
            parameters.add(new ParameterDTO("param6", "布尔参数", "true", "boolean"));
            parameters.add(new ParameterDTO("param7", "空默认值参数", null, "string"));
            
            // 验证
            ParameterValidator.validateParameterList(parameters);
            System.out.println("正常场景测试通过!");
        } catch (Exception e) {
            System.err.println("正常场景测试失败: " + e.getMessage());
            e.printStackTrace();
        }
    }
    
    /**
     * 测试重复code
     */
    private static void testDuplicateCode() {
        System.out.println("\n测试重复code...");
        try {
            List<ParameterDTO> parameters = Arrays.asList(
                new ParameterDTO("duplicate", "参数1", "value1", "string"),
                new ParameterDTO("duplicate", "参数2", "value2", "string")
            );
            
            ParameterValidator.validateParameterList(parameters);
            System.err.println("重复code测试失败:应该抛出异常但没有抛出");
        } catch (IllegalArgumentException e) {
            if (e.getMessage().contains("参数code重复")) {
                System.out.println("重复code测试通过:" + e.getMessage());
            } else {
                System.err.println("重复code测试失败:" + e.getMessage());
                e.printStackTrace();
            }
        } catch (Exception e) {
            System.err.println("重复code测试失败,异常类型不正确: " + e.getMessage());
            e.printStackTrace();
        }
    }
    
    /**
     * 测试类型不匹配
     */
    private static void testTypeMismatch() {
        System.out.println("\n测试类型不匹配...");
        try {
            List<ParameterDTO> parameters = Arrays.asList(
                new ParameterDTO("invalid_date", "无效日期", "2023-13-31", "date"),  // 无效月份
                new ParameterDTO("invalid_number", "无效数字", "abc", "number") // 非数字
            );
            
            ParameterValidator.validateParameterList(parameters);
            System.err.println("类型不匹配测试失败:应该抛出异常但没有抛出");
        } catch (IllegalArgumentException e) {
            if (e.getMessage().contains("参数默认值与类型不匹配")) {
                System.out.println("类型不匹配测试通过:" + e.getMessage());
            } else {
                System.err.println("类型不匹配测试失败:" + e.getMessage());
                e.printStackTrace();
            }
        } catch (Exception e) {
            System.err.println("类型不匹配测试失败,异常类型不正确: " + e.getMessage());
            e.printStackTrace();
        }
    }
    
    /**
     * 测试无效的正则表达式
     */
    private static void testInvalidRegex() {
        System.out.println("\n测试无效的正则表达式...");
        try {
            List<ParameterDTO> parameters = Arrays.asList(
                new ParameterDTO("invalid_regex", "无效正则", "[a-z", "regex") // 缺少右括号
            );
            
            ParameterValidator.validateParameterList(parameters);
            System.err.println("无效正则表达式测试失败:应该抛出异常但没有抛出");
        } catch (IllegalArgumentException e) {
            System.out.println("无效正则表达式测试通过:" + e.getMessage());
        } catch (Exception e) {
            System.err.println("无效正则表达式测试失败,异常类型不正确: " + e.getMessage());
            e.printStackTrace();
        }
    }
    
    /**
     * 测试缺少必填字段
     */
    private static void testMissingRequiredFields() {
        System.out.println("\n测试缺少必填字段...");
        
        // 测试null DTO
        try {
            ParameterValidator.validateParameterList(Arrays.asList(null));
            System.err.println("缺少必填字段测试失败:null DTO应该抛出异常但没有抛出");
        } catch (IllegalArgumentException e) {
            System.out.println("缺少必填字段测试通过(null DTO):" + e.getMessage());
        }
        
        // 测试空code
        try {
            ParameterDTO param = new ParameterDTO();
            param.setCode("");
            param.setName("测试参数");
            param.setType("string");
            ParameterValidator.validateParameterList(Arrays.asList(param));
            System.err.println("缺少必填字段测试失败:空code应该抛出异常但没有抛出");
        } catch (IllegalArgumentException e) {
            System.out.println("缺少必填字段测试通过(空code):" + e.getMessage());
        }
        
        // 测试空类型
        try {
            ParameterDTO param = new ParameterDTO();
            param.setCode("test");
            param.setName("测试参数");
            param.setType("");
            ParameterValidator.validateParameterList(Arrays.asList(param));
            System.err.println("缺少必填字段测试失败:空类型应该抛出异常但没有抛出");
        } catch (IllegalArgumentException e) {
            System.out.println("缺少必填字段测试通过(空类型):" + e.getMessage());
        }
    }
    
    /**
     * 测试RegexValidator工具类
     */
    private static void testRegexValidator() {
        System.out.println("\n测试RegexValidator工具类...");
        
        // 测试有效正则表达式
        String validRegex = "^[a-zA-Z0-9]+$";
        boolean isValid = RegexValidator.isValidRegex(validRegex);
        System.out.println("有效正则表达式测试:" + (isValid ? "通过" : "失败"));
        
        // 测试无效正则表达式
        String invalidRegex = "[a-z";
        isValid = RegexValidator.isValidRegex(invalidRegex);
        System.out.println("无效正则表达式测试:" + (!isValid ? "通过" : "失败"));
        
        // 测试获取错误信息
        String errorMsg = RegexValidator.getRegexErrorMessage(invalidRegex);
        System.out.println("获取错误信息测试:" + (errorMsg != null ? "通过 - " + errorMsg : "失败"));
        
        // 测试复杂正则表达式检测
        String complexRegex = "(a+)+b";
        boolean isComplex = RegexValidator.isPotentiallyComplex(complexRegex);
        System.out.println("复杂正则表达式检测测试:" + (isComplex ? "通过" : "失败"));
        
        // 测试安全匹配
        try {
            boolean matches = RegexValidator.safeMatches("^\\d+$", "123", 1000);
            System.out.println("安全匹配测试(成功):" + (matches ? "通过" : "失败"));
        } catch (Exception e) {
            System.err.println("安全匹配测试失败:" + e.getMessage());
        }
    }
}

springboot redis incr lua

java
package com.me.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;
import java.util.Collections;

@Component
public class RedisIncrementScriptService {

    @Autowired
    private  StringRedisTemplate stringRedisTemplate;


    // 建议修改Lua脚本为标准格式
    private static final String INCR_WITH_EXPIRE_SCRIPT =
            "local increment = redis.call('INCR', KEYS[1]) " +
                    "if increment == 1 then " +
                    "redis.call('EXPIRE', KEYS[1], ARGV[1]) " +
                    "end " +
                    "return increment";

    // 初始化 Redis 脚本
    private final RedisScript<Long> incrScript = new DefaultRedisScript<>(
            INCR_WITH_EXPIRE_SCRIPT,
            Long.class
    );

    /**
     * 原子化自增并设置过期时间
     * @param key Redis Key
     * @param expireSeconds 过期时间(秒)
     * @return 自增后的值
     */
    public Long atomicIncrementWithExpire(String key, long expireSeconds) {
        return stringRedisTemplate.execute(
                incrScript,
                Collections.singletonList(key),  // KEYS[1]
                String.valueOf(expireSeconds)    // ARGV[1]
        );
    }


}
java
package com.me.demo1.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.util.Collections;

@Component
public class RedisIncrementScriptService {


    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 对指定 key 自增,并在首次创建时设置过期时间
     *
     * @param key           Redis key
     * @param expireSeconds 过期时间(秒),仅在 key 首次创建时生效
     * @return 自增后的值
     */
    public Long incrementWithExpire(String key, long expireSeconds) {
        String scriptText =
                "local current = redis.call('INCR', KEYS[1]) " +
                        "if current == 1 then " +
                        "    redis.call('EXPIRE', KEYS[1], ARGV[1]) " +
                        "end " +
                        "return current";

        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        script.setScriptText(scriptText);
        script.setResultType(Long.class);
        Long execute = (Long) redisTemplate.execute(script, Collections.singletonList(key), String.valueOf(expireSeconds));
        return execute;
    }
   
}

前后端对象比对 那些是删除的 那些是修改的 那些是新增的

ParameterDTO

java
package com.me.param2;// 无包结构,直接放在根目录

import java.io.Serializable;

/**
 * 参数DTO类,用于存储参数信息
 */
public class ParameterDTO implements Serializable {
    private static final long serialVersionUID = 1L;
    
    // 参数唯一标识
    private String code;
    
    // 参数名称
    private String name;
    
    // 参数默认值
    private String defaultValue;
    
    // 参数类型
    private String type;
    
    // 构造方法
    public ParameterDTO() {
    }
    
    public ParameterDTO(String code, String name, String defaultValue, String type) {
        this.code = code;
        this.name = name;
        this.defaultValue = defaultValue;
        this.type = type;
    }
    
    // Getter和Setter方法
    public String getCode() {
        return code;
    }
    
    public void setCode(String code) {
        this.code = code;
    }
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    public String getDefaultValue() {
        return defaultValue;
    }
    
    public void setDefaultValue(String defaultValue) {
        this.defaultValue = defaultValue;
    }
    
    public String getType() {
        return type;
    }
    
    public void setType(String type) {
        this.type = type;
    }
    
    @Override
    public String toString() {
        return "ParameterDTO{" +
                "code='" + code + '\'' +
                ", name='" + name + '\'' +
                ", defaultValue='" + defaultValue + '\'' +
                ", type='" + type + '\'' +
                '}';
    }
}

ParameterComparator

java
package com.me.param2;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 参数比较工具类,用于对比数据库和前端的参数列表,识别新增、修改和删除的参数
 */
public class ParameterComparator {
    
    /**
     * 参数比较结果类,用于存储比较后的结果
     */
    public static class ComparisonResult {
        private List<ParameterDTO> addedParameters;
        private List<ParameterDTO> modifiedParameters;
        private List<ParameterDTO> deletedParameters;
        
        public ComparisonResult() {
            this.addedParameters = new ArrayList<>();
            this.modifiedParameters = new ArrayList<>();
            this.deletedParameters = new ArrayList<>();
        }
        
        public List<ParameterDTO> getAddedParameters() {
            return addedParameters;
        }
        
        public List<ParameterDTO> getModifiedParameters() {
            return modifiedParameters;
        }
        
        public List<ParameterDTO> getDeletedParameters() {
            return deletedParameters;
        }
        
        @Override
        public String toString() {
            return "ComparisonResult{" +
                    "addedParameters=" + addedParameters.size() + "条, " +
                    "modifiedParameters=" + modifiedParameters.size() + "条, " +
                    "deletedParameters=" + deletedParameters.size() + "条" +
                    '}';
        }
    }
    
    /**
     * 比较数据库查询出的参数列表和前端传的参数列表
     * @param dbParameters 数据库查询出的参数列表
     * @param frontendParameters 前端传的参数列表
     * @return 比较结果,包含新增、修改和删除的参数
     */
    public static ComparisonResult compareParameters(List<ParameterDTO> dbParameters, List<ParameterDTO> frontendParameters) {
        ComparisonResult result = new ComparisonResult();
        
        // 使用Stream创建数据库参数的Map,以code为键
        Map<String, ParameterDTO> dbParamMap = dbParameters.stream()
                .collect(Collectors.toMap(ParameterDTO::getCode, param -> param));
        
        // 使用Stream创建前端参数的Map,以code为键
        Map<String, ParameterDTO> frontendParamMap = frontendParameters.stream()
                .collect(Collectors.toMap(ParameterDTO::getCode, param -> param));
        
        // 使用Stream找出新增的参数(前端有,数据库没有)
        List<ParameterDTO> addedParams = frontendParameters.stream()
                .filter(frontendParam -> !dbParamMap.containsKey(frontendParam.getCode()))
                .collect(Collectors.toList());
        result.getAddedParameters().addAll(addedParams);
        
        // 使用Stream找出删除的参数(数据库有,前端没有)
        List<ParameterDTO> deletedParams = dbParameters.stream()
                .filter(dbParam -> !frontendParamMap.containsKey(dbParam.getCode()))
                .collect(Collectors.toList());
        result.getDeletedParameters().addAll(deletedParams);
        
        // 使用Stream找出修改的参数(两者都有,但字段值不同)
        List<ParameterDTO> modifiedParams = dbParameters.stream()
                .filter(dbParam -> frontendParamMap.containsKey(dbParam.getCode()))
                .map(dbParam -> {
                    ParameterDTO frontendParam = frontendParamMap.get(dbParam.getCode());
                    return isModified(dbParam, frontendParam) ? frontendParam : null;
                })
                .filter(param -> param != null)
                .collect(Collectors.toList());
        result.getModifiedParameters().addAll(modifiedParams);
        
        return result;
    }
    
    /**
     * 判断两个参数是否有修改
     * @param dbParam 数据库中的参数
     * @param frontendParam 前端传来的参数
     * @return 如果有修改返回true,否则返回false
     */
    private static boolean isModified(ParameterDTO dbParam, ParameterDTO frontendParam) {
        // 比较name字段
        if (!equals(dbParam.getName(), frontendParam.getName())) {
            return true;
        }
        
        // 比较type字段
        if (!equals(dbParam.getType(), frontendParam.getType())) {
            return true;
        }
        
        // 比较defaultValue字段(参数值)
        if (!equals(dbParam.getDefaultValue(), frontendParam.getDefaultValue())) {
            return true;
        }
        
        return false;
    }
    
    /**
     * 安全比较两个对象是否相等,处理null值
     * @param obj1 第一个对象
     * @param obj2 第二个对象
     * @return 如果相等返回true,否则返回false
     */
    private static boolean equals(Object obj1, Object obj2) {
        if (obj1 == null && obj2 == null) {
            return true;
        }
        if (obj1 == null || obj2 == null) {
            return false;
        }
        return obj1.equals(obj2);
    }
}

TEST

java
package com.me.param2;

import java.util.ArrayList;
import java.util.List;

/**
 * ParameterComparator工具类的测试类
 */
public class ParameterComparatorTest {
    
    public static void main(String[] args) {
        // 测试场景1:包含新增、修改和删除的情况
        testAllChanges();
        
        // 测试场景2:没有任何变化的情况
        testNoChanges();
        
        // 测试场景3:只有新增的情况
        testOnlyAdded();
        
        // 测试场景4:只有修改的情况
        testOnlyModified();
        
        // 测试场景5:只有删除的情况
        testOnlyDeleted();
    }
    
    /**
     * 测试包含新增、修改和删除的情况
     */
    private static void testAllChanges() {
        System.out.println("=== 测试场景1:包含新增、修改和删除的情况 ===");
        
        // 数据库中的参数列表
        List<ParameterDTO> dbParameters = new ArrayList<>();
        dbParameters.add(new ParameterDTO("param1", "参数1", "100", "Integer"));
        dbParameters.add(new ParameterDTO("param2", "参数2", "true", "Boolean"));
        dbParameters.add(new ParameterDTO("param3", "参数3", "test", "String"));
        
        // 前端传来的参数列表
        List<ParameterDTO> frontendParameters = new ArrayList<>();
        frontendParameters.add(new ParameterDTO("param1", "参数1-修改", "200", "Integer")); // 修改了名称和值
        frontendParameters.add(new ParameterDTO("param2", "参数2", "true", "Boolean")); // 没有变化
        frontendParameters.add(new ParameterDTO("param4", "参数4", "new", "String")); // 新增参数
        
        // 比较参数
        ParameterComparator.ComparisonResult result = ParameterComparator.compareParameters(dbParameters, frontendParameters);
        
        // 输出结果
        System.out.println("比较结果: " + result);
        System.out.println("新增参数: " + result.getAddedParameters());
        System.out.println("修改参数: " + result.getModifiedParameters());
        System.out.println("删除参数: " + result.getDeletedParameters());
        System.out.println();
    }
    
    /**
     * 测试没有任何变化的情况
     */
    private static void testNoChanges() {
        System.out.println("=== 测试场景2:没有任何变化的情况 ===");
        
        // 数据库中的参数列表
        List<ParameterDTO> dbParameters = new ArrayList<>();
        dbParameters.add(new ParameterDTO("param1", "参数1", "100", "Integer"));
        dbParameters.add(new ParameterDTO("param2", "参数2", "true", "Boolean"));
        
        // 前端传来的参数列表
        List<ParameterDTO> frontendParameters = new ArrayList<>();
        frontendParameters.add(new ParameterDTO("param1", "参数1", "100", "Integer"));
        frontendParameters.add(new ParameterDTO("param2", "参数2", "true", "Boolean"));
        
        // 比较参数
        ParameterComparator.ComparisonResult result = ParameterComparator.compareParameters(dbParameters, frontendParameters);
        
        // 输出结果
        System.out.println("比较结果: " + result);
        System.out.println("新增参数: " + result.getAddedParameters());
        System.out.println("修改参数: " + result.getModifiedParameters());
        System.out.println("删除参数: " + result.getDeletedParameters());
        System.out.println();
    }
    
    /**
     * 测试只有新增的情况
     */
    private static void testOnlyAdded() {
        System.out.println("=== 测试场景3:只有新增的情况 ===");
        
        // 数据库中的参数列表
        List<ParameterDTO> dbParameters = new ArrayList<>();
        dbParameters.add(new ParameterDTO("param1", "参数1", "100", "Integer"));
        
        // 前端传来的参数列表
        List<ParameterDTO> frontendParameters = new ArrayList<>();
        frontendParameters.add(new ParameterDTO("param1", "参数1", "100", "Integer"));
        frontendParameters.add(new ParameterDTO("param2", "参数2", "true", "Boolean"));
        frontendParameters.add(new ParameterDTO("param3", "参数3", "test", "String"));
        
        // 比较参数
        ParameterComparator.ComparisonResult result = ParameterComparator.compareParameters(dbParameters, frontendParameters);
        
        // 输出结果
        System.out.println("比较结果: " + result);
        System.out.println("新增参数: " + result.getAddedParameters());
        System.out.println("修改参数: " + result.getModifiedParameters());
        System.out.println("删除参数: " + result.getDeletedParameters());
        System.out.println();
    }
    
    /**
     * 测试只有修改的情况
     */
    private static void testOnlyModified() {
        System.out.println("=== 测试场景4:只有修改的情况 ===");
        
        // 数据库中的参数列表
        List<ParameterDTO> dbParameters = new ArrayList<>();
        dbParameters.add(new ParameterDTO("param1", "参数1", "100", "Integer"));
        dbParameters.add(new ParameterDTO("param2", "参数2", "true", "Boolean"));
        
        // 前端传来的参数列表
        List<ParameterDTO> frontendParameters = new ArrayList<>();
        frontendParameters.add(new ParameterDTO("param1", "参数1-修改", "200", "Integer"));
        frontendParameters.add(new ParameterDTO("param2", "参数2", "false", "Boolean"));
        
        // 比较参数
        ParameterComparator.ComparisonResult result = ParameterComparator.compareParameters(dbParameters, frontendParameters);
        
        // 输出结果
        System.out.println("比较结果: " + result);
        System.out.println("新增参数: " + result.getAddedParameters());
        System.out.println("修改参数: " + result.getModifiedParameters());
        System.out.println("删除参数: " + result.getDeletedParameters());
        System.out.println();
    }
    
    /**
     * 测试只有删除的情况
     */
    private static void testOnlyDeleted() {
        System.out.println("=== 测试场景5:只有删除的情况 ===");
        
        // 数据库中的参数列表
        List<ParameterDTO> dbParameters = new ArrayList<>();
        dbParameters.add(new ParameterDTO("param1", "参数1", "100", "Integer"));
        dbParameters.add(new ParameterDTO("param2", "参数2", "true", "Boolean"));
        dbParameters.add(new ParameterDTO("param3", "参数3", "test", "String"));
        
        // 前端传来的参数列表
        List<ParameterDTO> frontendParameters = new ArrayList<>();
        frontendParameters.add(new ParameterDTO("param1", "参数1", "100", "Integer"));
        
        // 比较参数
        ParameterComparator.ComparisonResult result = ParameterComparator.compareParameters(dbParameters, frontendParameters);
        
        // 输出结果
        System.out.println("比较结果: " + result);
        System.out.println("新增参数: " + result.getAddedParameters());
        System.out.println("修改参数: " + result.getModifiedParameters());
        System.out.println("删除参数: " + result.getDeletedParameters());
        System.out.println();
    }
}

对象 逐个字段比较 支持 继承的父类属性 并且支持跳过某些字段 返回差异字段

java

import java.lang.annotation.*;

/**
 * 自定义注解:标记该注解的字段,在对象对比时自动跳过(不参与字段比较)
 * 可标注在 子类字段、父类字段 上,均生效
 */
@Target({ElementType.FIELD}) // 只允许标注在【字段】上
@Retention(RetentionPolicy.RUNTIME) // 运行时生效,反射能读取到该注解
@Documented
public @interface SkipCompare {
}
java
@Data
@NoArgsConstructor
public class User  extends AbstractUser{
        private Long id;
        private String name;
        private Integer age;
        @SkipCompare
        private String email;
        @SkipCompare
        private Boolean active;
        
        public User(Long id, String name, Integer age, String email, Boolean active, String uid) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.email = email;
        this.active = active;
        this.setUid(uid);
    }
}
java
package com.me.util;
import java.lang.reflect.Field;
import java.util.*;

/**
 * 对象字段对比工具类【终极完整版】
 * 核心能力全量整合:
 * 1. 对比同类型两个对象的【当前类 + 所有父类】的全部字段
 * 2. 支持字段添加 @SkipCompare 注解 自动跳过对比
 * 3. 支持手动传入排除字段集合,跳过指定字段对比
 * 4. 纯JDK原生实现,无任何第三方依赖
 * 返回:有变更的字段Map,key=字段名,value=封装修改前/后值的对象
 */
public class ObjectCompareUtil {

    /**
     * 封装字段的修改前、修改后的值
     */
    public static class FieldChange {
        // 修改前的值(源对象字段值)
        private Object oldValue;
        // 修改后的值(目标对象字段值)
        private Object newValue;

        public FieldChange(Object oldValue, Object newValue) {
            this.oldValue = oldValue;
            this.newValue = newValue;
        }

        public Object getOldValue() {
            return oldValue;
        }

        public void setOldValue(Object oldValue) {
            this.oldValue = oldValue;
        }

        public Object getNewValue() {
            return newValue;
        }

        public void setNewValue(Object newValue) {
            this.newValue = newValue;
        }

        // 重写toString,控制台打印更友好直观
        @Override
        public String toString() {
            return "原值: " + oldValue + " → 新值: " + newValue;
        }
    }

    /**
     * 核心方法【主方法】:对比两个同类型对象,包含【当前类+所有父类】的所有字段
     * 自动跳过:添加了 @SkipCompare 注解的字段
     * @param sourceObj 源对象(修改前的对象)
     * @param targetObj 目标对象(修改后的对象)
     * @param <T> 泛型约束,强制两个对象为同一种类型
     * @return Map<String, FieldChange> 只返回有差异的字段,无差异返回空Map
     * @throws IllegalAccessException 反射访问私有字段的权限异常
     */
    public static <T> Map<String, FieldChange> compareObjectFields(T sourceObj, T targetObj) throws IllegalAccessException {
        Map<String, FieldChange> changeMap = new HashMap<>(16);
        // 1. 双空判断:两个对象都为空,无任何差异
        if (Objects.isNull(sourceObj) && Objects.isNull(targetObj)) {
            return changeMap;
        }
        // 2. 严格校验对象类型一致,避免不同类的无意义对比
        if (!Objects.equals(sourceObj.getClass(), targetObj.getClass())) {
            throw new IllegalArgumentException("【对象类型不一致】无法对比!源对象类型:" + sourceObj.getClass().getName() + ",目标对象类型:" + targetObj.getClass().getName());
        }

        Class<?> clazz = sourceObj.getClass();
        // 3. 递归获取【当前类 + 所有父类】的全部字段(直到Object类为止)
        List<Field> allFieldList = new ArrayList<>();
        Class<?> tempClass = clazz;
        while (tempClass != null && !tempClass.getName().equals(Object.class.getName())) {
            allFieldList.addAll(Arrays.asList(tempClass.getDeclaredFields()));
            tempClass = tempClass.getSuperclass();
        }
        Field[] fields = allFieldList.toArray(new Field[0]);

        // 4. 逐字段对比值差异
        for (Field field : fields) {
            // ===== 核心新增:判断字段是否有@SkipCompare注解,有则直接跳过对比 =====
            if (field.isAnnotationPresent(SkipCompare.class)) {
                continue;
            }

            // 暴力反射:解除私有字段的访问权限,必须加!实体类字段都是private
            field.setAccessible(true);
            // 获取源对象的字段值(修改前)
            Object sourceValue = field.get(sourceObj);
            // 获取目标对象的字段值(修改后)
            Object targetValue = field.get(targetObj);

            // 精准判断值是否不同,Objects.equals完美处理null值,不会空指针
            if (!Objects.equals(sourceValue, targetValue)) {
                changeMap.put(field.getName(), new FieldChange(sourceValue, targetValue));
            }
        }
        return changeMap;
    }

    /**
     * 重载方法【常用】:对比两个对象 + 手动排除指定字段 + 自动跳过注解字段
     * 优先级排序:@SkipCompare注解 > 手动排除字段,双重过滤,都生效
     * 适用于:主键id、创建时间createTime等无需对比的固定字段
     * @param sourceObj 源对象
     * @param targetObj 目标对象
     * @param excludeFields 需要手动排除的字段名集合,例如:Arrays.asList("id","createTime")
     */
    public static <T> Map<String, FieldChange> compareObjectFields(T sourceObj, T targetObj, List<String> excludeFields) throws IllegalAccessException {
        Map<String, FieldChange> changeMap = new HashMap<>(16);
        if (Objects.isNull(sourceObj) && Objects.isNull(targetObj)) {
            return changeMap;
        }
        if (!Objects.equals(sourceObj.getClass(), targetObj.getClass())) {
            throw new IllegalArgumentException("【对象类型不一致】无法对比!源对象类型:" + sourceObj.getClass().getName() + ",目标对象类型:" + targetObj.getClass().getName());
        }

        Class<?> clazz = sourceObj.getClass();
        // 递归获取当前类+所有父类的全部字段
        List<Field> allFieldList = new ArrayList<>();
        Class<?> tempClass = clazz;
        while (tempClass != null && !tempClass.getName().equals(Object.class.getName())) {
            allFieldList.addAll(Arrays.asList(tempClass.getDeclaredFields()));
            tempClass = tempClass.getSuperclass();
        }
        Field[] fields = allFieldList.toArray(new Field[0]);

        for (Field field : fields) {
            // ===== 双重过滤:1.注解字段跳过  2.手动排除字段跳过 =====
            if (field.isAnnotationPresent(SkipCompare.class) || excludeFields.contains(field.getName())) {
                continue;
            }

            field.setAccessible(true);
            Object sourceValue = field.get(sourceObj);
            Object targetValue = field.get(targetObj);

            if (!Objects.equals(sourceValue, targetValue)) {
                changeMap.put(field.getName(), new FieldChange(sourceValue, targetValue));
            }
        }
        return changeMap;
    }
}