spring AOP实现后端日志操作记录功能

一、引入配置文件

     <!--AOP-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <!-- guava工具包 -->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>30.0-jre</version>
        </dependency>

二、编写对应的枚举类

package com.baizhi.enus;
 
/**
 * @author xxs
 * @date 2022年08月10日 16:44
 */
public interface OpType {
 
    /**
     * 新增
     */
    public static final int ADD = 1;
 
    /**
     * 修改
     */
    public static final int UPDATE = 2;
    /**
     * 删除
     */
    public static final int DELETE = 3;
    /**
     * 查询-分页
     */
    public static final int QUERY_PAGE = 4;
    /**
     * 其他
     */
    public static final int OTHER = 5;
    /**
     * 登录
     */
    public static final int LOGIN = 6;
    /**
     * 登出
     */
    public static final int LOGOUT = 7;
    /**
     * 导出
     */
    public static final int EXPORT = 8;
    /**
     * 导入
     */
    public static final int IMPORT = 9;
    /**
     * 保存
     */
    public static final int SAVE = 10;
    /**
     * 发送邮件
     */
    public static final int SEND_EMAIL = 11;
    /**
     * 发送短信
     */
    public static final int SMS = 12;
    /**
     * 查询-不分页
     */
    public static final int QUERY_NO_PAGE = 13;
    /**
     * 查询明细
     */
    public static final int QUERY_DETAIL = 14;
    /**
     * 审核
     */
    public static final int AUDIT = 15;
    /**
     * 反审核
     */
    public static final int UN_AUDIT = 16;
    /**
     * 下载
     */
    public static final int DOWNLOAD = 17;
    /**
     * 上传
     */
    public static final int UPLOAD = 18;
    /**
     * 校验
     */
    public static final int CHECK = 19;
    /**
     * 安装
     */
    public static final int INSTALL = 20;
    /**
     * 卸载
     */
    public static final int UN_INSTALL = 21;
    /**
     * 启动
     */
    public static final int START = 22;
    /**
     * 停止
     */
    public static final int STOP = 23;
    /**
     * 重启
     */
    public static final int RESTART = 24;
    /**
     * 暂停
     */
    public static final int PAUSE = 25;
    /**
     * 恢复
     */
    public static final int RESUME = 26;
    /**
     * 刷新
     */
    public static final int REFRESH = 27;
    /**
     * 生成
     */
    public static final int GENERATE = 28;
    /**
     * 统计
     */
    public static final int STATISTICS = 29;
 
    /**
     * 同步
     */
    public static final int SYNC = 30;
    /**
     * 定时调度
     */
    public static final int SCHEDULE = 31;
 
 
 
}
package com.baizhi.enus;
 
import java.util.ArrayList;
import java.util.List;
 
/**
 * @author hz
 */
public enum OpTypeEnum {
    /*
        1. 增加
        2. 删除
        3. 修改
        4. 查询
        5. 其他
     */
    ADD(OpType.ADD, "新增"),
 
    UPDATE(OpType.UPDATE, "修改"),
 
    DELETE(OpType.DELETE, "删除"),
 
    QUERY_PAGE(OpType.QUERY_PAGE, "查询-分页", true),
 
    OTHER(OpType.OTHER, "其他"),
 
    LOGIN(OpType.LOGIN, "登录"),
 
    LOGOUT(OpType.LOGOUT, "登出"),
 
    EXPORT(OpType.EXPORT, "导出"),
 
    IMPORT(OpType.IMPORT, "导入"),
 
    SAVE(OpType.SAVE, "保存"),
 
    SEND_EMAIL(OpType.SEND_EMAIL, "发送邮件"),
 
    SMS(OpType.SMS, "发送短信"),
 
    QUERY_NO_PAGE(OpType.QUERY_NO_PAGE, "查询-不分页", true),
 
    QUERY_DETAIL(OpType.QUERY_DETAIL, "查询明细", true),
 
    AUDIT(OpType.AUDIT, "审核"),
 
    UN_AUDIT(OpType.UN_AUDIT, "反审核"),
 
    DOWNLOAD(OpType.DOWNLOAD, "下载"),
 
    UPLOAD(OpType.UPLOAD, "上传"),
 
    CHECK(OpType.CHECK, "校验"),
 
    INSTALL(OpType.INSTALL, "安装"),
 
    UN_INSTALL(OpType.UN_INSTALL, "卸载"),
 
    START(OpType.START, "启动"),
 
    STOP(OpType.STOP, "停止"),
 
    RESTART(OpType.RESTART, "重启"),
 
    PAUSE(OpType.PAUSE, "暂停"),
 
    RESUME(OpType.RESUME, "恢复"),
 
    REFRESH(OpType.REFRESH, "刷新"),
 
    GENERATE(OpType.GENERATE, "生成"),
 
    STATISTICS(OpType.STATISTICS, "统计"),
 
    SYNC(OpType.SYNC, "同步"),
 
    SCHEDULE(OpType.SCHEDULE, "定时调度"),
 
    last(0, "最后一个");
    final int key;
    final String desc;
    //是否忽略返回结果
    final boolean ignoreResult;
 
    OpTypeEnum(int key, String desc) {
        this.key = key;
        this.desc = desc;
        this.ignoreResult = false;
    }
 
