前言
最近公司在做优化方面的工作,其中有些服务需要重构。过程中会涉及到数据迁移、双写、补偿、补偿等,刚好一起做这块的有个刚从京东过来的大佬。
前天 Review 了 下这个大佬的代码,数据校验 自定义 Handler、数据对比定义工具类、大量使用设计模式、代码干净有条理!
经大佬同意,在此记录下他的数据对比代码,学习和分享下实现思路和严谨的代码风格!
对比工具一
使用场景:比较两个对象,如果有比较对象属性名的Map,则仅仅比较传入的,否则比较全部字段
/**
* 通用比对方法
*/
public class DataCompareUtil {
public static List<String> simpleFieldCompare(Object a, Object b) {
return simpleFieldCompare(a, b, null, false);
}
public static List<String> simpleFieldCompare(Object a, Object b, boolean ignoreNotExistField) {
return simpleFieldCompare(a, b, null, ignoreNotExistField);
}
/**
* 比较两个对象
*
* @param a
* @param b
* @param condition 要比较的属性名
* @return
*/
public static List<String> simpleFieldCompare(Object a, Object b, Map<String, String> condition) {
return simpleFieldCompare(a, b, condition, false);
}
/**
* @param aObject
* @param bObject
* @param condition 放一些需要比较的字段
* @param ignoreNotExistField 是否忽略不存在的field
* @return
*/
private static List<String> simpleFieldCompare(Object aObject, Object bObject, Map<String, String> condition, boolean ignoreNotExistField) {
List<String> diffRecord = new ArrayList<>();
if (aObject == null || bObject == null) {
if (aObject != bObject) {
diffRecord.add(String.format("有空对象:对象A=%s, 对象B=%s", JSON.toJSONString(aObject), JSON.toJSONString(bObject)));
}
return diffRecord;
}
// 获取aObject的所有字段
Class<?> aClass = aObject.getClass();
List<Field> aFields = getAllFields(aClass);
Class<?> bClass = bObject.getClass();
List<Field> bFields = getAllFields(bClass);
// key是fieldName,value是对应的Field
Map<String, Field> aFieldMap = aFields.stream().collect(Collectors.toMap(Field::getName, Function.identity(), ((x, y) -> x)));
Map<String, Field> bFieldMap = bFields.stream().collect(Collectors.toMap(Field::getName, Function.identity(), ((x, y) -> x)));
if (condition == null || condition.isEmpty()) {
diffRecord.addAll(aFieldMap.entrySet().stream().map(entry -> {
String aFieldName = entry.getKey();
Field aField = entry.getValue();
Field bField = bFieldMap.get(aFieldName);
// 说明两个对象的属性有所差异
if (bField == null) {
if (ignoreNotExistField) {
return null;
}
return "属性【" + aFieldName + "】在 对象B 中不存在";
}
// 俩对象属性名称对应,就开始比较相应的属性值
return compare(aFieldName, aFieldName, aField, bField, aObject, bObject);
}).filter(Objects::nonNull).collect(Collectors.toList()));
if (!ignoreNotExistField) {
diffRecord.addAll(bFieldMap.keySet().stream().map(bFieldName -> {
if (aFieldMap.get(bFieldName) == null) {
return "属性【" + bFieldName + "】在 对象A 中不存在";
}
return null;
}).filter(Objects::nonNull).collect(Collectors.toList()));
}
} else {
// 比对那些差异字段
diffRecord.addAll(condition.entrySet().stream().map(entry -> {
String aFieldName = entry.getKey();
String bFieldName = entry.getValue();
Field aField = aFieldMap.get(aFieldName);
Field bField = bFieldMap.get(bFieldName);
if (aField == null || bField == null) {
if (aField != bField) {
return String.format("有空对象:对象A=%s, 对象B=%s", JSON.toJSONString(aObject), JSON.toJSONString(bObject));
}
return null;
}
return compare(aFieldName, bFieldName, aField, bField, aObject, bObject);
}).filter(Objects::nonNull).collect(Collectors.toList()));
}
return diffRecord;
}
/**
* 对象属性值对比方法
*
* @param aName obj1待比较的属性名称
* @param bName obj2待比较的属性名称
* @param aField obj1待比较的Field
* @param bField obj2待比较的Field
* @return
*/
private static String compare(String aName, String bName, Field aField, Field bField, Object a, Object b) {
try {
// Filed.get(Object obj),获取对象对应的属性名。 妈的,差点把大哥的代码改了,,,我这沙雕
Object aObject = aField.get(a);
Object bObject = bField.get(b);
// 判断null的逻辑
if (aObject == null || bObject == null) {
// 属性值一个是null,一个不是
if (aObject != bObject) {
return "属性值不一致,对象A: " + aName + " = " + JSON.toJSONString(aObject) + ", 对象B: " + bName + " = " + JSON.toJSONString(bObject);
} else {
// 都为null
return null;
}
}
if (!aObject.equals(bObject)) {
return "属性值不一致,对象A: " + aName + " = " + JSON.toJSONString(aObject) + ", 对象B: " + bName + " = " + JSON.toJSONString(bObject);
} else {
return null;
}
} catch (IllegalAccessException e) {
return "属性比较异常: 对象A name =【" + aName + "】, 对象B name = 【" + bName + "】";
}
}
/**
* 获取当前类以及其父类所有的属性列表
*
* @param clazz
* @return
*/
private static List<Field> getAllFields(Class<?> clazz) {
List<Field> fieldList = new ArrayList<>();
Field[] fields = clazz.getDeclaredFields();
do {
if (fields.length > 0) {
fieldList.addAll(Arrays.stream(fields).peek(field -> field.setAccessible(true)).collect(Collectors.toList()));
}
clazz = clazz.getSuperclass();
fields = clazz.getFields();
}
while (!clazz.isInstance(Object.class));
return fieldList;
}
}
对比工具二
使用场景:
- 采用多线程异步的方式,基准值采用外部传入的方式,异步调取接口执行完封装好数据,才会有线程调用对比方法。 原值采用内部接口调用的方式
- 支持数据类型丰富:基本引用类型、集合、JSON字符串、Object等,且支持内部嵌套、类的继承、接口实现等
- 工具类的代码有条理,逻辑清晰,其中也含有递归、迭代等算法思想
/**
* @Description 数据比对线程
*/
@Slf4j
public class CompareToolsThread implements Runnable{
private Object param;
private JSONObject orderJson;
private Class beanClz;
private String methodName;
public CompareToolsThread(Object param, JSONObject orderJson, Class clz, String methodName) {
this.param = param;
this.orderJson = orderJson;
this.beanClz = clz;
this.methodName = methodName;
}
@Override
public void run() {
log.info("CompareToolsThread run-------");
Object beanClass = SpringUtils.getBean(beanClz);
JSONObject jsonResult = null;
try {
// 调用方法
Object object = beanClass.getClass().getDeclaredMethod(methodName, param.getClass()).invoke(beanClass, param);
if (object == null) {
log.info("查询接口失败,参数为:{}", JSONObject.toJSONString(param));
return;
}
if (object instanceof HashMap) {
jsonResult = new JSONObject((Map<String, Object>) object);
} else if (object instanceof JSONObject) {
jsonResult = (JSONObject) object;
}
} catch (Exception e) {
log.info("查询接口异常,参数为:{}", JSONObject.toJSONString(param));
}
for (Map.Entry entry : jsonResult.entrySet()) {
String key = (String) entry.getKey();
//要比对的对象
Object value = entry.getValue();
// 既然比较的是原值和基准值,那么就是拿着原值和基准值比较。不可能原值为Null,基准值存在的情况
//基准值 orderJson是异步的
Object originValue = orderJson.get(key);
//都为null,继续比对
if (value == null && originValue == null) {
continue;
}
if (value == null || originValue == null) {
log.info("对比失败,key===>{}", entry.getKey());
break;
}
boolean result = compareValue(value, originValue);
if (!result) {
log.info("对比失败,key===>{}", entry.getKey());
return;
}
}
}
/**
* @Description 内容对比
* @Param value,originValue
* @return boolean
**/
private boolean compareValue(Object value, Object originValue) {
// value原值 originValue基准值
if (originValue == null && value != null) {
return false;
}
if (originValue instanceof String) {
return value.equals(originValue);
}
if (originValue instanceof Integer) {
return value.equals(originValue);
}
if (originValue instanceof Long) {
return value.equals(originValue);
}
if (originValue instanceof Double) {
return value.equals(originValue);
}
if (originValue instanceof Float) {
return value.equals(originValue);
}
if (originValue instanceof Byte) {
return value.equals(originValue);
}
if (originValue instanceof Boolean) {
return value.equals(originValue);
}
if (originValue instanceof Date) {
return ((Date) value).compareTo((Date) originValue) == 0;
}
if (originValue instanceof BigDecimal) {
return (value instanceof BigDecimal ? (BigDecimal)value : new BigDecimal(value.toString())).compareTo((BigDecimal) originValue) == 0;
}
//toLowerCase(Locale.ROOT)):如果有其他语言,需要这个参数,如果仅仅是英语则无参就可以
if (originValue instanceof Enum) {
return value.equals(originValue.toString().toLowerCase(Locale.ROOT));
}
if (originValue instanceof ArrayList || originValue instanceof Set) {
if (CollectionUtils.isEmpty((Collection<?>) originValue) && CollectionUtils.isEmpty((Collection<?>) value)) {
return true;
}
if (CollectionUtils.isEmpty((Collection<?>) originValue) && !CollectionUtils.isEmpty((Collection<?>) value)) {
return false;
}
JSONArray array= JSONArray.parseArray(JSON.toJSONString(value));
JSONArray originArray= JSONArray.parseArray(JSON.toJSONString(originValue));
for (int i = 0; i< array.size(); i++) {
Map<String, Object> map = JSONObject.parseObject(array.get(i).toString(), HashMap.class);
Map<String, Object> originMap = JSONObject.parseObject(originArray.get(i).toString(), HashMap.class);
for (Map.Entry item : map.entrySet()) {
// 无线调用,这思路和布局绝了~
boolean result = compareValue(item.getValue(), originMap.get(item.getKey()));
if (!result) {
log.info("对比失败,key===>{}", item.getKey());
return false;
}
}
}
return true;
}
if (originValue instanceof JSONArray) {
if (originValue == null && value == null) {
return true;
}
if (value != null && originValue == null) {
return false;
}
JSONArray valueArray = (JSONArray) value;
JSONArray originArray = (JSONArray) originValue;
if (valueArray.size() == 0 && originArray.size() == 0) {
return true;
}
if (valueArray.size() > 0 && originArray.size() == 0) {
return false;
}
for (int i = 0; i < valueArray.size(); i++) {
JSONObject valueObject = (JSONObject) valueArray.get(0);
JSONObject originObject = (JSONObject) originArray.get(0);
for (Map.Entry item : valueObject.entrySet()) {
boolean result = compareValue(item.getValue(), originObject.get(item.getKey()));
if (!result) {
log.info("对比失败,key===>{}", item.getKey());
return false;
}
}
}
return true;
}
// 如果以上都没有匹配到,则按照Object处理
String valueStr = JSONObject.toJSONString(value);
String originStr = JSONObject.toJSONString(originValue);
if (StringUtils.isEmpty(valueStr) && StringUtils.isEmpty(originStr)) {
return true;
}
if (!StringUtils.isEmpty(valueStr) && StringUtils.isEmpty(originStr)) {
return false;
}
//上面如果没有匹配到,按对象处理,将对象转map
Map<String, Object> valueMap = JSON.parseObject(valueStr, new TypeReference<Map<String, Object>>() {});
Map<String, Object> originMap = JSON.parseObject(JSON.toJSONString(originValue), new TypeReference<Map<String, Object>>() {});
for (Map.Entry item : valueMap.entrySet()) {
boolean result = compareValue(item.getValue(), originMap.get(item.getKey()));
if (!result) {
log.info("对比失败,key===>{}", item.getKey());
return false;
}
}
return true;
}
}
注: 以上只是大佬开发的 对比工具 0.1版本,之后会继续开发优化,扩展更多的数据对比场景。还会提供一个注解版。让我们拭目以待!