首页 > 文章列表 > 怎么使用SpringBoot+Aop记录用户操作日志

怎么使用SpringBoot+Aop记录用户操作日志

springboot aop
305 2023-05-18

怎么使用SpringBoot+Aop记录用户操作日志

1、设计用户操作日志表: sys_oper_log

对应实体类为SysOperLog.java

import com.baomidou.mybatisplus.annotation.IdType;

import com.baomidou.mybatisplus.annotation.TableId;

import io.swagger.annotations.ApiModelProperty;

import lombok.Data;

import lombok.EqualsAndHashCode;

import lombok.experimental.Accessors;

import java.io.Serializable;

import java.util.Date;



/**

 * <p>

 * 操作日志记录

 * </p>

 */

@Data

@EqualsAndHashCode(callSuper = false)

@Accessors(chain = true)

public class SysOperLog implements Serializable {



    private static final long serialVersionUID = 1L;



    @TableId(value = "id", type = IdType.AUTO)

    @ApiModelProperty("主键Id")

    private Integer id;



    @ApiModelProperty("模块标题")

    private String title;



    @ApiModelProperty("参数")

    private String optParam;



    @ApiModelProperty("业务类型(0其它 1新增 2修改 3删除)")

    private Integer businessType;



    @ApiModelProperty("路径名称")

    private String uri;



    @ApiModelProperty("操作状态(0正常 1异常)")

    private Integer status;



    @ApiModelProperty("错误消息")

    private String errorMsg;



    @ApiModelProperty("操作时间")

    private Date operTime;

}

2、引入依赖

<dependency>

    <groupId>com.alibaba</groupId>

    <artifactId>fastjson</artifactId>

    <version>2.0.9</version>

</dependency>

<dependency>

   <groupId>org.springframework.boot</groupId>

   <artifactId>spring-boot-starter-aop</artifactId>

</dependency>

3、自定义用户操作日志注解

MyLog.java

import java.lang.annotation.*;



@Target({ElementType.PARAMETER, ElementType.METHOD})

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface MyLog {

    // 自定义模块名,eg:登录

    String title() default "";

    // 方法传入的参数

    String optParam() default "";

    // 操作类型,eg:INSERT, UPDATE...

    BusinessType businessType() default BusinessType.OTHER;  

}

BusinessType.java &mdash; 操作类型枚举类

public enum BusinessType {

    // 其它

    OTHER,

    // 查找

    SELECT,

    // 新增

    INSERT,

    // 修改

    UPDATE,

    // 删除

    DELETE,

}

4、自定义用户操作日志切面

LogAspect.java

import com.alibaba.fastjson.JSONObject;

import iot.sixiang.license.entity.SysOperLog;

import iot.sixiang.license.handler.IotLicenseException;

import iot.sixiang.license.jwt.UserUtils;

import iot.sixiang.license.service.SysOperLogService;

import lombok.extern.slf4j.Slf4j;

import org.apache.commons.lang3.StringUtils;

import org.aspectj.lang.JoinPoint;

import org.aspectj.lang.Signature;

import org.aspectj.lang.annotation.AfterReturning;

import org.aspectj.lang.annotation.AfterThrowing;

import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.Pointcut;

import org.aspectj.lang.reflect.MethodSignature;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Component;



import java.lang.reflect.Method;

import java.util.Date;

import java.util.HashMap;

import java.util.Map;



@Aspect

@Component

@Slf4j

public class LogAspect {

    /**

     * 该Service及其实现类相关代码请自行实现,只是一个简单的插入数据库操作

     */

    @Autowired

    private SysOperLogService sysOperLogService;

    

	/**

     * @annotation(MyLog类的路径) 在idea中,右键自定义的MyLog类-> 点击Copy Reference

     */

    @Pointcut("@annotation(xxx.xxx.xxx.MyLog)")

    public void logPointCut() {

        log.info("------>配置织入点");

    }



    /**

     * 处理完请求后执行

     *

     * @param joinPoint 切点

     */

    @AfterReturning(pointcut = "logPointCut()")

    public void doAfterReturning(JoinPoint joinPoint) {

        handleLog(joinPoint, null);

    }



    /**

     * 拦截异常操作

     *

     * @param joinPoint 切点

     * @param e         异常

     */

    @AfterThrowing(value = "logPointCut()", throwing = "e")

    public void doAfterThrowing(JoinPoint joinPoint, Exception e) {

        handleLog(joinPoint, e);

    }



