Skip to content

1.多线程的协调控制顺序

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();
    }
}

2.配置加密

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);
    }
}

3.多字段分组

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))
        );
    }
}

4.校验票据是否重复

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);
    }
}

5.参数校验

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

自定义异常类

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());
        }
    }
}

6.参数校验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());
        }
    }
}

7.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;
    }
   
}

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

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();
    }
}

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

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;
    }
}

10.poi导出excel xlsx

maven

xml
  <!-- 核心XSSF(XLSX)支持 -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>5.2.5</version>
        </dependency>
        <!-- 处理Excel样式/公式等依赖(poi-ooxml已间接依赖,可显式声明) -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>5.2.5</version>
        </dependency>
        <!-- 若需处理大文件,可引入SXSSF(流式导出,避免OOM) -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml-schemas</artifactId>
            <version>4.1.2</version>
        </dependency>
        <!-- 可选:处理日期格式化、单元格样式辅助 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.14.0</version>
        </dependency>

注解

java
package org.me.e2;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.*;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelColumn {
    String value();                     // 列标题
    int order() default 0;              // 排序

    // 新增:日期格式(如 "yyyy-MM-dd HH:mm:ss")
    String dateFormat() default "";

    // 新增:数字格式(如 "#,##0.00"、"0.00%"、"¥#,##0.00")
    String numberFormat() default "";
    boolean wrapText() default false;
}

sheet

java
package org.me.e2;

import java.util.List;

public class ExcelSheet<T> {
    private final List<T> data;
    private final Class<T> clazz;
    private final String sheetName;

    private final int dataStartRow; // 新增:数据从第几行开始写(0-based)

    public ExcelSheet(List<T> data, Class<T> clazz, String sheetName, int dataStartRow) {
        this.data = data;
        this.clazz = clazz;
        this.sheetName = sheetName == null || sheetName.trim().isEmpty() ? "Sheet" : sheetName;
        this.dataStartRow = dataStartRow;
    }
    public ExcelSheet(List<T> data, Class<T> clazz, String sheetName) {
        this(data, clazz, sheetName, 1); // 默认数据从第1行开始(第0行为表头)
    }

    // Getters
    public List<T> getData() { return data; }
    public Class<T> getClazz() { return clazz; }
    public String getSheetName() { return sheetName; }

    // 静态工厂方法,更优雅
    public static <T> ExcelSheet<T> of(List<T> data, Class<T> clazz, String sheetName) {
        return new ExcelSheet<>(data, clazz, sheetName);
    }

    // 新增:指定起始行的工厂方法
    public static <T> ExcelSheet<T> of(List<T> data, Class<T> clazz, String sheetName, int dataStartRow) {
        return new ExcelSheet<>(data, clazz, sheetName, dataStartRow);
    }

    public int getDataStartRow() {
        return dataStartRow;
    }
}

工具类

java
package org.me.e2;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import java.io.OutputStream;
import java.lang.reflect.Field;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
import java.util.stream.Collectors;

public class EasyExcelKit {

