最近在公司接到一个系统操作日志的开发,完成如下的一个小需求,记录相关人员在系统中的操作记录
目录
1. 分析
首先要完成如上的需求,我们要有一个大致的思路,新增、删除、修改三种操作日志,其中新增前端会上传对象信息,直接生成一条日志的记录,删除操作一般会上传id,根据id查询数据库,拿到你想要的字段。
难点在与修改操作:修改操作类似新增,不过会有该条数据的标识如表id,我们可以根据id去数据库中查询旧值(需要注意的是该操作必须在事务更新前执行,不然查询出来的数据会是新值,坑点),拿到旧值对象,接下来我们只需要对比新旧值之间有哪些字段值不同即可判断,修改了哪些内容。
2. 查阅资料
接到开发任务的我毫不慌张,毕竟网上的资源一大堆,根据自己的需求去问度娘................巴拉巴拉,终于找到一篇文章可以为我所用 Java比较两个对象并获取其中不相等的字段
该篇文章主要讲如何获取两个对象中不相等的字段(前提是类对象相同哈)。。。大家可直接前往参谋.....我这里不再赘述。
API调用工程师终于等到你。。不想看的人直接复制下面的依赖吧。。。
<dependency>
<groupId>com.github.dadiyang</groupId>
<artifactId>equator</artifactId>
<version>1.0.3</version>
</dependency>
3. 调用牛逼博主的API,说干就干
核心接口代码如下:
接口实现继承如下,每个方法该文的博主介绍的很详细,我这里简单说几句:
FieldBaseEquator 抽象类实现,我们主要用到这里类。
一个优秀的程序员,必须要学会 copy 大神的优秀代码,来吧上代码,先编写一个实体User类
public class User {
private int age;
private String name;
private List<String> friendName;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<String> getFriendName() {
return friendName;
}
public void setFriendName(List<String> friendName) {
this.friendName = friendName;
}
}
执行main方法,没有什么问题~
public static void main(String[] args) {
User user = new User();
user.setAge(25);
user.setFriendName(Arrays.asList("chq","tom","Thompson"));
user.setName("ChenHQ");
User user1 = new User();
user1.setAge(28);
user1.setFriendName(Arrays.asList("chq2","tom"));
user1.setName("ChenHQ");
FieldBaseEquator fieldBaseEquator = new FieldBaseEquator();
List<FieldInfo> diffFields = fieldBaseEquator.getDiffFields(user, user1);
for (FieldInfo diffField : diffFields) {
System.out.println(diffField);
}
}
运行结果如下:
我们可以看到,通过 getDiffFields 方法,我们可以获取到实体类“值”不同的属性
4. 升级版
我们通过博主的构造方法得到,可以指定某些字段进行对比,或者指定某些字段不进行对比,只需要在构造方法中传入属性名称即可
/**
* 指定包含或排除某些字段
*
* @param includeFields 包含字段,若为 null 或空集,则不指定
* @param excludeFields 排除字段,若为 null 或空集,则不指定
*/
public FieldBaseEquator(List<String> includeFields, List<String> excludeFields) {
super(includeFields, excludeFields);
}
来实现一下吧,首先编写一个自定义注解,该注解标识在属性字段上,用来注明是否需要对比的字段
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotationDemo {
String value() default "";
}
我们在 年龄,名字 字段上加上声明的注解 @AnnotationDemo,其他不做修改
public class User {
@AnnotationDemo("年龄")
private int age;
@AnnotationDemo("姓名")
private String name;
private List<String> friendName;
......
}
写一个静态方法,返回对象中被注解注释的属性名集合 (即: age,name)
/**
* 获取对象注解注释过的属性名集合
*
* @param obj
* @return
*/
private static List<String> getImportFieldName(Object obj) {
//创建集合对象,保存是否是重要字段名
List<String> importFieldName = new ArrayList<>();
//获取类加载器对象
Class studentClass = obj.getClass();
//获取所有的字段
Field[] declaredFields = studentClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
if (declaredField.isAnnotationPresent(AnnotationDemo.class)) {
//保存
importFieldName.add(declaredField.getName());
}
}
return importFieldName;
}
执行如下main方法
public static void main(String[] args) {
User user = new User();
user.setAge(25);
user.setFriendName(Arrays.asList("chq","tom","Thompson"));
user.setName("ChenHQ");
User user1 = new User();
user1.setAge(28);
user1.setFriendName(Arrays.asList("chq2","tom"));
user1.setName("ChenHQ");
//调用静态方法,获取需要比较的字段名集合
List<String> importFieldName = getImportFieldName(user);
//调用带参构造方法
FieldBaseEquator fieldBaseEquator = new FieldBaseEquator(importFieldName,null);
List<FieldInfo> diffFields = fieldBaseEquator.getDiffFields(user, user1);
for (FieldInfo diffField : diffFields) {
System.out.println(diffField);
}
}
此时运行结果如下:程序只检测 age,name字段,其他不做比较
这时,我们的底层对比已经差不多实现。只是对比过后返回的信息我们处理下,我们期望返回如下实体类集合形式,方便我们操作,来吧,我们进一步封装一下代码
public class ModifiedField {
/**
* 被修改的字段名
*/
private String fieldName;
/**
* 被修改的旧值
*/
private Object oldValue;
/**
* 被修改的新值
*/
private Object newValue;
/**
* 被修改的字段名中文解释
*/
private String remark;
......
}
/**
* 比较并返回被修改过的对象属性 ModifiedField
*
* @param modifyObject: 新值对象
* @param oldObject: 旧值对象
* @return
*/
public static List<ModifiedField> compareObject(Object modifyObject, Object oldObject) {
//创建结果对象
List<ModifiedField> result = new ArrayList<>();
//获取对比字段
List<String> importFieldName = getImportFieldName(modifyObject);
FieldBaseEquator fieldBaseEquator = new FieldBaseEquator(importFieldName, null);
//对比,返回属性值不同字段
List<FieldInfo> diffFields = fieldBaseEquator.getDiffFields(modifyObject, oldObject);
//封装成我们需要的对象
for (FieldInfo diffField : diffFields) {
ModifiedField modifiedField = new ModifiedField();
modifiedField.setFieldName(diffField.getFieldName());
modifiedField.setNewValue(diffField.getFirstVal());
modifiedField.setOldValue(diffField.getSecondVal());
//获取字段注解中文释义
String remark = getRemarkByFieldName(modifyObject, diffField.getFieldName());
modifiedField.setRemark(remark);
result.add(modifiedField);
}
return result;
}
/**
* 根据传入的对象,和字段名,获取对应注解的中文名称
*
* @param obj
* @param fieldName
* @return
*/
private static String getRemarkByFieldName(Object obj, String fieldName) {
Class<?> aClass = obj.getClass();
try {
Field field = aClass.getDeclaredField(fieldName);
if (field.isAnnotationPresent(AnnotationDemo.class)) {
AnnotationDemo annotation = field.getAnnotation(AnnotationDemo.class);
return annotation.value();
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
执行main方法
public static void main(String[] args) {
User user = new User();
user.setAge(25);
user.setFriendName(Arrays.asList("chq","tom","Thompson"));
user.setName("ChenHQ");
User user1 = new User();
user1.setAge(28);
user1.setFriendName(Arrays.asList("chq2","tom"));
user1.setName("ChenHQ");
List<ModifiedField> modifiedFields = compareObject(user, user1);
for (ModifiedField modifiedField : modifiedFields) {
System.out.println(modifiedField);
}
}
如期而遇~,接下来的文案入库操作,我就不多说了。。愿你们码农之路,一路顺畅
2022-03-31 嵌套对象更新
很多小伙伴私信我能不能支持嵌套对象查询,这里更新一版
我们这里需要重写一个字段比较器 MyFieldBaseEquator,使用了递归查询属性,代码如下
/**
* @author ChenHQ
* @description: TODO
* @date 2022/3/31 18:43
*/
public class MyFieldBaseEquator extends AbstractEquator {
public MyFieldBaseEquator() {
}
/**
* 指定包含或排除某些字段
*
* @param includeFields 包含字段,若为 null 或空集,则不指定
* @param excludeFields 排除字段,若为 null 或空集,则不指定
*/
public MyFieldBaseEquator(List<String> includeFields, List<String> excludeFields) {
super(includeFields, excludeFields);
}
/**
* {@inheritDoc}
*/
@Override
public List<FieldInfo> getDiffFields(Object first, Object second) {
if (first == second) {
return Collections.emptyList();
}
// 先尝试判断是否为简单数据类型
if (isSimpleField(first, second)) {
return compareSimpleField(first, second);
}
Object obj = first == null ? second : first;
Class<?> clazz = obj.getClass();
List<FieldInfo> diffField = new LinkedList<>();
// 获取所有字段
Field[] fields = clazz.getDeclaredFields();
// 遍历所有的字段
for (Field firstField : fields) {
String fieldName = firstField.getName();
try {
Field secondField = clazzSecond.getDeclaredField(fieldName);
firstField.setAccessible(true);
secondField.setAccessible(true);
// 开启访问权限,否则获取私有字段会报错
Object firstVal = first == null ? null : firstField.get(first);
Object secondVal = second == null ? null : secondField.get(second);
if (this.WRAPPER.contains(firstField.getType()) || (firstVal instanceof Collection && secondVal instanceof Collection)) {
// 封装字段信息
FieldInfo fieldInfo = new FieldInfo(fieldName, firstField.getType(), firstVal, secondVal);
boolean eq = isFieldEquals(fieldInfo);
if (!eq) {
// 记录不相等的字段
diffField.add(fieldInfo);
}
} else if (onlyOneNull(firstVal, secondVal)) {
FieldInfo fieldInfo = new FieldInfo(fieldName, firstField.getType(), firstVal, secondVal);
diffField.add(fieldInfo);
} else if (Objects.isNull(firstVal) && Objects.isNull(secondVal)) {
//双方都为null,不做记录
continue;
} else {
diffField.addAll(getDiffFields(firstVal, secondVal));
}
} catch (IllegalAccessException e) {
// 只要调用了 firstField.setAccessible(true) 就不会报这个异常
throw new IllegalStateException("获取属性进行比对发生异常: " + fieldName, e);
} catch (NoSuchFieldException e) {
}
}
return diffField;
}
private boolean onlyOneNull(Object firstVal, Object secondVal) {
return (Objects.isNull(firstVal) && Objects.nonNull(secondVal)) || (Objects.isNull(secondVal) && Objects.nonNull(firstVal));
}
/**
* 如果简单数据类型的对象则直接进行比对
*
* @param first 对象1
* @param second 对象2
* @return 不同的字段信息,相等返回空集,不等则 FieldInfo 的字段名为对象的类型名称
*/
List<FieldInfo> compareSimpleField(Object first, Object second) {
boolean eq = Objects.equals(first, second);
if (eq) {
return Collections.emptyList();
} else {
Object obj = first == null ? second : first;
Class<?> clazz = obj.getClass();
// 不等的字段名称使用类的名称
return Collections.singletonList(new FieldInfo(clazz.getSimpleName(), clazz, first, second));
}
}
private static final List<Class<?>> WRAPPER = Arrays.asList(Byte.class, Short.class,
Integer.class, Long.class, Float.class, Double.class, Character.class,
Boolean.class, String.class);
/**
* 判断是否为原始数据类型
*
* @param first 对象1
* @param second 对象2
* @return 是否为原始数据类型
*/
boolean isSimpleField(Object first, Object second) {
Object obj = first == null ? second : first;
Class<?> clazz = obj.getClass();
return clazz.isPrimitive() || WRAPPER.contains(clazz);
}
}
结果通过测试,这里不做展示了,仅需注意使用我们自定义重写的类MyFieldBaseEquator即可
public static void main(String[] args) {
..............
//自定义重写的类
MyFieldBaseEquator fieldBaseEquator = new MyFieldBaseEquator(importFieldName,null);
List<FieldInfo> diffFields = fieldBaseEquator.getDiffFields(user, user1);
for (FieldInfo diffField : diffFields) {
System.out.println(diffField);
}
}