用户操作日志记录字段修改前后值

你可能遇到这样的需求,要详细记录用户的操作日志,像下面这样:

用户张三将年龄从“20”改为“21”
用户张三将爱好从“篮球”改为“足球”

 通常,用户可以一次改多个字段,然后一次性保存,这些字段的数据修改记录要分别保存。

这样的日志需要知道以下数据:

  1. 用户修改了数据库中什么字段
  2. 这个字段对应的中文名是什么
  3. 这个字段原来的值是什么
  4. 这个字段新值是什么

这几个问题里,1和4是闭着眼都能搞明白的。

问题2,字段的中文名可以用一个枚举类把英文和中文对应起来,但是如果字段很多的话,很繁琐,然后想起了注解,对字段添加注解,是否可以获取字段的中文意思呢?

问题3,这个字段原来的值我们也是知道的,但如何保存呢?我首先想到的是:用户修改时将修改前的旧值和新值从前端一起传过来,但是这样有安全问题,因为用户可以按F12在浏览器修改页面上的旧值,所以需要在修改前从数据库先查询出所有字段的旧值,然后与新值依次对比,从中找到用户修改了哪些字段。

网上搜索一番,看到这篇文章,得到启示,下面按文章思路实现,并加以改进。

自定义一个注解类来标注字段对应的中文。

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 字段中文别名
 * @author test
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface FieldAlias {
    String value() default "";
}

然后,类需要记录日志的字段上加注解:

public class User {
	
	private int userId;
	
	@FieldAlias("姓名")
	private String name;
	
	@FieldAlias("年龄")
	private int age;
	
	@FieldAlias("爱好")
	private String hobby;
	
	public User(){
		
	}
	
	public User(int userId, String name, int age) {
		this.userId = userId;
		this.name = name;
		this.age = age;
	}

    // 这里省略get set 方法
}
/**
 * 两个对象差异-字段新旧值
 * @author test
 */
public class FieldDiff {

	/**
	 * 字段英文名
	 */
	private String fieldENName;
	
	/**
	 * 字段中文名
	 */
	private String fieldCNName;
	
	/**
	 * 旧值
	 */
	private Object oldValue;
	
	/**
	 * 新值
	 */
	private Object newValue;
	
	
	public FieldDiff(String fieldENName, String fieldCNName, Object oldValue, Object newValue) {
		this.fieldENName = fieldENName;
		this.fieldCNName = fieldCNName;
		this.oldValue = oldValue;
		this.newValue = newValue;
	}

    // 这里省略get set 方法

	@Override
	public String toString() {
		String oldVal = this.oldValue == null ? "" : this.oldValue.toString();
        String newVal = this.newValue == null ? "" : this.newValue.toString();
		return "将 " + this.fieldCNName + " 从“" + oldVal + "” 修改为 “" + newVal + "”";
	}
	
}
import java.util.ArrayList;
import java.util.List;

/**
 * 两个对象差异
 * @author test
 */
public class BeanDiff {

	/**
	 * 所有差异字段list
	 */
	private List<FieldDiff> fieldDiffList = new ArrayList<>();
	
	public void addFieldDiff(FieldDiff fieldDiff) {
		this.fieldDiffList.add(fieldDiff);
	}

	public List<FieldDiff> getFieldDiffList() {
		return fieldDiffList;
	}

	public void setFieldDiffList(List<FieldDiff> fieldDiffList) {
		this.fieldDiffList = fieldDiffList;
	}
	
}
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;


/**
 * 实例字段差异比较工具类
 * @author test
 */
public class BeanCompareUtils {
	
	private static final String INCLUDE = "INCLUDE";
	private static final String EXCLUDE = "EXCLUDE";
	private static final String FILTER_TYPE = "FILTER_TYPE";
	private static final String FILTER_ARRAY = "FILTER_ARRAY";
	
	// 存放过滤类型及过滤字段数组
	private static ThreadLocal<Map<String, Object>> threadLocal = new ThreadLocal<>();
	
	public static void main(String[] args) {
		
		User oldUser = new User(1, "张三", 20);
		User newUser = new User(2, "李四", 21);
		oldUser.setMoney(1100.1);
		newUser.setMoney(2200.52);
		
		String[] fieldArray = new String[]{"age","name","hobby"};
		
		BeanDiff beanDiff = compareInclude(oldUser, newUser,fieldArray);
		
		List<FieldDiff> list = beanDiff.getFieldDiffList();
		if (list != null && list.size() > 0) {
			for (int i = 0; i< list.size(); i ++) {
				FieldDiff fieldDiff = list.get(i);
				System.out.println(fieldDiff.toString());
			}
		}
	}
	
