AOP操作日志记录

 大家好,今天给大家带来一篇基于AOP实现的操作日志记录的解决的方案。大家可能会说,切,操作日志记录这么简单的东西,老生常谈了。不!

网上的操作日志一般就是记录操作人,操作的描述,ip等。好一点的增加了修改的数据和执行时间。那么!我这篇有什么不同呢!今天这种不仅可以记录上方所说的一切,还增加多表记录了操作前的数据

思路介绍:

    由于操作日志用注解方式的AOP记录操作日志比较便捷,所以想到了在注解中定义操作前查询数据

详情的bean,查询方法及参数,参数类型,在aop进行方法执行前,对指定的bean,方法,参数进行调用,获得修改前的参数,并进行保存.

此处需要注意:

1.在前面中调用指定bean的方法时,不可用反射进行调用,反射不能加载spring容器,无法获取指定的spring bean,下面方法中封装的获取spring bean的

  工具类也需要配置为bean,而且被spring加载,才可以;

2.@Aspect注解的类一定要配置成bean,而且被spring加载,才可以,即同时配置@Component和@Aspect,或在spring的配置文件进行bean的配置

3.如果配置了bean,要检索component-scan扫描范围是否包括Aspect类;

一.pom文件

        本次是基于idea创建spring boot 项目,不会的同学请自行百度,本次就不在这里赘述。

<dependencies>

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

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

        <dependency>
            <groupId>org.mybatis.generator</groupId>
            <artifactId>mybatis-generator-core</artifactId>
            <version>1.3.6</version>
        </dependency>

        <!--spring依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.46</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.9.2</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.2</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.69</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.4</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.2</version>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-collections4</artifactId>
            <version>4.1</version>
        </dependency>

    </dependencies>

 二.配置文件application.properties

spring.datasource.url=jdbc:mysql://localhost:3306/test?generateSimpleParameterMetadata=true
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
mybatis.type-aliases-package=com.example.recordlog.bean
#mybatis.mapper-locations=classpath:mybatis/mapper/*.xml

#引用mybatis-plus-boot-starter写法如下
mybatis-plus.mapper-locations=classpath:mybatis/mapper/*.xml

server.port=8080
#server.servlet.context-path=/recordLog


三.定义切面执行的注解


import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

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

/**
 * @author liuminglin
 */
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperateLog {

    /**
     * 查询的bean名称
     */
   String serviceClass() default "";

   // Class serviceClass();

    /**
     * 查询单个详情的bean的方法
     */
    String queryMethod() default "";

    /**
     * 查询详情的参数类型
     */
    String parameterType() default "";

    /**
     * 从页面参数中解析出要查询的id,
     * 如域名修改中要从参数中获取Id的值进行查询
     */
    String parameterKey() default "";

    /**
     * 是否为批量类型操作
     */
    boolean paramIsArray() default false;




}

四.切面执行的方法


import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.example.recordlog.tools.SpringContextUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
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.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 操作日志切面记录操作
 */
@Component
@Aspect
@Slf4j
public class OperateLogAspect {


     /**
     * 切入点
     */
    @Pointcut("@annotation(com.example.recordlog.anotation.OperateLog)")
    private void pointcut() {

    }


    @Around("RecordLogAspect.pointcut()")
    public Object around(ProceedingJoinPoint pjp) {

        //获取request对象
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        // 拦截的实体类,
        Object target = pjp.getTarget();
        // 获取签名
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Class[] parameterTypes = signature.getMethod().getParameterTypes();
        // 拦截的方法名称
        String methodName = pjp.getSignature().getName();
        Method method = null;
        // 拦截参数
        Object[] args = pjp.getArgs();
        List BeforeList = null;
        Object objectResult = null;

        try {
            // 获得被拦截的方法
            method = target.getClass().getMethod(methodName, parameterTypes);
            JSONArray operateParamArray = new JSONArray();
            for (int i = 0; i < args.length; i++) {
                Object paramsObj = args[i];
                //通过该方法可查询对应的object属于什么类型:
                String type = paramsObj.getClass().getName();
                operateParamArray.add(paramsObj);
            }

            if (null != method) {
                // 判断是否包含自定义的注解,说明一下这里的SystemLog就是我自己自定义的注解
                if (method.isAnnotationPresent(OperateLog.class)) {
                    OperateLog systemlog = method.getAnnotation(OperateLog.class);
                   
                    BeforeList = QueryTools.getBeforeOperateData(operateParamArray, systemlog);
                    objectResult = pjp.proceed();

                }
            } else {
                //不需要拦截直接执行
                objectResult = pjp.proceed();
            }
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        } finally {
            Map logMap = new HashMap();
            logMap.put("url", request.getRequestURL().toString());
            logMap.put("RequestMethod", request.getMethod());
            logMap.put("preModifiedData", BeforeList);
            logMap.put("modifiedData", JSON.toJSONString(args));
            //保存进数据库logservice.saveLog(log);

            log.info("logMap :{}", JSON.toJSONString(logMap));

        }
        return objectResult;
    }
 
}

 五.获取spring bean的工具类


