公司来了个京东T6,只用两个工具类教会了我如何进行数据对比

本文介绍了在服务重构中涉及的数据迁移和校验过程,分享了一位来自京东的专家所编写的高效数据对比工具。工具包括两种场景:一是通用对象属性比较,二是多线程异步数据比对。代码使用了设计模式,具备良好的可读性和灵活性,支持各种数据类型的比较,包括对象、集合、JSON等,并能处理内部嵌套和继承。
摘要由CSDN通过智能技术生成

前言

最近公司在做优化方面的工作,其中有些服务需要重构。过程中会涉及到数据迁移、双写、补偿、补偿等,刚好一起做这块的有个刚从京东过来的大佬。

前天 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版本,之后会继续开发优化,扩展更多的数据对比场景。还会提供一个注解版。让我们拭目以待!

评论 50
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱编程的大李子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值