java记录操作日志(对象修改细节)

2023-10-26

背景

由于业务涉及收入敏感信息,需记录数据变更前的内容和变更后的内容,但是不能为完成任务而硬编码,要适用于不同bean。针对这种情况,本文使用泛型、反射和基于AOP的自定义注解技术来完成,对对象属性的描述通过自定义注解来完成,读取里面的属性进而记录修改历史。

需求分析

利用泛型、反射和自定义注解技术,分别比较修改前后两个Bean实例的、所有添加了自定义注解的成员变量,当值不一致时,记录变量名称和修改前后的值。 这种方法适用于处理不同的bean,可以达到一次编码,多处复用的效果。

工具类定义如下:

import com.swagger.demo.bean.ChangePropertyMsg;
import com.swagger.demo.bean.PropertyMsg;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 *
 * @param <T>
 */
@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<>();
        // 属性改变个数
        final int[] i = {1};
        fields.forEach(field -> {
            field.setAccessible(true);
            if (field.isAnnotationPresent(PropertyMsg.class)) {
                try {
                    // 获取属性值
                    Object newValue = field.get(newBean);
                    Object oldValue = field.get(oldBean);
                    if (ObjectUtils.notEqual(oldValue, newValue)) {
                        fieldList.add(field.getName());
                        str.append(i[0] + "、" + field.getAnnotation(PropertyMsg.class).propertyName() + ":")
                                .append("修改前->" + oldValue + ",修改后->" + newValue + "\n");
                        i[0]++;
                    }
                } catch (Exception e) {
                    log.error("比对Bean属性是否变化失败,", e);
                }
            }
        });
        cf.setChangeMsg(str.toString());
        cf.setProperties(fieldList);
        return cf;
    }
}

在对象中,需要比对是否变化的属性上加上自定义注解@PropertyMsg,propertyName设置为属性的中文描述。
新bean一般由前端传过来,而旧bean需要去数据库查询。自定义注解@PropertyMsg如下所示:

import java.lang.annotation.*;

/**
 *  属性信息注解,仅仅可以用于域声明
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface PropertyMsg {
    /**
     * 提示语,用于标记哪个字段发生变更
     *
     * @return 提示语
     */
    String value() default "";

    /**
     * 变更属性名
     * @return
     */
    String propertyName();

}

下面创建一个User case:

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

@Slf4j
public class TestChange {

    public static void main(String[] args) {
        User u1 = new User(30L, "Wiener", 27);
        User u2 = new User(30L, "楼兰胡杨", 20);
        BeanChangeUtil<User> t = new BeanChangeUtil<>();
        ChangePropertyMsg cfs = t.contrastObj(u1, u2);
        if (StringUtils.isBlank(cfs.getChangeMsg())) {
            log.info("未有改变");
        } else {
            // 属性发生变化,增加业务主键
            cfs.setBizNum(u2.getId().toString());
            log.info("属性变化:{}", cfs);
        }
    }

}
-- -----------------------------------

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

import java.io.Serializable;

@Getter
@Setter
@ToString
public class User implements Serializable {
    //实现serializable接口
    private static final long serialVersionUID = -2241172936329900646L;
    
    private Long id;
    @PropertyMsg(propertyName = "用户姓名")
    private String userName;
    private String msg;
    @PropertyMsg(propertyName = "年龄")
    private Integer age;

    /**
     * 无参构造器
     */
    public User() {
    }

    public User(Long id, String userName, Integer age) {
        this.id = id;
        this.userName = userName;
        this.age = age;
    }


-- -----------------------------------

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

import java.io.Serializable;

@Getter
@Setter
@ToString
public class User implements Serializable {
    //实现serializable接口
    private static final long serialVersionUID = -2241172936329900646L;

    private Long id;
    @PropertyMsg(propertyName = "用户姓名")
    private String userName;
    private String msg;
    @PropertyMsg(propertyName = "年龄")
    private Integer age;

    /**
     * 无参构造器
     */
    public User() {
    }

    public User(Long id, String userName, Integer age) {
        this.id = id;
        this.userName = userName;
        this.age = age;
    }

其中,ChangePropertyMsg用于记录属性变更结果,加到需要记录变化的字段,此注解代码如下:

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

import java.util.List;

@Getter
@Setter
@ToString
public class ChangePropertyMsg {

    /**
     * 业务主键
     */
    private String bizNum;
    /**
     * 变更信息
     */
    private String changeMsg;
    /**
     * 变更属性集合
     */
    private List<String> properties;
}

执行测试用例中的main函数,在控制台输出如下信息:

20:52:10.443 [main] INFO com.swagger.demo.bean.TestChange - 属性变化:ChangePropertyMsg(changeMsg=1、用户姓名:修改前->Wiener,修改后->楼兰胡杨
2、年龄:修改前->27,修改后->20
, properties=[userName, age])

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

java记录操作日志(对象修改细节) 的相关文章

随机推荐

  • 小程序 - 日期选择器

    效果图 实现步骤 1 创建组件 wxml
  • Unity3D控制Animator播放

    有一个需求 要求第一次打开一个界面触发一个动画播放 关闭界面动画播放关闭 再次打开界面继续上次到播放而不是重新开始播放 如果动画播放结束关闭打开界面入口 即无法在此进入该界面 有两种实现方法 方法1 第一次打开界面开始播放特效动画 调用an
  • AI—漂亮的花简单制作(含详细教程)

