对象比较工具类:实现对业务的修改记录保存(对象字段差异对比)

测试

1:User类 

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {

    @FieldLabel("姓名")
    private String name;

    @FieldLabel("年龄")
    private Integer age;

    @FieldLabel("手机")
    private String phone;

    @FieldLabel("手机号")
    private String phoneNumber;

    @Nullable
    private Address address;
}

2:自定义注解

import java.lang.annotation.*;

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface FieldLabel {
    
    String value(); // 中文标识
}

3:工具类

依赖:
        <!-- google java lib -->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>17.0</version>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.5</version>
        </dependency>

utils:

package com.example.juc.utils.比较对象的差异工具类;

import com.google.gson.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.converter.json.GsonFactoryBean;

import java.util.*;

/**
 * @author: xlj
 * @create: 2024-10-09 15:29
 * @description: 对象比较工具类
 */
@Slf4j
public class ModelDiffUtil {

    private static final Gson gson = new Gson();
    private final static String SOURCE_VALUE = "source";
    private final static String TARGET_VALUE = "target";
    private final static String DOT_SEPARATOR = ".";

    /**
     * 将两个对象的差异转换为Map格式。
     *
     * @param sourceObject  原始对象
     * @param targetObject 当前对象
     * @return 包含差异的Map
     */
    public static<T> Map<String, Map<String, String>> getDiffMap(T sourceObject, T targetObject) {
        return getDiffMap(sourceObject, targetObject, null);
    }

    /**
     * 将两个对象的差异转换为Map格式,可以指定忽略的字段。
     *
     * @param sourceObject  原始对象
     * @param targetObject 当前对象
     * @param ignoreFields  忽略的字段列表
     * @return 包含差异的Map
     */
    public static <T> Map<String, Map<String, String>> getDiffMap(T sourceObject, T targetObject, List<String> ignoreFields) {
        try {
            String sourceJsonStr = gson.toJson(sourceObject);
            String targetJsonStr = gson.toJson(targetObject);
            return getDiffMap(sourceJsonStr, targetJsonStr, ignoreFields);
        } catch (JsonSyntaxException e) {
            log.error("Failed to parse object to JSON", e);
            return new HashMap<>();
        }
    }

    /**
     * 从JSON字符串中提取差异。
     *
     * @param sourceJsonStr  原始JSON字符串
     * @param targetJsonStr 当前JSON字符串
     * @return 包含差异的Map
     */
    public static Map<String, Map<String, String>> getDiffMap(String sourceJsonStr, String targetJsonStr) {
        return getDiffMap(sourceJsonStr, targetJsonStr, null);
    }

    /**
     * 从JSON字符串中提取差异,可以指定忽略的字段。
     *
     * @param sourceJsonStr  原始JSON字符串
     * @param targetJsonStr 当前JSON字符串
     * @param ignoreFields   忽略的字段列表
     * @return 包含差异的Map
     */
    public static Map<String, Map<String, String>> getDiffMap(String sourceJsonStr, String targetJsonStr, List<String> ignoreFields) {
        try {
            JsonObject sourceJson = new JsonParser().parse(sourceJsonStr).getAsJsonObject();
            JsonObject targetJson = new JsonParser().parse(targetJsonStr).getAsJsonObject();
            Map<String, String> sourceMap = new LinkedHashMap<>();
            Map<String, String> targetMap = new LinkedHashMap<>();
            convertJsonToMap(sourceJson, StringUtils.EMPTY, sourceMap, ignoreFields);
            convertJsonToMap(targetJson, StringUtils.EMPTY, targetMap, ignoreFields);
            return doCompare(sourceMap, targetMap);
        } catch (JsonSyntaxException e) {
            log.error("Failed to parse JSON string", e);
            return new HashMap<>();
        }
    }