import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
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;

/**
 * @author liuminglin
 * @date 2021/6/22 10:48
 * @description: TODO
 */

@Component
public class SpringContextUtil implements ApplicationContextAware {

    /**
     * 上下文对象实例
     */

    private static ApplicationContext applicationContext;


    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        SpringContextUtil.applicationContext = applicationContext;
    }

    /**
     * 获取applicationContext
     *
     * @return
     */
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    /**
     * 通过name获取 Bean.
     */
    public static Object getBean(String name) {
        return applicationContext.getBean(name);
    }

    /**
     * 通过class获取Bean.
     */
    public static <T> Object getBean(Class<T> cls) {
        return applicationContext.getBean(cls);
    }

    /**
     * 通过name,以及Clazz返回指定的Bean
     *
     * @param name
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getBean(String name, Class<T> clazz) {
        return getApplicationContext().getBean(name, clazz);
    }

    /**
     * 获取HttpServletRequest
     */
    public static HttpServletRequest getHttpServletRequest() {
        return ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
    }

    public static String getDomain() {
        HttpServletRequest request = getHttpServletRequest();
        StringBuffer url = request.getRequestURL();
        return url.delete(url.length() - request.getRequestURI().length(), url.length()).toString();
    }


}

六.切面工具类


import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

@Slf4j
public class AspectUtil {


    private AspectUtil() {
    }


    /**
     * @Desc 获取HttpServlet 请求
     */
    protected static HttpServletRequest getHttpServletRequest() {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        return attributes.getRequest();
    }

    /**
     * @Desc 获取HttpServlet 响应
     */
    protected HttpServletResponse getHttpServletResponse() {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        return attributes.getResponse();
    }

    /**
     * @Desc 获取目标类的所有参数
     * @Param proceedingJoinPoint
     * @Param paramClass
     * @Return List<T>
     */
    protected static <T> List<T> paramsOfTargetClass(ProceedingJoinPoint proceedingJoinPoint, Class<T> paramClass) {
        List<T> result = new ArrayList<>();
        Object[] args = proceedingJoinPoint.getArgs();
        if (ArrayUtils.isNotEmpty(args)) {
            for (Object object : args) {
                //paramClass.isAssignableFrom 此Class对象所表示的类或接口与指定的Class 参数所表示的类或接口是否相同,或是否是其超类或超接口。如果是则返回 true;否则返回 false
                if (paramClass.isAssignableFrom(object.getClass())) {
                    //result.add((T) object);
                }
                result.add((T) object);
            }
        }
        return result;
    }


    /**
     * @Desc 获取目标类的第一个参数
     * @Param proceedingJoinPoint
     * @Param paramClass  没有目标类可以为null
     * @Return List<T>
     */
    public static <T> T getFirstParamsOfTargetClass(ProceedingJoinPoint proceedingJoinPoint, Class<T> paramClass) {
        List<T> targets = paramsOfTargetClass(proceedingJoinPoint, paramClass);
        return CollectionUtils.isEmpty(targets) ? null : targets.iterator().next();
    }


    /**
     * @Desc 获取方法名称
     */
    protected static String getMethodName(ProceedingJoinPoint proceedingJoinPoint) {
        return proceedingJoinPoint.getSignature().getDeclaringTypeName() + "." + proceedingJoinPoint.getSignature().getName();
    }


    /**
     * @Desc 获取请求信息
     */
    public static HttpServletRequest getRequestInfo() {
        HttpServletRequest request = getHttpServletRequest();
        log.info("URL : {}", request.getRequestURL().toString());
        log.info("HTTP_METHOD : {}", request.getMethod());
        log.info("IP : {}", request.getRemoteAddr());
        return request;
    }


    /**
     * @Desc 记录请求信息
     * @Param proceedingJoinPoint
     */
    public static void logRequestInfo(ProceedingJoinPoint proceedingJoinPoint) {
        getRequestInfo();
        getClassMethodName(proceedingJoinPoint);
    }

