AOP操作日志升级版

2,196 阅读6分钟

最近有小伙伴问我,我们管理端的后端日志是如果实现的,是每个接口都需要加上一段保存日志的代码吗?

这样当然是可以实现的了,但是相比稍微有点开发经验的同学们,肯定此刻脑子已浮现出“AOP”了,废话不多扯了,直接上代码了。

丐版

实现效果如下,保存操作时的接口入参,实现简单,但是日志可读性不高。

public enum LogTypeEnum implements IBaseEnum<Integer> {
    ADD(1, "新增数据"),
    UPDATE (2, "修改数据"),
    DELETE (3, "删除数据"),
    UNKNOWN(-1, "未知"),
    ;
    @Getter
    private Integer value;

    @Getter
    private String label;

    @Getter
    private Integer type;

    LogTypeEnum(Integer value, String label) {
        this.value = value;
        this.label = label;
    }
}
@Aspect
@Component
@Slf4j
public class OperLogAspect {

    @Resource
    private SysLogService sysLogService;

    /**
     * 设置操作日志切入点 记录操作日志 在注解的位置切入代码
     */
    @Pointcut("@annotation(com.oneam.cnps.annotation.OperLog)")
    public void operLogPoinCut() {
    }

    /**
     * 正常返回通知,拦截用户操作日志,连接点正常执行完成后执行,
     * 如果连接点抛出异常,则不会执行
     */
    @AfterReturning(value = "operLogPoinCut()", returning = "keys")
    public void saveOperLog(JoinPoint joinPoint, Object keys) {
        // 获取RequestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        // 从获取RequestAttributes中获取HttpServletRequest的信息
        HttpServletRequest request = (HttpServletRequest) requestAttributes
                .resolveReference(RequestAttributes.REFERENCE_REQUEST);
        SysLog sysLog = new SysLog();
        try {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            OperLog operLog = method.getAnnotation(OperLog.class);
            String operDesc = "";
            LogTypeEnum logType = LogTypeEnum.UNKNOWN;
            if (sysLog != null) {
                logType = operLog.operType();
                operDesc = operLog.operDesc();
            }
            // 获取方法入参
            Object[] args = joinPoint.getArgs();
            // 将参数所在的数组转换成json
            String params = "";
            if (args.length > 0) {
                params = JSON.toJSONString(args[0]);
                operDesc = operDesc + ",参数:" + params;
            }
            sysLog.setOperDesc(operDesc);
            // 可以从请求数据里获取
            String username = "";
            sysLog.setOperUser(username);
            sysLogService.saveLog(request, username, logType, operDesc);
        } catch (Exception e) {
            log.error(e.getMessage());
        }
    }
}
@OperLog(operType = LogTypeEnum.ADD, operDesc = "新增民俗文化数据")

升级版

日志的目的是为了方便查看数据修改记录,肯定是需要比较数据修改前后的数据项。

下面实现的前提是,工程已集成了mybaits-plus。

public interface ContentParser {

    /**
     * 获取信息返回查询出的对象
     *
     * @param joinPoint       查询条件的参数
     * @param enableModifyLog 注解
     * @return 获得的结果
     */
    Object getOldResult(JoinPoint joinPoint, EnableModifyLog enableModifyLog);

    /**
     * 获取信息返回查询出的对象
     *
     * @param joinPoint       查询条件的参数
     * @param enableModifyLog 注解
     * @return 获得的结果
     */
    Object getNewResult(JoinPoint joinPoint, EnableModifyLog enableModifyLog);

}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
@Documented
public @interface DataName {

    /**
     * 字段对应的中文注释
     */
    String name() default ""; 
}
@Component
public class DefaultContentParse implements ContentParser {

    @Override
    public Object getOldResult(JoinPoint joinPoint, EnableModifyLog enableModifyLog) {
        Object info = joinPoint.getArgs()[0];
        Long id = (Long) ReflectionUtils.getFieldValue(info, "id");
        Assert.notNull(id,"未解析到id值,请检查前台传递参数是否正确");
        Class idType=enableModifyLog.idType();
        if(idType.isInstance(id)){
            Class cls=enableModifyLog.serviceClass();
            IService service = (IService) SpringUtil.getBean(cls);
            Object result=service.getById(id);
            return  result;
        }else {
            throw new RuntimeException("请核实id type");
        }
    }