    /**
     * 将JSON对象转换为Map,忽略指定的字段。
     *
     * @param json         JSON对象
     * @param root         当前JSON路径
     * @param resultMap    存储转换结果的Map
     * @param ignoreFields 忽略的字段列表
     */
    /**
     * 将JSON对象转换为Map,忽略指定的字段。
     *
     * @param json         JSON对象
     * @param root         当前JSON路径
     * @param resultMap    存储转换结果的Map
     * @param ignoreFields 忽略的字段列表
     */
    private static void convertJsonToMap(JsonElement json, String root, Map<String, String> resultMap, List<String> ignoreFields) {
        if (json.isJsonObject()) {
            JsonObject jsonObject = json.getAsJsonObject();
            for (Map.Entry<String, JsonElement> entry : jsonObject.entrySet()) {
                String key = entry.getKey();
                if (CollectionUtils.isNotEmpty(ignoreFields) && ignoreFields.contains(key)) {
                    continue;
                }
                JsonElement value = entry.getValue();
                String newRoot = (root.isEmpty())? key : root + DOT_SEPARATOR + key;
                if (value.isJsonObject() || value.isJsonArray()) {
                    convertJsonToMap(value, newRoot, resultMap, ignoreFields);
                } else if(value.isJsonPrimitive()){
                    resultMap.put(newRoot, value.getAsJsonPrimitive().getAsString());
                }
            }
        } else if (json.isJsonArray()) {
            JsonArray jsonArray = json.getAsJsonArray();
            for (int i = 0; i < jsonArray.size(); i++) {
                JsonElement value = jsonArray.get(i);
                String newRoot = (root.isEmpty())? "[" + i + "]" : root + "[" + i + "]";
                if (value.isJsonObject() || value.isJsonArray()) {
                    convertJsonToMap(value, newRoot, resultMap, ignoreFields);
                } else if(value.isJsonPrimitive()){
                    resultMap.put(newRoot, value.getAsJsonPrimitive().getAsString());
                }
            }
        }
    }

    /**
     * 执行实际的比较操作,返回包含差异的Map
     *
     * @param sourceMap  原始Map
     * @param targetMap 当前Map
     * @return 包含差异的Map
     */
    private static Map<String, Map<String, String>> doCompare(Map<String, String> sourceMap, Map<String, String> targetMap) {
        Map<String, Map<String, String>> diffMap = new HashMap<>();
        for (Map.Entry<String, String> entry : targetMap.entrySet()) {
            String key = entry.getKey();
            String newValue = entry.getValue();
            String oldValue = sourceMap.get(key);
            if (sourceMap.containsKey(key)) {
                if (!ObjectUtils.equals(newValue, oldValue)) {
                    addDiffMap(diffMap, key, oldValue, newValue);
                }
            } else {
                addDiffMap(diffMap, key, StringUtils.EMPTY, newValue);
            }
        }
        return diffMap;
    }


    /**
     * 将差异项添加到差异映射中。
     *
     * 此方法用于在处理两个数据集的差异时,将特定的差异项添加到一个映射中,
     * 其中每个差异项由一个键值对表示,包括源值和目标值。
     *
     * @param diffMap 保存差异项的映射,其中键是差异项的标识,值是包含源值和目标值的映射。
     * @param key 差异项的标识,用于在diffMap中作为键。
     * @param value 源对象中的值,表示差异的起始点。
     * @param targetValue 目标对象中的值,表示与源值不同的目标值。
     */
    private static void addDiffMap(Map<String, Map<String, String>> diffMap, String key, String value, String targetValue) {
        Map<String, String> diffItemMap = new HashMap<>();
        diffItemMap.put(SOURCE_VALUE, value);
        diffItemMap.put(TARGET_VALUE, targetValue);
        diffMap.put(key, diffItemMap);
    }
}

比较2个不同类型的字段是否有差异

package tech.tongyu.etrade.trsmanager.utils;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.NullNode;

import java.math.BigDecimal;
import java.util.*;

public class ObjectDiffUtil {

    private static final ObjectMapper mapper = new ObjectMapper();

