自定义注解记录操作日志

自定义注解

自定义注解首先要知道元注解,也就是注解的注解,是jdk内置的。元注解有四种:

  • @Retention 注解保留策略
    @Retention(RetentionPolicy.SOURCE) 仅存在于源码中
    @Retention(RetentionPolicy.CLASS) 存在于class字节码中,但运行时无法获取
    @Retention(RetentionPolicy.RUNTIME) 存在于class字节码中,运行时可以通过反射获取
  • @Target 注解的作用范围
    @Target(ElementType.TYPE) 接口、类等
    @Target(ElementType.FIELD) 字段
    @Target(ElementType.METHOD) 方法
    @Target(ElementType.PARAMETER) 方法参数
    @Target(ElementType.CONSTRUCTOR) 构造函数
    @Target(ElementType.LOCAL_VARIABLE) 局部变量
    @Target(ElementType.ANNOTATION_TYPE) 注解
    @Target(ElementType.PACKAGE) 包
  • @Document 注解包含在javadoc中
  • @Inherited 注解可以被继承

实现方法

引入jar包

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

自定义注解

import java.lang.annotation.*;

/**
 * @author 晓风残月Lx
 * @date 2023/5/28 11:32
 * 前台自定义操作日志注解
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD}) //注解放置的目标位置即方法级别
@Documented
public @interface BusinessLogger {

    String value() default "";   // 操作模块

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

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

    boolean save() default true;  // 是否将当前日志记录到数据库中

}

编写切面类

切面类根据功能需求不同,进行修改,这里是将操作存入日志。

import cn.dev33.satoken.stp.StpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.xfcy.blog.annotation.BusinessLogger;
import com.xfcy.blog.common.R;
import com.xfcy.blog.common.constant.Constant;
import com.xfcy.blog.entity.ExceptionLog;
import com.xfcy.blog.entity.UserLog;
import com.xfcy.blog.mapper.ExceptionLogMapper;
import com.xfcy.blog.mapper.UserLogMapper;
import com.xfcy.blog.utils.AspectUtils;
import com.xfcy.blog.utils.IpUtils;
import com.xfcy.blog.vo.SystemUserVO;
import eu.bitwalker.useragentutils.UserAgent;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.Objects;

/**
 * @author 晓风残月Lx
 * @date 2023/5/28 11:38
 */
@Aspect
@Component
@RequiredArgsConstructor
public class BusinessLoggerAspect {

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

    private final UserLogMapper userLogMapper;

    private final ExceptionLogMapper exceptionLogMapper;

    /**
     * 设置操作日志切入点 在注解的位置切入代码
     *
     * @param businessLogger
     */
    // execution(public * com.xfcy.blog.controller.*.*(..)) 表示所有controller
    @Pointcut(value = "@annotation(businessLogger)")
    public void pointAspect(BusinessLogger businessLogger) {
    }

    /**
     * Around注解可以用来在调用一个具体方法前和调用后来完成一些具体的任务。
     *
     * @param joinPoint      方法的执行点
     * @param businessLogger 方法返回值
     * @return
     */
    @Around(value = "pointAspect(businessLogger)")
    public Object doAround(ProceedingJoinPoint joinPoint, BusinessLogger businessLogger) throws Throwable {
        System.out.println("--------Around方法开始执行");
        //先执行业务
        Object result = joinPoint.proceed();

        try {
            // 日志收集
            handle(joinPoint, (R) result);
        } catch (Exception e) {
            logger.error("日志记录出错!", e);
        }


        return result;
    }

    @AfterThrowing(value = "pointAspect(businessLogger)", throwing = "e")
    public void afterThrowing(JoinPoint joinPoint, BusinessLogger businessLogger, Throwable e) {
        // 获取RequestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        // 从requestAttributes中获取HttpServletRequest的信息
        assert requestAttributes != null;
        HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
        String ip = IpUtils.getIp(request);

        String paramsJson = getParamsJson((ProceedingJoinPoint) joinPoint);
        String operationName = AspectUtils.INSTANCE.parseParams(joinPoint.getArgs(), businessLogger.value());



        ExceptionLog exceptionLog = ExceptionLog.builder().ip(ip)
                .ipSource(IpUtils.getIp2region(ip)).params(paramsJson)
                .method(joinPoint.getSignature().getName())
                .username("游客")
                .operation(operationName).exceptionJson(JSON.toJSONString(e))
                .exceptionMessage(e.getMessage()).createTime(LocalDateTime.now()).build();

        exceptionLogMapper.insert(exceptionLog);
    }

    /**
     * 记录操作日志
     *
     * @param joinPoint 方法的执行点
     * @param result    方法返回值
     */
    private void handle(ProceedingJoinPoint joinPoint, R result) {
        // 获取RequestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        // 从requestAttributes中获取HttpServletRequest的信息
        assert requestAttributes != null;
        HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);

        // 从切面织入点处通过反射机制获取织入点处的方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // 获取切入点所在的方法
        Method method = signature.getMethod();
        // 获取操作
        BusinessLogger annotation = method.getAnnotation(BusinessLogger.class);
        if (!annotation.save()) {
            return;
        }

        assert request != null;
        String ip = IpUtils.getIp(request);
        UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("user-agent"));
        String clientType = userAgent.getOperatingSystem().getDeviceType().toString();
        String os = userAgent.getOperatingSystem().getName();
        String browser = userAgent.getBrowser().toString();

        UserLog userLog = UserLog.builder().model(annotation.value()).type(annotation.type())
                .description(annotation.desc()).ip(ip).address(IpUtils.getIp2region(ip))
                .clientType(clientType).accessOs(os).browser(browser).result(result.getMsg()).
                createTime(LocalDateTime.now()).build();
        if (StpUtil.isLogin()) {
            userLog.setUserId(StpUtil.getLoginIdAsLong());
        }
        userLogMapper.insert(userLog);
    }


    private String getParamsJson(ProceedingJoinPoint joinPoint) {
        // 参数值
        Object[] args = joinPoint.getArgs();
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        String[] parameterNames = methodSignature.getParameterNames();

        // 通过map封装参数和参数值
        HashMap<String, Object> paramMap = new HashMap<>();
        for (int i = 0; i < parameterNames.length; i++) {
            paramMap.put(parameterNames[i], args[i]);
        }

        boolean isContains = paramMap.containsKey("request");
        if (isContains) paramMap.remove("request");
        return JSONObject.toJSONString(paramMap);
    }
}

测试类

/**
 * 前台文章管理器
 *
 * @author 晓风残月Lx
 * @date 2023/4/9 22:07
 */
@RestController
@RequestMapping("/web/article")
@Api(tags = "前台文章管理")
public class WebArticleController {

    @Resource
    private ArticleService articleService;

    @BusinessLogger(value = "首页-用户访问首页",type = "查询",desc = "查询所有文章")
    @GetMapping("/list")
    @ApiOperation(value = "文章列表", httpMethod = "GET", response = R.class, notes = "文章列表")
    public R list(@RequestParam Integer pageNo, Integer pageSize) {
        return articleService.listWebArticle(pageNo, pageSize);
    }
}

通过以上方式,就可以在需要记录日志的方法上使用注解,在注解处理器中通过切面编程的方式记录日志,实现了自定义注解记录操作日志的功能。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

晓风残月Lx

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

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

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

打赏作者

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

抵扣说明:

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

余额充值