    @Override
    public Object getNewResult(JoinPoint joinPoint, EnableModifyLog enableModifyLog) {
        return getOldResult(joinPoint,enableModifyLog);
    }
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface EnableModifyLog {
    /**
     * @return 操作的类型 可以直接调用ModifyName 不传时根据METHOD自动确定
     */
    LogTypeEnum modifyType() default LogTypeEnum.UNKNOWN;

    /**
     * @return 获取编辑信息的解析类,目前为使用id获取,复杂的解析需要自己实现,默认不填写
     *       则使用默认解析类
     */
    Class<? extends ContentParser> parseClass() default DefaultContentParse.class;

    /**
     * @return 查询数据库所调用的class文件
     */
    Class<? extends IService> serviceClass() default IService.class;

    /**
     * @return 是否需要默认的改动比较
     */
    boolean needDefaultCompare() default false;

    /**
     * @return id的类型
     */
    Class<?> idType() default Long.class;

    /**
     * @return 操作的描述
     */
    String operDesc() default "";
}
/**
 * 反射工具类.
 * 提供调用getter/setter方法, 访问私有变量, 调用私有方法, 获取泛型类型Class, 被AOP过的真实类等工具函数.
 */
@SuppressWarnings("rawtypes")
public class ReflectionUtils {

    private static final String SETTER_PREFIX = "set";

    private static final String GETTER_PREFIX = "get";

    private static final String CGLIB_CLASS_SEPARATOR = "$$";

    private static Logger logger = LoggerFactory.getLogger(ReflectionUtils.class);


    /**
     * 直接读取对象属性值, 无视private/protected修饰符, 不经过getter函数.
     * @param obj 读取的对象
     * @param fieldName 读取的列
     * @return 属性值
     */
    public static Object getFieldValue(final Object obj, final String fieldName) {
        Field field = getAccessibleField(obj, fieldName);

        if (field == null) {
            throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + obj + "]");
        }

        Object result = null;
        try {
            result = field.get(obj);
        } catch (IllegalAccessException e) {
            logger.error("不可能抛出的异常{}", e.getMessage());
        }
        return result;
    }


    /**
     *  循环向上转型, 获取对象的DeclaredField, 并强制设置为可访问.
     *   如向上转型到Object仍无法找到, 返回null.
     * @param obj  查找的对象
     * @param fieldName  列名
     * @return 列
     */
    public static Field getAccessibleField(final Object obj, final String fieldName) {
        for (Class<?> superClass = obj.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()) {
            try {
                Field field = superClass.getDeclaredField(fieldName);
                makeAccessible(field);
                return field;
            } catch (NoSuchFieldException e) {//NOSONAR
                // Field不在当前类定义,继续向上转型
                continue;// new add
            }
        }
        return null;
    }

    /**
     * 改变private/protected的成员变量为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。
     * @param field  列
     */

    public static void makeAccessible(Field field) {
        if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers()) || Modifier
                .isFinal(field.getModifiers())) && !field.isAccessible()) {
            field.setAccessible(true);
        }
    }

    /**
     * 获取两个对象同名属性内容不相同的列表
     * @param class1 old对象
     * @param class2 new对象
     * @return  区别列表
     * @throws ClassNotFoundException 异常
     * @throws IllegalAccessException 异常
     */
    public static List<Map<String ,Object>> compareTwoClass(Object class1, Object class2) throws ClassNotFoundException, IllegalAccessException {
        List<Map<String,Object>> list=new ArrayList<Map<String, Object>>();
        //获取对象的class
        Class<?> clazz1 = class1.getClass();
        Class<?> clazz2 = class2.getClass();
        //获取对象的属性列表
        Field[] field1 = clazz1.getDeclaredFields();
        Field[] field2 = clazz2.getDeclaredFields();
        StringBuilder sb=new StringBuilder();
        //遍历属性列表field1
        for(int i=0;i<field1.length;i++) {
            //遍历属性列表field2
            for (int j = 0; j < field2.length; j++) {
                //如果field1[i]属性名与field2[j]属性名内容相同
                if (field1[i].getName().equals(field2[j].getName())) {
                    if (field1[i].getName().equals(field2[j].getName())) {
                        field1[i].setAccessible(true);
                        field2[j].setAccessible(true);
                        //如果field1[i]属性值与field2[j]属性值内容不相同
                        if (!compareTwo(field1[i].get(class1), field2[j].get(class2))) {
                            Map<String, Object> map2 = new HashMap<String, Object>();
                            DataName name=field1[i].getAnnotation(DataName.class);
                            String fieldName="";
                            if(name!=null){
                                fieldName=name.name();
                            }else {
                                fieldName=field1[i].getName();
                            }
                            map2.put("name", fieldName);
                            map2.put("old", field1[i].get(class1));
                            map2.put("new", field2[j].get(class2));
                            list.add(map2);
                        }
                        break;
                    }
                }
            }
        }
        return list;

    }
    /**
     * 对比两个数据是否内容相同
     *
     * @param  object1  比较对象1
     * @param  object2  比较对象2
     * @return boolean类型
     */
    public static boolean compareTwo(Object object1,Object object2){

        if(object1==null&&object2==null){
            return true;
        }
        if(object1==null&&object2!=null){
            return false;
        }
        if(object1.equals(object2)){
            return true;
        }
        return false;
    }
}
@Slf4j
public class ObjectCompareUtil {

