记录通过Aop实现 记录系统操作日志功能

通过Aop实现的方式比较简单:

步骤:

1.自定义一个注解,让它可用于 想要记录日志的方法上;
2.通过Aop 统一处理这些标记了自定义注解的类;
3.在Aop通知中添加逻辑,获取操作日志想要记录的信息,最后添加到自己设计的操作日志表里去;功能完成;

具体实现:

1.自定义一个注解,让它可用于 想要记录日志的方法上;

package com.lance.flowable.operationLog;

import java.lang.annotation.*;

/**
 * @description:自定义操作日志注解
 */
@Target(ElementType.METHOD)//描述注解使用范围,现在代表可以在方法上使用
@Retention(RetentionPolicy.RUNTIME)//标识注解的生命周期
@Documented //元注解,
public @interface Log {
//下面的属性可以自定义
    /**
     * 操作模块
     * @return
     */
    String modul() default "";

    /**
     * 操作类型
     * @return
     */
    String type() default "";

    /**
     * 操作说明
     * @return
     */
    String desc() default "";

}


2.通过Aop 统一处理这些标记了自定义注解的类;
3.在Aop通知中添加逻辑,获取操作日志想要记录的信息,最后添加到自己设计的操作日志表里去;功能完成;

package com.lance.flowable.operationLog;

import com.alibaba.fastjson.JSON;
import com.lance.flowable.utils.IPUtils;
import com.lance.flowable.utils.SecurityUserUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;

/**
 * @description:切面处理类,操作日志,异常日志,记录,处理
 */
@Aspect
@Component
public class LogAspect {

    /**
     * 操作版本号
     * 项目启动时从命令行传入,例如:java -jar xxx.war --version=201902
     */
    @Value("${version}")
    private String version;

    /**
     * 统计请求的处理时间
     */
    ThreadLocal<Long> startTime = new ThreadLocal<>();

    @Autowired
    private LogInfoService logInfoService;

    @Autowired
    private LogErrorInfoService logErrorInfoService;

    /**
     * @methodName:logPoinCut
     * @description:设置操作日志切入点 记录操作日志 在注解的位置切入代码
     * @author:tanyp
     * @dateTime:2021/11/18 14:22
     * @Params: []
     * @Return: void
     * @editNote:
     */
    @Pointcut("execution(* com.lance.flowable.controller..*.*(..))")
    public void logPoinCut() {
    }

    /**
     * @methodName:exceptionLogPoinCut
     * @description:设置操作异常切入点记录异常日志 扫描所有controller包下操作
     * @author:tanyp
     * @dateTime:2021/11/18 14:22
     * @Params: []
     * @Return: void
     * @editNote:
     */
    @Pointcut("execution(* com.lance.flowable.controller..*.*(..))")
    public void exceptionLogPoinCut() {

    }

    //前置通知,
    @Before("logPoinCut()")
    public void doBefore() {
        // 接收到请求,记录请求开始时间
        startTime.set(System.currentTimeMillis());
    }

    /**
     * @methodName:doAfterReturning
     * @description:正常返回通知,拦截用户操作日志,连接点正常执行完成后执行, 如果连接点抛出异常,则不会执行
     * @author:tanyp
     * @Params: [joinPoint, keys] 连接点
     * @Return: void
     * @editNote:
     * // 声明keys时指定的类型会限制目标方法必须返回指定类型的值或没有返回*值
		*	// 此处将rvt的类型声明为Object,意味着对目标方法的返回值不加限制
		*	1)pointcut/value:这两个属性的作用是一样的,它们都属于指定切入点对应的切入表达式。一样既可以是已有的切入点,也可直接定义切入点表达式。当指定了pointcut属性值后,value属性值将会被覆盖。
*2)returning:该属性指定一个形参名,用于表示Advice方法中可定义与此同名的形参,该形参可用于访问目标方法的返回值。除此之外,在Advice方法中定义该形参(代表目标方法的返回值)时指定的类型,会限制目标方法必须返回指定类型的值或没有返回值。   */
    @AfterReturning(value = "logPoinCut()", returning = "keys")
    public void doAfterReturning(JoinPoint joinPoint, Object keys) {
        // 获取RequestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

        // 从获取RequestAttributes中获取HttpServletRequest的信息
        HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);