    /**
     * @Desc 获取类方法名
     */
    protected static void getClassMethodName(ProceedingJoinPoint proceedingJoinPoint) {
        String classMethod = getMethodName(proceedingJoinPoint);
        log.info("methodName : {}", classMethod);
        log.info("ARGS : {}", proceedingJoinPoint.getArgs());
    }


    /**
     * @Desc 获取注解
     * @Param proceedingJoinPoint
     * @Param annotationClass
     * @Return T
     */
    public static <T extends Annotation> T getAnnotation(ProceedingJoinPoint proceedingJoinPoint, Class<T> annotationClass) {
        Signature signature = proceedingJoinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        // Class[] parameterTypes = signature.getMethod().getParameterTypes();
        //method = pjp.getTarget().getClass().getMethod(methodName, parameterTypes);
        Method method = methodSignature.getMethod();
        // 判断是否包含自定义的注解
        if (null != method && method.isAnnotationPresent(annotationClass)) {
            return method.getAnnotation(annotationClass);
        }

        return null;
    }

    /**
     * @Desc 获取注解
     * @Param joinPoint
     * @Param annotationClass
     * @Return T
     */
    public <T extends Annotation> T getAnnotation(JoinPoint joinPoint, Class<T> annotationClass) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method targetMethod = methodSignature.getMethod();
        return targetMethod.getAnnotation(annotationClass);
    }


    //是源等于还是目标的子类
    protected boolean isSourceEqualOrSubclassOfTarget(Class<?> srcClazz, Class<?> targetClazz) {
        return targetClazz.isAssignableFrom(srcClazz);
    }

    protected <T> boolean isBaseResponse(Class<T> clazz) {
        return isSourceEqualOrSubclassOfTarget(clazz, RestResponse.class);
    }

    /**
     * @Desc 获取方法返回类
     */
    protected Class getMethodReturnClass(ProceedingJoinPoint proceedingJoinPoint) {
        return ((MethodSignature) proceedingJoinPoint.getSignature()).getReturnType();
    }

    /**
     * @Desc 钩子方法
     */
    protected RestResponse doCheck(ProceedingJoinPoint proceedingJoinPoint) {
        return new RestResponse();
    }


}

 七.查询工具类


import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.example.recordlog.anotation.OperateLog;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.ReflectionUtils;
import java.lang.reflect.Method;
import java.util.List;

public class QueryTools {

    private QueryTools() {
    }


    /**
     * @author
     * @Date 2021/9/2 15:26
     * @Desc Class<T> annotationClass 注解类
     * @Param operateParamArray 请求参数
     * @Return
     */
    public static List getBeforeOperateData(JSONArray operateParamArray, OperateLog annotationClass) {

        //请求查询操作前数据的spring bean
        // Class serviceClass = systemlog.serviceClass();

        boolean isArrayResult = annotationClass.paramIsArray();
        String serviceClass = annotationClass.serviceClass();
        //请求查询数据的方法
        String queryMethod = annotationClass.queryMethod();
        String parameterType = annotationClass.parameterType();
        String parameterKey = annotationClass.parameterKey();

        List beforeList = new JSONArray();
        //JSONObject beforeParam =new JSONObject();
        if (StringUtils.isNotBlank(serviceClass)
                && StringUtils.isNotBlank(queryMethod)
                && StringUtils.isNotBlank(parameterType)
                && StringUtils.isNotBlank(parameterKey)) {
            //批量操作

            if (isArrayResult) {
                //从请求的参数中解析出查询key对应的value值
                String value = "";
                for (int i = 0; i < operateParamArray.size(); i++) {
                    JSONObject params = operateParamArray.getJSONObject(i);
                    JSONArray paramArray = (JSONArray) params.get(parameterKey);
                    if (paramArray != null) {
                        for (int j = 0; j < paramArray.size(); j++) {
                            String paramId = paramArray.getString(j);
                            //在此处判断spring bean查询的方法参数类型
                            Object data = getOperateBeforeData(parameterType, serviceClass, queryMethod, paramId);
                            //beforeParam = (JSONObject) JSON.toJSON(data);
                            beforeList.add(data);
                        }
                    }
                }

                //单量操作
            } else {

                //从请求的参数中解析出查询key对应的value值
                String value = "";
                for (int i = 0; i < operateParamArray.size(); i++) {
                    JSONObject params = operateParamArray.getJSONObject(i);
                    value = params.getString(parameterKey);
                    if (StringUtils.isNotBlank(value)) {
                        break;
                    }
                }

                //在此处获取操作前的spring bean的查询方法
                Object data = getOperateBeforeData(parameterType, serviceClass, queryMethod, value);
                beforeList.add(data);

            }

        }
        return beforeList;

    }