    /**
     * 一行代码导出!
     */
    public static <T> void export(OutputStream out, List<T> data, Class<T> clazz) throws Exception {
        try (SXSSFWorkbook workbook = new SXSSFWorkbook(500)) { // 保留500行在内存
            Sheet sheet = workbook.createSheet("数据");

            // 获取排序后的字段列表
            List<Field> fields = getOrderedFields(clazz);

            // 创建表头
            Row headerRow = sheet.createRow(0);
            for (int i = 0; i < fields.size(); i++) {
                Field field = fields.get(i);
                ExcelColumn ec = field.getAnnotation(ExcelColumn.class);
                headerRow.createCell(i).setCellValue(ec.value());
            }

            // 填充数据
            int rowIndex = 1;
            for (T item : data) {
                Row row = sheet.createRow(rowIndex++);
                for (int i = 0; i < fields.size(); i++) {
                    Field field = fields.get(i);
                    field.setAccessible(true);
                    Object value = field.get(item);
                    Cell cell = row.createCell(i);
                    if (value != null) {
                        cell.setCellValue(value.toString());
                    }
                }
            }

            // 输出
            workbook.write(out);
            workbook.dispose(); // 清理临时文件
        }
    }
    /**
     * 一行代码导出!
     */
    public static <T> void export(OutputStream out, List<T> data, Class<T> clazz, String sheetName) throws Exception {
        try (SXSSFWorkbook workbook = new SXSSFWorkbook(500)) { // 保留500行在内存
            Sheet sheet = workbook.createSheet(sheetName);

            // 获取排序后的字段列表
            List<Field> fields = getOrderedFields(clazz);

            // 创建表头
            Row headerRow = sheet.createRow(0);
            for (int i = 0; i < fields.size(); i++) {
                Field field = fields.get(i);
                ExcelColumn ec = field.getAnnotation(ExcelColumn.class);
                headerRow.createCell(i).setCellValue(ec.value());
            }

            // 填充数据
            int rowIndex = 1;
            for (T item : data) {
                Row row = sheet.createRow(rowIndex++);
                for (int i = 0; i < fields.size(); i++) {
                    Field field = fields.get(i);
                    field.setAccessible(true);
                    Object value = field.get(item);
                    Cell cell = row.createCell(i);
                    if (value != null) {
                        cell.setCellValue(value.toString());
                    }
                }
            }

            // 输出
            workbook.write(out);
            workbook.dispose(); // 清理临时文件
        }
    }
    /**
     * 一行代码导出多个 Sheet ✅
     */
    public static void export(OutputStream out, List<ExcelSheet<?>> sheets) throws Exception {
        try (SXSSFWorkbook workbook = new SXSSFWorkbook(500)) {
            for (ExcelSheet<?> sheetDef : sheets) {
                writeSheet(workbook, sheetDef);
            }
            workbook.write(out);
            workbook.dispose();
        }
    }


    private static void writeSheet(SXSSFWorkbook workbook, ExcelSheet<?> sheetDef) throws Exception {
        String name = sheetDef.getSheetName();
        Sheet sheet = workbook.createSheet(name);

        Class<?> clazz = sheetDef.getClazz();
        List<Field> fields = getOrderedFields(clazz);

        // === 新增:样式缓存 ===
        Map<String, CellStyle> styleCache = new HashMap<>();

        // 表头(无格式)
        Row headerRow = sheet.createRow(0);
        for (int i = 0; i < fields.size(); i++) {
            ExcelColumn ec = fields.get(i).getAnnotation(ExcelColumn.class);
            Cell cell = headerRow.createCell(i);

            String headerText = ec.value();
            cell.setCellValue(headerText); // 包含
            // 如果启用了 wrapText,设置样式
            if (ec.wrapText()) {
                CellStyle style = workbook.createCellStyle();
                style.setWrapText(true);      // ✅ 启用换行
                style.setAlignment(HorizontalAlignment.CENTER); // 可选:居中
                style.setVerticalAlignment(VerticalAlignment.CENTER);
                cell.setCellStyle(style);
            }
        }

        // 数据行
        int dataStartRow = sheetDef.getDataStartRow();
        int rowIndex = dataStartRow;
        for (Object item : sheetDef.getData()) {
            Row row = sheet.createRow(rowIndex++);
            for (int i = 0; i < fields.size(); i++) {
                Field field = fields.get(i);
                field.setAccessible(true);
                Object value = field.get(item);
                Cell cell = row.createCell(i);

                if (value == null) continue;

                ExcelColumn ec = field.getAnnotation(ExcelColumn.class);
                String dateFormat = ec.dateFormat().trim();
                String numberFormat = ec.numberFormat().trim();

                // ===== 格式化逻辑 =====
                if (!dateFormat.isEmpty()) {
                    // 处理日期类型
                    if (value instanceof java.util.Date || value instanceof java.time.LocalDateTime ||
                            value instanceof java.time.LocalDate || value instanceof java.time.LocalTime) {

                        CellStyle style = styleCache.computeIfAbsent(dateFormat, k -> {
                            CellStyle s = workbook.createCellStyle();
                            short format = workbook.createDataFormat().getFormat(k);
                            s.setDataFormat(format);
                            return s;
                        });
                        cell.setCellStyle(style);

                        if (value instanceof LocalDateTime) {
                            LocalDateTime ldt = (LocalDateTime) value;
                            java.util.Date date = java.util.Date.from(
                                    ldt.atZone(java.time.ZoneId.systemDefault()).toInstant()
                            );
                            cell.setCellValue(date);
                        } else if (value instanceof LocalDate) {
                            LocalDate ld = (LocalDate) value;
                            cell.setCellValue(java.sql.Date.valueOf(ld));
                        } else if (value instanceof LocalTime) {
                            // 单独时间不推荐,简化处理
                            cell.setCellValue(new java.util.Date());
                        } else if (value instanceof java.util.Date) {
                            cell.setCellValue((java.util.Date) value);
                        }
                        continue;
                    }
                }

                if (!numberFormat.isEmpty()) {
                    // 处理数字
                    if (value instanceof Number) {
                        CellStyle style = styleCache.computeIfAbsent(numberFormat, k -> {
                            CellStyle s = workbook.createCellStyle();
                            short format = workbook.createDataFormat().getFormat(k);
                            s.setDataFormat(format);
                            return s;
                        });
                        cell.setCellStyle(style);
                        cell.setCellValue(((Number) value).doubleValue());
                        continue;
                    }
                }

                // 默认:toString()
                cell.setCellValue(value.toString());
            }
        }
    }
    /**
     * 获取按 order 排序的字段列表
     */
    private static <T> List<Field> getOrderedFields(Class<T> clazz) {
        return Arrays.stream(clazz.getDeclaredFields())
                .filter(f -> f.isAnnotationPresent(ExcelColumn.class))
                .sorted(Comparator.comparingInt(f -> f.getAnnotation(ExcelColumn.class).order()))
                .collect(Collectors.toList());
    }
}
java
package org.me.e2;

