Springboot通过AOP实现用户操作行为记录

wylc123 1年前 ⋅ 1090 阅读

1. 使用背景

在Spring框架中,使用AOP配合自定义注解可以方便的实现用户操作的监控。

2. 引入依赖

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

3. 自定义注解

标注需要监控的不同层级的方法,比如我们可以定义一个SystemControllerLog用于标注Controller级别的方法,定义一个SystemServiceLog用于标注Service层级的方法。

SystemControllerLog.java

package com.cnki.aop;

import java.lang.annotation.*;

/**
 * Title: SystemControllerLog
 * @date 2020年8月24日
 * @version V1.0
 * Description:  自定义注解,拦截controller
 */

@Target({ElementType.PARAMETER, ElementType.METHOD})//作用在参数和方法上
@Retention(RetentionPolicy.RUNTIME)//运行时注解
@Documented//表明这个注解应该被 javadoc工具记录
public @interface SystemControllerLog {
    String description() default "";
}

SystemServiceLog.java

package com.cnki.aop;

import java.lang.annotation.*;


/**
 * Title: SystemServiceLogs
 * @date 2020年8月24日
 * @version V1.0
 * Description:  自定义注解,拦截service
 */
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented//表明这个注解应该被 javadoc工具记录
public @interface SystemServiceLog {
    String description() default "";
}

两者本质上是一样的,只是名称不一样,在不同层级标注时用不同的名称。

4. 定义一个LogAspect类

使用@Aspect标注让其成为一个切面,使用前置通知@Before("controllerAspect()")用于拦截Controller层记录用户的操作,切点为使用@SystemControllerLog注解标注的方法;使用异常通知@AfterThrowing(pointcut = "serviceAspect()",throwing = "e")用于拦截service层记录异常日志,切点为使用@SystemServiceLogs注解标注的方法。

package com.cnki.aop;

import cnki.bdms.common.session.SessionHelper;
import com.cnki.model.UserActionLog;
import com.cnki.model.enums.LogType;
import com.alibaba.fastjson.JSON;
import com.cnki.service.IUserActionLogService;
import com.cnki.tool.base.JsonUtils;
import com.cnki.tool.base.StringUtil;
import com.cnki.tool.base.UserActionLogUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.json.JsonObject;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * Title: SystemControllerLog
 * @date 2018年8月31日
 * @version V1.0
 * Description: 切点类
 */
@Aspect
@Component
@SuppressWarnings("all")
public class SystemLogAspect {
    //注入Service用于把日志保存数据库,实际项目入库采用队列做异步
    @Resource
    private IUserActionLogService userActionLogService;
    //本地异常日志记录对象
    private static final Logger logger = LoggerFactory.getLogger(SystemLogAspect.class);
    //Service层切点
    @Pointcut("@annotation(SystemServiceLog)")
    public void serviceAspect(){
    }

    //Controller层切点
    @Pointcut("@annotation(SystemControllerLog)")
    public void controllerAspect(){
    }

    /**
     * @Description  前置通知  用于拦截Controller层记录用户的操作
     * @date 2018年9月3日 10:38
     */