    public static String defaultDealUpdate(Object newObject, Map<String, Object> oldMap) {
        try {
            AtomicReference<Boolean> isUpdate = new AtomicReference<>(false);
            Map<String, Object> newMap = (Map<String, Object>) objectToMap(newObject);
            Long id = (Long) newMap.get("id");
            StringBuilder str = new StringBuilder();
            str.append("修改了ID为【").append(id).append("】的数据;\n");
            Object finalNewObject = newObject;
            oldMap.forEach((k, v) -> {
                Object newResult = newMap.get(k);
                if ((v==null && newResult !=null) || (v != null && !v.equals(newResult))) {
                    Field field = ReflectionUtils.getAccessibleField(finalNewObject, k);
                    if(!field.getName().equals("updateTime")){
                        isUpdate.set(true);
                        DataName dataName = field.getAnnotation(DataName.class);
                        if (dataName != null) {
                            str.append("【").append(dataName.name()).append("】从【")
                                    .append(v).append("】改为了【").append(newResult).append("】;\n");
                        } else {
                            str.append("【").append(field.getName()).append("】从【")
                                    .append(v).append("】改为了【").append(newResult).append("】;\n");
                        }
                    }
                }
            });
            if (!isUpdate.get()) {
                return null;
            }
            return str.toString();
        } catch (Exception e) {
            log.error("比较异常", e);
            throw new RuntimeException("比较异常", e);
        }
    }

    public static String defaultDealUpdate(Object newObject, Object oldObject) {
        try {
            AtomicReference<Boolean> isUpdate = new AtomicReference<>(false);
            Map<String, Object> newMap = (Map<String, Object>) objectToMap(newObject);
            Map<String, Object> oldMap = (Map<String, Object>) objectToMap(oldObject);
            Long id = (Long) newMap.get("id");
            StringBuilder str = new StringBuilder();
            str.append("修改了ID为【").append(id).append("】的数据;\n");
            Object finalNewObject = newObject;
            oldMap.forEach((k, v) -> {
                Object newResult = newMap.get(k);
                if (v != null && !v.equals(newResult)) {
                    Field field = ReflectionUtils.getAccessibleField(finalNewObject, k);
                    if(!field.getName().equals("updateTime")){
                        isUpdate.set(true);
                        DataName dataName = field.getAnnotation(DataName.class);
                        if (dataName != null) {
                            str.append("【").append(dataName.name()).append("】从【")
                                    .append(v).append("】改为了【").append(newResult).append("】;\n");
                        } else {
                            str.append("【").append(field.getName()).append("】从【")
                                    .append(v).append("】改为了【").append(newResult).append("】;\n");
                        }
                    }
                }
            });
            if (!isUpdate.get()) {
                return null;
            }
            return str.toString();
        } catch (Exception e) {
            log.error("比较异常", e);
            throw new RuntimeException("比较异常", e);
        }
    }

    public static Map<?, ?> objectToMap(Object obj) {
        if (obj == null) {
            return null;
        }
        ObjectMapper mapper = new ObjectMapper();
        mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        //mapper.addMixIn(Object.class, IgnoreHibernatePropertiesInJackson.class);
        Map<?, ?> mappedObject = mapper.convertValue(obj, Map.class);
        return mappedObject;
    }
}
@Aspect
@Component
public class ModifyAspect {

    private final static Logger logger = LoggerFactory.getLogger(ModifyAspect.class);

    @Resource
    private SysLogService sysLogService;

    @Around("@annotation(enableModifyLog)")
    public Object around(ProceedingJoinPoint joinPoint, EnableModifyLog enableModifyLog) throws Throwable {
        Map<String, Object> oldMap = new HashMap<>();
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        if (LogTypeEnum.UPDATE.equals(enableModifyLog.modifyType())) {
            try {
                ContentParser contentParser = (ContentParser) SpringUtil.getBean(enableModifyLog.parseClass());
                Object oldObject = contentParser.getOldResult(joinPoint, enableModifyLog);
                if (enableModifyLog.needDefaultCompare()) {
                    oldMap = (Map<String, Object>) ObjectCompareUtil.objectToMap(oldObject);
                }
            } catch (Exception e) {
                logger.error("service加载失败:", e);
            }
        }
        Object object = joinPoint.proceed();
        Object newObject = null;
        if (LogTypeEnum.UPDATE.equals(enableModifyLog.modifyType())) {
            ContentParser contentParser;
            try {
                contentParser = (ContentParser) SpringUtil.getBean(enableModifyLog.parseClass());
                newObject = contentParser.getNewResult(joinPoint, enableModifyLog);
            } catch (Exception e) {
                logger.error("service加载失败:", e);
            }
            String operDesc = enableModifyLog.operDesc();
            String updateContent = ObjectCompareUtil.defaultDealUpdate(newObject, oldMap);
            if(StringUtils.isNotBlank(updateContent)){
                logger.info("修改内容为:{}", updateContent);
                sysLogService.saveLog(request, AuthContextUtil.getUsername(), LogTypeEnum.UPDATE, operDesc + ":" +updateContent);
            }
        }
        return object;
    }
}