import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

public class Test3 {
    public static void main(String[] args)  throws Exception{
        List<User> users = new ArrayList<>();
        User    user    = new User();
        user.setId(1L);
        user.setName("张三");
        user.setAge(18);
        user.setEmail("<EMAIL>");
        user.setCreateTime(new Date());
        users.add(user);
// 构造多个 Sheet
        List<ExcelSheet<?>> sheets = Arrays.asList(
                ExcelSheet.of(users, User.class, "用户列表",2),
                ExcelSheet.of(users, User.class, "订单明细")
        );
        try (FileOutputStream fos = new FileOutputStream("users.xlsx")) {
            EasyExcelKit.export(fos, sheets); // 👈 就这一行!

            System.out.println("导出完成!");
        }
    }
}

11. frontList dbList 判断 新增 修改,删除

java
package com.me.util;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 列表对比工具类
 * 根据唯一键判断记录的新增、修改、删除状态
 * 修改定义:前后端都存在相同唯一键的记录(不比较具体内容)
 */
public class ListComparatorPlus {
    
    /**
     * 对比两个列表,根据唯一键判断新增、修改、删除
     * 
     * @param frontList 前端列表
     * @param dbList 数据库列表  
     * @param uniqueKey 唯一键定义
     * @param <T> 对象类型
     * @return 对比结果(包含新增、修改、删除)
     */
    public static <T> ListResult<T> compareLists(
            List<T> frontList, 
            List<T> dbList, 
            UniqueKey<T> uniqueKey) {

        ListResult<T> result = new ListResult<>();
        
        // 处理空值情况
        if ((frontList == null || frontList.isEmpty()) && (dbList == null || dbList.isEmpty())) {
            // 情况1: 两边都为空
            return result;
        }
        
        if (frontList == null || frontList.isEmpty()) {
            // 情况2: 前端为空,数据库非空 -> 全部删除
            result.getDeleted().addAll(dbList);
            return result;
        }
        
        if (dbList == null || dbList.isEmpty()) {
            // 情况3: 数据库为空,前端非空 -> 全部新增
            result.getAdded().addAll(frontList);
            return result;
        }
        
        // 情况4: 两列表均非空,执行正常对比逻辑
        return performFullComparison(frontList, dbList, uniqueKey);
    }
    
