你可能遇到这样的需求,要详细记录用户的操作日志,像下面这样:
用户张三将年龄从“20”改为“21”
用户张三将爱好从“篮球”改为“足球”
通常,用户可以一次改多个字段,然后一次性保存,这些字段的数据修改记录要分别保存。
这样的日志需要知道以下数据:
- 用户修改了数据库中什么字段
- 这个字段对应的中文名是什么
- 这个字段原来的值是什么
- 这个字段新值是什么
这几个问题里,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;
}
}