場(chǎng)景
記錄變更日志骇两,例如:
場(chǎng)景1:管理員李四态贤,新增了用戶(hù):昵稱(chēng)【lll】气笙;真實(shí)姓名【李四】次企;是否被凍結(jié)【0】
場(chǎng)景2:管理員張三,修改了用戶(hù)數(shù)據(jù):地址【北京市】->【鄭州市】
場(chǎng)景1實(shí)現(xiàn)
教師類(lèi):
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;
/**
* 昵稱(chēng)
*/
@ApiModelProperty("昵稱(chēng)")
private String nickName;
/**
* 密碼
*/
@ApiModelProperty("密碼")
private String password;
/**
* 真實(shí)姓名
*/
@ApiModelProperty("真實(shí)姓名")
private String trueName;
/**
* 手機(jī)號(hào)
*/
@ApiModelProperty("手機(jī)號(hào)")
private String phone;
/**
* 地址
*/
@ApiModelProperty("地址")
private String address;
/**
* 學(xué)生集合
*/
@ApiModelProperty("學(xué)生集合")
private List<Student> students;
/**
* 創(chuàng)建時(shí)間
*/
@ApiModelProperty("創(chuàng)建時(shí)間")
private LocalDateTime dbctime;
/**
* 創(chuàng)建時(shí)間
*/
@ApiModelProperty("修改時(shí)間")
private LocalDateTime dbutime;
}
PS:我們項(xiàng)目中生成實(shí)體類(lèi)的方式是用MyBatisX生成潜圃,會(huì)自帶很多不需要打印的字段缸棵,一些特殊的VO可能還有學(xué)生集合需要打印,還有一些繼承關(guān)系的屬性需要打犹菲凇(這只是我能想到的場(chǎng)景堵第,可能還有一些其他的業(yè)務(wù)場(chǎng)景)吧凉。
打印工具類(lèi)如下:
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 對(duì)象比較器
* @Date 4:19 下午 2021/10/22
*/
@Slf4j
public class ObjectCompareUtils {
/**
* 排除不需要對(duì)比的屬性
*/
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}";
/**
* 單個(gè)對(duì)象輸出中文toString
*/
private static final String TEMPLATE_OBJECT = "%s: 【%s】;";
/**
* 輸出對(duì)象toString中文版本
* @param o1 傳遞的對(duì)象
* @return toString中文
* @throws IllegalAccessException
*/
public static String getObjectContent(Object o1){
if (o1 == null) {
throw new RuntimeException("對(duì)象toString中文轉(zhuǎn)換異常踏志,對(duì)象不能為空");
}
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("通過(guò)反射獲取對(duì)象屬性失敗");
}
ApiModelProperty apiModelProperty = field.getAnnotation(ApiModelProperty.class);
String property = apiModelProperty == null || apiModelProperty.value().isEmpty() ? field.getName() : apiModelProperty.value();
if (value instanceof Collection) {
//如果是對(duì)象中包含集合阀捅,則通過(guò)遞歸可以打印出所有的屬性
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();
}
}
測(cè)試類(lèi):
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));
}
結(jié)果:
昵稱(chēng): 【趙老師】;真實(shí)姓名: 【趙四】针余;地址: 【北京】饲鄙;學(xué)生集合: {(學(xué)生姓名: 【小紅】;手機(jī)號(hào): 【12222222222】圆雁;)(學(xué)生姓名: 【小張】忍级;手機(jī)號(hào): 【13333333333】;)(學(xué)生姓名: 【小李】伪朽;手機(jī)號(hào): 【14444444444】轴咱;)}
場(chǎng)景2實(shí)現(xiàn)
在實(shí)現(xiàn)對(duì)比類(lèi)的時(shí)候只實(shí)現(xiàn)了PO(持久化對(duì)象)的對(duì)比。因?yàn)榭瓷厦娴慕Y(jié)果可以得出烈涮,如果對(duì)比學(xué)生集合的不同輸出結(jié)果太過(guò)于復(fù)雜朴肺;而且對(duì)于業(yè)務(wù)邏輯來(lái)講,所有的邏輯都是基于某張表的crud坚洽,此處的對(duì)比打印只涉及到update語(yǔ)句戈稿。
記錄日志前可以先通過(guò)select語(yǔ)句查詢(xún)出原始數(shù)據(jù),調(diào)用工具類(lèi)對(duì)比即可酪术。
教師類(lèi):
同上面的教師類(lèi)器瘪,自行把學(xué)生列表去掉
對(duì)比工具:
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 對(duì)象比較器
* @Date 4:19 下午 2021/10/22
*/
@Slf4j
public class ObjectCompareUtils {
/**
* 排除不需要對(duì)比的屬性
*/
private static final Map<String, Integer> EXCLUDE = new HashMap<String, Integer>(){{
put("id", 0);
put("dbctime", 0);
put("dbutime", 0);
put("serialVersionUID", 0);
}};
/**
* 屬性對(duì)比后生成的模板
*/
private static final String TEMPLATE_DIFFERENT = "%s: 【%s】 -> 【%s】;";
/**
* 對(duì)比兩個(gè)對(duì)象的不同
* @param o1 原來(lái)的對(duì)象
* @param o2 新對(duì)象
* @return
* @throws IllegalAccessException
*/
public static String getDifferentContent(Object o1, Object o2){
if (o2 == null) {
throw new RuntimeException("對(duì)比異常绘雁,新對(duì)象不能為空");
}
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("對(duì)比異常,兩個(gè)參數(shù)非同一個(gè)類(lèi)");
}
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("通過(guò)反射獲取對(duì)象屬性失敗");
}
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));
}
}
跟上面的工具類(lèi)兩個(gè)可以寫(xiě)在一起援所,博客里面分開(kāi)是方便大家理解
測(cè)試類(lèi):
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));
}
結(jié)果:
手機(jī)號(hào): 【12222222222】 -> 【13333333333】庐舟;地址: 【北京】 -> 【鄭州】;