java AOP环绕通知记录操作日志

一.创建数据库日志表

CREATE TABLE `uc_system_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `user_code` varchar(64) DEFAULT NULL COMMENT '用户编码',
  `user_name` varchar(128) DEFAULT NULL COMMENT '用户名称',
  `is_login` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否登录',
  `client` varchar(32) DEFAULT NULL COMMENT '客户端(PC-网页、APP-移动应用、API-开放接口)',
  `module_code` varchar(32) DEFAULT NULL COMMENT '操作模块',
  `action_code` varchar(32) DEFAULT NULL COMMENT '操作类型/行为',
  `remark` text COMMENT '操作描述(失败原因)',
  `is_success` tinyint(4) NOT NULL DEFAULT '1' COMMENT '是否操作成功',
  `ip` varchar(32) DEFAULT NULL COMMENT 'IP地址',
  `user_dept_code` varchar(64) DEFAULT NULL COMMENT '用户操作时所在组织编码',
  `user_dept_name` varchar(128) DEFAULT NULL COMMENT '用户操作时所在组织名称',
  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `app_code` varchar(64) DEFAULT NULL,
  `app_name` varchar(128) DEFAULT NULL,
  `refer_app_code` varchar(64) DEFAULT NULL COMMENT '来源应用编码',
  `refer_app_name` varchar(128) DEFAULT NULL COMMENT '来源应用名称',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_user_code` (`user_code`) USING BTREE,
  KEY `idx_app_code` (`app_code`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=969269 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='操作日志表';

java对象


import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import lombok.experimental.Accessors;

import java.time.LocalDateTime;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("uc_system_log")
public class SystemLog{

    /**
     * 主键ID
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    /**
     * 用户编码
     */
    @TableField("user_code")
    private String userCode;

    /**
     * 用户编码
     */
    @TableField("user_name")
    private String userName;

    /**
     * 是否登录
     * 0-失败,1-成功
     */
    @TableField("is_login")
    private Boolean login;

    /**
     * 客户端
     * PC-网页、APP-移动应用、API-开放接口
     */
    @TableField("client")
    private SystemLogClientEnum client;

    /**
     * 操作模块
     */
    @TableField("module_code")
    private SystemLogModuleEnum module;

    /**
     * 操作类型(行为)
     */
    @TableField("action_code")
    private SystemLogActionEnum action;

    /**
     * 操作描述
     * 失败时有值
     */
    @TableField("remark")
    private String remark;

    /**
     * 操作结果
     * 0-失败,1-成功
     */
    @TableField("is_success")
    private Boolean success;

    /**
     * IP地址
     */
    @TableField("ip")
    private String ip;

    /**
     * 用户操作时所在组织编码
     */
    @TableField("user_dept_code")
    private String userDeptCode;

    /**
     * 用户操作时所在组织名称
     */
    @TableField("user_dept_name")
    private String userDeptName;

    /**
     * 创建时间
     */
    @TableField("gmt_create")
    private LocalDateTime gmtCreate;

    /**
     * 应用编码
     */
    @TableField("app_code")
    private String appCode;

    /**
     * 应用名称
     */
    @TableField("app_name")
    private String appName;

    /**
     * 来源应用编码
     */
    @TableField("refer_app_code")
    private String referAppCode;

    /**
     * 来源应用名称
     */
    @TableField("refer_app_name")
    private String referAppName;

}

 2.定义枚举


public interface BaseStringEnum {

    /**
     * 获取枚举值
     *
     * @return
     */
    String getCode();

    /**
     * 获取枚举文本
     *
     * @return
     */
    String getName();

    /**
     * 根据枚举值和type获取枚举
     */
    static <T extends BaseStringEnum> T getEnum(Class<T> type, String value) {
        T[] objs = type.getEnumConstants();
        for (T em : objs) {
            if (em.getCode().equals(value)) {
                return em;
            }
        }
        return null;
    }
}

系统模块枚举 

import com.dcqc.summarize.service.BaseStringEnum;
import lombok.AllArgsConstructor;
import lombok.Getter;

import java.util.Arrays;
/**
 * 系统模块枚举
 *
 * @author ws
 * Create on 2023-06-09
 */
@Getter
@AllArgsConstructor
public enum SystemLogModuleEnum  implements BaseStringEnum {

    LOGIN("登录/登出"),
    REGULAR_CONFIG("规则配置"),
    USER_MANAGE("用户管理"),
    MENU_MANAGE("菜单管理"),
    ROLE_MANAGE("角色管理"),
    RESOURCE_MANAGE("资源管理"),
    DEPT_MANAGE("组织管理"),
    APP_MANAGE("应用管理"),
    ATTACH_MANAGE("附件管理"),
    TASK_MANAGE("任务管理"),
    OTHER("其他"),
    AUTH_MANAGE("授权管理"),
    ORDER_MANAGE("工单管理"),
    ;

    private String desc;

    @Override
    public String getCode() {
        return name();
    }

    @Override
    public String getName() {
        return desc;
    }

    public static SystemLogModuleEnum valueByCode(String code) {
        return Arrays.stream(SystemLogModuleEnum.values())
                .filter(item -> item.getCode().equals(code))
                .findFirst()
                .orElse(null);
    }
}

 日志行为枚举类

import com.dcqc.summarize.service.BaseStringEnum;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;

/**
 * 日志行为枚举类
 *
 * @author ws
 * 2023-06-19
 */
@Getter
@AllArgsConstructor
public enum SystemLogActionEnum implements BaseStringEnum {

    QR_LOGIN("扫码登录"),
    ENABLE("启用"),
    REGISTER("注册"),
    DISABLE("禁用"),
    ADD("新增"),
    UPDATE("更新"),
    DELETE("删除"),
    OTHER("其他"),
    AUTH_CODE_LOGIN("授权码免登"),
    USER_CODE_LOGIN("用户编码免登"),
    IRS_TOKEN_LOGIN("IRS免登"),
    REFRESH_COOKIE("刷新COOKIE"),
    LOGOUT("退出登录"),
    INTER_LOGIN("聚合登录"),
    CHANGE_USER_DEPT("变更用户部门"),
    CHANGE_USER_ROLE("变更用户角色"),
    UPLOAD("上传"),
    DOWNLOAD("下载"),
    EXECUTE("执行"),
    // 免登包括了认证和授权
    SSO_LOGIN("SSO免登"),
    // 单纯的认证,不包括授权,只返回简单的用户信息
    SSO_AUTHENTICATION("SSO认证"),
    AGREE("同意"),
    REJECT("拒绝"),
    DELETE_USER("删除用户"),
    ;

    private String desc;

    @Override
    public String getCode() {
        return name();
    }

    @Override
    public String getName() {
        return desc;
    }

    public static SystemLogActionEnum valueByCode(String code) {
        return Arrays.stream(SystemLogActionEnum.values())
                .filter(item -> item.getCode().equals(code))
                .findFirst()
                .orElse(null);
    }

}

日志客户端枚举 


import com.dcqc.summarize.service.BaseStringEnum;
import lombok.AllArgsConstructor;
import lombok.Getter;

import java.util.Arrays;

/**
 * 日志客户端枚举类
 *
 * @author ws
 * Create on 2023-06-09
 */
@Getter
@AllArgsConstructor
public enum SystemLogClientEnum implements BaseStringEnum {

    PC("网页"),
    APP("移动应用"),
    OPEN_API("开放接口"),
    API("接口"),
    OTHER("其他"),
    ;

    private String desc;

    public static SystemLogClientEnum valueByCode(String code) {
        return Arrays.stream(SystemLogClientEnum.values())
                .filter(systemLogClientEnum -> systemLogClientEnum.name().equals(code))
                .findFirst()
                .orElse(null);
    }

    @Override
    public String getCode() {
        return name();
    }

    @Override
    public String getName() {
        return desc;
    }


}

三.记录日志注解

import com.dcqc.summarize.entity.SystemLogClientEnum;
import com.dcqc.summarize.entity.SystemLogModuleEnum;
import com.dcqc.summarize.entity.SystemLogActionEnum;

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 RecordLog {

    SystemLogModuleEnum module() default SystemLogModuleEnum.LOGIN;

    SystemLogActionEnum action() default SystemLogActionEnum.QR_LOGIN;

    SystemLogClientEnum client() default SystemLogClientEnum.OTHER;

    String appCode() default "";

}

四.Controller方法添加@RecordLog注解

  @ApiOperation("用户登陆")
    @PostMapping("/login")
    @RecordLog(module = SystemLogModuleEnum.LOGIN, action = SystemLogActionEnum.QR_LOGIN, client = SystemLogClientEnum.PC)
    public Result login(@RequestBody LoginRO entity) {
        try {
           //登陆业务
        }catch (Exception e) {
            throw new UserLoginErrorException("userCode", "userName", e);
        }
        return Result.success();
    }

用户登陆异常类 

import lombok.Data;

@Data
public class UserLoginErrorException extends RuntimeException {

    private String userCode;

    private String userName;

    public UserLoginErrorException(String userCode, String userName, Throwable cause) {
        super(userCode + ":" + userName, cause);
        this.userCode = userCode;
        this.userName = userName;
    }

}

五.AOP切面

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

public class BaseAspect {
    public BaseAspect() {
    }

    protected Class<?> getClass(JoinPoint joinPoint) {
        return joinPoint.getTarget().getClass();
    }

    protected Method getMethod(JoinPoint joinPoint) {
        return ((MethodSignature)joinPoint.getSignature()).getMethod();
    }

    protected <T extends Annotation> T getAnn(JoinPoint joinPoint, Class<T> annClazz) {
        return this.getAnn(this.getMethod(joinPoint), annClazz);
    }

    protected <T extends Annotation> T getAnn(Method method, Class<T> annClazz) {
        return AnnotationUtils.findAnnotation(method, annClazz);
    }

    protected String[] getParamNames(Method method) {
        return (new LocalVariableTableParameterNameDiscoverer()).getParameterNames(method);
    }



    protected HttpServletRequest getRequest() {
        return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
    }
}

import cn.hutool.extra.servlet.ServletUtil;
import com.dcqc.summarize.entity.SystemLog;
import com.dcqc.summarize.entity.SystemLogClientEnum;
import com.dcqc.summarize.exception.UserLoginErrorException;
import com.dcqc.summarize.mapper.SystemLogMapper;
import com.dcqc.summarize.service.RecordLog;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;

import java.util.Objects;
@Slf4j
@Aspect
@Component
public class RecordLogAspect extends BaseAspect {
    @Autowired
    private SystemLogMapper systemLogMapper;
    /**
     * 处理日志注解
     *
     * @param point 切点
     * @return 处理结果
     * @throws Throwable 处理异常
     */
    @Around("@annotation(com.dcqc.summarize.service.RecordLog)")
    public Object recordLogAround(ProceedingJoinPoint point) throws Throwable {
        SystemLog systemLogDTO = new SystemLog();
        try {
            // 1,获取操作日志切面注解
            RecordLog recordLog = getAnn(point, RecordLog.class);
            if (Objects.isNull(recordLog)) {
                return point.proceed();
            }

            // 2,获取注解对应的请求的客户端IP
            HttpServletRequest httpRequest = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            String clientIP = ServletUtil.getClientIP(httpRequest);

            // 3,从header里取值
            String appCode = httpRequest.getHeader("appCode");
            if (StringUtils.isNotBlank(appCode)) {
                systemLogDTO.setAppCode(appCode);
            }
            // 获取客户端类型
            SystemLogClientEnum client = recordLog.client();
            if (SystemLogClientEnum.OTHER.equals(client)) {
                if (httpRequest.getRequestURI().contains("openapi")) {
                    client = SystemLogClientEnum.OPEN_API;
                } else if (httpRequest.getRequestURI().contains("api")) {
                    client = SystemLogClientEnum.API;
                } else {
                    client = SystemLogClientEnum.PC;
                }
            }

            // 4,封装操作日志业务信息
            systemLogDTO.setIp(clientIP);
            systemLogDTO.setModule(recordLog.module());
            systemLogDTO.setAction(recordLog.action());
            systemLogDTO.setClient(client);
        } catch (Exception e) {
            log.error("操作日志解析异常", e);
        }
        // 6,执行切点并记录操作日志
        Object result = null;
        try {
            result = point.proceed();
        } catch (Exception e) {
            if (e instanceof UserLoginErrorException) {
                systemLogDTO.setUserCode(((UserLoginErrorException) e).getUserCode());
                systemLogDTO.setUserName(((UserLoginErrorException) e).getUserName());
            }
            // 6.1,执行失败,记录失败日志
            systemLogDTO.setRemark(e.getMessage());
            systemLogMapper.insert(systemLogDTO);
            throw e;
        }
        // 6.1,执行成功,记录成功日志
        systemLogMapper.insert(systemLogDTO);
        return result;
    }
}

调用方法,可以看到日志被记录 

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

今朝花落悲颜色

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值