表结构
CREATE TABLE `hcm_operation_log` (
`operation_id` varchar(32) DEFAULT NULL COMMENT 'id',
`business_key` varchar(64) DEFAULT NULL COMMENT '业务id',
`business_type` varchar(32) DEFAULT NULL COMMENT '业务类型',
`operation_user` varchar(32) DEFAULT NULL COMMENT '操作人',
`operation_time` datetime DEFAULT NULL COMMENT '操作时间',
`operation_field` varchar(64) DEFAULT NULL COMMENT '操作字段',
`operation_field_name` varchar(64) DEFAULT NULL COMMENT '操作字段中文名',
`before_value` text COMMENT '操作前的值',
`after_value` text COMMENT '操作后的值',
`dict_value` varchar(255) DEFAULT NULL COMMENT '数据字典',
`database_name` varchar(100) DEFAULT NULL COMMENT '数据库表名',
`remark1` varchar(255) DEFAULT NULL COMMENT '预留字段1',
`remark2` varchar(255) DEFAULT NULL COMMENT '预留字段2',
`remark3` varchar(255) DEFAULT NULL COMMENT '预留字段3'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='操作日志';
自定义LOG注解
@Retention(RetentionPolicy.RUNTIME)
public @interface LogTable{
/**
* 数据库表名
*/
String name();
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogField{
/**
* 该字段页面展示的中文名称
*/
String name() default "";
/**
* 该字段页面展示的英文名称
*/
String enName() default "";
/**
* 该嵌套对象页面展示的中文名称,如 交付部门
*/
String entityName() default "";
/**
* 该嵌套对象页面展示的英文名称
*/
String enEntityName() default "";
/**
* 该嵌套对象页面展示的中文名称,如 交付部门
* 指定嵌套对象内的属性值作为后缀。如 MPIT
*/
String entityNameSuffix() default "";
/**
* 格式化
* 需要进行格式化时,传入指定格式,返回String.format("指定格式", value);
*/
String format() default "";
/**
* 格式化类型
* 数据需要进行转换时,实现DataFormatter,重写convert方法
*/
Class<? extends DataFormatter> formatClass() default DefaultFormatter.class;
/**
* 排序字段,值越小越靠前
*/
int sort() default 0;
/**
* 该字段的前缀
*/
String prefix() default "";
/**
* 该字段的后缀,如单位w
*/
String suffix() default "";
/**
* 需要字典转义时,标明字典的类型
*/
String dictType() default "";
/**
* 格式化类型
* 字典数据需要进行转换时,实现DataFormatter,重写convert方法
*/
Class<? extends DictionaryFormatter> dictFormatClass() default
DefaultDictionaryFormatter.class;
}
注解中引用的类
/**
* 默认converter,什么也不做
*/
public class DefaultFormatter implements DataFormatter{
@Override
public Object convert(Object value){
return null;
}
}
public interface DataFormatter{
/**
* 普通转换,只需要原值
*
* @param value 原字段值
* @return
*/
Object convert(Object value);
}
/**
* 默认字典converter,什么也不做
*/
public class DefaultDictionaryFormatter implements DictionaryFormatter{
@Override
public Object convertByDictionary(String dictType, Object value){
return null;
}
}
public interface DictionaryFormatter{
/**
* 字典转义,重写此方法后可处理所有词典类型数据转义
*
* @param dictType 字典类型
* @param value 原字段值
* @return
*/
Object convertByDictionary(String dictType, Object value);
}
自定义注解使用
/**
* 银行卡信息操作记录
*/
@Data
//@LogTable用来记录日志中的数据库表名
@LogTable(name="hcm_staff_reg_form")
public class BankInfoLog{
/**
* dictType 指的是词典表的词典type值 dictFormatClass 指的是词典表的格式化类
*/
@ApiModelProperty("开户银行")
@LogField(name="开户银行",enName="openAccountBank",dictType="openAccountBank",dictFormatClass= HcmDictFormatter.class)
private String openAccountBank;
@ApiModelProperty("银行账户")
@LogField(name="银行账户",enName="accountBank",formatClass= HcmDecryptFormatter.class)
private String accountBank;
@ApiModelProperty("银行卡类型")
@LogField(name="银行卡类型",enName="bankCardType",dictType="bank_card_type",dictFormatClass= HcmDictFormatter.class)
private String bankCardType;
/**
* formatClass 指的是自定义格式化类 可以继承DataFormatter 实现自定义格式化
*/
@ApiModelProperty("开户城市")
@LogField(name="开户城市",enName="openAccountCity",formatClass= HcmCityFormatter.class)
private String openAccountCity;
@ApiModelProperty("开户网点")
@LogField(name="开户网点",enName="openAccountNetwork")
private String openAccountNetwork;
}
格式化类使用
/**
* 操作入职词典转换器
*/
public class HcmDictFormatter implements DictionaryFormatter{
@Override
public Object convertByDictionary(String dictType, Object value){
DictValueService dictValueService=SpringBeanFactory.bean(DictValueService.class);
Map<String, String> typeMap=dictValueService.findDataByTypeCode(dictType)
.stream().collect(Collectors.toMap(DictDataDto::getCode, DictDataDto::getValue));
return typeMap.get(value.toString());
}
}
日志工具类
package com.wildyak.care.log.util;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import com.wildyak.care.log.annotation.LogTable;
import com.wildyak.care.log.entity.OperationLog;
import com.wildyak.care.log.service.OperationLogService;
import com.wildyak.context.ContextHolder;
import com.wildyak.context.identity.UserIdDetails;
import com.wildyak.core.bean.SpringBeanFactory;
import com.wildyak.core.log.DataFormatter;
import com.wildyak.core.log.DefaultDictionaryFormatter;
import com.wildyak.core.log.DefaultFormatter;
import com.wildyak.core.log.DictionaryFormatter;
import com.wildyak.core.log.LogField;
import com.wildyak.hutool.core.String2Utils;
import com.wildyak.hutool.core.collection.CollectionUtil;
import com.wildyak.hutool.core.util.ObjectUtil;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Component
public class LogUtils{
/**
* 基础类型(后续有遇到其他类型需补充)
*/
public static final List<Class<?>> simpleTypeList=CollectionUtil.toList(
byte.class, Byte.class,
boolean.class, Boolean.class,
char.class, Character.class,
short.class, Short.class,
int.class, Integer.class,
float.class, Float.class,
double.class, Double.class,
String.class,
BigDecimal.class,
Date.class,
Enum.class);
/**
* 比较对象属性,构建日志内容(remarks字段)
*
* @param oldObj 数据库原数据,需要手动查询赋值到此对象中
* @param newObj 前端传入,本次要修改的对象
*/
public static <T> void buildLog(T oldObj, T newObj){
OperationLogService operationLogService=SpringBeanFactory.bean(OperationLogService.class);
UserIdDetails userIdDetails=ContextHolder.me().userDetails();
Date date=new Date();
Class<?> oldObjClass=oldObj.getClass();
String className=oldObjClass.getSimpleName();
String tableName;//数据库表名
if(oldObjClass.isAnnotationPresent(LogTable.class)){
LogTable tableAnnotation=oldObjClass.getAnnotation(LogTable.class);
tableName=tableAnnotation.name();
}else{
tableName=null;
}
Field[] oldFields=oldObjClass.getDeclaredFields();
// 过滤出所有的加@LogField的字段
List<Field> logFields=Arrays.stream(oldFields)
.filter(oldField -> oldField.getAnnotation(LogField.class) != null)
.toList();
List<OperationLog> logList=new ArrayList<>();
// 遍历字段进行比较
logFields.forEach(field -> {
LogField logField=field.getAnnotation(LogField.class);
try{
field.setAccessible(true);
Object oldValue=field.get(oldObj);
Object newValue=field.get(newObj);
if(simpleTypeList.contains(field.getType())){
// 基础类型,直接比较
if(isChanged(field, oldObj, newObj)){
// 数据转换
OperationLog operationLog=new OperationLog();
oldValue=convert(oldValue, logField);
newValue=convert(newValue, logField);
operationLog.setOperationUser(userIdDetails.getUserId());
operationLog.setOperationTime(date);
operationLog.setBusinessType(className);
operationLog.setDictValue(logField.dictType());
operationLog.setOperationField(logField.enName());
operationLog.setOperationFieldName(logField.name());
operationLog.setBeforeValue(ObjectUtil.isNull(oldValue) || "null".equals(oldValue) ? "" : oldValue.toString());
operationLog.setAfterValue(ObjectUtil.isNull(newValue) || "null".equals(newValue) ? "" : newValue.toString());
operationLog.setDatabaseName(tableName);
logList.add(operationLog);
}
}
}catch(Exception e){
log.error("LogUtils 构建日志内容出错:" + e.getMessage());
}
});
operationLogService.saveBatch(logList);
}
/**
* 比较对象属性,判断是否修改
*/
private static boolean isChanged(Field field, Object oldObj, Object newObj) throws IllegalAccessException{
field.setAccessible(true);
Object oldValue=field.get(oldObj);
Object newValue=field.get(newObj);
if(oldValue == null && newValue == null){
return false;
}else if(oldValue != null && newValue != null){
// BigDecimal类型比较 1.0 和 1.00 时,equals判断不等, compareTo判断相等
if(field.getType().equals(BigDecimal.class)){
return ((BigDecimal) oldValue).compareTo((BigDecimal) newValue) != 0;
}
return !oldValue.equals(newValue);
}else{
return true;
}
}
/**
* 比较完后对数据进行处理
*/
private static Object convert(Object value, LogField logField) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException{
if(value == null){
return "";
}
String dictType=logField.dictType();
Object result;
// 数据转换
Class<? extends DataFormatter> formatClass=logField.formatClass();
Class<? extends DictionaryFormatter> dictFormatClass=logField.dictFormatClass();
// 普通转换器
if(formatClass != DefaultFormatter.class){
DataFormatter dataConverter=formatClass.getConstructor().newInstance();
value=dataConverter.convert(value);
}
// 词典转换器
if(String2Utils.hasText(dictType) && dictFormatClass != DefaultDictionaryFormatter.class){
DictionaryFormatter dictionaryFormatter=dictFormatClass.getConstructor().newInstance();
value=dictionaryFormatter.convertByDictionary(dictType, value);
}
// 数据格式化
String format=logField.format();
if(String2Utils.isNotBlank(format)){
// 支持多个格式占位符
int count=StringUtils.countOccurrencesOf(format, "%");
Object[] args=new Object[count];
Arrays.fill(args, value);
value=String.format(format, args);
}
// 拼接前后缀
String prefix=logField.prefix();
String suffix=logField.suffix();
result=prefix + value + suffix;
return result;
}
}