    /**
     * 比较两个对象是否有差异
     */
    public static boolean isChanged(Object oldObj, Object newObj) {
        JsonNode oldNode = oldObj == null ? NullNode.getInstance() : mapper.valueToTree(oldObj);
        JsonNode newNode = newObj == null ? NullNode.getInstance() : mapper.valueToTree(newObj);
        return isChangedJsonNode(oldNode, newNode);
    }

    private static boolean isChangedJsonNode(JsonNode oldNode, JsonNode newNode) {
        // 都为 null
        if (oldNode == null || oldNode.isNull()) {
            return !(newNode == null || newNode.isNull());
        }
        if (newNode == null || newNode.isNull()) {
            return true;
        }

        // 数组/集合(顺序不敏感)比较
        if (oldNode.isArray() && newNode.isArray()) {
            if (oldNode.size() != newNode.size()) return true;

            List<JsonNode> oldList = new ArrayList<>();
            oldNode.forEach(oldList::add);
            List<JsonNode> newList = new ArrayList<>();
            newNode.forEach(newList::add);

            // 用计数 map 判断集合是否相等
            Map<String, Integer> countMap = new HashMap<>();
            for (JsonNode node : oldList) {
                String key = node.toString();
                countMap.put(key, countMap.getOrDefault(key, 0) + 1);
            }
            for (JsonNode node : newList) {
                String key = node.toString();
                if (!countMap.containsKey(key)) return true;
                countMap.put(key, countMap.get(key) - 1);
                if (countMap.get(key) == 0) countMap.remove(key);
            }
            return !countMap.isEmpty();
        }

        // 对象比较
        if (oldNode.isObject() && newNode.isObject()) {
            Iterator<String> fieldNames = oldNode.fieldNames();
            while (fieldNames.hasNext()) {
                String field = fieldNames.next();
                JsonNode oldField = oldNode.get(field);
                JsonNode newField = newNode.get(field);
                if (isChangedJsonNode(oldField, newField)) {
                    return true;
                }
            }
            // 检查 newNode 是否有 oldNode 没有的字段
            Iterator<String> newFieldNames = newNode.fieldNames();
            while (newFieldNames.hasNext()) {
                String field = newFieldNames.next();
                if (!oldNode.has(field)) return true;
            }
            return false;
        }

        // 数值类型统一用 BigDecimal 比较
        if (oldNode.isNumber() && newNode.isNumber()) {
            BigDecimal oldNum = oldNode.decimalValue();
            BigDecimal newNum = newNode.decimalValue();
            return oldNum.compareTo(newNum) != 0;
        }

        // 时间类型或字符串类型直接比较
        if (oldNode.isTextual() && newNode.isTextual()) {
            return !oldNode.asText().equals(newNode.asText());
        }

        // 布尔类型
        if (oldNode.isBoolean() && newNode.isBoolean()) {
            return oldNode.asBoolean() != newNode.asBoolean();
        }

        // 默认 JsonNode equals
        return !oldNode.equals(newNode);
    }
}

测试:
public class 对象字段差异 {

    public static void main(String[] args) {
        User user = new User();
        user.setName("夏天");
        user.setAge(18);
        user.setPhone("苹果");
        user.setPhoneNumber("177");

        List<String> ignoreFields = Collections.singletonList("address");

        User user1 = new User();
        user1.setName("夏天");
        user1.setAge(188);
        user1.setPhone("华为");
        user1.setPhoneNumber("177");

        Map<String, Map<String, String>> diffMap = ModelDiffUtil.getDiffMap(user, user1, ignoreFields);
        System.out.println(diffMap);

        Map<String, String> map = AnnotationUtil.printFieldLabels(User.class);
        System.out.println(map);
        System.out.println("--------------------------");
        List<String> diff = new java.util.ArrayList<>(Collections.emptyList());

        diffMap.forEach((k, v) -> {
            String object = map.get(k);
            if (StrUtil.isNotBlank(object)) {
                diff.add(object);
            }
        });
        System.out.println(diff);

    }
}
结果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

大大怪~将军

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

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

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

打赏作者

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

抵扣说明:

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

余额充值