    /**
     * 执行完整的列表对比逻辑
     * 根据唯一键判断新增、修改、删除(不比较具体内容)
     */
    private static <T> ListResult<T> performFullComparison(
            List<T> frontList, 
            List<T> dbList, 
            UniqueKey<T> uniqueKey) {
        
        ListResult<T> result = new ListResult<>();
        
        // 构建索引Map,提高查找效率
        Map<Object, T> frontMap = buildIndexMap(frontList, uniqueKey);
        Map<Object, T> dbMap = buildIndexMap(dbList, uniqueKey);
        
        // 遍历前端列表,判断新增或修改
        for (T frontObj : frontList) {
            Object key = uniqueKey.extractKey(frontObj);
            if (!dbMap.containsKey(key)) {
                // 唯一键在数据库中找不到 -> 新增
                result.getAdded().add(frontObj);
            } else {
                // 唯一键在数据库中找到 -> 修改(存在即可,不比较内容)
                T dbObj = dbMap.get(key);
                result.getModified().add(frontObj);
            }
        }
        
        // 遍历数据库列表,判断删除
        for (T dbObj : dbList) {
            Object key = uniqueKey.extractKey(dbObj);
            if (!frontMap.containsKey(key)) {
                // 唯一键在前端列表中找不到 -> 删除
                result.getDeleted().add(dbObj);
            }
            // 如果找到了说明是修改,已经在上面处理过了
        }
        
        return result;
    }
    
    /**
     * 构建唯一键索引Map
     */
    private static <T> Map<Object, T> buildIndexMap(List<T> list, UniqueKey<T> uniqueKey) {
        return list.stream()
                .filter(Objects::nonNull)
                .collect(Collectors.toMap(
                    uniqueKey::extractKey,
                    Function.identity(),
                    (existing, replacement) -> existing  // 处理重复键的情况
                ));
    }
    
    /**
     * 创建单字段唯一键
     */
    public static <T, F> UniqueKey<T> singleField(Function<T, F> keyExtractor) {
        return new UniqueKey.SingleFieldKey<>(keyExtractor);
    }
    
    /**
     * 创建多字段组合唯一键
     */
    @SafeVarargs
    public static <T> UniqueKey<T> compositeKey(Function<T, Object>... keyExtractors) {
        return new UniqueKey.CompositeKey<>(keyExtractors);
    }
    
    /**
     * 快速对比方法
     */
    public static <T> ListResult<T> quickCompare(
            List<T> frontList, 
            List<T> dbList, 
            UniqueKey<T> uniqueKey) {
        return compareLists(frontList, dbList, uniqueKey);
    }
}

返回结果

java
package com.me.util;

import lombok.Data;

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

/**
 * 列表对比结果
 * 包含新增、修改、删除的记录分类
 * 
 * @param <T> 对象类型
 */
@Data
public class ListResult<T> {

    /**
     * 新增的记录列表
     */
    private final List<T> added;

    /**
     * 修改的记录列表(包含旧值和新值)
     */
    private final List<T> modified;

    /**
     * 删除的记录列表
     */
    private final List<T> deleted;

    public ListResult() {
        this.added = new ArrayList<>();
        this.modified = new ArrayList<>();
        this.deleted = new ArrayList<>();
    }

    public ListResult(List<T> added, List<T> modified, List<T> deleted) {
        this.added = added;
        this.modified = modified;
        this.deleted = deleted;
    }

    // Getters
    public List<T> getAdded() {
        return added;
    }

    public List<T> getModified() {
        return modified;
    }

    public List<T> getDeleted() {
        return deleted;
    }
    
    /**
     * 判断是否有任何变更
     * @return true表示有变更,false表示无变更
     */
    public boolean hasChanges() {
        return !added.isEmpty() || !modified.isEmpty() || !deleted.isEmpty();
    }
    
    /**
     * 获取变更总数
     * @return 变更记录总数
     */
    public int getTotalChanges() {
        return added.size() + modified.size() + deleted.size();
    }
    
    @Override
    public String toString() {
        return "ListComparisonResult{" +
                "added=" + added.size() +
                ", modified=" + modified.size() +
                ", deleted=" + deleted.size() +
                ", totalChanges=" + getTotalChanges() +
                '}';
    }
    

}

唯一键

java
package com.me.util;

import java.io.Serializable;
import java.util.List;
import java.util.Objects;

/**
 * 唯一键接口
 * 用于定义对象的唯一标识,支持单字段或多字段组合
 * 
 * @param <T> 对象类型
 */
