记录Java对象修改前和修改后的变化
一、记录跟变信息对象
package com.yf.client.entity.log; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.yf.auth.client.util.StpLoginUserUtil; import com.yf.utils.StringUtils; import lombok.Getter; import lombok.Setter; import lombok.ToString; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.List; /** * @author FengQing * @program yf-client * @description * @date 2023/11/01 */ @Getter @Setter @ToString public class ChangePropertyMsg { /** * 变更信息 */ private String changeMsg; /** * 变更属性集合 */ private List<String> properties; /** * 变更信息(JSON数组) */ private JSONArray changeList; public ChangePropertyMsg() { this.changeList = new JSONArray(); } public void addChange(String fieldName, Object oldValue, Object newValue, String attribute) { JSONObject changeObject = new JSONObject(); changeObject.put("fieldName", fieldName); changeObject.put("attribute", attribute); if (StringUtils.isNotEmpty(oldValue)) { changeObject.put("oldBean", oldValue); } else { changeObject.put("oldBean", ""); } changeObject.put("newBean", newValue); changeObject.put("updateTime", formatLocalDateTime(LocalDateTime.now())); changeObject.put("userName", StpLoginUserUtil.getLoginUser().getNickname()); changeList.add(changeObject); } private String formatLocalDateTime(LocalDateTime localDateTime) { DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); return localDateTime.format(formatter); } public JSONArray getChangeList() { return changeList; } }
二、工具类(传入两个相同类型的对象,对比属性得到修改信息)
提示:监听属性变化是通过【ApiModelProperty】注解,当然你也可以通过自定义注解实现;
import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.ArrayUtil; import com.yf.client.entity.log.ChangePropertyMsg; import com.yf.utils.StringUtils; import lombok.extern.slf4j.Slf4j; import io.swagger.annotations.ApiModelProperty; import org.apache.commons.lang.ObjectUtils; import java.lang.reflect.Field; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.List; /** * @author FengQing * @program yf-client * @description * @date 2023/11/01 */ @Slf4j public class BeanChangeUtil<T> { /** * 传入两个相同类型的对象,对比属性得到修改信息 * @param oldBean * @param newBean * @return 属性修改信息 */ public static <aClass> String getChangeInfo(Object oldBean, Object newBean){ Class aClass = oldBean.getClass(); BeanChangeUtil<aClass> t = new BeanChangeUtil<>(); ChangePropertyMsg cfs = t.contrastObj(oldBean, newBean); if (StringUtils.isNotEmpty(cfs.getChangeMsg())) { return cfs.getChangeMsg(); } return null; } /** * 传入两个相同类型的对象,对比属性得到修改信息 * @param oldBean * @param newBean * @return **完整属性修改信息** */ public ChangePropertyMsg contrastObj(Object oldBean, Object newBean) { // 转换为传入的泛型T T oldPojo = (T) oldBean; // 通过反射获取类型及字段属性 Field[] fields = oldPojo.getClass().getDeclaredFields(); return jdk8OrAfter(Arrays.asList(fields), oldPojo, (T) newBean); } // lambda表达式,表达式内部的变量都是final修饰,需要传入final类型的数组 private ChangePropertyMsg jdk8OrAfter(List<Field> fields, T oldBean, T newBean) { ChangePropertyMsg cf = new ChangePropertyMsg(); // 创建字符串拼接对象 StringBuilder str = new StringBuilder(); List<String> fieldList = new ArrayList<>(); fields.forEach(field -> { field.setAccessible(true); if (field.isAnnotationPresent(ApiModelProperty.class)) { try { // 获取属性值 Object newValue = field.get(newBean); Object oldValue = field.get(oldBean); if (StringUtils.isNotNull(newValue)) { if (ObjectUtils.notEqual(oldValue, newValue)) { boolean isOldValueArray = ArrayUtil.isArray(oldValue); boolean isNewValueArray = ArrayUtil.isArray(newValue); if (isOldValueArray && isNewValueArray) { Object[] oldArray = (Object[]) oldValue; Object[] newArray = (Object[]) newValue; if (!Arrays.deepEquals(oldArray, newArray)) { fieldList.add(field.getName()); str.append(field.getAnnotation(ApiModelProperty.class).value() + ":"); str.append("修改前=【" + Arrays.toString(oldArray) + "】,修改后=【" + Arrays.toString(newArray) + "】;\n"); cf.addChange(field.getAnnotation(ApiModelProperty.class).value(), Arrays.toString(oldArray), Arrays.toString(newArray)); } } else { fieldList.add(field.getName()); str.append(field.getAnnotation(ApiModelProperty.class).value() + ":"); str.append("修改前=【" + formatPropertyValue(oldValue) + "】,修改后=【" + formatPropertyValue(newValue) + "】;\n"); cf.addChange(field.getAnnotation(ApiModelProperty.class).value(), formatPropertyValue(oldValue), formatPropertyValue(newValue)); } } } } catch (Exception e) { log.error("比对Bean属性是否变化失败,", e); } } }); cf.setChangeMsg(str.toString()); cf.setProperties(fieldList); return cf; } /** * 时间处理 * @param value * @return */ private static Object formatPropertyValue(Object value) { if (value instanceof Date) { return DateUtil.format((Date) value, "yyyy-MM-dd"); } else if (value instanceof LocalDateTime) { LocalDateTime localDateTimeValue = (LocalDateTime) value; return localDateTimeValue.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); } return value; } }
三、在修改业务中使用步骤(我这边使用的mybatis-plus,其它的同理方式处理
/**
* 更新操作(业务层)
* @param channel * @return */ @Override @Transactional(rollbackFor = {Exception.class, RuntimeException.class, MybatisPlusException.class}) public void updateChannel(ChannelParam channel) { // 1、查询修改前的数据 ChannelVo channelVo = this.selectChannelById(channel.getId());
// 2、修改前的对象跟前端返回的对象不一样如:前端传来的“ChannelParam”,修改前是“ChannelVo”对象,把它复制到统一对象中 ChannelParam oldParam = new ChannelParam(); BeanUtil.copyProperties(channelVo, oldParam); Channel data = new Channel;
BeanUtil.copyProperties(channel, data); super.updateById(data); // 记录日志对象 ChannelLogParam logParam = new ChannelLogParam();
// 调用监听属性变化工具类 BeanChangeUtil<ChannelParam> t = new BeanChangeUtil<>(); ChangePropertyMsg cfs = t.contrastObj(oldParam, channel); if (StringUtils.isNotBlank(cfs.getChangeMsg())) {
logParam.setUpdateObj(cfs.getProperties().toString()); logParam.setUpdateMsg(cfs.getChangeMsg());
logParam.setJsonMsg(cfs.getChangeList().toString());
}
// 添加日志记录
channelLogService.insertChannelLogging(logParam);
}
四、数据库存储效果如下图:
到这里就大功告成了。