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