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

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

前言


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


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

相关文章
|
7月前
|
缓存 分布式计算 算法
码农死磕这份Java高级开发文档,成功'挤'进一线大厂,这也太强了吧
拿到一份offer比什么都重要,所以笔者专门花了近一个月的时间整理好了一份专门为Java面试而生的总结,注意的是笔者仅仅对面试技术方面的题目进行的总结,至于如何去和面试官去聊,怎么聊,聊得嗨,这里笔者就不谈了,因为这方面并不是笔者擅长的。
|
SQL 前端开发 JavaScript
基于java+springboot的外卖点餐网站、外卖点餐管理系统
该系统是基于java+springboot开发的外卖点餐网站、外卖点餐管理系统。是给师弟开发的课程作业。运行过程中的问题,可以在github咨询作者。
126 0
|
算法 Java 程序员
这份Java面试指南在GitHub上超百万下载量!现惨遭大厂集体封杀
作为程序员,你反感面试的时候做题吗?有很多公司在面试中都会有让求职者现场做题的环节,尤其是与开发相关的岗位,比较常见。
|
移动开发 JavaScript Java
开发拍卖软件源码选择公司要点和推荐
随着在线拍卖市场的不断增长,越来越多的企业和创业者考虑进入这个潜力巨大的市场,开发拍卖APP。在着手开发之前,必须仔细考虑所需的功能,并选择适合项目需求的拍卖APP源码。本文将讨论选择和开发拍卖APP源码的关键要点,并介绍一款备受推荐的拍卖APP源码,即"东莞梦幻网络科技"的拍卖直播系统源码,这是一个快速启动的解决方案。
|
Android开发 开发者
科普技术贴:个人开发者的那些赚钱方式
公众号:smart_android 作者:耿广龙|loonggg   移动互联网的火爆,衍生出了许多新兴的职业,比如:个人开发者,自媒体等等。还出现了许多所谓的互联网思维,反正就是带来了许多让人无法理解或者可以轻松赚钱的渠道。因为我是一名程序员,我今天就来谈谈个人开发者是如何来赚钱的。     其实最直接的方式无非就有两种,一种是接私活,一种是加广告。    
2436 0
|
缓存 小程序 前端开发
一个小程序也能获得 50W 天使投资!图书社交小程序的核心步骤及源码分享 (上)
2017年共享经济很火,所以很多人在共享这事儿上动脑筋,也包括我。现在事实证明当时大部分的共享经济概念都死了。咱们国内图书价格非常便宜,共享借书连物流成本都负担不起,商业计划中的社交体系短期内也不可能搭建起来。
332 0
一个小程序也能获得 50W 天使投资!图书社交小程序的核心步骤及源码分享 (上)
|
设计模式 算法 数据库
零代码以“王者荣耀”为例解析设计七原则,助你面试拿“五杀”
面试设计原则还在死记硬背?一文助你深入理解设计模式七大原则。
15192 0
零代码以“王者荣耀”为例解析设计七原则,助你面试拿“五杀”
|
存储 小程序
怎么做一个社交类软件
交友从来都是网络的主旋律之一,在这个互联网发达的今天,我们通过社交软件有效增加了沟通便利,让越来越多的人可通过社交软件直接沟通,可根据时间和地域的限制。
183 0
怎么做一个社交类软件
|
人工智能 自然语言处理 安全
2019年,支付宝4亿行代码背后的故事
每敲下一行代码,影响的都是十几亿人
2957 0
2019年,支付宝4亿行代码背后的故事