aop实现前后数据对比的操作记录

aop实现前后数据对比的操作记录

先看效果

效果

定义注解

CoverItem:这个注解用于修饰属性,目的是保存记录时将字段的英文转成注解中的value值,也就是转成中文

import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CoverItem {

    String value();

    boolean isCheck() default true;

}

OperLog:这个注解作为切入点

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OperLog {

    int type();

    Class clazz();

    Class convertClass();

}


aspectj业务层

逻辑不难直接上代码了,实现就是查询未修改之前的对象和修改之后的对象,通过对比两个对象的field,判断是否修改值,再将修改的值封装成一条日志保存入库。
这里考虑到我的业务所以并没有做通用的全覆盖,需要用的话,参考着改一下应该可以很轻松的应用到自己的业务场景中。
aop环向切面

import cn.flydiy.cloud.base.context.User;
import cn.flydiy.cloud.common.lang.StringUtils;
import cn.flydiy.cloud.common.utils.SecurityUtils;
import cn.hutool.extra.spring.SpringUtil;
import com.flydiy.sample.auto.dao.OperatorLogMapper;
import com.flydiy.sample.auto.pojo.po.OperatorLogPO;
import com.flydiy.sample.ext.aspectj.annotation.CoverItem;
import com.flydiy.sample.ext.aspectj.annotation.OperLog;
import com.flydiy.sample.ext.convert.CustomerConvert;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.*;

@Aspect
@Component
public class OperatorLogAspectj {

    @Pointcut("@annotation(com.flydiy.sample.ext.aspectj.annotation.OperLog)")
    public void pointcut(){}

    /**
     * 操作日志记录切面
     * @param point
     * @throws Exception
     */
    @Around("pointcut()")
    public void operatorLog(ProceedingJoinPoint point) throws Exception {
        Object obj = getArg(point);
        int type = getType(point);
        Class clazz = getClazz(point);
        Class convertClass = getConvertClass(point);
        Long originId = getId(obj);

        Object originObj = getObj(clazz,convertClass,originId);

        try {
            point.proceed();
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }

        Object updateObj = getObj(clazz,convertClass,originId);

        //构建日志对象
        OperatorLogPO operatorLog = createOperLog(originObj,updateObj,type,originId);

        if(Objects.isNull(operatorLog)) {
            return;
        }
        //保存日志
        saveLog(operatorLog);
    }

    private Class getClazz(ProceedingJoinPoint point) {
        OperLog log = getLog(point);
        return log.clazz();
    }

    private Class getConvertClass(ProceedingJoinPoint point) {
        OperLog log = getLog(point);
        return log.convertClass();
    }

    private int getType(ProceedingJoinPoint point) {
        OperLog log = getLog(point);
        return log.type();
    }

    private OperLog getLog(ProceedingJoinPoint point) {
        OperLog log = getAnnotationLog(point);
        if(Objects.isNull(log)) {
            throw new RuntimeException("接口缺少OperLog注解");
        }
        return log;
    }

    private void saveLog(OperatorLogPO operatorLog) {
        OperatorLogMapper mapper = SpringUtil.getBean(OperatorLogMapper.class);
        mapper.insert(operatorLog);
    }

    private OperatorLogPO createOperLog(Object originObj, Object updateObj, int type, Long originId) throws IllegalAccessException {
        StringJoiner sj = new StringJoiner("|");
        Class<?> clazz = originObj.getClass();
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            Object originField = getField(field,originObj);
            Object updateField = getField(field,updateObj);

            //如果属性变更,则记录
            if(!originField.equals(updateField)) {
                CoverItem coverItem = field.getAnnotation(CoverItem.class);
                isValidCoverItem(coverItem,field);
                if(!coverItem.isCheck()) {
                    continue;
                }
                String desc = createLogDesc(field,originField,updateField);
                sj.add(desc);
            }
        }
        if(StringUtils.isBlank(sj.toString())) {
            return null;
        }
        OperatorLogPO operatorLog = buildOperLog(sj);
        operatorLog.setType(type);
        operatorLog.setOriginId(originId);