    private void handleLog(final JoinPoint joinPoint, final Exception e) {



        // 获得MyLog注解

        MyLog controllerLog = getAnnotationLog(joinPoint);

        if (controllerLog == null) {

            return;

        }

        SysOperLog operLog = new SysOperLog();

        // 操作状态(0正常 1异常)

        operLog.setStatus(0);

        // 操作时间

        operLog.setOperTime(new Date());

        if (e != null) {

            operLog.setStatus(1);

            // IotLicenseException为本系统自定义的异常类,读者若要获取异常信息,请根据自身情况变通

            operLog.setErrorMsg(StringUtils.substring(((IotLicenseException) e).getMsg(), 0, 2000));

        }



        // UserUtils.getUri();获取方法上的路径 如:/login,本文实现方法如下:

        // 1、在拦截器中 String uri = request.getRequestURI();

        // 2、用ThreadLocal存放uri,UserUtils.setUri(uri);

        // 3、UserUtils.getUri();

        String uri = UserUtils.getUri();

        operLog.setUri(uri);

        // 处理注解上的参数

        getControllerMethodDescription(joinPoint, controllerLog, operLog);

        // 保存数据库

        sysOperLogService.addOperlog(operLog.getTitle(), operLog.getBusinessType(), operLog.getUri(), operLog.getStatus(), operLog.getOptParam(), operLog.getErrorMsg(), operLog.getOperTime());

    }



    /**

     * 是否存在注解,如果存在就获取,不存在则返回null

     * @param joinPoint

     * @return

     */

    private MyLog getAnnotationLog(JoinPoint joinPoint) {

        Signature signature = joinPoint.getSignature();

        MethodSignature methodSignature = (MethodSignature) signature;

        Method method = methodSignature.getMethod();

        if (method != null) {

            return method.getAnnotation(MyLog.class);

        }

        return null;

    }



    /**

     * 获取Controller层上MyLog注解中对方法的描述信息

     * @param joinPoint 切点

     * @param myLog 自定义的注解

     * @param operLog 操作日志实体类

     */

    private void getControllerMethodDescription(JoinPoint joinPoint, MyLog myLog, SysOperLog operLog) {

        // 设置业务类型(0其它 1新增 2修改 3删除)

        operLog.setBusinessType(myLog.businessType().ordinal());

        // 设置模块标题,eg:登录

        operLog.setTitle(myLog.title());

        // 对方法上的参数进行处理,处理完:userName=xxx,password=xxx

        String optParam = getAnnotationValue(joinPoint, myLog.optParam());

        operLog.setOptParam(optParam);



    }



    /**

     * 对方法上的参数进行处理

     * @param joinPoint

     * @param name

     * @return

     */

    private String getAnnotationValue(JoinPoint joinPoint, String name) {

        String paramName = name;

        // 获取方法中所有的参数

        Map<String, Object> params = getParams(joinPoint);

        // 参数是否是动态的:#{paramName}

        if (paramName.matches("^#\\{\\D*\\}")) {

            // 获取参数名,去掉#{ }

            paramName = paramName.replace("#{", "").replace("}", "");

            // 是否是复杂的参数类型:对象.参数名

            if (paramName.contains(".")) {

                String[] split = paramName.split("\\.");

                // 获取方法中对象的内容

                Object object = getValue(params, split[0]);

                // 转换为JsonObject

                JSONObject jsonObject = (JSONObject) JSONObject.toJSON(object);

                // 获取值

                Object o = jsonObject.get(split[1]);

                return String.valueOf(o);

            } else {// 简单的动态参数直接返回

                StringBuilder str = new StringBuilder();

                String[] paraNames = paramName.split(",");

                for (String paraName : paraNames) {

                    

                    String val = String.valueOf(getValue(params, paraName));

                    // 组装成 userName=xxx,password=xxx,

                    str.append(paraName).append("=").append(val).append(",");

                }

                // 去掉末尾的,

                if (str.toString().endsWith(",")) {

                    String substring = str.substring(0, str.length() - 1);

                    return substring;

                } else {

                    return str.toString();

                }

            }

        }

        // 非动态参数直接返回

        return name;

    }



    /**

     * 获取方法上的所有参数,返回Map类型, eg: 键:"userName",值:xxx  键:"password",值:xxx

    * @param joinPoint

     * @return

     */

    public Map<String, Object> getParams(JoinPoint joinPoint) {

        Map<String, Object> params = new HashMap<>(8);

        // 通过切点获取方法所有参数值["zhangsan", "123456"]

        Object[] args = joinPoint.getArgs();

        // 通过切点获取方法所有参数名 eg:["userName", "password"]

        MethodSignature signature = (MethodSignature) joinPoint.getSignature();

        String[] names = signature.getParameterNames();

        for (int i = 0; i < args.length; i++) {

            params.put(names[i], args[i]);

        }

        return params;

    }



    /**

     * 从map中获取键为paramName的值,不存在放回null

     * @param map

     * @param paramName

     * @return

     */

    private Object getValue(Map<String, Object> map, String paramName) {

        for (Map.Entry<String, Object> entry : map.entrySet()) {

            if (entry.getKey().equals(paramName)) {

                return entry.getValue();

            }

        }

        return null;

    }

}

5、MyLog注解的使用

@GetMapping("login")

@MyLog(title = "登录", optParam = "#{userName},#{password}", businessType = BusinessType.OTHER)

public DataResult login(@RequestParam("userName") String userName, @RequestParam("password") String password) {

 ...

}

6、最终效果