public interface UniqueKey<T> extends Serializable {
    
    /**
     * 从对象中提取唯一键值
     * @param obj 对象实例
     * @return 唯一键值
     */
    Object extractKey(T obj);
    
    /**
     * 单字段唯一键实现
     */
    class SingleFieldKey<T, F> implements UniqueKey<T> {
        private final java.util.function.Function<T, F> keyExtractor;
        
        public SingleFieldKey(java.util.function.Function<T, F> keyExtractor) {
            this.keyExtractor = keyExtractor;
        }
        
        @Override
        public Object extractKey(T obj) {
            return keyExtractor.apply(obj);
        }
        
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            SingleFieldKey<?, ?> that = (SingleFieldKey<?, ?>) o;
            return Objects.equals(keyExtractor, that.keyExtractor);
        }
        
        @Override
        public int hashCode() {
            return Objects.hash(keyExtractor);
        }
    }
    
    /**
     * 多字段组合唯一键实现
     */
    class CompositeKey<T> implements UniqueKey<T> {
        private final List<java.util.function.Function<T, Object>> keyExtractors;
        
        @SafeVarargs
        public CompositeKey(java.util.function.Function<T, Object>... extractors) {
            this.keyExtractors = java.util.Arrays.asList(extractors);
        }
        
        @Override
        public Object extractKey(T obj) {
            if (obj == null) {
                return null;
            }
            
            // 创建组合键对象
            return new KeyTuple(keyExtractors.stream()
                .map(extractor -> extractor.apply(obj))
                .toArray());
        }
        
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            CompositeKey<?> that = (CompositeKey<?>) o;
            return Objects.equals(keyExtractors, that.keyExtractors);
        }
        
        @Override
        public int hashCode() {
            return Objects.hash(keyExtractors);
        }
        
        /**
         * 组合键元组类
         */
        private static class KeyTuple implements Serializable {
            private final Object[] values;
            
            public KeyTuple(Object... values) {
                this.values = values;
            }
            
            @Override
            public boolean equals(Object o) {
                if (this == o) return true;
                if (o == null || getClass() != o.getClass()) return false;
                KeyTuple keyTuple = (KeyTuple) o;
                return Objects.deepEquals(values, keyTuple.values);
            }
            
            @Override
            public int hashCode() {
                return Objects.hash(values);
            }
            
            @Override
            public String toString() {
                return "KeyTuple" + java.util.Arrays.toString(values);
            }
        }
    }
}

test

java
package com.me;

import com.me.dto.AccountDto;
import com.me.util.ListComparator;
import com.me.util.ListComparatorPlus;
import com.me.util.ListComparisonResult;
import com.me.util.ListResult;

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

public class Tes3 {
    public static void main(String[] args) {
        List<AccountDto> frontList = frontList();
        List<AccountDto> dbList = dbList();
        ListResult<AccountDto> res = ListComparatorPlus.quickCompare
                (frontList, dbList, ListComparator.compositeKey(AccountDto::getAccountNo, AccountDto::getCurrency));
//        System.out.println(res.getAdded());
//        System.out.println(res.getModified());
        System.out.println(res.getDeleted());
    }

    public static List<AccountDto> frontList() {
        List<AccountDto> frontList = new ArrayList<>();
        frontList.add(new AccountDto("1001", "CNY", "100.00", "中国工商银行"));
        frontList.add(new AccountDto("1001", "USD", "100.00", "中国工商银行"));
        return frontList;
    }
    public static List<AccountDto> dbList() {
        List<AccountDto> frontList = new ArrayList<>();
        frontList.add(new AccountDto("1001", "CNY", "100.00", "中国工商银行"));
        frontList.add(new AccountDto("1002", "USD", "100.00", "北京银行"));
        frontList.add(new AccountDto("1003", "USD", "100.00", "北京银行美国分行"));
        return frontList;
    }
}

12. jwt 生成 token 设置有效期,获取 claims 验证是否有效

java
package com.me.util;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * JWT Utility class for creating and validating JWT tokens
 * Implements JWT functionality without third-party dependencies
 */
public class JwtUtil {
    
