参考文章:https://blog.csdn.net/sunnyzyq/article/details/124603360
核心逻辑是原博主的,只是根据自己的需要做了部分调整.
变动部分
- 支持属性字典
package com.asggo.tools.compare;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Created by IntelliJ IDEA.
*
* @author eric 2022/12/2 10:21
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Compare {
/**
* 字段中文名称.
*
* @return 字段中文名称.
*/
String value();
/**
* 是否元数据 默认 false.
*
* @return true/false.
*/
boolean isMeta() default false;
}
package com.asggo.tools.compare;
import lombok.Data;
/**
* Created by IntelliJ IDEA.
*
* @author eric 2022/12/2 10:22
*/
@Data
public class CompareNode {
/**
* 字段
*/
private String fieldKey;
/**
* 字段值
*/
private Object fieldValue;
/**
* 字段名称
*/
private String fieldName;
}
package com.asggo.tools.compare;
import org.springframework.lang.Nullable;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
/**
* Created by IntelliJ IDEA.
*
* @author eric 2022/12/2 10:09
*/
public class CompareUtils<T> {
private static final String COMMA = ",";
private static final String EMPTY = "";
/**
* 属性比较
*
* @param source 源数据对象.
* @param target 目标数据对象.
* @param ignoreProperties 忽略比较的字段.
* @return 对应属性值的比较变化
*/
public String compare(T source, T target, @Nullable String... ignoreProperties) {
return compare(source, target, Map.of(), ignoreProperties);
}
/**
* 属性比较
*
* @param source 源数据对象.
* @param target 目标数据对象.
* @param metaMap 元数据映射.
* @param ignoreProperties 忽略比较的字段.
* @return 对应属性值的比较变化
*/
public String compare(T source, T target, Map<Object, Object> metaMap, @Nullable String... ignoreProperties) {
if (Objects.isNull(source) && Objects.isNull(target)) {
return EMPTY;
}
if (Objects.isNull(target) || Objects.isNull(metaMap)) {
return EMPTY;
}
Map<String, CompareNode> sourceMap = this.getFiledValueMap(source, metaMap);
Map<String, CompareNode> targetMap = this.getFiledValueMap(target, metaMap);
if (sourceMap.isEmpty() && targetMap.isEmpty()) {
return EMPTY;
}
// 如果源数据为空,则只显示目标数据,不显示属性变化情况
if (sourceMap.isEmpty()) {
return doInit(targetMap, ignoreProperties);
}
// 如果源数据不为空,则显示属性变化情况
String diffText = doCompare(sourceMap, targetMap, ignoreProperties);
if (!diffText.endsWith(COMMA)) {
return diffText;
}
return diffText.substring(0, diffText.length() - 1);
}
private String doInit(Map<String, CompareNode> targetMap, @Nullable String... ignoreProperties) {
StringBuilder sb = new StringBuilder();
Collection<CompareNode> values = targetMap.values();
int size = values.size();
int current = 0;
List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);
for (CompareNode node : values) {
current++;
Object o = Optional.ofNullable(node.getFieldValue()).orElse(EMPTY);
if (Objects.nonNull(ignoreList) && ignoreList.contains(node.getFieldKey())) {
continue;
}
if (o.toString().length() > 0) {
sb.append("[").append(node.getFieldName()).append(": ").append(o).append("]");
if (current < size) {
sb.append(COMMA);
}
}
}
return sb.toString();
}
private String doCompare(Map<String, CompareNode> sourceMap, Map<String, CompareNode> targetMap,
@Nullable String... ignoreProperties) {
StringBuilder sb = new StringBuilder();
Set<String> keys = sourceMap.keySet();
int size = keys.size();
int current = 0;
List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);
for (String key : keys) {
current++;
CompareNode sn = sourceMap.get(key);
CompareNode tn = targetMap.get(key);
if (Objects.nonNull(ignoreList) && ignoreList.contains(sn.getFieldKey())) {
continue;
}
String sv = Optional.ofNullable(sn.getFieldValue()).orElse(EMPTY).toString();
String tv = Optional.ofNullable(tn.getFieldValue()).orElse(EMPTY).toString();
// 只有两者属性值不一致时, 才显示变化情况
if (!sv.equals(tv)) {
sb.append(String.format("[%s: %s -> %s]", sn.getFieldName(), sv, tv));
if (current < size) {
sb.append(COMMA);
}
}
}
return sb.toString();
}
private Map<String, CompareNode> getFiledValueMap(T t, Map<Object, Object> metaMap) {
if (Objects.isNull(t)) {
return Collections.emptyMap();
}
Field[] fields = t.getClass().getDeclaredFields();
if (fields.length == 0) {
return Collections.emptyMap();
}
Map<String, CompareNode> map = new LinkedHashMap<>();
for (Field field : fields) {
Compare compareAnnotation = field.getAnnotation(Compare.class);
if (Objects.isNull(compareAnnotation)) {
continue;
}
field.setAccessible(true);
try {
String fieldKey = field.getName();
CompareNode node = new CompareNode();
node.setFieldKey(fieldKey);
node.setFieldValue(compareAnnotation.isMeta() ? metaMap.getOrDefault(field.get(t), field.get(t))
: field.get(t));
node.setFieldName(compareAnnotation.value());
map.put(field.getName(), node);
} catch (IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
}
return map;
}
public static void main(String[] args) {
User u1 = new User();
u1.setSex(1);
u1.setName("张三");
u1.setAge(13);
User u2 = new User();
u2.setSex(2);
u2.setName("李四");
u2.setAge(13);
String diffText = new CompareUtils<User>().compare(u1, u2);
System.out.println(diffText);
diffText = new CompareUtils<User>().compare(u1, u2, Map.of(1, "男", 2, "女", 0, "未知"));
System.out.println(diffText);
}
}
package com.asggo.tools.compare;
import lombok.Data;
/**
* Created by IntelliJ IDEA.
*
* @author eric 2022/12/2 10:42
*/
@Data
public class User {
@Compare("姓名")
private String name;
@Compare("年龄")
private Integer age;
@Compare(value = "性别", isMeta = true)
private Integer sex;
}
运行结果:
[姓名: 张三 -> 李四],[性别: 1 -> 2]
[姓名: 张三 -> 李四],[性别: 男 -> 女]