当前位置: 首页 > article >正文

Java使用反射记录操作日志

表结构

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;
    }
}


http://www.kler.cn/a/413731.html

相关文章:

  • 23省赛区块链应用与维护(房屋租凭【下】)
  • json object转x-www-form-urlencoded
  • ShuffleNet V2:高效卷积神经网络架构设计的实用指南
  • 计算机视觉中的距离变换:经典案例与Python代码解析
  • 无人机产业发展如何?如何进行产业分析?
  • 视觉感知与处理:解密计算机视觉的未来
  • MySQL子查询介绍和where后的标量子查询
  • yarn 任务 beyond the ‘PHYSICAL‘ memory limit 报错处理
  • 半导体、晶体管、集成电路、芯片、CPU、单片机、单片机最小系统、单片机开发板-概念串联辨析
  • 【网络安全设备系列】12、态势感知
  • Z2400023基于Java+Servlet+jsp+mysql的酒店管理系统的设计与实现 源码 调试 文档
  • 在Manjaro Gnome桌面的基础上安装Budgie桌面环境
  • 【入门篇】小游戏——多语言求解版
  • 希尔排序:一个“跳房子游戏”
  • 前端新手教程:HTML、CSS 和 JavaScript 全面详解及实用案例
  • 大数据新视界 -- Hive 数据分区:精细化管理的艺术与实践(上)(7/ 30)
  • 如何在 Ubuntu 22.04 上安装 Metabase 数据可视化分析工具
  • ssm194线上学习网站+vue(论文+源码)_kaic
  • 词云图大师(WordCloudMaster): 探索创意无限的词云世界!
  • Panzerdogs 游戏宣布将在 SuiPlay0X1 上线