    private static final String ALGORITHM = "HmacSHA256";
    private static final String DEFAULT_SECRET = "my-secret-key-for-jwt-signing-should-be-secure";
    
    /**
     * Create a JWT token with default expiration (24 hours = 86400 seconds)
     * @param claims Map of claims to include in the token
     * @return JWT token string
     */
    public static String createToken(Map<String, Object> claims) {
        return createToken(claims, 86400); // Default 24 hours in seconds
    }
    
    /**
     * Create a JWT token with custom expiration in seconds
     * @param claims Map of claims to include in the token
     * @param expireSeconds Number of seconds until token expires
     * @return JWT token string
     */
    public static String createToken(Map<String, Object> claims, int expireSeconds) {
        return createToken(claims, expireSeconds, DEFAULT_SECRET);
    }
    
    /**
     * Create a JWT token with custom expiration and secret
     * @param claims Map of claims to include in the token
     * @param expireSeconds Number of seconds until token expires
     * @param secret Secret key for signing
     * @return JWT token string
     */
    public static String createToken(Map<String, Object> claims, int expireSeconds, String secret) {
        // Add standard claims
        Map<String, Object> payload = new HashMap<>(claims);
        long now = Instant.now().getEpochSecond();
        payload.put("iat", now); // Issued at
        payload.put("exp", now + expireSeconds); // Expiration time in seconds
        payload.put("jti", UUID.randomUUID().toString()); // JWT ID
        
        // Create header
        String header = "{\"alg\":\"HS256\",\"typ\":\"JWT\"}";
        String encodedHeader = base64UrlEncode(header.getBytes(StandardCharsets.UTF_8));
        
        // Create payload
        String payloadJson = mapToJson(payload);
        String encodedPayload = base64UrlEncode(payloadJson.getBytes(StandardCharsets.UTF_8));
        
        // Create signature
        String dataToSign = encodedHeader + "." + encodedPayload;
        String signature = hmacSha256(dataToSign, secret);
        String encodedSignature = base64UrlEncode(signature.getBytes(StandardCharsets.UTF_8));
        
        // Combine all parts
        return encodedHeader + "." + encodedPayload + "." + encodedSignature;
    }
    
    /**
     * Validate JWT token
     * @param token JWT token to validate
     * @return true if valid, false otherwise
     */
    public static boolean validateToken(String token) {
        return validateToken(token, DEFAULT_SECRET);
    }
    
    /**
     * Validate JWT token with custom secret
     * @param token JWT token to validate
     * @param secret Secret key for verification
     * @return true if valid, false otherwise
     */
    public static boolean validateToken(String token, String secret) {
        try {
            if (token == null || token.isEmpty()) {
                return false;
            }
            
            String[] parts = token.split("\\.");
            if (parts.length != 3) {
                return false;
            }
            
            String header = parts[0];
            String payload = parts[1];
            String signature = parts[2];
            
            // Verify signature
            String dataToSign = header + "." + payload;
            String expectedSignature = hmacSha256(dataToSign, secret);
            String encodedExpectedSignature = base64UrlEncode(expectedSignature.getBytes(StandardCharsets.UTF_8));
            
            if (!signature.equals(encodedExpectedSignature)) {
                return false;
            }
            
            // Check expiration
            Map<String, Object> claims = getClaimsFromToken(token);
            Long exp = (Long) claims.get("exp");
            if (exp != null && Instant.now().getEpochSecond() > exp) {
                return false; // Token expired
            }
            
            return true;
        } catch (Exception e) {
            return false;
        }
    }
    
    /**
     * Extract claims from JWT token
     * @param token JWT token
     * @return Map of claims
     */
    public static Map<String, Object> getClaimsFromToken(String token) {
        try {
            String[] parts = token.split("\\.");
            if (parts.length != 3) {
                throw new IllegalArgumentException("Invalid token format");
            }
            
            String payload = parts[1];
            byte[] decodedPayload = base64UrlDecode(payload);
            String payloadJson = new String(decodedPayload, StandardCharsets.UTF_8);
            
            return jsonToMap(payloadJson);
        } catch (Exception e) {
            throw new RuntimeException("Failed to extract claims from token", e);
        }
    }
    