    @Before("controllerAspect()")
    public void doBefore(JoinPoint joinPoint){
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        HttpSession session = request.getSession();
        Map<String, String[]> parameterMap = new HashMap(request.getParameterMap());
        //读取session中的用户
        String userCode = SessionHelper.getLoginUserCode();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        //获取用户请求方法的参数并序列化为JSON格式字符串
//        String params = getParams(joinPoint,method);
        String params = "";
        try {
            String methodDes = getControllerMethodDescription(joinPoint);
            if(methodDes.indexOf("上传") == -1){
                Object contentObj = parameterMap.get("content");
                String content = "";
                if(contentObj!=null){
                    String[] contentArr = (String[])contentObj;
                    if(contentArr.length==1){
                        content = contentArr[0];
                        if(content.length() > 100){
                            content = content.substring(0,100) + "......";
                            String[] contentArr2 = {content};
                            parameterMap.put("content",contentArr2);
                        }
                    }
                }
                Object contentStrObj = parameterMap.get("contentStr");
                String contentStr = "";
                if(contentStrObj!=null){
                    String[] contentStrArr = (String[])contentStrObj;
                    if(contentStrArr.length==1){
                        contentStr = contentStrArr[0];
                        if(contentStr.length() > 100){
                            contentStr = contentStr.substring(0,100) + "......";
                            String[] contentStrArr2 = {contentStr};
                            parameterMap.put("contentStr",contentStrArr2);
                        }
                    }
                }
                params = JSON.toJSONString(parameterMap);
            }else{
                params = "上传的文件";
            }
            //*========控制台输出=========*//
            logger.debug("==============前置通知开始==============");
            logger.debug("请求方法" + (joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName()));
            logger.debug("方法描述:" + methodDes);
            logger.debug("请求人:"+userCode);
            logger.debug("请求ip:"+ UserActionLogUtil.getIP());

            //*========数据库日志=========*//
            UserActionLog action = new UserActionLog();
            action.setLogInfo(methodDes);
            action.setMethodDescription(methodDes);
            action.setParams(params);
            action.setMethod(joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()");
            action.setLogType(LogType.ControllerLog.getValue());
            action.setIp(UserActionLogUtil.getIP());
            action.setUserCode(userCode);
            action.setCreateTime(new Date());
            //保存数据库
            userActionLogService.addUserActionLog(action);
        }catch (Exception e){
            //记录本地异常日志
            logger.error("==前置通知异常==");
            logger.error("异常信息:{}",e.getMessage());
        }
    }

    /**
     * @Description  异常通知 用于拦截service层记录异常日志
     * @date 2018年9月3日 下午5:43
     */
    @AfterThrowing(pointcut = "serviceAspect()",throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint,Throwable e){
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        HttpSession session = request.getSession();
        //读取session中的用户
        String userCode = SessionHelper.getLoginUserCode();
        //获取请求ip
        String ip = UserActionLogUtil.getIP();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        //获取用户请求方法的参数并序列化为JSON格式字符串
//        String params = getParams(joinPoint,method);
        String params = "";
        if (joinPoint.getArgs()!=null&&joinPoint.getArgs().length>0){
            for (int i = 0; i < joinPoint.getArgs().length; i++) {
                params+= JsonUtils.objectToJson(joinPoint.getArgs()[i])+";";
            }
        }
        try{
            /*========控制台输出=========*/
            logger.debug("=====异常通知开始=====");
            logger.debug("异常代码:" + e.getClass().getName());
            logger.debug("异常信息:" + e.getMessage());
            logger.debug("异常方法:" + (joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()"));
            logger.debug("方法描述:" + getServiceMethodDescription(joinPoint));
            logger.debug("请求人:" + userCode);
            logger.debug("请求IP:" + ip);
            logger.debug("请求参数:" + params);
            /*==========数据库日志=========*/
            UserActionLog action = new UserActionLog();
            action.setParams(params);
            action.setMethod(joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()");
            action.setMethodDescription(getServiceMethodDescription(joinPoint));
            action.setLogInfo(e.getMessage());
            action.setLogType(LogType.ServiceLog.getValue());
            action.setUserCode(userCode);
            action.setIp(ip);
            action.setCreateTime(new Date());
            //保存到数据库
            userActionLogService.addUserActionLog(action);
        }catch (Exception ex){
            //记录本地异常日志
            logger.error("==异常通知异常==");
            logger.error("异常信息:{}", ex.getMessage());
        }
    }


    // 获取请求的参数名称
    private String getParams(JoinPoint joinPoint, Method method) {
        // 请求的方法参数值
        Object[] args = joinPoint.getArgs();
        // 请求的方法参数名称
        LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
        String[] paramNames = u.getParameterNames(method);
        String params = null;
        if (args != null && paramNames != null) {
            for (int i = 0; i < args.length; i++) {
                params += "  " + paramNames[i] + ": " + args[i];
            }
        }
        return params;
    }

    /**
     * @Description  获取注解中对方法的描述信息 用于service层注解
     * @date 2018年9月3日 下午5:05
     */
    public static String getServiceMethodDescription(JoinPoint joinPoint)throws Exception{
        String targetName = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        Object[] arguments = joinPoint.getArgs();
        Class targetClass = Class.forName(targetName);
        Method[] methods = targetClass.getMethods();
        String description = "";
        for (Method method:methods) {
            if (method.getName().equals(methodName)){
                Class[] clazzs = method.getParameterTypes();
                if (clazzs.length==arguments.length){
                    description = method.getAnnotation(SystemServiceLog.class).description();
                    break;
                }
            }
        }
        return description;
    }



    /**
     * @Description  获取注解中对方法的描述信息 用于Controller层注解
     * @date 2018年9月3日 上午12:01
     */
    public static String getControllerMethodDescription(JoinPoint joinPoint) throws Exception {
        String targetName = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();//目标方法名
        Object[] arguments = joinPoint.getArgs();
        Class targetClass = Class.forName(targetName);
        Method[] methods = targetClass.getMethods();
        String description = "";
        for (Method method:methods) {
            if (method.getName().equals(methodName)){
                Class[] clazzs = method.getParameterTypes();
                if (clazzs.length==arguments.length){
                    description = method.getAnnotation(SystemControllerLog.class).description();
                    break;
                }
            }
        }
        return description;
    }
}

在该类中实现将方法的参数,描述等存入mysql。

5. 切点注解标注示例

1)切点为使用@SystemControllerLog注解标注的方法

/**
     * 词频统计接口
     * @param request
     * @return
     * @throws UnsupportedEncodingException
     */
    @SystemControllerLog(description = "切分词-调用词频统计接口,保存统计结果并显示")
    @ApiOperation(value="词频统计接口,保存统计结果并显示", notes="词频统计接口")
    @ResponseBody
    @PostMapping(value = "/wordFrequency")
    public BaseResponse<Object> wordFrequency(HttpServletRequest request,String corpusId, String intervalStr, String contentStr){//,String contentStr
        BaseResponse<Object> result = new BaseResponse<>();
        String loginUser = SessionHelper.getLoginUserCode();
        try {
            if(StringUtil.isBlank(contentStr)){
                result.setCode(205);
                result.setMsg("请不要导入空文件!");
            }else{
                int id = 0;
                if(StringUtil.isNotBlank(corpusId)){
                    id = Integer.valueOf(corpusId);
                }
                int interval = 10;
                if(StringUtil.isNotBlank(intervalStr)){
                    interval = Integer.valueOf(intervalStr);
                }
                String segmentresult = WordsStatistics.wordFrequency(contentStr,"/", "");
                WordSegmentCorpus corpus = wordSegmentCorpusService.findById(id);
                corpus.setSegmentresult(segmentresult);
                corpus.setModifyuser(loginUser);
                corpus.setState(5);
                int flag = wordSegmentCorpusService.update(corpus);
                if(flag>0){
                    JSONArray jsonArray =  JSONObject.parseArray(segmentresult);
                    JSONObject resultJson = getFormatJosn(interval,jsonArray);
                    String resultJsonStr = JSONObject.toJSONString(resultJson,SerializerFeature.DisableCircularReferenceDetect);
                    result.setData(resultJsonStr);
                    result.setCode(200);
                    result.setMsg("词频统计数据获取成功!");
                }else{
                    result.setCode(201);
                    result.setMsg("添加词频统计结果到数据库失败!");
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            result.setCode(500);
            result.setMsg("服务器错误!");
        }
        return result;
    }

2)切点为使用@SystemServiceLogs注解标注的方法

/**
     * 批量添加词条
     * @param list
     * @return
     */
    @SystemServiceLog(description = "词典管理-批量添加词条Service")
    public int batAdd(String tablename,List<KgWordItem> list){
        int result = kgWordItemMapper.batAdd(tablename,list);
        return result;
    }
更多内容请访问:IT源点

相关文章推荐

全部评论: 0

    我有话说: