切面aop记录系统操作日志 Java

切面原理

AOP切面获取操作日志的原理是通过在切点之前或之后执行日志记录的通知代码。每次目标方法执行时,切面会拦截执行过程,在执行前或执行后将相关信息记录到日志中,从而实现操作日志的获取。通过AOP的织入机制,可以将操作日志记录代码和业务代码进行分离,提高代码的可维护性和复用性

代码结构

主要代码:
在这里插入图片描述

MyLog

自定义注解记录系统操作日志,在方法上加注解如下,注解内容可以根据自己实际情况更改,我实在Impl层的方法上加的,这样就能保证到调接口使用到这个方法时,能够把对应的操作、操作时间、操作人等相关信息进行记录。**

@MyLog(title=“设备管理”,content=“对xx设备进行修改”)

**

/**
 * @title: 自定义注解记录系统操作日志
 * @Description: 自定义注解记录系统操作日志
 * @Author: Momo
 * @Date: 2023-11-23 11:14
 */
//Target注解决定 MyLog 注解可以加在哪些成分上,如加在类身上,或者属性身上,或者方法身上等成分
@Target({ElementType.PARAMETER, ElementType.METHOD})
//Retention注解括号中的"RetentionPolicy.RUNTIME"意思是让 MyLog 这个注解的生命周期一直程序运行时都存在
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLog {
    /**
     * 模块标题
     */
    String title() default "";

    /**
     * 日志内容
     */
    String content() default "";
}

OperLog 实体类

这是对应数据库的实体类,存储的这些字段,可以根据自己系统的实际情况进行增减


/**
 * @title: 操作日志记录实体类
 * @Description:  操作日志记录表实体类
 * @Author: Momo
 * @Date:  2024-02-01 12:50
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class OperLog {
    /**
     * 唯一id
     */
    private Integer id;
    /**
     * 操作人员
     */
    private String operName;
    /**
     * 模块标题
     */
    private String title;
    /**
     * 日志内容
     */
    private String content;
    /**
     * 方法名称
     */
    private String method;
    /**
     * ip地址
     */
    private String ip;
    /**
     * 操作状态(0正常 1异常)
     */
    private String status;
    /**
     * 错误信息
     */
    private String errorMsg;
    /**
     * 操作时间
     */
    private String operTime;

}

OperLogAspect 切面处理工具


import com.alibaba.fastjson.JSON;

import com.xtd.im.annotion.MyLog;


import com.xtd.im.entity.DO.OperLog;
import com.xtd.im.mapper.UserManagementMapper;
import com.xtd.im.utils.TokenUtils;
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.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * @title: 切面处理工具
 * @Description: 切面处理类,记录操作日志到数据库
 * @Author: Momo
 * @Date:  2024-02-01 13:44
 */
@Aspect
@Component
public class OperLogAspect {

    @Resource
    private UserManagementMapper operLogMapper;

    //为了记录方法的执行时间
    ThreadLocal<Long> startTime = new ThreadLocal<>();

    /**
     * 设置操作日志切入点,这里介绍两种方式:
     * 1、基于注解切入(也就是打了自定义注解的方法才会切入)
     * 2、基于包扫描切入
     */
    @Pointcut("@annotation(com.xtd.im.annotion.MyLog)")//在注解的位置切入代码
    //@Pointcut("execution(public * org.wujiangbo.controller..*.*(..))")//从controller切入
    public void operLogPoinCut() {
    }

    @Before("operLogPoinCut()")
    public void beforMethod(JoinPoint point) {
        startTime.set(System.currentTimeMillis());
    }

    /**
     * 设置操作异常切入点记录异常日志 扫描所有controller包下操作
     */
    @Pointcut("execution(* com.xtd.im.controller..*.*(..))")
    public void operExceptionLogPoinCut() {
    }


    /**
     * 正常返回通知,拦截用户操作日志,连接点正常执行完成后执行, 如果连接点抛出异常,则不会执行
     *
     * @param joinPoint 切入点
     * @param result    返回结果
     */
    @AfterReturning(value = "operLogPoinCut()", returning = "result")
    public void saveOperLog(JoinPoint joinPoint, Object result) {
        // 获取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();
            // 获取操作
            MyLog myLog = method.getAnnotation(MyLog.class);

            OperLog operlog = new OperLog();
            if (myLog != null) {
                operlog.setTitle(myLog.title());//设置模块名称
                operlog.setContent(myLog.content());//设置日志内容
            }
            // 获取请求的类名
            String className = joinPoint.getTarget().getClass().getName();
            // 获取请求的方法名
            String methodName = method.getName();
            methodName = className + "." + methodName + "()";
            operlog.setMethod(methodName); //设置请求方法
            String username = request.getSession().getAttribute("username").toString();

            String token = request.getHeader("token");
            operlog.setOperName(TokenUtils.getUsername(token));
            // 获取用户名(真实环境中,肯定有工具类获取当前登录者的账号或ID的,或者从token中解析而来)
            operlog.setIp(getIp(request)); // IP地址

            operlog.setStatus("0");//操作状态(0正常 1异常)
            //插入数据库
            operLogMapper.insertLog(operlog);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 异常返回通知,用于拦截异常日志信息 连接点抛出异常后执行
     */
    @AfterThrowing(pointcut = "operExceptionLogPoinCut()", throwing = "e")
    public void saveExceptionLog(JoinPoint joinPoint, Throwable e) {
        // 获取RequestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        // 从获取RequestAttributes中获取HttpServletRequest的信息
        HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);

        OperLog operlog = new OperLog();
        try {
            // 从切面织入点处通过反射机制获取织入点处的方法
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            // 获取切入点所在的方法
            Method method = signature.getMethod();
            // 获取请求的类名
            String className = joinPoint.getTarget().getClass().getName();
            // 获取请求的方法名
            String methodName = method.getName();
            methodName = className + "." + methodName + "()";
            // 获取操作
            MyLog myLog = method.getAnnotation(MyLog.class);
            if (myLog != null) {
                operlog.setTitle(myLog.title());//设置模块名称
                operlog.setContent(myLog.content());//设置日志内容
            }
            operlog.setMethod(methodName); //设置请求方法
            String token = request.getHeader("token");
            operlog.setOperName(TokenUtils.getUsername(token));
            operlog.setIp(getIp(request)); // IP地址
            operlog.setStatus("1");//操作状态(0正常 1异常)
            operlog.setErrorMsg(stackTraceToString(e.getClass().getName(), e.getMessage(), e.getStackTrace()));//记录异常信息
            //插入数据库
            operLogMapper.insertLog(operlog);
        } catch (Exception e2) {
            e2.printStackTrace();
        }
    }

    /**
     * 转换异常信息为字符串
     */
    public String stackTraceToString(String exceptionName, String exceptionMessage, StackTraceElement[] elements) {
        StringBuffer strbuff = new StringBuffer();
        for (StackTraceElement stet : elements) {
            strbuff.append(stet + "\n");
        }
        String message = exceptionName + ":" + exceptionMessage + "\n\t" + strbuff.toString();
        message = substring(message, 0, 2000);
        return message;
    }