        com.demo.domain.LogInfo logInfo = com.demo.domain.LogInfo.builder().build();
        try {
            // 从切面织入点处通过反射机制获取织入点处的方法
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();

            // 获取切入点所在的方法
            Method method = signature.getMethod();

            // 获取请求的类名
            String className = joinPoint.getTarget().getClass().getName();

            // 获取操作
            Log log = method.getAnnotation(Log.class);
            if (Objects.nonNull(log)) {
                logInfo.setModule(log.modul());
                logInfo.setType(log.type());
                logInfo.setMessage(log.desc());
            }

            logInfo.setId(UUID.randomUUID().toString());
            logInfo.setMethod(className + "." + method.getName()); // 请求的方法名
            logInfo.setReqParam(JSON.toJSONString(converMap(request.getParameterMap()))); // 请求参数
            logInfo.setResParam(JSON.toJSONString(keys)); // 返回结果
            logInfo.setUserId(SecurityUserUtils.getUser().getId()); // 请求用户ID
            logInfo.setUserName(SecurityUserUtils.getUser().getUsername()); // 请求用户名称
            logInfo.setIp(IPUtils.getIpAddress(request)); // 请求IP
            logInfo.setUri(request.getRequestURI()); // 请求URI
            logInfo.setCreateTime(LocalDateTime.now()); // 创建时间
            logInfo.setVersion(version); // 操作版本
            logInfo.setTakeUpTime(System.currentTimeMillis() - startTime.get()); // 耗时
            logInfoService.save(logInfo);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * @methodName:doAfterThrowing
     * @description:异常返回通知,用于拦截异常日志信息 连接点抛出异常后执行
     * @author:tanyp
     * @dateTime:2021/11/18 14:23
     * @Params: [joinPoint, e]
     * @Return: void
     * @editNote:
     */
    @AfterThrowing(pointcut = "exceptionLogPoinCut()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
        // 获取RequestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

        // 从获取RequestAttributes中获取HttpServletRequest的信息
        HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);

        try {
            // 从切面织入点处通过反射机制获取织入点处的方法
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();

            // 获取切入点所在的方法
            Method method = signature.getMethod();

            // 获取请求的类名
            String className = joinPoint.getTarget().getClass().getName();

            logErrorInfoService.save(
                    LogErrorInfo.builder()
                            .id(UUID.randomUUID().toString())
                            .reqParam(JSON.toJSONString(converMap(request.getParameterMap()))) // 请求参数
                            .method(className + "." + method.getName()) // 请求方法名
                            .name(e.getClass().getName()) // 异常名称
                            .message(stackTraceToString(e.getClass().getName(), e.getMessage(), e.getStackTrace())) // 异常信息
                            .userId(SecurityUserUtils.getUser().getId()) // 操作员ID
                            .userName(SecurityUserUtils.getUser().getUsername()) // 操作员名称
                            .uri(request.getRequestURI()) // 操作URI
                            .ip(IPUtils.getIpAddress(request)) // 操作员IP
                            .version(version) // 版本号
                            .createTime(LocalDateTime.now()) // 发生异常时间
                            .build()
            );
        } catch (Exception e2) {
            e2.printStackTrace();
        }
    }

    /**
     * @methodName:converMap
     * @description:转换request 请求参数
     * @author:tanyp
     * @dateTime:2021/11/18 14:12
     * @Params: [paramMap]
     * @Return: java.util.Map<java.lang.String, java.lang.String>
     * @editNote:
     */
    public Map<String, String> converMap(Map<String, String[]> paramMap) {
        Map<String, String> rtnMap = new HashMap<String, String>();
        for (String key : paramMap.keySet()) {
            rtnMap.put(key, paramMap.get(key)[0]);
        }
        return rtnMap;
    }

    /**
     * @methodName:stackTraceToString
     * @description:转换异常信息为字符串
     * @author:tanyp
     * @dateTime:2021/11/18 14:12
     * @Params: [exceptionName, exceptionMessage, elements]
     * @Return: java.lang.String
     * @editNote:
     */
    public String stackTraceToString(String exceptionName, String exceptionMessage, StackTraceElement[] elements) {
        StringBuffer strbuff = new StringBuffer();
        for (StackTraceElement stet : elements) {
            strbuff.append(stet + "
"
); } String message = exceptionName + ":" + exceptionMessage + "
"
+ strbuff.toString(); return message; } }

相关知识

1. @Aspect 标记在类上 , 代表为切面类  
2. @Component 标记在类上,通过扫描 创建对象  
3. @Pointcut 标记在方法上,方法不需要返回值,不需要参数, 用来配置切入点表达式
4. @Before 前置增强,标记在方法上,参数指定切入点表达式对应的方法名  
5. @After 最终增强,标记在方法上,参数指定切入点表达式对应的方法名  
6. @AfterReturning 后置增强,标记在方法上,参数指定切入点表达式对应的方法名  
7. @AfterThrowing 异常增强,标记在方法上,参数指定切入点表达式对应的方法名  
8. @Around 环绕增强,一般单独使用,不会与前四种增强混合,标记在方法上,参数指定切入点表达式对应的方法名  
9. <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
  	开启aop的注解支持,
10. @EnableAspectJAutoProxy 纯注解时 开启aop注解支持  

springboot配置Aop不生效解决:

原因1.启动类没有扫描到Aop的类,也就是启动类或者AOP类的位置不对;修改位置或者@ComponentScan扫描
原因2.没有正确创建类; 正确的做法: 通过Java Class创建aspect类,然后加上 @Aspect和@Component注解。 我试了从Java Class创建aspect类,把之前无效的代码完整拷贝到这个新建的文件里,就生效了。至于上面那种创建aspect类为何会导致无效的内在原因,还不清楚,有知情的朋友请留言回复。 虽然两种创建方式不同,但最终代码呈现完全是一样的,就是一个有用一个无用。
原因3:以上两个方法没有解决我的问题: 最终是在 启动类上添加 @EnableAspectJAutoProxy 纯注解时 (开启aop注解支持 );
默认情况下SpringBoot是自动支持这个注解的,但是不知道为什么没用;

2022/08/02

你可能感兴趣的:(java,spring,spring,boot)