场景
记录变更日志,例如:
场景1:管理员李四,新增了用户:昵称【lll】;真实姓名【李四】;是否被冻结【0】
场景2:管理员张三,修改了用户数据:地址【北京市】->【郑州市】
场景1实现
教师类:
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
import java.util.List;
/**
* @Author nitric oxide
* @Description
* @Date 6:25 下午 2021/11/8
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class Teacher {
/**
* id
*/
@ApiModelProperty("id")
private Integer id;
/**
* 昵称
*/
@ApiModelProperty("昵称")
private String nickName;
/**
* 密码
*/
@ApiModelProperty("密码")
private String password;
/**
* 真实姓名
*/
@ApiModelProperty("真实姓名")
private String trueName;
/**
* 手机号
*/
@ApiModelProperty("手机号")
private String phone;
/**
* 地址
*/
@ApiModelProperty("地址")
private String address;
/**
* 学生集合
*/
@ApiModelProperty("学生集合")
private List<Student> students;
/**
* 创建时间
*/
@ApiModelProperty("创建时间")
private LocalDateTime dbctime;
/**
* 创建时间
*/
@ApiModelProperty("修改时间")
private LocalDateTime dbutime;
}
PS:我们项目中生成实体类的方式是用MyBatisX生成,会自带很多不需要打印的字段,一些特殊的VO可能还有学生集合需要打印,还有一些继承关系的属性需要打印(这只是我能想到的场景,可能还有一些其他的业务场景)。
打印工具类如下:
import io.swagger.annotations.ApiModelProperty;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/**
* @Author nitric oxide
* @Description 对象比较器
* @Date 4:19 下午 2021/10/22
*/
@Slf4j
public class ObjectCompareUtils {
/**
* 排除不需要对比的属性
*/
private static final Map<String, Integer> EXCLUDE = new HashMap<String, Integer>(){{
put("id", 0);
put("dbctime", 0);
put("dbutime", 0);
put("serialVersionUID", 0);
}};
/**
* 集合模板
*/
private static final String TEMPLATE_COLLECTION = "%s: {%s}";
/**
* 单个对象输出中文toString
*/
private static final String TEMPLATE_OBJECT = "%s: 【%s】;";
/**
* 输出对象toString中文版本
* @param o1 传递的对象
* @return toString中文
* @throws IllegalAccessException
*/
public static String getObjectContent(Object o1){
if (o1 == null) {
throw new RuntimeException("对象toString中文转换异常,对象不能为空");
}
StringBuilder sb = new StringBuilder();
Class c1 = o1.getClass();
while (c1 != null) {
Field[] fields = c1.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
if (EXCLUDE.get(field.getName()) != null) {
continue;
}
Object value = null;
try {
value = field.get(o1);
} catch (IllegalAccessException e) {
log.warn("通过反射获取对象属性失败");
}
ApiModelProperty apiModelProperty = field.getAnnotation(ApiModelProperty.class);
String property = apiModelProperty == null || apiModelProperty.value().isEmpty() ? field.getName() : apiModelProperty.value();
if (value instanceof Collection) {
//如果是对象中包含集合,则通过递归可以打印出所有的属性
sb.append(String.format(TEMPLATE_COLLECTION, property, recursionObjectContent((Collection)value)));
} else if (value != null) {
sb.append(String.format(TEMPLATE_OBJECT, property, value));
}
}
//兼容打印继承的属性
c1 = c1.getSuperclass();
}
return sb.toString();
}
private static String recursionObjectContent(Collection o1) {
if (o1 == null) {
return "";
}
StringBuilder sb = new StringBuilder();
o1.forEach(c -> {
sb.append("(");
sb.append(getObjectContent(c));
sb.append(")");
});
return sb.toString();
}
}
测试类:
public static void main(String[] args) throws IllegalAccessException {
List<Student> students = new ArrayList<Student>(){{
add(new Student().setId(233).setTrueName("小红").setPhone("12222222222"));
add(new Student().setId(244).setTrueName("小张").setPhone("13333333333"));
add(new Student().setId(255).setTrueName("小李").setPhone("14444444444"));
}};
Teacher object = new Teacher().setNickName("赵老师").setTrueName("赵四").setAddress("北京")
.setStudents(students)
.setDbctime(LocalDateTime.now())
.setDbutime(LocalDateTime.now())
.setId(12);
System.out.println(ObjectCompareUtils.getObjectContent(object));
}
结果:
昵称: 【赵老师】;真实姓名: 【赵四】;地址: 【北京】;学生集合: {(学生姓名: 【小红】;手机号: 【12222222222】;)(学生姓名: 【小张】;手机号: 【13333333333】;)(学生姓名: 【小李】;手机号: 【14444444444】;)}
场景2实现
在实现对比类的时候只实现了PO(持久化对象)的对比。因为看上面的结果可以得出,如果对比学生集合的不同输出结果太过于复杂;而且对于业务逻辑来讲,所有的逻辑都是基于某张表的crud,此处的对比打印只涉及到update语句。
记录日志前可以先通过select语句查询出原始数据,调用工具类对比即可。
教师类:
同上面的教师类,自行把学生列表去掉
对比工具:
import io.swagger.annotations.ApiModelProperty;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
/**
* @Author ldy
* @Description 对象比较器
* @Date 4:19 下午 2021/10/22
*/
@Slf4j
public class ObjectCompareUtils {
/**
* 排除不需要对比的属性
*/
private static final Map<String, Integer> EXCLUDE = new HashMap<String, Integer>(){{
put("id", 0);
put("dbctime", 0);
put("dbutime", 0);
put("serialVersionUID", 0);
}};
/**
* 属性对比后生成的模板
*/
private static final String TEMPLATE_DIFFERENT = "%s: 【%s】 -> 【%s】;";
/**
* 对比两个对象的不同
* @param o1 原来的对象
* @param o2 新对象
* @return
* @throws IllegalAccessException
*/
public static String getDifferentContent(Object o1, Object o2){
if (o2 == null) {
throw new RuntimeException("对比异常,新对象不能为空");
}
Class c = o2.getClass();
if (o1 == null) {
try {
o1 = c.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
}
if (o1.getClass() != o2.getClass()) {
throw new RuntimeException("对比异常,两个参数非同一个类");
}
Field[] fields = c.getDeclaredFields();
StringBuilder sb = new StringBuilder();
for (Field field : fields) {
field.setAccessible(true);
if (EXCLUDE.get(field.getName()) != null) {
continue;
}
Object oldValue = null;
Object newValue = null;
try {
oldValue = field.get(o1);
newValue = field.get(o2);
} catch (IllegalAccessException e) {
log.warn("通过反射获取对象属性失败");
}
if (!nullableEquals(oldValue, newValue)) {
ApiModelProperty apiModelProperty = field.getAnnotation(ApiModelProperty.class);
String property = apiModelProperty == null || apiModelProperty.value().isEmpty() ? field.getName() : apiModelProperty.value();
sb.append(String.format(TEMPLATE_DIFFERENT, property, oldValue, newValue));
}
}
return sb.toString();
}
private static boolean nullableEquals(Object a, Object b) {
return (a == null && b == null) || (a != null && a.equals(b));
}
}
跟上面的工具类两个可以写在一起,博客里面分开是方便大家理解
测试类:
public static void main(String[] args) throws IllegalAccessException {
Teacher oldObject = new Teacher().setNickName("赵老师").setTrueName("赵四").setAddress("北京")
.setPhone("12222222222")
.setDbctime(LocalDateTime.now())
.setDbutime(LocalDateTime.now())
.setId(12);
Teacher newObject = new Teacher().setNickName("赵老师").setTrueName("赵四").setAddress("郑州")
.setPhone("13333333333")
.setDbctime(LocalDateTime.now())
.setDbutime(LocalDateTime.now())
.setId(12);
System.out.println(ObjectCompareUtils.getDifferentContent(oldObject, newObject));
}
结果:
手机号: 【12222222222】 -> 【13333333333】;地址: 【北京】 -> 【郑州】;
网友评论