        return operatorLog;

    }

    private Object getField(Field field,Object originObj) throws IllegalAccessException {
        Object obj = field.get(originObj);
        if(Objects.isNull(obj)) {
            obj = "null";
        }
        return obj;
    }

    private OperatorLogPO buildOperLog(StringJoiner sj) {
        User user = SecurityUtils.getLoginUser().get();
        if(Objects.isNull(user)) {
            throw new RuntimeException("获取用户信息异常");
        }
        String username = user.getUsername();
        String userId = user.getId();
        String desc = sj.toString();
        OperatorLogPO operator = new OperatorLogPO();
        operator.setOperatorLog(desc);
        operator.setUesrName(username);
        operator.setCreatedBy(userId);
        operator.setCreatedDate(new Date());
        return operator;
    }

    private String createLogDesc(Field field, Object originField, Object updateField) {
        CoverItem annotation = field.getAnnotation(CoverItem.class);
        String desc = "将字段[%s],从[%s]变更为[%s]";
        desc = String.format(desc,annotation.value(),originField,updateField);
        return desc;
    }

    private void isValidCoverItem(CoverItem coverItem, Field field) {
        if(Objects.isNull(coverItem)) {
            throw new RuntimeException(field.getName()+"缺少@CoverItem注解");
        }
    }

    private Object getObj(Class clazz, Class convertClass, Long id) throws Exception {
        Object obj = SpringUtil.getBean(clazz);
        Method method = clazz.getMethod("selectById", Serializable.class);
        Object invoke = method.invoke(obj, id);
        Object convert = CustomerConvert.convert(invoke, convertClass);
        return convert;
    }

    private Long getId(Object obj) throws Exception {
        Field field = obj.getClass().getDeclaredField("id");
        field.setAccessible(true);
        Long id = (Long) field.get(obj);
        return id;
    }

    private Object getArg(ProceedingJoinPoint point) {
        Object[] args = point.getArgs();
        if(Objects.isNull(args) || args.length < 1) {
            throw new RuntimeException("参数异常");
        }
        return args[0];
    }

    private OperLog getAnnotationLog(ProceedingJoinPoint point) {
        Signature signature = point.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        if(Objects.nonNull(method)) {
            return method.getAnnotation(OperLog.class);
        }
        return null;
    }

}

其中用到的工具类,po2po工具类

import com.flydiy.sample.auto.pojo.po.ColorSamplePO;
import com.flydiy.sample.auto.pojo.po.SampleInfoPO;
import com.flydiy.sample.auto.pojo.vo.ColorSampleExportVO;
import com.flydiy.sample.auto.pojo.vo.SampleInfoExportVO;
import com.flydiy.sample.ext.pojo.vo.ColorSampleExtExportVO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Objects;

public class CustomerConvert {

    /**
     * 类型转换
     * @param obj
     * @param clazz
     * @return
     * @throws Exception
     */
    public static Object convert(Object obj, Class clazz) throws Exception {
        Object targetObj = clazz.getDeclaredConstructor().newInstance();
        Class<?> originClass = obj.getClass();
        Field[] fields = originClass.getDeclaredFields();

        for (Field field : fields) {
            if(notValidField(field) || isStaticModifier(field)) {
                continue;
            }
            field.setAccessible(true);
            Object fileAtr = field.get(obj);

            Field field1 = null;
            try {
                field1 = clazz.getDeclaredField(field.getName());
            } catch (NoSuchFieldException e) {
                continue;
            }
            if(notValidField(field1)) {
                continue;
            }
            field1.setAccessible(true);
            field1.set(targetObj,fileAtr);
        }

        return targetObj;
    }

    private static boolean notValidField(Field field) {
        return Objects.isNull(field);
    }

    private static boolean isStaticModifier(Field field) {
        return Modifier.isStatic(field.getModifiers());
    }

}

使用

在接口加上@OperLog注解

@ApiOperation("单条更新")
    @PostMapping({"/api/v1/sample-info/ext/update", Constant.INNER_PATH_PREFIX + "/api/v1/sample-info/ext/update"})
    @OperLog(type = OperatorTypeConstant.SAMPLEINFOTYPE, clazz = SampleInfoMapper.class, convertClass = SampleInfoExtPO.class)
    public ResponseInfo<Boolean> update(@RequestBody SampleInfoExtPO sampleInfoExtPO) throws Exception {
        // 验证入参
        ParameterSupport.validateParameter(sampleInfoExtPO);
        // 验证表单是否完整
        ParameterSupport.validateForm(sampleInfoExtPO);
        // 格式化入参
        ParameterSupport.formatParameter(sampleInfoExtPO);
        SampleInfoPO sampleInfoPO = (SampleInfoPO) CustomerConvert.convert(sampleInfoExtPO, SampleInfoPO.class);
        // 调用Service
        boolean flag = sampleInfoExtService.updateById(sampleInfoPO);
        return ResponseInfo.<Boolean>success().data(flag);
    }

状态的日志记录是 [将发布状态从1更新为2] 这种形式,看起来很不直观,
如果能达到 [将发布状态从未发布 更新为 已发布] 这种效果就好了。

那么怎么才能解决呢。当然是有办法的啦:

我们可以在我们的自定义枚举中加一个字段fieldCover,标识数字对应的状态值。

import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CoverItem {

    String value();

    boolean isCheck() default true;

	String fieldCover();

}

这个字段的值我们可以写 数字对应的中文的json字符串。
例如:

public class Person{
	@CoverItem(fieldCover="{\n" +
            "    \"1\": \"未发布\",\n" +
            "    \"2\": \"已发布\"\n" +
            "}")
    private Integer status;
}

然后在获取field字段时判断有没有这个注解修饰,并且是否有fieldCover值,如果有这个属性值,直接用JSONUtil工具转成JsonObject对象,然后将field字段的值转为对应的中文。
代码我就不写了,如果实在有需要的话,可以评论区留言。

最后,如果本文对你有一点点的帮助的话,请帮忙点点赞哦,当然要是能顺便点个关注的话,那up会更开心的 闪闪发光(o゜▽゜)o☆

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值