	/**
	 * bean比较
	 * @param oldBean
	 * @param newBean
	 * @return
	 */
	public static BeanDiff compare(Object oldBean, Object newBean) {
		BeanDiff beanDiff = new BeanDiff();
		
		Class oldClass = oldBean.getClass();
		Class newClass = newBean.getClass();
		
		if (oldClass.equals(newClass)) {
			List<Field> fieldList = new ArrayList<>();
			fieldList = getCompareFieldList(fieldList, newClass);
			
			Map<String, Object> map = threadLocal.get();
			
			boolean needInclude = false;
			boolean needExclude = false;
			boolean hasArray = false;
			String[] fieldArray = null;
			
			if(map != null) {
				fieldArray = (String[])map.get(FILTER_ARRAY);
				String type = (String)map.get(FILTER_TYPE);
				
				if (fieldArray != null && fieldArray.length > 0) {
					// 数组排序
					Arrays.sort(fieldArray);
					hasArray = true;
					
					if (INCLUDE.equals(type)) {
						needInclude = true;
					} else if (EXCLUDE.equals(type)) {
						needExclude = true;
					}
				}
			}
			
			for (int i = 0; i < fieldList.size(); i ++) {
				Field field = fieldList.get(i);
				field.setAccessible(true);
				FieldAlias alias = field.getAnnotation(FieldAlias.class);
				
				try {
					Object oldValue = field.get(oldBean);
					Object newValue = field.get(newBean);
					
					if (hasArray) {
						// 二分法查找该字段是否被排除或包含
						int idx = Arrays.binarySearch(fieldArray, field.getName());
						
						// 该字段被指定排除或没有指定包含
						if ((needExclude && idx > -1) || (needInclude && idx < 0)) {
							continue;
						}
					}
					
					if (nullableNotEquals(oldValue, newValue)) {
						FieldDiff fieldDiff = new FieldDiff(field.getName(), alias.value(), oldValue, newValue);
						
						// 打印
						System.out.println(fieldDiff.toString());
						
						beanDiff.addFieldDiff(fieldDiff);
					}
					
				} catch (IllegalArgumentException e) {
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					e.printStackTrace();
				}
			}
		}
		
		return beanDiff;
	}
	
	/**
	 * bean比较
	 * @param oldBean
	 * @param newBean
	 * @param includeFieldArray 需要包含的字段
	 * @return
	 */
	public static BeanDiff compareInclude(Object oldBean, Object newBean, String[] includeFieldArray) {
		Map<String, Object> map = new HashMap<>();
		map.put(FILTER_TYPE, INCLUDE);
		map.put(FILTER_ARRAY, includeFieldArray);
		threadLocal.set(map);
		
		return compare(oldBean, newBean);
	}
	
	/**
	 * bean比较
	 * @param oldBean
	 * @param newBean
	 * @param excludeFieldArray 需要排除的字段
	 * @return
	 */
	public static BeanDiff compareExclude(Object oldBean, Object newBean, String[] excludeFieldArray) {
		Map<String, Object> map = new HashMap<>();
		map.put(FILTER_TYPE, EXCLUDE);
		map.put(FILTER_ARRAY, excludeFieldArray);
		threadLocal.set(map);
		
		return compare(oldBean, newBean);
	}
	
	
	/**
	 * 获取需要比较的字段list
	 * @param fieldList
	 * @param clazz
	 * @return
	 */
	private static List<Field> getCompareFieldList(List<Field> fieldList, Class clazz) {
		Field[] fieldArray = clazz.getDeclaredFields();
		
		List<Field> list = Arrays.asList(fieldArray);
		
		for (int i = 0; i < list.size(); i ++) {
			Field field = list.get(i);
			FieldAlias alias = field.getAnnotation(FieldAlias.class);
			if (alias != null) {
				fieldList.add(field);
			}
		}
		
		Class superClass = clazz.getSuperclass();
		if (superClass != null) {
			getCompareFieldList(fieldList, superClass);
		}
		return fieldList;
	}
	
	
	/**
	 * 比较值是否不相等
	 * @param oldValue
	 * @param newValue
	 * @return
	 */
	private static boolean nullableNotEquals(Object oldValue, Object newValue) {
		
		if (oldValue == null && newValue == null) {
			return false;
		}

		if (oldValue != null && oldValue.equals(newValue)) {
			return false;
		}
		
		if (("".equals(oldValue) && newValue == null) || ("".equals(newValue) && oldValue == null)) {
			return false;
		}
		
		return true;
	}
	
}

Java中可以使用Log4j或Logback等日志框架来记录日志,并且这些日志框架都支持将日志存储到数据库中。下面以Log4j2为例,介绍如何将日志记录到数据库。 1. 添加依赖 在项目中添加Log4j2的依赖,以及将日志记录到数据库所需的数据库驱动依赖。例如: ```xml <!-- log4j2依赖 --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>2.14.1</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.14.1</version> </dependency> <!-- 将日志存储到数据库所需的数据库驱动依赖 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.26</version> </dependency> ``` 2. 配置Log4j2 在项目中添加log4j2.xml配置文件,并进行相应的配置,例如: ```xml <?xml version="1.0" encoding="UTF-8"?> <Configuration status="WARN"> <Appenders> <!-- 将日志存储到数据库中的Appender --> <Jdbc name="databaseAppender" tableName="log_table"> <ConnectionFactory class="com.mysql.cj.jdbc.MysqlDataSource"> <param name="url" value="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=false"/> <param name="user" value="root"/> <param name="password" value="123456"/> </ConnectionFactory> <Column name="eventDate" isEventTimestamp="true"/> <Column name="level" pattern="%level"/> <Column name="logger" pattern="%logger"/> <Column name="message" pattern="%message"/> </Jdbc> <!-- 控制台输出日志的Appender --> <Console name="console" target="SYSTEM_OUT"> <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} [%t] %-5level %logger{36} - %msg%n"/> </Console> </Appenders> <Loggers> <Root level="info"> <AppenderRef ref="console"/> <AppenderRef ref="databaseAppender"/> </Root> </Loggers> </Configuration> ``` 上述配置中,使用Jdbc Appender将日志记录到数据库中,需要指定数据库连接信息、表名以及需要记录字段等。 3. 使用日志框架记录日志Java代码中,使用log4j2记录日志,例如: ```java import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; public class LogTest { private static final Logger logger = LogManager.getLogger(LogTest.class); public static void main(String[] args) { logger.info("Hello, world!"); } } ``` 上述代码中,使用LogManager获取Logger对象,并使用Logger对象记录日志日志信息会同时输出到控制台和数据库中。 以上就是将日志记录到数据库的简单实现方法,当然具体实现还需根据实际情况进行调整和优化。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值