    OpTypeEnum(int key, String desc, boolean ignoreResult) {
        this.key = key;
        this.desc = desc;
        this.ignoreResult = ignoreResult;
    }
 
    /**
     * 根据key获取desc
     */
    public static String getDesc(Integer key) {
        for (OpTypeEnum opTypeEnum : OpTypeEnum.values()) {
            if (opTypeEnum.getKey() == key) {
                return opTypeEnum.getDesc();
            }
        }
        return "";
    }
 
    /**
     * 获取所有忽略返回结果的Key集合
     */
    public static List<Integer> getIgnoreResultKeys() {
        List<Integer> keys = new ArrayList<>();
        for (OpTypeEnum opTypeEnum : OpTypeEnum.values()) {
            if (opTypeEnum.isIgnoreResult()) {
                keys.add(opTypeEnum.getKey());
            }
        }
        return keys;
    }
 
    public int getKey() {
        return key;
    }
 
    public String getDesc() {
        return desc;
    }
 
    public boolean isIgnoreResult() {
        return ignoreResult;
    }
 
}

三、这里我使用的是注解的形式进行的切面

package com.baizhi.annotation;

import com.baizhi.enus.OpType;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER,ElementType.METHOD})
public @interface OptLog {
    /**
     * 业务类型,如新增、删除、修改
     * 默认 其他
     * @return
     */

    int opType() default OpType.OTHER;

    /**
     * 业务对象名称,如订单、库存、价格
     *
     * @return
     */
     String opItem();

    /**
     * 业务对象编号表达式,描述了如何获取订单号的表达式
     *
     * @return
     */
     String opItemIdExpression();
    /**
    *是否使用注解切面默认是使用的注解切面
    **/

    boolean isSaveRequestData() default true;


}

四、编写AOP

package com.baizhi.aop;

import com.baizhi.annotation.OptLog;
import com.baizhi.enus.OpTypeEnum;
import io.micrometer.core.instrument.util.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import com.google.common.base.CaseFormat;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Optional;


@Aspect
@Component
public class LogMethodAspect {

    private static final Logger log = LoggerFactory.getLogger(LogMethodAspect.class);

    /**
     * 指定切面类的位置*/
    @Pointcut("@annotation(com.baizhi.annotation.OptLog)")
    public void myAnnotationAspect(){}
        @Autowired
        HttpServletRequest request;

        @Around("myAnnotationAspect()")
        public Object log(ProceedingJoinPoint pjp) throws Exception {

            Method method = ((MethodSignature)pjp.getSignature()).getMethod();
            OptLog opLog = method.getAnnotation(OptLog.class);

            Object response = null;

            try {
                // 目标方法执行
                response = pjp.proceed();
            } catch (Throwable throwable) {
                throw new Exception(throwable);
            }

            if (StringUtils.isNotEmpty(opLog.opItemIdExpression()) && opLog.isSaveRequestData()) {
                SpelExpressionParser parser = new SpelExpressionParser();
                Expression expression = parser.parseExpression(opLog.opItemIdExpression());

                EvaluationContext context = new StandardEvaluationContext();
                // 获取参数值
                Object[] args = pjp.getArgs();

                // 获取运行时参数的名称
                LocalVariableTableParameterNameDiscoverer discoverer
                        = new LocalVariableTableParameterNameDiscoverer();
                String[] parameterNames = discoverer.getParameterNames(method);

                // 将参数绑定到context中
                if (parameterNames != null) {
                    for (int i = 0; i < parameterNames.length; i++) {
                        context.setVariable(parameterNames[i], args[i]);
                    }
                }

                // 将方法的resp当做变量放到context中,变量名称为该类名转化为小写字母开头的驼峰形式
                if (response != null) {
                    context.setVariable(
                            CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, response.getClass().getSimpleName()),
                            response);
                }

                // 解析表达式,获取结果
                String itemId = String.valueOf(expression.getValue(context));
                //TODO 缺少登录用户id
                // 执行日志记录
                handle(opLog.opType(), opLog.opItem(), itemId);
            }

            return response;
        }

//这里模拟的是数据库的操作插入对应的log表执行插入操作。我这里的插入人还没有如果大写写的话建议在新增一个插入人员的id方便我们后期对log表查询的时候知道插入的人的,人员信息以便我们项目对日志有着很好的管理
        private void handle(Integer opType,  String opItem, String opItemId) {
            //
            String orElse= Optional.ofNullable(OpTypeEnum.getDesc(opType)).orElse("");
            // 通过日志打印输出
            log.info("opType = " + opItem +",opItem = " +orElse + ",opItemId = " +opItemId);
        }
    }

 五、编写controller测试

  /**
     * 编辑数据
     *
     * @param log 实体
     * @return 编辑结果
     */

//opItemIdExpression = "#log.id" 这里接收的值是根据前台传入的值确定的 相当于记录的管理员或者其//他人员操作的唯一id方便我们后期对日志可以进行有效的排查。
//如果为get请求?或者是/ 接收的后台参数直接拿 opItemIdExpression = "#id" 进行接收


    @OptLog(opType = OpType.UPDATE, opItem = "log查询", opItemIdExpression = "#log.id", isSaveRequestData = true)
    @PutMapping
    public ResponseEntity<Log> edit(@RequestBody Log log) {
        return ResponseEntity.ok(this.logService.update(log));
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

把柄

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

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

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

打赏作者

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

抵扣说明:

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

余额充值