    暑假在家刷短视频 不 不 不 球球 别别别 平面制作搞起来 跟数媒小可爱学炒鸡有意思的AI吧 今天带来的是一朵漂亮的花 gt 效果图入下 步骤一 用 星形 工具 gt 角点数 为25 如图 gt 确定 步骤二 按住ALT键 复制一个星形 缩
  • 再也不用担心网吧开黑队友听不清了!降噪解决方案了解一下?

    欢迎大家前往腾讯云 社区 获取更多腾讯海量技术实践干货哦 本文由腾讯游戏云发表于云 社区专栏 经常逛游戏论坛的朋友会深有感触 很多玩家经常会在论坛里吐槽在网吧开黑的体验很差 噪音太多 在游戏语音开黑的过程中 如果其中一个队友身处网吧 则其他
  • chronyc sources -V

    Chrony 是一个时钟同步工具 而 chronyc sources V 是它的命令行参数 用于显示当前时钟源的详细信息 使用这个参数后 会输出当前时钟源的信息 包括 源类型 IP 地址 可用性 偏差和误差等 通过这些信息 可以了解时钟源的
  • Linux 编译运行查找头文件和库的顺序

    linux中在使用gcc进行编译时 可能会出现找不到相应库或头文件的情况 往往让人十分头疼 因此 此文描述了库和头文件的查找顺序和一些注意事项 希望能帮助大家在出错时能够快速定位和解决 头文件 gcc在编译时按照如下顺序寻找所需要的头文件
  • set,env和export的区别

    http blog csdn net longxibendi article details 6125075 http blog chinaunix net uid 128922 id 289956 html set 显示 设置 shell
  • JSP编程手记------jsp页url中传中文参数乱码问题续

    一个偶然的机会 发现了一个解决jsp页url中传中文参数乱码的方法 比我前面说的用隐形的form表单来的更加漂亮和容易 具体方法如下 程序清单 response sendRedirect gh 0001 xm java net URLEnc
  • Qt手动设置Kits套件

    Qt编译器版本手动设置方式 文章目录 Qt编译器版本手动设置方式 前言 一 安装MSVC2019 32bit 二 手动设置kits套件 总结 前言 在给Qt Creator添加自定义控件时 遇到的版本问题让人头疼 我用的Qt5 12 10
  • 那一年读过的技术经典书

    转载请注明 http blog csdn net xinzhangyanxiang article details 10199757 大学刚毕业 总结起来读过的书并不算多 而且主要集中在大四的时期读的 在此把经典统一总结一遍 先总结机器学习
  • Qt信号和信号槽(二)

    目录 信号槽使用拓展 示例1 一个信号可以对应多个槽函数 在上篇文章的代码中进行修改 示例2 用信号连接信号 信号槽的连接方式 示例 Lambda表达式 语法格式 定义和调用 信号槽使用拓展 一个信号可以连接多个槽函数 发送一个信号有多个处
  • SpringBoot使用quartz, 注入feignClient, client为null

    使用 autowired 活着构造方法注入 打断点查看注入的feigncliet为null 搜索网上的一些博客 大致原因是因为quartz启动是通过反射将一些类注入进来 启动的时候引用这个类还没初始化好 下面是解决方案 使用spring 提
  • OpenRestry-demo

    openRestry 基于Nginx和lua的高性能web平台内置了大量lua库和第三方的模块 为了使用lua开发一些定制功能 安装环境 安装环境 yum y install pcre devel openssl devel gcc cur
  • 基本的传染病模型:SI、SIS、SIR及其Python代码实现

    本文主要参考博客 http chengjunwang com en 2013 08 learn basic epidemic models with python 该博客有一些笔误 并且有些地方表述不准确 推荐大家阅读Albert Lasz
  • 【腾讯云 Cloud Studio 实战训练营】通过云IDE构建Web3项目

    iOS开发上架主页 在强者的眼中 没有最好 只有更好 移动开发领域优质创作者 阿里云专家博主 文章目录 背景 一 前言 二 Cloud Studio 主要功能和应用场景 三 Cloud Studio 实验前期准备 3 1 打开官网 3 2
  • 在MATLAB中,用Simulink搭建一个二阶传递函数模型

    文章目录 1 模型准备 二阶传递函数模型 1 1 二阶传递函数模型 1 1 1 时域模型 1 1 2 频域模型 1 2 二阶传递函数公式和参数 2 开始建模 在simulink中搭建二阶传递函数模型 2 1 从simulink库中添加传递函
  • python选择文件夹,并自动发送这个文件夹下的所有文件(以附件的形式)至指定邮箱(通过163邮箱)

    工作中需要将自动生成的表格发送给办公邮箱继续处理 结合遍历文件夹和自动发邮件 做出了可以选择文件夹 并自动发送这个文件夹下的所有文件 以附件的形式 至指定邮箱 import smtplib from email mime multipart
  • jdk8新特性-方法引用

    1 作用 什么是方法引用 方法引用是jdk8推出的一个新特性 在一定的条件下可以替换lambda表达式 可以理解为方法引用实际上还是一个lambda表达式 lambda表达式用法可参见 https blog csdn net xiao yu
  • Centos7 使用yum命令安装软件失败,报错"Couldn't open file /media/cdrom/repodata/repomd.xml"

    今天使用CentOS7安装docker的时候 安装失败 报错 yum install docker 已加载插件 fastestmirror langpacks file media cdrom repodata repomd xml Err
  • java记录操作日志(对象修改细节)

    背景 由于业务涉及收入敏感信息 需记录数据变更前的内容和变更后的内容 但是不能为完成任务而硬编码 要适用于不同bean 针对这种情况 本文使用泛型 反射和基于AOP的自定义注解技术来完成 对对象属性的描述通过自定义注解来完成 读取里面的属性