    /**
     * 参数拼装
     */
    private String argsArrayToString(Object[] paramsArray) {
        String params = "";
        if (paramsArray != null && paramsArray.length > 0) {
            for (Object o : paramsArray) {
                if (o != null) {
                    try {
                        Object jsonObj = JSON.toJSON(o);
                        params += jsonObj.toString() + " ";
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        return params.trim();
    }

    //字符串截取
    public static String substring(String str, int start, int end) {
        if (str == null) {
            return null;
        } else {
            if (end < 0) {
                end += str.length();
            }
            if (start < 0) {
                start += str.length();
            }
            if (end > str.length()) {
                end = str.length();
            }
            if (start > end) {
                return "";
            } else {
                if (start < 0) {
                    start = 0;
                }

                if (end < 0) {
                    end = 0;
                }
                return str.substring(start, end);
            }
        }
    }

    /**
     * 转换request 请求参数
     *
     * @param paramMap request获取的参数数组
     */
    public Map<String, String> converMap(Map<String, String[]> paramMap) {
        Map<String, String> returnMap = new HashMap<>();
        for (String key : paramMap.keySet()) {
            returnMap.put(key, paramMap.get(key)[0]);
        }
        return returnMap;
    }

    //根据HttpServletRequest获取访问者的IP地址
    public static String getIp(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }
}


TokenUtil 工具类

/**
 * @title: token工具类
 * @Description: token工具类
 * @Author: Momo
 * @Date:  2024-02-01 13:37
 */
public class TokenUtils {
    // 令牌过期时间(毫秒),此处设置为30分钟
    private static final long EXPIRE_DATE = 30 * 60 * 1000;

    // 用于JWT加密的密钥
    private static final String TOKEN_SECRET = "3A8Ii4F2DEyRqLFjNLy3uTRWmq16ZvRq";

    /**
     * 根据用户名和密码生成JWT Token
     * @param username 用户名
     * @param password 用户密码
     * @return 返回生成的Token字符串
     */
    public static String token(String username, String password) {
        String token = "";
        try {
            // 计算Token的过期时间
            Date date = new Date(System.currentTimeMillis() + EXPIRE_DATE);

            // 使用指定的密钥和算法进行加密
            Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);

            // 设置Token的头部信息
            Map<String, Object> header = new HashMap<>();
            header.put("typ", "JWT");
            header.put("alg", "HS256");

            // 携带用户名和密码信息生成签名
            token = JWT.create()
                    .withHeader(header) // 指定头部信息
                    .withClaim("username", username) // 包含username的信息
                    .withClaim("password", password) // 包含password的信息
                    .withExpiresAt(date) // 设置过期时间
                    .sign(algorithm); // 进行加密签名
        } catch (Exception e) {
            // 打印异常堆栈信息,并返回null
            e.printStackTrace();
            return null;
        }
        // 返回生成的Token
        return token;
    }

    /**
     * 检查Token是否已经过期
     * @param token 需要检查的Token串
     * @return true 如果Token已经过期,否则false
     */
    public static boolean isTokenExpired(String token) {
        // 如果Token的过期时间小于当前时间,则Token已经过期
        return JWT.decode(token).getExpiresAt().before(new Date());
    }

    /**
     * 验证Token是否合法
     * @param token 需要验证的Token串
     * @return true 如果Token验证通过,否则false
     */
    public static boolean verify(String token) {
        try {
            // 根据密钥和算法创建Token验证对象
            Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
            JWTVerifier verifier = JWT.require(algorithm).build();
            // 验证Token
            DecodedJWT jwt = verifier.verify(token);
            // 如果没有抛出异常,则验证通过,返回true
            return true;
        } catch (Exception e) {
            // 如果验证过程中发生异常(如Token非法、过期等),打印异常堆栈信息,并返回false
            e.printStackTrace();
            return false;
        }
    }
    /**
      * @description: 解析token获取用户名
      * @param token token令牌
      * @return 用户名
      * @Date:  2024-02-01 13:37
      * @author: Momo
      */

    public static String getUsername(String token){
        //判空
        if(StringUtils.isEmpty(token)) {
            return "";
        }
        Claim username = null;
        try {
            DecodedJWT decode = JWT.decode(token);
            Map<String, Claim> claims = decode.getClaims();
            username = claims.get("username");
        } catch (JWTDecodeException e) {
            e.printStackTrace();
            return "";
        }
        return username.asString();
    }
}
Spring AOP是一个强大的框架,可以帮助我们实现各种切面,其中包括日志记录。下面是实现日志记录的步骤: 1. 添加Spring AOP依赖 在Maven或Gradle中添加Spring AOP依赖。 2. 创建日志切面 创建一个用于记录日志切面。这个切面可以拦截所有需要记录日志的方法。在这个切面中,我们需要使用@Aspect注解来声明这是一个切面,并使用@Pointcut注解来定义哪些方法需要被拦截。 ```java @Aspect @Component public class LoggingAspect { @Pointcut("execution(* com.example.demo.service.*.*(..))") public void serviceMethods() {} @Around("serviceMethods()") public Object logServiceMethods(ProceedingJoinPoint joinPoint) throws Throwable { // 获取方法名,参数列表等信息 String methodName = joinPoint.getSignature().getName(); Object[] args = joinPoint.getArgs(); // 记录日志 System.out.println("Method " + methodName + " is called with args " + Arrays.toString(args)); // 执行方法 Object result = joinPoint.proceed(); // 记录返回值 System.out.println("Method " + methodName + " returns " + result); return result; } } ``` 在上面的代码中,我们使用了@Around注解来定义一个环绕通知,它会在拦截的方法执行前后执行。在方法执行前,我们记录了该方法的名称和参数列表,然后在方法执行后记录了该方法的返回值。 3. 配置AOP 在Spring的配置文件中配置AOP。首先,我们需要启用AOP: ```xml <aop:aspectj-autoproxy/> ``` 然后,我们需要将创建的日志切面添加到AOP中: ```xml <bean id="loggingAspect" class="com.example.demo.aspect.LoggingAspect"/> <aop:config> <aop:aspect ref="loggingAspect"> <aop:pointcut id="serviceMethods" expression="execution(* com.example.demo.service.*.*(..))"/> <aop:around method="logServiceMethods" pointcut-ref="serviceMethods"/> </aop:aspect> </aop:config> ``` 在上面的代码中,我们将创建的日志切面声明为一个bean,并将其添加到AOP中。我们还定义了一个切入点,并将其与日志切面的方法进行关联。 4. 测试 现在,我们可以测试我们的日志记录功能了。在我们的业务逻辑中,所有匹配切入点的方法都会被拦截,并记录它们的输入和输出。我们可以在控制台中看到这些日志信息。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值