    /**
     * Check if token is expired
     * @param token JWT token
     * @return true if expired, false otherwise
     */
    public static boolean isTokenExpired(String token) {
        try {
            Map<String, Object> claims = getClaimsFromToken(token);
            Long exp = (Long) claims.get("exp");
            return exp != null && Instant.now().getEpochSecond() > exp;
        } catch (Exception e) {
            return true; // Treat invalid tokens as expired
        }
    }
    
    /**
     * Get remaining time until token expires
     * @param token JWT token
     * @return Remaining seconds until expiration, negative if expired
     */
    public static long getRemainingTime(String token) {
        try {
            Map<String, Object> claims = getClaimsFromToken(token);
            Long exp = (Long) claims.get("exp");
            if (exp == null) {
                return -1; // No expiration set
            }
            return exp - Instant.now().getEpochSecond();
        } catch (Exception e) {
            return -1; // Invalid token
        }
    }
    
    /**
     * HMAC SHA256 implementation
     */
    public static String hmacSha256(String data, String secret) {
        try {
            Mac mac = Mac.getInstance(ALGORITHM);
            SecretKeySpec secretKeySpec = new SecretKeySpec(
                secret.getBytes(StandardCharsets.UTF_8), ALGORITHM);
            mac.init(secretKeySpec);
            byte[] hash = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
            return Base64.getEncoder().encodeToString(hash);
        } catch (Exception e) {
            throw new RuntimeException("Failed to generate HMAC", e);
        }
    }
    
    /**
     * Base64 URL encode (RFC 7515 compliant)
     */
    public static String base64UrlEncode(byte[] data) {
        return Base64.getUrlEncoder().withoutPadding().encodeToString(data);
    }
    
    /**
     * Base64 URL decode (RFC 7515 compliant)
     */
    public static byte[] base64UrlDecode(String data) {
        return Base64.getUrlDecoder().decode(data);
    }
    
    /**
     * Simple JSON to Map converter (basic implementation)
     */
    @SuppressWarnings("unchecked")
    private static Map<String, Object> jsonToMap(String json) {
        Map<String, Object> result = new HashMap<>();
        // Remove braces
        String content = json.trim();
        if (content.startsWith("{") && content.endsWith("}")) {
            content = content.substring(1, content.length() - 1);
        }
        
        // Split by comma, but be careful with nested objects
        String[] pairs = content.split(",");
        for (String pair : pairs) {
            String[] keyValue = pair.split(":", 2);
            if (keyValue.length == 2) {
                String key = keyValue[0].trim().replaceAll("\"", "");
                String value = keyValue[1].trim();
                
                // Handle different value types
                if (value.startsWith("\"") && value.endsWith("\"")) {
                    // String value
                    result.put(key, value.substring(1, value.length() - 1));
                } else if (value.matches("-?\\d+")) {
                    // Integer
                    result.put(key, Long.parseLong(value));
                } else if (value.matches("-?\\d+\\.\\d+")) {
                    // Double
                    result.put(key, Double.parseDouble(value));
                } else if ("true".equals(value)) {
                    result.put(key, true);
                } else if ("false".equals(value)) {
                    result.put(key, false);
                } else if ("null".equals(value)) {
                    result.put(key, null);
                }
            }
        }
        return result;
    }
    
    /**
     * Simple Map to JSON converter (basic implementation)
     */
    private static String mapToJson(Map<String, Object> map) {
        StringBuilder sb = new StringBuilder();
        sb.append("{");
        boolean first = true;
        
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            if (!first) {
                sb.append(",");
            }
            sb.append("\"").append(entry.getKey()).append("\":");
            
            Object value = entry.getValue();
            if (value instanceof String) {
                sb.append("\"").append(value).append("\"");
            } else {
                sb.append(value);
            }
            first = false;
        }
        sb.append("}");
        return sb.toString();
    }
    
    /**
     * Set custom secret key
     * @param secret New secret key
     */
    public static void setSecret(String secret) {
        // In a real application, you'd want to store this securely
        // This is a simplified implementation
        System.setProperty("jwt.secret", secret);
    }
    
    /**
     * Get current secret key
     * @return Current secret key
     */
    public static String getSecret() {
        return System.getProperty("jwt.secret", DEFAULT_SECRET);
    }
}