多线程的协调控制顺序
CountDownLatch T1 T2 T3 按照顺序执行
java
package com.me.test;
import java.util.concurrent.CountDownLatch;
/**
* T1 T2 T3 顺序执行
*
* T1 执行完毕后 调用 countDownLatch.countDown(); 通知 T2
* T2 一致处于阻塞态 countDownLatch.await(); 当T1完成后 自动执行
* 执行完毕后 调用 countDownLatch2.countDown();
*
* T3 一致处于阻塞态 countDownLatch2.await(); 当T2完成后 自动执行
*
*
*
*/
public class Test1123 {
public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(1);
CountDownLatch countDownLatch2 = new CountDownLatch(1);
Thread thread = new Thread(()->{
System.out.println(Thread.currentThread().getName()+"hello world");
countDownLatch.countDown();
}, "thread1");
Thread thread2 = new Thread(()->{
try {
countDownLatch.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName()+"hello world2");
countDownLatch2.countDown();
}, "thread2");
Thread thread3 = new Thread(()->{
try {
countDownLatch2.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName()+"hello world3");
}, "thread3");
thread.start();
thread2.start();
thread3.start();
}
}ReentrantLock Condition 接口实现
java
package com.me.test;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
*
* 多线程同步 顺序 基于 ReentrantLock Condition
* 有几个线程 就有几个Condition 再加一个标志位 来控制
* 需要使用循环控制线程的等待与唤醒
*
**/
public class TestThread {
static ReentrantLock lock = new ReentrantLock();
static Condition condition = lock.newCondition();
static Condition condition2 = lock.newCondition();
static Condition condition3 = lock.newCondition();
static int flag = 0; // 0-1 1-2 2-3
public static void main(String[] args) {
Thread t1 = new Thread(()->{
try {
lock.lock();
while (flag != 0){
condition.await();
}
System.out.println(Thread.currentThread().getName()+"hello world");
flag = 1;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}, "t1");
Thread t2 = new Thread(()->{
try {
lock.lock();
while (flag != 1){
condition2.await();
}
System.out.println(Thread.currentThread().getName()+"hello world");
flag = 2;
condition3.signal();
} catch (Exception e) {
throw new RuntimeException(e);
}finally {
lock.unlock();
}
}, "t2");
Thread t3 = new Thread(()->{
try {
lock.lock();
while (flag != 2){
condition3.await();
}
System.out.println(Thread.currentThread().getName()+"hello world");
flag = 0;
condition.signal();
}catch ( Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}, "t3");
t1.start();
t2.start();
t3.start();
}
}synchronized wait notifyAll
java
package com.me.test;
/*
* 基于 synchronized wait notifyAll
*
* */
public class TestT23 {
private int flag = 0;
public synchronized void test() throws InterruptedException {
while (flag!=0){
this.wait();
}
System.out.println("thread:"+Thread.currentThread().getName()+"count:"+flag);
flag = 1;
this.notifyAll();
}
public synchronized void test2() throws InterruptedException {
while (flag!=1){
this.wait();
}
System.out.println("thread:"+Thread.currentThread().getName()+"count:"+flag);
flag = 2;
this.notifyAll();
}
public synchronized void test3() throws InterruptedException {
while (flag!=2){
this.wait();
}
System.out.println("thread:"+Thread.currentThread().getName()+"count:"+flag);
flag = 1;
this.notifyAll();
}
public static void main(String[] args) {
TestT23 testT23 = new TestT23();
Thread t1 = new Thread(()->{
try {
testT23.test();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "t1");
Thread t2 = new Thread(()->{
try {
testT23.test2();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "t2");
Thread t3 = new Thread(()->{
try {
testT23.test3();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "t3");
t1.start();
t2.start();
t3.start();
}
}配置加密
BeanPostProcessor 基于这个拓展点实现
java
package com.me.config;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
@Configuration
@Data
public class IDMSProperties {
@Value("${idms.data}")
private String data;
}java
package com.me.config;
import com.me.util.AES;
import lombok.Data;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Configuration;
@Configuration
@Data
public class IDMSConfig implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof IDMSProperties){
IDMSProperties idmsConfig = (IDMSProperties) bean;
System.out.println("原始数据:"+idmsConfig.getData());
String decrypt = null;
try {
decrypt = AES.Decrypt(idmsConfig.getData());
} catch (Exception e) {
throw new RuntimeException(e);
}
idmsConfig.setData(decrypt);
System.out.println("解密数据:"+decrypt);
}
return bean;
}
}InitializingBean 方式
java
package com.me.config;
import com.me.util.AESUtil;
import lombok.Data;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
@Configuration
@Data
public class SecuretData implements InitializingBean {
@Value("${sec.data}")
private String secData ;
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("SEC_DATA front :"+secData);
secData = AESUtil.decrypt(secData);
System.out.println("SEC_DATA:"+secData);
}
}多字段分组
java
package com.me.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.time.LocalDate;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class BillDTO {
private Long id;
private String billNo; // 票据号码
private String subBillRange; // 子票区间
private BigDecimal amount; // 票据金额
private LocalDate dueDate; // 到期日
}分组键
java
package com.me.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class BillKey {
private String billNo;
private String subBillRange;
@Override
public String toString() {
return String.format("票据号码=%s, 子票区间=%s", billNo, subBillRange);
}
}分组方法实现 三种
java
package com.me;
import com.me.dto.BillDTO;
import com.me.dto.BillKey;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class Test {
public static void main(String[] args) throws Exception {
// 构建测试数据
List<BillDTO> billList = createTestData();
// 方式1:按票据号码+子票区间分组(复合键使用List)
Map<List<String>, List<BillDTO>> groupByBillNoAndSubRange = billList.stream()
.collect(Collectors.groupingBy(
bill -> Arrays.asList(bill.getBillNo(), bill.getSubBillRange()),
Collectors.toList()
));
// 方式2:按票据号码+子票区间分组(复合键使用自定义对象)
Map<BillKey, List<BillDTO>> groupByCustomKey = billList.stream()
.collect(Collectors.groupingBy(
bill -> new BillKey(bill.getBillNo(), bill.getSubBillRange()),
Collectors.toList()
));
// 输出分组结果
System.out.println("=== 按票据号码+子票区间分组(List复合键) ===");
groupByBillNoAndSubRange.forEach((key, bills) -> {
System.out.printf("分组键:票据号码=%s, 子票区间=%s\n", key.get(0), key.get(1));
bills.forEach(bill -> System.out.printf(" - %s\n", bill));
});
System.out.println("\n=== 按票据号码+子票区间分组(自定义Key) ===");
groupByCustomKey.forEach((key, bills) -> {
System.out.printf("分组键:%s\n", key);
bills.forEach(bill -> System.out.printf(" - %s\n", bill));
});
}
// 构建测试数据
private static List<BillDTO> createTestData() {
return Arrays.asList(
new BillDTO(1L, "BJ2025001", "001-010", new BigDecimal("10000.00"), LocalDate.of(2025, 12, 31)),
new BillDTO(2L, "BJ2025001", "001-010", new BigDecimal("15000.00"), LocalDate.of(2025, 12, 31)),
new BillDTO(3L, "BJ2025001", "011-020", new BigDecimal("20000.00"), LocalDate.of(2026, 1, 15)),
new BillDTO(4L, "BJ2025002", "001-010", new BigDecimal("5000.00"), LocalDate.of(2025, 11, 30)),
new BillDTO(5L, "BJ2025002", "001-010", new BigDecimal("8000.00"), LocalDate.of(2025, 11, 30)),
new BillDTO(6L, "BJ2025003", "021-030", new BigDecimal("30000.00"), LocalDate.of(2026, 2, 28)),
new BillDTO(7L, "BJ2025001", "001-010", new BigDecimal("12000.00"), LocalDate.of(2025, 12, 31)),
new BillDTO(8L, "BJ2025003", "021-030", new BigDecimal("25000.00"), LocalDate.of(2026, 2, 28))
);
}
}java
package com.me;
import com.me.dto.BillDTO;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class Test2 {
public static void main(String[] args) {
// 创建测试数据
List<BillDTO> billList = createTestData();
System.out.println("原始数据:");
billList.forEach(System.out::println);
System.out.println("=====================================");
// 使用Stream按照票据号码和子票区间进行分组
Map<String, List<BillDTO>> groupedBills = billList.stream()
.collect(Collectors.groupingBy(bill ->
bill.getBillNo() + "_" + bill.getSubBillRange()
));
// 打印分组结果
System.out.println("分组结果:");
groupedBills.forEach((key, value) -> {
System.out.println("分组键: " + key);
value.forEach(System.out::println);
System.out.println("---");
});
// System.out.println("=====================================");
//
// // 更复杂的分组:使用自定义分组键对象
// Map<GroupKey, List<BillDTO>> groupedByKeyObject = billList.stream()
// .collect(Collectors.groupingBy(bill ->
// new GroupKey(bill.getBillNumber(), bill.getSubTicketRange())
// ));
//
// System.out.println("使用自定义分组键对象的分组结果:");
// groupedByKeyObject.forEach((key, value) -> {
// System.out.println("分组键: " + key);
// System.out.println("该组票据总金额: " +
// value.stream().mapToDouble(BillDTO::getBillAmount).sum());
// value.forEach(System.out::println);
// System.out.println("---");
// });
//
// System.out.println("=====================================");
//
// // 分组并统计每个组的汇总信息
// Map<String, Double> amountSummary = billList.stream()
// .collect(Collectors.groupingBy(
// bill -> bill.getBillNo() + "_" + bill.getSubBillRange(),
// Collectors.summingDouble(BillDTO::getBillAmount)
// ));
//
// System.out.println("每个分组的金额汇总:");
// amountSummary.forEach((key, amount) -> {
// System.out.println("分组: " + key + ", 总金额: " + amount);
// });
}
private static List<BillDTO> createTestData() {
return Arrays.asList(
new BillDTO(1L, "BJ2025001", "001-010", new BigDecimal("10000.00"), LocalDate.of(2025, 12, 31)),
new BillDTO(2L, "BJ2025001", "001-010", new BigDecimal("15000.00"), LocalDate.of(2025, 12, 31)),
new BillDTO(3L, "BJ2025001", "011-020", new BigDecimal("20000.00"), LocalDate.of(2026, 1, 15)),
new BillDTO(4L, "BJ2025002", "001-010", new BigDecimal("5000.00"), LocalDate.of(2025, 11, 30)),
new BillDTO(5L, "BJ2025002", "001-010", new BigDecimal("8000.00"), LocalDate.of(2025, 11, 30)),
new BillDTO(6L, "BJ2025003", "021-030", new BigDecimal("30000.00"), LocalDate.of(2026, 2, 28)),
new BillDTO(7L, "BJ2025001", "001-010", new BigDecimal("12000.00"), LocalDate.of(2025, 12, 31)),
new BillDTO(8L, "BJ2025003", "021-030", new BigDecimal("25000.00"), LocalDate.of(2026, 2, 28))
);
}
}校验票据是否重复
java
package com.me.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.time.LocalDate;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class BillDTO {
/**
* 票据号码
*/
private String billNo;
/**
* 票据区间开始
*/
private Long rangeStart;
/**
* 票据区间结束
*/
private Long rangeEnd;
/**
* 票据金额
*/
private BigDecimal amount;
/**
* 到期日
*/
private LocalDate dueDate;
}java
package com.me.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class BillValidationResult {
/**
* 校验通过的票据
*/
private List<BillDTO> validBills;
/**
* 校验不通过的票据
*/
private List<BillDTO> invalidBills;
}校验工具 核心
java
package com.me;
import com.me.dto.BillDTO;
import com.me.dto.BillValidationResult;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class BillValidator {
/**
* 校验票据列表,返回通过和不通过的票据
*
* @param billList 待校验的票据列表
* @return 校验结果
*/
public BillValidationResult validateBills(List<BillDTO> billList) {
if (billList == null || billList.isEmpty()) {
return new BillValidationResult(Collections.emptyList(), Collections.emptyList());
}
// 按票据号码分组
Map<String, List<BillDTO>> billMap = billList.stream()
.collect(Collectors.groupingBy(BillDTO::getBillNo));
List<BillDTO> validBills = new ArrayList<>();
List<BillDTO> invalidBills = new ArrayList<>();
// 遍历每个票据号码对应的票据列表
for (Map.Entry<String, List<BillDTO>> entry : billMap.entrySet()) {
List<BillDTO> sameNoBills = entry.getValue();
// 按区间开始排序,方便检查交叉
sameNoBills.sort(Comparator.comparing(BillDTO::getRangeStart));
// 存储已通过校验的区间(用于后续交叉检查)
List<BillDTO> passedBills = new ArrayList<>();
for (BillDTO currentBill : sameNoBills) {
// 检查当前票据区间是否与已通过的票据区间有交叉
boolean isConflict = passedBills.stream()
.anyMatch(passedBill -> isRangeOverlap(passedBill.getRangeStart(), passedBill.getRangeEnd(),
currentBill.getRangeStart(), currentBill.getRangeEnd()));
if (isConflict) {
invalidBills.add(currentBill);
} else {
validBills.add(currentBill);
passedBills.add(currentBill);
}
}
}
return new BillValidationResult(validBills, invalidBills);
}
/**
* 判断两个区间是否有交叉
* 区间规则:[start1, end1] 和 [start2, end2]
*
* @param start1 第一个区间开始
* @param end1 第一个区间结束
* @param start2 第二个区间开始
* @param end2 第二个区间结束
* @return 是否交叉
*/
private boolean isRangeOverlap(Long start1, Long end1, Long start2, Long end2) {
// 区间不交叉的条件:第一个区间在第二个区间之后,或第一个区间在第二个区间之前
return !(end1 < start2 || start1 > end2);
}
}java
package com.me.dto;
import com.me.BillValidator;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.List;
public class Main {
public static void main(String[] args) {
// 构造测试数据
BillDTO bill1 = new BillDTO("NO001", 1L, 10L, new BigDecimal("1000"), LocalDate.of(2025, 12, 31));
BillDTO bill2 = new BillDTO("NO001", 11L, 20L, new BigDecimal("2000"), LocalDate.of(2025, 12, 31));
BillDTO bill3 = new BillDTO("NO001", 15L, 25L, new BigDecimal("1500"), LocalDate.of(2025, 12, 31)); // 与bill2交叉
BillDTO bill4 = new BillDTO("NO002", 1L, 5L, new BigDecimal("500"), LocalDate.of(2025, 11, 30));
List<BillDTO> billList = Arrays.asList(bill1, bill2, bill3, bill4);
// 执行校验
BillValidator validator = new BillValidator();
BillValidationResult result = validator.validateBills(billList);
// 输出结果
System.out.println("校验通过的票据:");
result.getValidBills().forEach(System.out::println);
System.out.println("\n校验不通过的票据:");
result.getInvalidBills().forEach(System.out::println);
}
}参数校验
校验规则 参数名称不能重复 参数的值类型要匹配
自定义异常类
java
public class DtoValidationException extends RuntimeException {
public DtoValidationException(String message) {
super(message);
}
}DTO 类定义
java
import lombok.Data;
@Data
public class ParamDto {
/** 参数编码 */
private String code;
/** 参数名称 */
private String name;
/** 参数默认值 */
private String defaultValue;
/** 参数类型(日期/数字/字符串/正则表达式) */
private String type;
}校验工具类
java
package com.me.param;
import java.time.LocalDate;
import java.time.format.DateTimeParseException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
public class DtoValidator {
// 支持的参数类型
private static final Set<String> SUPPORTED_TYPES = new HashSet<>();
static {
SUPPORTED_TYPES.add("字符串");
SUPPORTED_TYPES.add("数字");
SUPPORTED_TYPES.add("日期");
SUPPORTED_TYPES.add("正则表达式");
}
// 数字正则表达式
private static final String NUMBER_REGEX = "^-?\\d+(\\.\\d+)?$";
// 简单日期正则表达式(yyyy-MM-dd)
private static final String DATE_REGEX = "^\\d{4}-\\d{2}-\\d{2}$";
/**
* 校验DTO列表
* @param dtoList DTO列表
*/
public static void validateDtoList(List<ParamDto> dtoList) {
if (dtoList == null || dtoList.isEmpty()) {
throw new DtoValidationException("DTO列表不能为空");
}
// 校验参数code重复
validateCodeDuplication(dtoList);
// 逐个校验DTO
for (ParamDto dto : dtoList) {
validateSingleDto(dto);
}
}
/**
* 校验参数code是否重复
*/
private static void validateCodeDuplication(List<ParamDto> dtoList) {
Set<String> codeSet = new HashSet<>();
for (ParamDto dto : dtoList) {
if (dto.getCode() == null || dto.getCode().trim().isEmpty()) {
throw new DtoValidationException("参数code不能为空");
}
String code = dto.getCode().trim();
if (codeSet.contains(code)) {
throw new DtoValidationException("参数code重复: " + code);
}
codeSet.add(code);
}
}
/**
* 校验单个DTO
*/
private static void validateSingleDto(ParamDto dto) {
// 校验参数类型
validateParamType(dto);
// 校验默认值的类型匹配
validateDefaultValueType(dto);
// 校验正则表达式(如果类型是正则表达式)
if ("正则表达式".equals(dto.getType())) {
validateRegex(dto);
}
}
/**
* 校验参数类型是否合法
*/
private static void validateParamType(ParamDto dto) {
String type = dto.getType();
if (type == null || type.trim().isEmpty()) {
throw new DtoValidationException("参数类型不能为空,code: " + dto.getCode());
}
type = type.trim();
if (!SUPPORTED_TYPES.contains(type)) {
throw new DtoValidationException("不支持的参数类型: " + type + ",code: " + dto.getCode());
}
}
/**
* 校验默认值与类型是否匹配
*/
private static void validateDefaultValueType(ParamDto dto) {
String type = dto.getType().trim();
String defaultValue = dto.getDefaultValue();
// 默认值为空的情况处理
if (defaultValue == null || defaultValue.trim().isEmpty()) {
throw new DtoValidationException("参数默认值不能为空,code: " + dto.getCode());
}
defaultValue = defaultValue.trim();
switch (type) {
case "数字":
validateNumberValue(dto, defaultValue);
break;
case "日期":
validateDateValue(dto, defaultValue);
break;
case "字符串":
// 字符串类型默认值不需要特殊校验,非空即可
break;
case "正则表达式":
// 正则表达式的格式校验在专门的方法中处理
break;
default:
throw new DtoValidationException("未知的参数类型: " + type + ",code: " + dto.getCode());
}
}
/**
* 校验数字类型的默认值
*/
private static void validateNumberValue(ParamDto dto, String value) {
if (!value.matches(NUMBER_REGEX)) {
throw new DtoValidationException("参数类型为数字,但默认值不是合法数字: " + value + ",code: " + dto.getCode());
}
}
/**
* 校验日期类型的默认值(yyyy-MM-dd格式)
*/
private static void validateDateValue(ParamDto dto, String value) {
if (!value.matches(DATE_REGEX)) {
throw new DtoValidationException("参数类型为日期,但默认值格式不正确(应为yyyy-MM-dd): " + value + ",code: " + dto.getCode());
}
try {
LocalDate.parse(value); // 进一步验证日期的合法性(如2月30日会失败)
} catch (DateTimeParseException e) {
throw new DtoValidationException("参数类型为日期,但默认值不是合法日期: " + value + ",code: " + dto.getCode());
}
}
/**
* 校验正则表达式是否正确
*/
private static void validateRegex(ParamDto dto) {
String regex = dto.getDefaultValue().trim();
try {
Pattern.compile(regex);
} catch (PatternSyntaxException e) {
throw new DtoValidationException("正则表达式格式错误,code: " + dto.getCode() + ",错误: " + e.getMessage());
}
}
}使用示例
java
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
// 创建测试数据
List<ParamDto> dtoList = new ArrayList<>();
ParamDto dto1 = new ParamDto();
dto1.setCode("CODE001");
dto1.setName("参数1");
dto1.setType("字符串");
dto1.setDefaultValue("默认值");
dtoList.add(dto1);
ParamDto dto2 = new ParamDto();
dto2.setCode("CODE002");
dto2.setName("参数2");
dto2.setType("正则表达式");
dto2.setDefaultValue("[a-z]+"); // 合法的正则表达式
dtoList.add(dto2);
ParamDto dto3 = new ParamDto();
dto3.setCode("CODE001"); // 重复的code
dto3.setName("参数3");
dto3.setType("数字");
dto3.setDefaultValue("123");
dtoList.add(dto3);
try {
// 执行校验
DtoValidator.validateDtoList(dtoList);
System.out.println("校验通过");
} catch (DtoValidationException e) {
System.err.println("校验失败: " + e.getMessage());
}
}
}参数校验2
DTO
java
// 无包结构,直接放在根目录
import java.io.Serializable;
/**
* 参数DTO类,用于存储参数信息
*/
public class ParameterDTO implements Serializable {
private static final long serialVersionUID = 1L;
// 参数唯一标识
private String code;
// 参数名称
private String name;
// 参数默认值
private String defaultValue;
// 参数类型
private String type;
// 构造方法
public ParameterDTO() {
}
public ParameterDTO(String code, String name, String defaultValue, String type) {
this.code = code;
this.name = name;
this.defaultValue = defaultValue;
this.type = type;
}
// Getter和Setter方法
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDefaultValue() {
return defaultValue;
}
public void setDefaultValue(String defaultValue) {
this.defaultValue = defaultValue;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
@Override
public String toString() {
return "ParameterDTO{" +
"code='" + code + '\'' +
", name='" + name + '\'' +
", defaultValue='" + defaultValue + '\'' +
", type='" + type + '\'' +
'}';
}
}枚举
java
// 无包结构,直接放在根目录
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
/**
* 参数类型枚举类,定义支持的参数类型
*/
public enum ParameterTypeEnum {
// 日期类型
DATE("date") {
@Override
public boolean validate(String value) {
if (value == null || value.isEmpty()) {
return true; // 允许空值
}
try {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
sdf.setLenient(false);
sdf.parse(value);
return true;
} catch (ParseException e) {
try {
// 尝试其他常见日期格式
SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
sdf2.setLenient(false);
sdf2.parse(value);
return true;
} catch (ParseException ex) {
return false;
}
}
}
},
// 数字类型
NUMBER("number") {
@Override
public boolean validate(String value) {
if (value == null || value.isEmpty()) {
return true; // 允许空值
}
try {
Double.parseDouble(value);
return true;
} catch (NumberFormatException e) {
return false;
}
}
},
// 字符串类型
STRING("string") {
@Override
public boolean validate(String value) {
// 字符串类型总是有效的,包括null和空字符串
return true;
}
},
// 正则表达式类型
REGEX("regex") {
@Override
public boolean validate(String value) {
if (value == null || value.isEmpty()) {
return true; // 允许空值
}
try {
Pattern.compile(value);
return true;
} catch (PatternSyntaxException e) {
return false;
}
}
},
// 整数类型
INTEGER("integer") {
@Override
public boolean validate(String value) {
if (value == null || value.isEmpty()) {
return true; // 允许空值
}
return value.matches("^-?\\d+$");
}
},
// 布尔类型
BOOLEAN("boolean") {
@Override
public boolean validate(String value) {
if (value == null || value.isEmpty()) {
return true; // 允许空值
}
return "true".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value);
}
};
private final String type;
ParameterTypeEnum(String type) {
this.type = type;
}
public String getType() {
return type;
}
/**
* 验证值是否符合该类型
* @param value 要验证的值
* @return 如果值符合类型要求则返回true,否则返回false
*/
public abstract boolean validate(String value);
/**
* 根据类型字符串获取枚举实例
* @param type 类型字符串
* @return 对应的枚举实例
* @throws IllegalArgumentException 如果类型不支持
*/
public static ParameterTypeEnum fromType(String type) {
if (type == null || type.isEmpty()) {
throw new IllegalArgumentException("类型不能为空");
}
for (ParameterTypeEnum enumType : values()) {
if (enumType.type.equalsIgnoreCase(type)) {
return enumType;
}
}
throw new IllegalArgumentException("不支持的参数类型: " + type);
}
}参数验证工具类
java
// 无包结构,直接放在根目录
import ParameterDTO;
import ParameterTypeEnum;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* 参数验证工具类,用于验证参数DTO列表的有效性
*/
public class ParameterValidator {
/**
* 验证参数DTO列表
* @param parameterDTOList 要验证的参数DTO列表
* @throws IllegalArgumentException 如果验证失败
*/
public static void validateParameterList(List<ParameterDTO> parameterDTOList) {
if (parameterDTOList == null || parameterDTOList.isEmpty()) {
throw new IllegalArgumentException("参数列表不能为空");
}
// 验证单个参数的基本信息
for (ParameterDTO parameterDTO : parameterDTOList) {
validateSingleParameter(parameterDTO);
}
// 检查code是否重复
checkDuplicateCode(parameterDTOList);
// 验证参数类型匹配
validateParameterTypeMatch(parameterDTOList);
// 特别验证正则表达式类型
validateRegexTypeParameters(parameterDTOList);
}
/**
* 验证单个参数的基本信息
* @param parameterDTO 参数DTO
* @throws IllegalArgumentException 如果参数基本信息不合法
*/
private static void validateSingleParameter(ParameterDTO parameterDTO) {
if (parameterDTO == null) {
throw new IllegalArgumentException("参数DTO不能为null");
}
if (parameterDTO.getCode() == null || parameterDTO.getCode().trim().isEmpty()) {
throw new IllegalArgumentException("参数code不能为空");
}
if (parameterDTO.getName() == null || parameterDTO.getName().trim().isEmpty()) {
throw new IllegalArgumentException("参数名称不能为空,code: " + parameterDTO.getCode());
}
if (parameterDTO.getType() == null || parameterDTO.getType().trim().isEmpty()) {
throw new IllegalArgumentException("参数类型不能为空,code: " + parameterDTO.getCode());
}
}
/**
* 检查参数code是否重复
* @param parameterDTOList 参数DTO列表
* @throws IllegalArgumentException 如果存在重复的code
*/
private static void checkDuplicateCode(List<ParameterDTO> parameterDTOList) {
Set<String> codeSet = new HashSet<>();
for (ParameterDTO parameterDTO : parameterDTOList) {
String code = parameterDTO.getCode();
if (!codeSet.add(code)) {
throw new IllegalArgumentException("参数code重复: " + code);
}
}
}
/**
* 验证参数类型匹配
* @param parameterDTOList 参数DTO列表
* @throws IllegalArgumentException 如果参数类型不匹配
*/
private static void validateParameterTypeMatch(List<ParameterDTO> parameterDTOList) {
for (ParameterDTO parameterDTO : parameterDTOList) {
String type = parameterDTO.getType();
String defaultValue = parameterDTO.getDefaultValue();
try {
// 验证类型是否支持
ParameterTypeEnum parameterType = ParameterTypeEnum.fromType(type);
// 验证默认值是否符合类型要求
if (!parameterType.validate(defaultValue)) {
throw new IllegalArgumentException(
"参数默认值与类型不匹配,code: " + parameterDTO.getCode() +
", type: " + type + ", defaultValue: " + defaultValue);
}
} catch (IllegalArgumentException e) {
// 直接抛出异常
throw e;
} catch (Exception e) {
throw new IllegalArgumentException(
"参数类型验证失败,code: " + parameterDTO.getCode() + ", type: " + type, e);
}
}
}
/**
* 特别验证正则表达式类型的参数
* @param parameterDTOList 参数DTO列表
* @throws IllegalArgumentException 如果正则表达式无效
*/
private static void validateRegexTypeParameters(List<ParameterDTO> parameterDTOList) {
for (ParameterDTO parameterDTO : parameterDTOList) {
if ("regex".equalsIgnoreCase(parameterDTO.getType())) {
String defaultValue = parameterDTO.getDefaultValue();
if (defaultValue != null && !defaultValue.isEmpty()) {
try {
ParameterTypeEnum.REGEX.validate(defaultValue);
} catch (Exception e) {
throw new IllegalArgumentException(
"正则表达式无效,code: " + parameterDTO.getCode() +
", regex: " + defaultValue, e);
}
}
}
}
}
}参数验证器测试类
java
// 无包结构,直接放在根目录
import ParameterDTO;
import ParameterValidator;
import RegexValidator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* 参数验证器测试类
*/
public class ParameterValidatorTest {
public static void main(String[] args) {
System.out.println("===== 参数验证器测试开始 =====");
// 运行所有测试
testNormalCase();
testDuplicateCode();
testTypeMismatch();
testInvalidRegex();
testMissingRequiredFields();
testRegexValidator();
System.out.println("===== 参数验证器测试结束 =====");
}
/**
* 测试正常场景
*/
private static void testNormalCase() {
System.out.println("\n测试正常场景...");
try {
List<ParameterDTO> parameters = new ArrayList<>();
// 添加不同类型的参数
parameters.add(new ParameterDTO("param1", "日期参数", "2023-12-31", "date"));
parameters.add(new ParameterDTO("param2", "数字参数", "123.45", "number"));
parameters.add(new ParameterDTO("param3", "字符串参数", "hello", "string"));
parameters.add(new ParameterDTO("param4", "正则表达式参数", "^[a-zA-Z0-9]+$", "regex"));
parameters.add(new ParameterDTO("param5", "整数参数", "123", "integer"));
parameters.add(new ParameterDTO("param6", "布尔参数", "true", "boolean"));
parameters.add(new ParameterDTO("param7", "空默认值参数", null, "string"));
// 验证
ParameterValidator.validateParameterList(parameters);
System.out.println("正常场景测试通过!");
} catch (Exception e) {
System.err.println("正常场景测试失败: " + e.getMessage());
e.printStackTrace();
}
}
/**
* 测试重复code
*/
private static void testDuplicateCode() {
System.out.println("\n测试重复code...");
try {
List<ParameterDTO> parameters = Arrays.asList(
new ParameterDTO("duplicate", "参数1", "value1", "string"),
new ParameterDTO("duplicate", "参数2", "value2", "string")
);
ParameterValidator.validateParameterList(parameters);
System.err.println("重复code测试失败:应该抛出异常但没有抛出");
} catch (IllegalArgumentException e) {
if (e.getMessage().contains("参数code重复")) {
System.out.println("重复code测试通过:" + e.getMessage());
} else {
System.err.println("重复code测试失败:" + e.getMessage());
e.printStackTrace();
}
} catch (Exception e) {
System.err.println("重复code测试失败,异常类型不正确: " + e.getMessage());
e.printStackTrace();
}
}
/**
* 测试类型不匹配
*/
private static void testTypeMismatch() {
System.out.println("\n测试类型不匹配...");
try {
List<ParameterDTO> parameters = Arrays.asList(
new ParameterDTO("invalid_date", "无效日期", "2023-13-31", "date"), // 无效月份
new ParameterDTO("invalid_number", "无效数字", "abc", "number") // 非数字
);
ParameterValidator.validateParameterList(parameters);
System.err.println("类型不匹配测试失败:应该抛出异常但没有抛出");
} catch (IllegalArgumentException e) {
if (e.getMessage().contains("参数默认值与类型不匹配")) {
System.out.println("类型不匹配测试通过:" + e.getMessage());
} else {
System.err.println("类型不匹配测试失败:" + e.getMessage());
e.printStackTrace();
}
} catch (Exception e) {
System.err.println("类型不匹配测试失败,异常类型不正确: " + e.getMessage());
e.printStackTrace();
}
}
/**
* 测试无效的正则表达式
*/
private static void testInvalidRegex() {
System.out.println("\n测试无效的正则表达式...");
try {
List<ParameterDTO> parameters = Arrays.asList(
new ParameterDTO("invalid_regex", "无效正则", "[a-z", "regex") // 缺少右括号
);
ParameterValidator.validateParameterList(parameters);
System.err.println("无效正则表达式测试失败:应该抛出异常但没有抛出");
} catch (IllegalArgumentException e) {
System.out.println("无效正则表达式测试通过:" + e.getMessage());
} catch (Exception e) {
System.err.println("无效正则表达式测试失败,异常类型不正确: " + e.getMessage());
e.printStackTrace();
}
}
/**
* 测试缺少必填字段
*/
private static void testMissingRequiredFields() {
System.out.println("\n测试缺少必填字段...");
// 测试null DTO
try {
ParameterValidator.validateParameterList(Arrays.asList(null));
System.err.println("缺少必填字段测试失败:null DTO应该抛出异常但没有抛出");
} catch (IllegalArgumentException e) {
System.out.println("缺少必填字段测试通过(null DTO):" + e.getMessage());
}
// 测试空code
try {
ParameterDTO param = new ParameterDTO();
param.setCode("");
param.setName("测试参数");
param.setType("string");
ParameterValidator.validateParameterList(Arrays.asList(param));
System.err.println("缺少必填字段测试失败:空code应该抛出异常但没有抛出");
} catch (IllegalArgumentException e) {
System.out.println("缺少必填字段测试通过(空code):" + e.getMessage());
}
// 测试空类型
try {
ParameterDTO param = new ParameterDTO();
param.setCode("test");
param.setName("测试参数");
param.setType("");
ParameterValidator.validateParameterList(Arrays.asList(param));
System.err.println("缺少必填字段测试失败:空类型应该抛出异常但没有抛出");
} catch (IllegalArgumentException e) {
System.out.println("缺少必填字段测试通过(空类型):" + e.getMessage());
}
}
/**
* 测试RegexValidator工具类
*/
private static void testRegexValidator() {
System.out.println("\n测试RegexValidator工具类...");
// 测试有效正则表达式
String validRegex = "^[a-zA-Z0-9]+$";
boolean isValid = RegexValidator.isValidRegex(validRegex);
System.out.println("有效正则表达式测试:" + (isValid ? "通过" : "失败"));
// 测试无效正则表达式
String invalidRegex = "[a-z";
isValid = RegexValidator.isValidRegex(invalidRegex);
System.out.println("无效正则表达式测试:" + (!isValid ? "通过" : "失败"));
// 测试获取错误信息
String errorMsg = RegexValidator.getRegexErrorMessage(invalidRegex);
System.out.println("获取错误信息测试:" + (errorMsg != null ? "通过 - " + errorMsg : "失败"));
// 测试复杂正则表达式检测
String complexRegex = "(a+)+b";
boolean isComplex = RegexValidator.isPotentiallyComplex(complexRegex);
System.out.println("复杂正则表达式检测测试:" + (isComplex ? "通过" : "失败"));
// 测试安全匹配
try {
boolean matches = RegexValidator.safeMatches("^\\d+$", "123", 1000);
System.out.println("安全匹配测试(成功):" + (matches ? "通过" : "失败"));
} catch (Exception e) {
System.err.println("安全匹配测试失败:" + e.getMessage());
}
}
}springboot redis incr lua
java
package com.me.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;
import java.util.Collections;
@Component
public class RedisIncrementScriptService {
@Autowired
private StringRedisTemplate stringRedisTemplate;
// 建议修改Lua脚本为标准格式
private static final String INCR_WITH_EXPIRE_SCRIPT =
"local increment = redis.call('INCR', KEYS[1]) " +
"if increment == 1 then " +
"redis.call('EXPIRE', KEYS[1], ARGV[1]) " +
"end " +
"return increment";
// 初始化 Redis 脚本
private final RedisScript<Long> incrScript = new DefaultRedisScript<>(
INCR_WITH_EXPIRE_SCRIPT,
Long.class
);
/**
* 原子化自增并设置过期时间
* @param key Redis Key
* @param expireSeconds 过期时间(秒)
* @return 自增后的值
*/
public Long atomicIncrementWithExpire(String key, long expireSeconds) {
return stringRedisTemplate.execute(
incrScript,
Collections.singletonList(key), // KEYS[1]
String.valueOf(expireSeconds) // ARGV[1]
);
}
}java
package com.me.demo1.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import java.util.Collections;
@Component
public class RedisIncrementScriptService {
@Autowired
private RedisTemplate redisTemplate;
/**
* 对指定 key 自增,并在首次创建时设置过期时间
*
* @param key Redis key
* @param expireSeconds 过期时间(秒),仅在 key 首次创建时生效
* @return 自增后的值
*/
public Long incrementWithExpire(String key, long expireSeconds) {
String scriptText =
"local current = redis.call('INCR', KEYS[1]) " +
"if current == 1 then " +
" redis.call('EXPIRE', KEYS[1], ARGV[1]) " +
"end " +
"return current";
DefaultRedisScript<Long> script = new DefaultRedisScript<>();
script.setScriptText(scriptText);
script.setResultType(Long.class);
Long execute = (Long) redisTemplate.execute(script, Collections.singletonList(key), String.valueOf(expireSeconds));
return execute;
}
}前后端对象比对 那些是删除的 那些是修改的 那些是新增的
ParameterDTO
java
package com.me.param2;// 无包结构,直接放在根目录
import java.io.Serializable;
/**
* 参数DTO类,用于存储参数信息
*/
public class ParameterDTO implements Serializable {
private static final long serialVersionUID = 1L;
// 参数唯一标识
private String code;
// 参数名称
private String name;
// 参数默认值
private String defaultValue;
// 参数类型
private String type;
// 构造方法
public ParameterDTO() {
}
public ParameterDTO(String code, String name, String defaultValue, String type) {
this.code = code;
this.name = name;
this.defaultValue = defaultValue;
this.type = type;
}
// Getter和Setter方法
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDefaultValue() {
return defaultValue;
}
public void setDefaultValue(String defaultValue) {
this.defaultValue = defaultValue;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
@Override
public String toString() {
return "ParameterDTO{" +
"code='" + code + '\'' +
", name='" + name + '\'' +
", defaultValue='" + defaultValue + '\'' +
", type='" + type + '\'' +
'}';
}
}ParameterComparator
java
package com.me.param2;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 参数比较工具类,用于对比数据库和前端的参数列表,识别新增、修改和删除的参数
*/
public class ParameterComparator {
/**
* 参数比较结果类,用于存储比较后的结果
*/
public static class ComparisonResult {
private List<ParameterDTO> addedParameters;
private List<ParameterDTO> modifiedParameters;
private List<ParameterDTO> deletedParameters;
public ComparisonResult() {
this.addedParameters = new ArrayList<>();
this.modifiedParameters = new ArrayList<>();
this.deletedParameters = new ArrayList<>();
}
public List<ParameterDTO> getAddedParameters() {
return addedParameters;
}
public List<ParameterDTO> getModifiedParameters() {
return modifiedParameters;
}
public List<ParameterDTO> getDeletedParameters() {
return deletedParameters;
}
@Override
public String toString() {
return "ComparisonResult{" +
"addedParameters=" + addedParameters.size() + "条, " +
"modifiedParameters=" + modifiedParameters.size() + "条, " +
"deletedParameters=" + deletedParameters.size() + "条" +
'}';
}
}
/**
* 比较数据库查询出的参数列表和前端传的参数列表
* @param dbParameters 数据库查询出的参数列表
* @param frontendParameters 前端传的参数列表
* @return 比较结果,包含新增、修改和删除的参数
*/
public static ComparisonResult compareParameters(List<ParameterDTO> dbParameters, List<ParameterDTO> frontendParameters) {
ComparisonResult result = new ComparisonResult();
// 使用Stream创建数据库参数的Map,以code为键
Map<String, ParameterDTO> dbParamMap = dbParameters.stream()
.collect(Collectors.toMap(ParameterDTO::getCode, param -> param));
// 使用Stream创建前端参数的Map,以code为键
Map<String, ParameterDTO> frontendParamMap = frontendParameters.stream()
.collect(Collectors.toMap(ParameterDTO::getCode, param -> param));
// 使用Stream找出新增的参数(前端有,数据库没有)
List<ParameterDTO> addedParams = frontendParameters.stream()
.filter(frontendParam -> !dbParamMap.containsKey(frontendParam.getCode()))
.collect(Collectors.toList());
result.getAddedParameters().addAll(addedParams);
// 使用Stream找出删除的参数(数据库有,前端没有)
List<ParameterDTO> deletedParams = dbParameters.stream()
.filter(dbParam -> !frontendParamMap.containsKey(dbParam.getCode()))
.collect(Collectors.toList());
result.getDeletedParameters().addAll(deletedParams);
// 使用Stream找出修改的参数(两者都有,但字段值不同)
List<ParameterDTO> modifiedParams = dbParameters.stream()
.filter(dbParam -> frontendParamMap.containsKey(dbParam.getCode()))
.map(dbParam -> {
ParameterDTO frontendParam = frontendParamMap.get(dbParam.getCode());
return isModified(dbParam, frontendParam) ? frontendParam : null;
})
.filter(param -> param != null)
.collect(Collectors.toList());
result.getModifiedParameters().addAll(modifiedParams);
return result;
}
/**
* 判断两个参数是否有修改
* @param dbParam 数据库中的参数
* @param frontendParam 前端传来的参数
* @return 如果有修改返回true,否则返回false
*/
private static boolean isModified(ParameterDTO dbParam, ParameterDTO frontendParam) {
// 比较name字段
if (!equals(dbParam.getName(), frontendParam.getName())) {
return true;
}
// 比较type字段
if (!equals(dbParam.getType(), frontendParam.getType())) {
return true;
}
// 比较defaultValue字段(参数值)
if (!equals(dbParam.getDefaultValue(), frontendParam.getDefaultValue())) {
return true;
}
return false;
}
/**
* 安全比较两个对象是否相等,处理null值
* @param obj1 第一个对象
* @param obj2 第二个对象
* @return 如果相等返回true,否则返回false
*/
private static boolean equals(Object obj1, Object obj2) {
if (obj1 == null && obj2 == null) {
return true;
}
if (obj1 == null || obj2 == null) {
return false;
}
return obj1.equals(obj2);
}
}TEST
java
package com.me.param2;
import java.util.ArrayList;
import java.util.List;
/**
* ParameterComparator工具类的测试类
*/
public class ParameterComparatorTest {
public static void main(String[] args) {
// 测试场景1:包含新增、修改和删除的情况
testAllChanges();
// 测试场景2:没有任何变化的情况
testNoChanges();
// 测试场景3:只有新增的情况
testOnlyAdded();
// 测试场景4:只有修改的情况
testOnlyModified();
// 测试场景5:只有删除的情况
testOnlyDeleted();
}
/**
* 测试包含新增、修改和删除的情况
*/
private static void testAllChanges() {
System.out.println("=== 测试场景1:包含新增、修改和删除的情况 ===");
// 数据库中的参数列表
List<ParameterDTO> dbParameters = new ArrayList<>();
dbParameters.add(new ParameterDTO("param1", "参数1", "100", "Integer"));
dbParameters.add(new ParameterDTO("param2", "参数2", "true", "Boolean"));
dbParameters.add(new ParameterDTO("param3", "参数3", "test", "String"));
// 前端传来的参数列表
List<ParameterDTO> frontendParameters = new ArrayList<>();
frontendParameters.add(new ParameterDTO("param1", "参数1-修改", "200", "Integer")); // 修改了名称和值
frontendParameters.add(new ParameterDTO("param2", "参数2", "true", "Boolean")); // 没有变化
frontendParameters.add(new ParameterDTO("param4", "参数4", "new", "String")); // 新增参数
// 比较参数
ParameterComparator.ComparisonResult result = ParameterComparator.compareParameters(dbParameters, frontendParameters);
// 输出结果
System.out.println("比较结果: " + result);
System.out.println("新增参数: " + result.getAddedParameters());
System.out.println("修改参数: " + result.getModifiedParameters());
System.out.println("删除参数: " + result.getDeletedParameters());
System.out.println();
}
/**
* 测试没有任何变化的情况
*/
private static void testNoChanges() {
System.out.println("=== 测试场景2:没有任何变化的情况 ===");
// 数据库中的参数列表
List<ParameterDTO> dbParameters = new ArrayList<>();
dbParameters.add(new ParameterDTO("param1", "参数1", "100", "Integer"));
dbParameters.add(new ParameterDTO("param2", "参数2", "true", "Boolean"));
// 前端传来的参数列表
List<ParameterDTO> frontendParameters = new ArrayList<>();
frontendParameters.add(new ParameterDTO("param1", "参数1", "100", "Integer"));
frontendParameters.add(new ParameterDTO("param2", "参数2", "true", "Boolean"));
// 比较参数
ParameterComparator.ComparisonResult result = ParameterComparator.compareParameters(dbParameters, frontendParameters);
// 输出结果
System.out.println("比较结果: " + result);
System.out.println("新增参数: " + result.getAddedParameters());
System.out.println("修改参数: " + result.getModifiedParameters());
System.out.println("删除参数: " + result.getDeletedParameters());
System.out.println();
}
/**
* 测试只有新增的情况
*/
private static void testOnlyAdded() {
System.out.println("=== 测试场景3:只有新增的情况 ===");
// 数据库中的参数列表
List<ParameterDTO> dbParameters = new ArrayList<>();
dbParameters.add(new ParameterDTO("param1", "参数1", "100", "Integer"));
// 前端传来的参数列表
List<ParameterDTO> frontendParameters = new ArrayList<>();
frontendParameters.add(new ParameterDTO("param1", "参数1", "100", "Integer"));
frontendParameters.add(new ParameterDTO("param2", "参数2", "true", "Boolean"));
frontendParameters.add(new ParameterDTO("param3", "参数3", "test", "String"));
// 比较参数
ParameterComparator.ComparisonResult result = ParameterComparator.compareParameters(dbParameters, frontendParameters);
// 输出结果
System.out.println("比较结果: " + result);
System.out.println("新增参数: " + result.getAddedParameters());
System.out.println("修改参数: " + result.getModifiedParameters());
System.out.println("删除参数: " + result.getDeletedParameters());
System.out.println();
}
/**
* 测试只有修改的情况
*/
private static void testOnlyModified() {
System.out.println("=== 测试场景4:只有修改的情况 ===");
// 数据库中的参数列表
List<ParameterDTO> dbParameters = new ArrayList<>();
dbParameters.add(new ParameterDTO("param1", "参数1", "100", "Integer"));
dbParameters.add(new ParameterDTO("param2", "参数2", "true", "Boolean"));
// 前端传来的参数列表
List<ParameterDTO> frontendParameters = new ArrayList<>();
frontendParameters.add(new ParameterDTO("param1", "参数1-修改", "200", "Integer"));
frontendParameters.add(new ParameterDTO("param2", "参数2", "false", "Boolean"));
// 比较参数
ParameterComparator.ComparisonResult result = ParameterComparator.compareParameters(dbParameters, frontendParameters);
// 输出结果
System.out.println("比较结果: " + result);
System.out.println("新增参数: " + result.getAddedParameters());
System.out.println("修改参数: " + result.getModifiedParameters());
System.out.println("删除参数: " + result.getDeletedParameters());
System.out.println();
}
/**
* 测试只有删除的情况
*/
private static void testOnlyDeleted() {
System.out.println("=== 测试场景5:只有删除的情况 ===");
// 数据库中的参数列表
List<ParameterDTO> dbParameters = new ArrayList<>();
dbParameters.add(new ParameterDTO("param1", "参数1", "100", "Integer"));
dbParameters.add(new ParameterDTO("param2", "参数2", "true", "Boolean"));
dbParameters.add(new ParameterDTO("param3", "参数3", "test", "String"));
// 前端传来的参数列表
List<ParameterDTO> frontendParameters = new ArrayList<>();
frontendParameters.add(new ParameterDTO("param1", "参数1", "100", "Integer"));
// 比较参数
ParameterComparator.ComparisonResult result = ParameterComparator.compareParameters(dbParameters, frontendParameters);
// 输出结果
System.out.println("比较结果: " + result);
System.out.println("新增参数: " + result.getAddedParameters());
System.out.println("修改参数: " + result.getModifiedParameters());
System.out.println("删除参数: " + result.getDeletedParameters());
System.out.println();
}
}对象 逐个字段比较 支持 继承的父类属性 并且支持跳过某些字段 返回差异字段
java
import java.lang.annotation.*;
/**
* 自定义注解:标记该注解的字段,在对象对比时自动跳过(不参与字段比较)
* 可标注在 子类字段、父类字段 上,均生效
*/
@Target({ElementType.FIELD}) // 只允许标注在【字段】上
@Retention(RetentionPolicy.RUNTIME) // 运行时生效,反射能读取到该注解
@Documented
public @interface SkipCompare {
}java
@Data
@NoArgsConstructor
public class User extends AbstractUser{
private Long id;
private String name;
private Integer age;
@SkipCompare
private String email;
@SkipCompare
private Boolean active;
public User(Long id, String name, Integer age, String email, Boolean active, String uid) {
this.id = id;
this.name = name;
this.age = age;
this.email = email;
this.active = active;
this.setUid(uid);
}
}java
package com.me.util;
import java.lang.reflect.Field;
import java.util.*;
/**
* 对象字段对比工具类【终极完整版】
* 核心能力全量整合:
* 1. 对比同类型两个对象的【当前类 + 所有父类】的全部字段
* 2. 支持字段添加 @SkipCompare 注解 自动跳过对比
* 3. 支持手动传入排除字段集合,跳过指定字段对比
* 4. 纯JDK原生实现,无任何第三方依赖
* 返回:有变更的字段Map,key=字段名,value=封装修改前/后值的对象
*/
public class ObjectCompareUtil {
/**
* 封装字段的修改前、修改后的值
*/
public static class FieldChange {
// 修改前的值(源对象字段值)
private Object oldValue;
// 修改后的值(目标对象字段值)
private Object newValue;
public FieldChange(Object oldValue, Object newValue) {
this.oldValue = oldValue;
this.newValue = newValue;
}
public Object getOldValue() {
return oldValue;
}
public void setOldValue(Object oldValue) {
this.oldValue = oldValue;
}
public Object getNewValue() {
return newValue;
}
public void setNewValue(Object newValue) {
this.newValue = newValue;
}
// 重写toString,控制台打印更友好直观
@Override
public String toString() {
return "原值: " + oldValue + " → 新值: " + newValue;
}
}
/**
* 核心方法【主方法】:对比两个同类型对象,包含【当前类+所有父类】的所有字段
* 自动跳过:添加了 @SkipCompare 注解的字段
* @param sourceObj 源对象(修改前的对象)
* @param targetObj 目标对象(修改后的对象)
* @param <T> 泛型约束,强制两个对象为同一种类型
* @return Map<String, FieldChange> 只返回有差异的字段,无差异返回空Map
* @throws IllegalAccessException 反射访问私有字段的权限异常
*/
public static <T> Map<String, FieldChange> compareObjectFields(T sourceObj, T targetObj) throws IllegalAccessException {
Map<String, FieldChange> changeMap = new HashMap<>(16);
// 1. 双空判断:两个对象都为空,无任何差异
if (Objects.isNull(sourceObj) && Objects.isNull(targetObj)) {
return changeMap;
}
// 2. 严格校验对象类型一致,避免不同类的无意义对比
if (!Objects.equals(sourceObj.getClass(), targetObj.getClass())) {
throw new IllegalArgumentException("【对象类型不一致】无法对比!源对象类型:" + sourceObj.getClass().getName() + ",目标对象类型:" + targetObj.getClass().getName());
}
Class<?> clazz = sourceObj.getClass();
// 3. 递归获取【当前类 + 所有父类】的全部字段(直到Object类为止)
List<Field> allFieldList = new ArrayList<>();
Class<?> tempClass = clazz;
while (tempClass != null && !tempClass.getName().equals(Object.class.getName())) {
allFieldList.addAll(Arrays.asList(tempClass.getDeclaredFields()));
tempClass = tempClass.getSuperclass();
}
Field[] fields = allFieldList.toArray(new Field[0]);
// 4. 逐字段对比值差异
for (Field field : fields) {
// ===== 核心新增:判断字段是否有@SkipCompare注解,有则直接跳过对比 =====
if (field.isAnnotationPresent(SkipCompare.class)) {
continue;
}
// 暴力反射:解除私有字段的访问权限,必须加!实体类字段都是private
field.setAccessible(true);
// 获取源对象的字段值(修改前)
Object sourceValue = field.get(sourceObj);
// 获取目标对象的字段值(修改后)
Object targetValue = field.get(targetObj);
// 精准判断值是否不同,Objects.equals完美处理null值,不会空指针
if (!Objects.equals(sourceValue, targetValue)) {
changeMap.put(field.getName(), new FieldChange(sourceValue, targetValue));
}
}
return changeMap;
}
/**
* 重载方法【常用】:对比两个对象 + 手动排除指定字段 + 自动跳过注解字段
* 优先级排序:@SkipCompare注解 > 手动排除字段,双重过滤,都生效
* 适用于:主键id、创建时间createTime等无需对比的固定字段
* @param sourceObj 源对象
* @param targetObj 目标对象
* @param excludeFields 需要手动排除的字段名集合,例如:Arrays.asList("id","createTime")
*/
public static <T> Map<String, FieldChange> compareObjectFields(T sourceObj, T targetObj, List<String> excludeFields) throws IllegalAccessException {
Map<String, FieldChange> changeMap = new HashMap<>(16);
if (Objects.isNull(sourceObj) && Objects.isNull(targetObj)) {
return changeMap;
}
if (!Objects.equals(sourceObj.getClass(), targetObj.getClass())) {
throw new IllegalArgumentException("【对象类型不一致】无法对比!源对象类型:" + sourceObj.getClass().getName() + ",目标对象类型:" + targetObj.getClass().getName());
}
Class<?> clazz = sourceObj.getClass();
// 递归获取当前类+所有父类的全部字段
List<Field> allFieldList = new ArrayList<>();
Class<?> tempClass = clazz;
while (tempClass != null && !tempClass.getName().equals(Object.class.getName())) {
allFieldList.addAll(Arrays.asList(tempClass.getDeclaredFields()));
tempClass = tempClass.getSuperclass();
}
Field[] fields = allFieldList.toArray(new Field[0]);
for (Field field : fields) {
// ===== 双重过滤:1.注解字段跳过 2.手动排除字段跳过 =====
if (field.isAnnotationPresent(SkipCompare.class) || excludeFields.contains(field.getName())) {
continue;
}
field.setAccessible(true);
Object sourceValue = field.get(sourceObj);
Object targetValue = field.get(targetObj);
if (!Objects.equals(sourceValue, targetValue)) {
changeMap.put(field.getName(), new FieldChange(sourceValue, targetValue));
}
}
return changeMap;
}
}