一种简化操作日志记录方案

这篇具有很好参考价值的文章主要介绍了一种简化操作日志记录方案。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、背景:

后台系统配置越来越多的出现需要进行日志记录的功能,且当前已有日志记录不可复用,需要统一日志记录格式,提高日志记录开发效率。

二、预期效果展示:

新建动作:

修改动作:

删除动作:

三、数据存储:

注:可以选择其他存储方式,这里只简单举个例子

`biz_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '业务id',
`biz_type` tinyint(4) NOT NULL DEFAULT 0 COMMENT '业务类型',
`operator_id` varchar(128) NOT NULL DEFAULT '' COMMENT '操作人',
`operate_content` text COMMENT '操作内容',
`change_before` text COMMENT '修改前',
`change_after` text COMMENT '修改后',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'

四、原理简述:

日志构建关注两个对象,一个是修改前,修改后:

修改前:null + 修改后:X = 新建

修改前:Y + 修改后:X = 更新

修改前:Y + 修改后:null = 删除

修改内容判断依据传入的两个对象,对两个对象的每个属性进行逐一对比,如果发生变化则是需要进行日志记录字段;关注的属性使用注解进行标注。

五、具体实现:

注解

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface LogField {

    String name() default "";

    String valueFun() default "";

    boolean spliceValue() default true;
}


name: name值表示该字段如果被修改,应在日志中记录的字段名;默认取字段名

valueFun: 表示获取改变字段内容的获取方法;默认取字段值,若valueFun方法不存在,则取默认值

spliceValue: 日志是否需要拼接变更内容,默认拼接

注解处理:

@Service
@Slf4j
public class OperateLogService {
    @Resource
    private CommonOperateLogService commonOperateLogService;

    enum ActionEnum{
        ADD("新建"),
        UPDATE("修改"),
        DELETE("删除");

        ActionEnum(String desc) {
            this.desc = desc;
        }
        public String desc;
    }

    private int insertLog(CommonOperatorLog commonOperatorLog){
        String result = commonOperateLogService.insertLog(JSON.toJSONString(commonOperatorLog));
        Response response = JSON.parseObject(result, Response.class);
        return Objects.isNull(response) || ApiResponse.Status.fail.equals(response.getStatus()) ? 0 : (int) response.getContent();
    }

    public PageOutput<CommonOperatorLog> queryList(Long bizId, Integer bizType, Integer pageNum, Integer pageSize){
        String result = commonOperateLogService.queryLog(bizId, bizType, pageNum, pageSize);
        PageOutput pageOutput = JSON.parseObject(result, new TypeReference<PageOutput<CommonOperatorLog>>() {});
        return pageOutput;
    }

    public <T> void saveLog(String operatorId,Long bizId, Integer bizType, T target, T original){
        if(StringUtils.isBlank(operatorId) || (Objects.isNull(target) && Objects.isNull(original))){
            throw new IllegalArgumentException();
        }
        if(Objects.nonNull(target) && Objects.nonNull(original) && !target.getClass().isAssignableFrom(original.getClass())){
            throw new IllegalArgumentException();
        }
        ActionEnum action = getAction(target, original);
        List<Triple<String, String, LogField>> changeInfos = getChangeInfoList(target, original);
        List<String> changeInfoList = new ArrayList<>();
        if(CollectionUtils.isEmpty(changeInfos) && !ActionEnum.UPDATE.equals(action)){
            changeInfoList.add(0, action.desc);
        }else if (CollectionUtils.isEmpty(changeInfos)){
            return;
        }else {
            changeInfoList = changeInfos.stream().map(i -> i.getRight().spliceValue() ?
                            action.desc + StringUtils.joinWith("为:", i.getLeft(), i.getMiddle()) :
                            action.desc + StringUtils.join("了", i.getLeft()))
                    .collect(Collectors.toList());
        }

        String operateContext = StringUtils.join(changeInfoList, "\n");
        operateContext = operateContext.replaceAll("\"","")
                .replaceAll("\\[","").replaceAll("\\]","");
        CommonOperatorLog operatorLog = new CommonOperatorLog();
        operatorLog.setBizId(bizId);
        operatorLog.setBizType(bizType);
        operatorLog.setOperateContent(operateContext);
        operatorLog.setOperatorId(operatorId);
        operatorLog.setChangeBefore(JSON.toJSONString(original));
        operatorLog.setChangeAfter(JSON.toJSONString(target));
        this.insertLog(operatorLog);
    }

    private ActionEnum getAction(Object target, Object original){
        ActionEnum action = ActionEnum.ADD;
        if(Objects.nonNull(target) && Objects.nonNull(original)){
            action = ActionEnum.UPDATE;
        }else if(Objects.nonNull(target)){
            action = ActionEnum.ADD;
        }else if (Objects.nonNull(original)){
            action = ActionEnum.DELETE;
        }
        return action;
    }


    private<T> List<Triple<String, String, LogField>> getChangeInfoList(T target, T original){
        if(Objects.isNull(target) || Objects.isNull(original)){
            return new ArrayList<>();
        }        
        List<Pair<Field, Object>> targetFields = allFields(target);
        List<Pair<Field, Object>> originalFields = allFields(original);
        if(targetFields.size() != originalFields.size()){
            //理论上不可能执行到这
            throw new IllegalArgumentException();
        }

        List<Triple<String, String, LogField>> result = new ArrayList<>();
        for (int i = 0; i < targetFields.size(); i++) {
            Pair<Field, Object> targetField = targetFields.get(i);
            Pair<Field, Object> originalField = originalFields.get(i);
            ReflectionUtils.makeAccessible(targetField.getKey());
            ReflectionUtils.makeAccessible(originalField.getKey());
            Object targetValue = ReflectionUtils.getField(targetField.getKey(), targetField.getValue());
            Object originalValue = ReflectionUtils.getField(originalField.getKey(), originalField.getValue());
               if(targetValue != originalValue && (Objects.isNull(targetValue) ||
                        (!targetValue.equals(originalValue) &&
                        compareTo(Pair.of(targetField.getKey(), targetValue), Pair.of(originalField.getKey(), originalValue)) &&
                        !JSON.toJSONString(targetValue).equals(JSON.toJSONString(originalValue))))){
                result.add(Triple.of(getFieldName(targetField.getKey()), getFieldValue(targetField.getKey(), targetField.getValue()), targetField.getKey().getAnnotation(LogField.class)));
            }
        }
        return result;
    }

    private boolean compareTo(Pair<Field, Object> targetField, Pair<Field, Object> originalField){
        Field field = targetField.getKey();
        Object targetValue = targetField.getValue();
        Object originalValue = originalField.getValue();
        boolean canCompare = Arrays.stream(field.getType().getInterfaces()).anyMatch(i -> Comparable.class.getName().equals(i.getName()));
        if(canCompare && Objects.nonNull(targetValue) && Objects.nonNull(originalValue)){
            Method compareTo = ReflectionUtils.findMethod(field.getType(), "compareTo", field.getType());
            if(Objects.isNull(compareTo)){
                return true;
            }
            Object compared = ReflectionUtils.invokeMethod(compareTo, targetValue, originalValue);
            return (int)compared != 0 ;
        }
        return true;
    }

    private <T> List<Pair<Field, Object>> allFields(T obj){
        List<Triple<Field, Object, Boolean>> targetField = findField(obj);
        List<Triple<Field, Object, Boolean>> allField = Lists.newArrayList(targetField);
        List<Triple<Field, Object, Boolean>> needRemove = new ArrayList<>();
        for (int i = 0; i < allField.size(); i++) {
            Triple<Field, Object, Boolean> fieldObjectDes = allField.get(i);
            if(!fieldObjectDes.getRight()){
                ReflectionUtils.makeAccessible(fieldObjectDes.getLeft());
                Object fieldV = ReflectionUtils.getField(fieldObjectDes.getLeft(), fieldObjectDes.getMiddle());
                List<Triple<Field, Object, Boolean>> fieldList = findField(fieldV);
                if(CollectionUtils.isNotEmpty(fieldList)){
                    allField.addAll(fieldList);
                    needRemove.add(fieldObjectDes);
                }
            }
        }

        if(CollectionUtils.isNotEmpty(needRemove)){
            allField.removeAll(needRemove);
        }
        return allField.stream().map(i->Pair.of(i.getLeft(), i.getMiddle())).collect(Collectors.toList());
    }

    private <T> List<Triple<Field, Object, Boolean>> findField(T obj){
        Class<?> objClass = obj.getClass();
        Field[] declaredFields = objClass.getDeclaredFields();
        List<Field> allFields = Lists.newArrayList(declaredFields);
        if(Objects.nonNull(objClass.getSuperclass())){
            Field[] superClassFields = objClass.getSuperclass().getDeclaredFields();
            allFields.addAll(Arrays.asList(superClassFields));
        }
        List<Triple<Field, Object, Boolean>> result = new ArrayList<>();
        for (Field declaredField : allFields) {
            LogField annotation = declaredField.getAnnotation(LogField.class);
            if(Objects.nonNull(annotation)){
                result.add(Triple.of(declaredField, obj, declaredField.getType().getPackage().getName().startsWith("java")));
            }
        }
        return result;
    }


    private String getFieldName(Field field){
        LogField annotation = field.getAnnotation(LogField.class);
        String name = annotation.name();
        if(StringUtils.isBlank(name)){
            name = field.getName();
        }
        return name;
    }

    private <T> String getFieldValue(Field field, T targetObj){
        LogField annotation = field.getAnnotation(LogField.class);
        if(!annotation.spliceValue()){
            return "";
        }
        String valueFun = annotation.valueFun();
        if(StringUtils.isBlank(valueFun)){
            Object fieldValue = ReflectionUtils.getField(field, targetObj);
            return getStrValue(fieldValue);
        }else {
            Method valueMethod = ReflectionUtils.findMethod(targetObj.getClass(), valueFun);
            if(Objects.isNull(valueMethod)){
                Object fieldValue = ReflectionUtils.getField(field, targetObj);
                return getStrValue(fieldValue);
            }else {
                ReflectionUtils.makeAccessible(valueMethod);
                Object invokeMethodRes = ReflectionUtils.invokeMethod(valueMethod, targetObj);
                return getStrValue(invokeMethodRes);
            }
        }
    }

    private String getStrValue(Object fieldValue){
        List<String> emptyStr = ImmutableList.of("\"\"", "{}","[]");
        String value = Objects.isNull(fieldValue) ? "无" : JSON.toJSONString(fieldValue);
        return emptyStr.contains(value) ? "无" : value;
    }
}


六、使用示例:

1、使用的日志记录对象(这个对象只为日志服务)

public class SubsidyRateLog {

    @LogField(name = "补贴率名称")
    private String name;

    @LogField(name = "适用城市", valueFun = "getCityNames")
    private List<Integer> cityIds;

    private List<String> cityNames;
}


name是直接展示字段,所以修改值即name本身的值;cityIds 是我们关心比较字段,当它值不一样时进行 字段value 值获取,这个值是展示在前端的,所以可以根据需要进行格式定义,默认是将取到的值进行toJSON;当前例子中获取的是getCityNames方法返回的值;

2、无专用日志对象(大多数时候我们有自己的实体对象,但不包含具体日志描述字段),需要进行继承

public class SubsidyRate {

    @LogField(name = "补贴率名称")
    private String name;

    @LogField(name = "适用城市", valueFun = "getCityNames")
    private List<Integer> cityIds;
}

@Data
public class SubsidyRateLog extends SubsidyRate{
    
    private List<String> cityNames;
}



此方式适用于兼容现有对象,而不去破坏现有对象的完整性

3、对象包含子对象(比较复杂的大对象,如Task中的券信息)

public class SubsidyRateLog {

    @LogField(name = "补贴率名称")
    private String name;

    @LogField
    private Address address;
}

public class Address {
    @LogField(name = "省份")
    private String province;
    @LogField(name = "城市")
    private String city;
}


此情况下会将所有信息平铺,如果 Address 中 没有_LogField_ 注解,那么会直接使用将获取address值,如果存在注解,那么将忽略address本身,只关注注解字段。

作者:京东零售 祁伟

来源:京东云开发者社区 转载请注明来源文章来源地址https://www.toymoban.com/news/detail-746213.html

到了这里,关于一种简化操作日志记录方案的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 使用SpringBoot AOP记录操作日志和异常日志

    平时我们在做项目时经常需要对一些重要功能操作记录日志,方便以后跟踪是谁在操作此功能;我们在操作某些功 能时也有可能会发生异常,但是每次发生异常要定位原因我们都要到服务器去查询日志才能找到,而且也不能对发 生的异常进行统计,从而改进我们的项目,要

    2024年02月04日
    浏览(34)
  • AOP案例-记录日志操作

    目录 案例 操作日志 思路分析 步骤 准备 编码 将案例中的增删改接口的操作日志记录到数据表中 操作日志 日志信息包括:操作人、操作时间、执行方法的全类名、执行方法名、方法运行时的参数、返回值、犯法运行时长。 思路分析 需要对于所有业务类中的增删改查方法统

    2024年02月09日
    浏览(31)
  • 实现用户操作日志记录

    java自带的日志框架是java.util.logging(JUL),从JDK1.4(2002)开始捆绑在JDK中。可以使用JUL来记录操作日志。以下是使用JUL记录事务的示例: 系统日志 :统日志主要是为开发排查问题提供依据,一般打印在日志文件中;系统日志的可读性要求没那么高,日志中会包含代码的信息

    2024年02月15日
    浏览(31)
  • 使用SpringBoot记录用户操作日志

    在工作中我们可能会遇到一个需求,就是记录用户的操作信息,接下来使用spring的aop特性实现这一需求 一、首先引入我们必要的一些依赖 二、自定义一个注解,用于标注需要监控的方法 三、在数据库中创建一张表,用来保存用户的操作日志 字段可以有  用户名  用户操作

    2024年02月06日
    浏览(36)
  • 如何在 MySQL 中开启日志记录并排查操作记录

    在数据库管理中,能够追踪和审查操作记录是至关重要的。这不仅有助于识别和分析正常的数据库活动,还可以在数据泄露或未经授权的更改发生时进行调查和响应。本文将介绍如何在 MySQL 中开启通用日志记录,并如何排查操作记录。 通用日志记录了发往 MySQL 服务器的每一

    2024年04月12日
    浏览(38)
  • 简单记录下gin中使用中间件记录操作日志

    1、直接定义中间件 2、在需要使用的地方直接使用就可以,自动会收集日志到数据库中

    2024年02月09日
    浏览(32)
  • 用户登录后IP记录日志的六种实现方案探讨

    之前大群里有小伙伴在讨论用户IP日志记录的一些方案,也有小伙伴在做这个需求,私底下跟我咨询过,所以在此特地汇总梳理一下。 ### 方案1 在登录业务中直接记录用户每次登录的IP日志,如下图所示: 用户请求登录的Controller,原先用户直接调用登录的service,这里假设用

    2024年01月17日
    浏览(31)
  • nohup 输出到指定文件 Linux nohup 实现命令后台运行并输出或记录到指定日志文件 设置日志结果文件名称 重定向到某个文件 标准误 标准错误输出定向 输入报错信息保留

    # yourcommand:启动对象命令。可以跟该命令需要的各种参数。 # 是指在后台运行,但当用户推出(挂起)的时候,命令自动也跟着退出. nohup与结合起来,可以实现不挂断的后台运行。 实现屏幕输出记录到日志文件 # 0 – stdin (standard input),1 – stdout (standard output),2 – stderr (standa

    2024年02月05日
    浏览(49)
  • Spring Boot + Aop 记录用户操作日志

    本文主要介绍通过Aop记录用户操作日志,这也是目前比较常用的用法,由于水平有限,所以可能存在错漏之处,望指正。 对应实体类为 SysOperLog.java MyLog.java BusinessType.java — 操作类型枚举类 LogAspect.java 用户操作日志是AOP最常见的一种业务场景,这里只是简单记录了少量信息,

    2024年02月06日
    浏览(38)
  • SpringBoot项目如何优雅的实现操作日志记录

    在实际开发当中,对于某些关键业务,我们通常需要记录该操作的内容,一个操作调一次记录方法,每次还得去收集参数等等,会造成大量代码重复。 我们希望代码中只有业务相关的操作,在项目中使用注解来完成此项功能。 通常就是使用Spring中的AOP特性来实现的,那么在

    2024年01月18日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包