需求:记录出用户修改的字段,做详细日志记录。
思路:
- 创建FeildDiff、BeanDiff实体类,FeildDiff包括字段英文名、字段中文名、旧值、新值、目录五个属性,BeanDiff包括FeildDiff集合一个属性。
- 获取旧对象数据。
- 获取新对象数据。
- 新旧对象属性对比,存进FeildDiff实体类。把每一个属性对比得到的FeildDiff存进BeanDiff。
难点:
- 对象里面包含复杂对象,比如属性为Person,List,Map应如何比较?
- 不想比较对象的某些属性,比如updatetime,应如何实现?
- 动态实现不想要比较的某些属性,比如新对象的一些属性为null,表示不更新此字段,但是不能记录日志该字段的新值就是null,该如何实现?
伪代码:
FieldDiff.java
/**
* @description:两个对象差异-字段新旧值
* @create: 2021-09-02 11:01
**/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class FieldDiff {
/**
* 字段英文名
*/
@JsonProperty(value = "field_en_name")
private String fieldENName;
/**
* 字段中文名
*/
@JsonProperty(value = "field_cn_name")
private String fieldCNName;
/**
* 旧值
*/
@JsonProperty(value = "old_value")
private Object oldValue;
/**
* 新值
*/
@JsonProperty(value = "new_value")
private Object newValue;
/**
* 目录
*/
@JsonProperty(value = "catalog")
private String catalog;
@Override
public String toString() {
String oldVal = this.oldValue == null ? "" : this.oldValue.toString();
String newVal = this.newValue == null ? "" : this.newValue.toString();
return "将 " + this.fieldCNName + " 从“" + oldVal + "” 修改为 “" + newVal + "”";
}
}
BeanDiff.java
/**
* @description: 两个对象差异
* @create: 2021-09-02 11:03
**/
public class BeanDiff {
/**
* 所有差异字段list
*/
private List<FieldDiff> fieldDiffList = new ArrayList<>();
public void addFieldDiff(FieldDiff fieldDiff) {
this.fieldDiffList.add(fieldDiff);
}
public List<FieldDiff> getFieldDiffList() {
return fieldDiffList;
}
}
BeanCompareUtils.java
/**
* 实例字段差异比较工具类
* @author test
*/
public class BeanCompareUtils {
/**
* bean比较
* @param oldBean
* @param newBean
* @return
*/
public static BeanDiff compare(Object oldBean, Object newBean) {
BeanDiff beanDiff = new BeanDiff();
Class oldClass = oldBean.getClass();
Class newClass = newBean.getClass();
if (oldClass.equals(newClass)) {
List<Field> fieldList = new ArrayList<>();
fieldList = getCompareFieldList(fieldList, newClass);
for (int i = 0; i < fieldList.size(); i ++) {
//要比较对象的属性
Field field = fieldList.get(i);
//设置最高权限
field.setAccessible(true);
FieldAlias alias = field.getAnnotation(FieldAlias.class);
try {
Object oldValue = field.get(oldBean);
Object newValue = field.get(newBean);
//如果新旧值不相等,则放入集合里
if (nullableNotEquals(oldValue, newValue)) {
FieldDiff fieldDiff = new FieldDiff(field.getName(), alias.value(), oldValue, newValue, alias.catalog());
beanDiff.addFieldDiff(fieldDiff);
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
return beanDiff;
}
/**
* bean比较,忽略一些指定字段,比如修改实体的null字段
* @param oldBean
* @param newBean
* @return
*/
public static BeanDiff compareExcludeFieldList(Object oldBean, Object newBean, List<String> ignoreFieldList) {
BeanDiff beanDiff = new BeanDiff();
Class oldClass = oldBean.getClass();
Class newClass = newBean.getClass();
if (oldClass.equals(newClass)) {
List<Field> fieldList = new ArrayList<>();
fieldList = getCompareFieldListWithIgnore(fieldList, newClass, ignoreFieldList);
for (int i = 0; i < fieldList.size(); i ++) {
//要比较对象的属性
Field field = fieldList.get(i);
//设置最高权限
field.setAccessible(true);
FieldAlias alias = field.getAnnotation(FieldAlias.class);
try {
Object oldValue = field.get(oldBean);
Object newValue = field.get(newBean);
//如果新旧值不相等,则放入集合里
if (nullableNotEquals(oldValue, newValue)) {
FieldDiff fieldDiff = new FieldDiff(field.getName(), alias.value(), oldValue, newValue, alias.catalog());
beanDiff.addFieldDiff(fieldDiff);
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
return beanDiff;
}
/**
* 获取需要比较的字段list
* @param fieldList
* @param clazz
* @return
*/
private static List<Field> getCompareFieldList(List<Field> fieldList, Class clazz) {
Field[] fieldArray = clazz.getDeclaredFields();
List<Field> list = Arrays.asList(fieldArray);
for (int i = 0; i < list.size(); i ++) {
Field field = list.get(i);
FieldAlias alias = field.getAnnotation(FieldAlias.class);
if (alias != null) {
fieldList.add(field);
}
}
Class superClass = clazz.getSuperclass();
if (superClass != null) {
getCompareFieldList(fieldList, superClass);
}
return fieldList;
}
/**
* 获取添加注解的字段list,但是忽略一些字段
* @param fieldList
* @param clazz
* @return
*/
private static List<Field> getCompareFieldListWithIgnore(List<Field> fieldList, Class clazz, List<String> ignoreFieldList) {
Field[] fieldArray = clazz.getDeclaredFields();
List<Field> list = Arrays.asList(fieldArray);
for (int i = 0; i < list.size(); i ++) {
Field field = list.get(i);
FieldAlias alias = field.getAnnotation(FieldAlias.class);
if (alias != null && !ignoreFieldList.contains(field.getName())) {
fieldList.add(field);
}
}
Class superClass = clazz.getSuperclass();
if (superClass != null) {
getCompareFieldListWithIgnore(fieldList, superClass, ignoreFieldList);
}
return fieldList;
}
/**
* 比较值是否不相等
* @param oldValue
* @param newValue
* @return
*/
private static boolean nullableNotEquals(Object oldValue, Object newValue) {
if (oldValue == null && newValue == null) {
return false;
}
if (oldValue != null && oldValue.equals(newValue)) {
return false;
}
if (("".equals(oldValue) && newValue == null) || ("".equals(newValue) && oldValue == null)) {
return false;
}
return true;
}
}
使用:
添加自定义注解FieldAlias
/**
* 字段中文别名
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface FieldAlias {
/**
* 所在目录
* @return
*/
String catalog() default "";
/**
* 中文名称
* @return
*/
String value() default "";
}
SkuDetailCnDTO.java
/**
* @description:
* @create: 2021-09-10 14:12
**/
@Data
public class SkuDetailCnDTO {
/**
* SKU名称
*/
@FieldAlias(catalog = "商品管理-编辑SKU", value = "SKU名称")
private String skuName;
/**
* 品牌描述
*/
@FieldAlias(catalog = "商品管理-编辑SKU", value = "品牌描述")
private String description;
/**
* 物品规格描述
*/
@FieldAlias(catalog = "商品管理-编辑SKU", value = "物品规格描述")
private String standardDescription;
/**
* 质检标准
*/
@FieldAlias(catalog = "商品管理-编辑SKU", value = "质检标准")
private String qc;
/**
* SKU长(cm)
*/
@FieldAlias(catalog = "商品管理-编辑SKU", value = "SKU长(cm)")
private BigDecimal skuLength;
/**
* SKU宽(cm)
*/
@FieldAlias(catalog = "商品管理-编辑SKU", value = "SKU宽(cm)")
private BigDecimal skuWidth;
/**
* SKU高(cm)
*/
@FieldAlias(catalog = "商品管理-编辑SKU", value = "SKU高(cm)")
private BigDecimal skuHeight;
/**
* SKU重量(g)
*/
@FieldAlias(catalog = "商品管理-编辑SKU", value = "SKU重量(g)")
private BigDecimal skuWeight;
/**
* 数量单位
*/
@FieldAlias(catalog = "商品管理-编辑SKU", value = "数量单位")
private String quantityUm;
}
//新对象数据
SkuDetailCnDTO dbSkuClone = skuDao2CN(dbSkuDetail);
//旧对象数据
SkuDetailCnDTO editSkuClone = skuDao2CN(editSkuDetail);
//获取编辑实体为null的字段,排除这些字段,不修改
String[] nullPropertyNames = BeanUtil.getNullPropertyNames(editSkuClone);
//获得两个对象中不相等属性的详细数据
List<FieldDiff> fieldDiffList = BeanCompareUtils.compareExcludeFieldList(dbSkuClone, editSkuClone, Arrays.asList(nullPropertyNames)).getFieldDiffList();
难点解决:
- 对象里面包含复杂对象。当前方式不支持嵌套,可以把复杂对象的属性平铺在新对象同一层实体属性里,这样只需要关心新对象的属性即可。
- 不想比较对象的某些属性。通过加自定义注解的形式遍历对象属性来得到哪些属性需要比较,同时也解决了各个属性的中文名称不一致的问题。
- 动态实现不想要比较的某些属性。首先获取到新对象为null的属性列表,在工具类里添加新方法传进该集合,遍历对象属性时如果该属性既有自定义注解又不在此集合里,则进行比较。