    /**
     * @param paramType:参数类型
     * @param serviceClass:bean名称
     * @param queryMethod:查询method
     * @param value:查询id的value
     */
    private static Object getOperateBeforeData(String paramType, String serviceClass, String queryMethod, String value) {
        Object obj = new Object();
        //在此处解析请求的参数类型,根据id查询数据,id类型有四种:int,Integer,long,Long
        if (paramType.equals("int")) {
            int id = Integer.parseInt(value);
            Method mh = ReflectionUtils.findMethod(SpringContextUtil.getBean(serviceClass).getClass(), queryMethod, Long.class);
            //用spring bean获取操作前的参数,此处需要注意:传入的id类型与bean里面的参数类型需要保持一致
            obj = ReflectionUtils.invokeMethod(mh, SpringContextUtil.getBean(serviceClass), id);

        } else if (paramType.equals("Integer")) {
            Integer id = Integer.valueOf(value);
            Method mh = ReflectionUtils.findMethod(SpringContextUtil.getBean(serviceClass).getClass(), queryMethod, Long.class);
            //用spring bean获取操作前的参数,此处需要注意:传入的id类型与bean里面的参数类型需要保持一致
            obj = ReflectionUtils.invokeMethod(mh, SpringContextUtil.getBean(serviceClass), id);

        } else if (paramType.equals("long")) {
            long id = Long.parseLong(value);
            Method mh = ReflectionUtils.findMethod(SpringContextUtil.getBean(serviceClass).getClass(), queryMethod, Long.class);
            //用spring bean获取操作前的参数,此处需要注意:传入的id类型与bean里面的参数类型需要保持一致
            obj = ReflectionUtils.invokeMethod(mh, SpringContextUtil.getBean(serviceClass), id);

        } else if (paramType.equals("Long")) {
            Long id = Long.valueOf(value);
            Method mh = ReflectionUtils.findMethod(SpringContextUtil.getBean(serviceClass).getClass(), queryMethod, Long.class);
            //用spring bean获取操作前的参数,此处需要注意:传入的id类型与bean里面的参数类型需要保持一致
            obj = ReflectionUtils.invokeMethod(mh, SpringContextUtil.getBean(serviceClass), id);
        }
        return obj;

    }
}

 八.调用Service

注意事项:一定要指定beanName,否则SpringContextUtil.getBean获取不到

//指定beanName
@Service(value = "userInfoService")

import com.example.recordlog.bean.UserInfo;
import com.example.recordlog.mapper.UserInfoMapper;
import com.example.recordlog.service.UserInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service(value = "userInfoService")
public class UserInfoServiceImpl implements UserInfoService {


    @Autowired
    private UserInfoMapper userInfoMapper;



    @Override
    public UserInfo selectByPrimaryKey(Long id) {
        return userInfoMapper.selectByPrimaryKey(id);
    }

  

    @Override
    public int updateByPrimaryKeySelective(UserInfo record) {
        return userInfoMapper.updateByPrimaryKeySelective(record);
    }

 九.调用controller


import com.example.recordlog.aspect.OperateLog;
import com.example.recordlog.bean.UserInfo;
import com.example.recordlog.mapper.GoodsMapper;
import com.example.recordlog.service.UserInfoService;
import com.example.recordlog.tools.RestResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/api")
public class OperateController {

    @Autowired
    private UserInfoService userInfoService;

    @Autowired
    private GoodsMapper goodsMapper;

  
    @RequestMapping(value = "/updateInfo", produces = "application/json;charset=utf-8", method = {RequestMethod.POST})
    @OperateLog(serviceClass = "userInfoService", queryMethod = "selectByPrimaryKey", parameterType = "Long", parameterKey = "id")
    public RestResponse updateInfo(@RequestBody UserInfo info) {
        int count = userInfoService.updateByPrimaryKeySelective(info);
        RestResponse restResponse = RestResponse.success(count);

        return restResponse;
    }

}

 十.数据如图

logMap :{"RequestMethod":"POST","preModifiedData":[{"address":"上海市","creatTime":1625811791000,"email":"1345555@163.com","hometown":"上海","id":"123221","modifyDate":1625811791000,"phone":"13611303411","userId":"zhangsn","userName":"杨过绝情谷"}],

"modifiedData":"[{\"id\":\"123221\",\"userName\":\"天下第一\"}]","url":"http://localhost:8080/api/updateInfo"}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值