v5tech / notes

notes
https://ameizi.gitee.io/notes
MIT License
1.52k stars 378 forks source link

使用Spring AOP记录Controller层操作日志 #152

Open v5tech opened 7 years ago

v5tech commented 7 years ago

数据模型

import java.io.Serializable;
import java.util.Date;

/**
 * author : fengjing
 * createTime : 2016-07-26
 * description : 系统日志
 * version : 1.0
 */
public class Log implements Serializable{

    /**
     * 主键
     */
    private String id;

    /**
     * 应用名称
     */
    private String appName;
    /**
     * 日志类型 0操作日志,1异常日志
     */
    private Integer logType;
    /**
     * 操作人
     */
    private String user;
    /**
     * 方法名称
     */
    private String methodName;
    /**
     * 请求参数
     */
    private String requestParams;
    /**
     * 方法描述
     */
    private String methodDescription;
    /**
     * 访问者ip
     */
    private String requestIp;
    /**
     * 请求uri
     */
    private String requestUri;
    /**
     * 请求path
     */
    private String requestPath;
    /**
     * 异常编码
     */
    private String exceptionCode;
    /**
     * 异常详情
     */
    private String exceptionDetail;
    /**
     * 创建时间
     */
    private Date createTime;
    /**
     * 请求响应状态
     */
    private String status;
    /**
     * 请求响应内容
     */
    private String content;    
}

数据库表结构

CREATE TABLE `LOG` (
  `ID` char(36) NOT NULL COMMENT '主键ID',
  `APP_NAME` char(36) NOT NULL COMMENT '所属应用',
  `LOG_TYPE` int(11) DEFAULT NULL COMMENT '日志类型,0为操作日志,1为异常日志',
  `USER` varchar(100) DEFAULT NULL COMMENT '访问者/请求者',
  `METHOD_NAME` varchar(100) DEFAULT NULL COMMENT '方法名',
  `REQUEST_PARAMS` varchar(500) DEFAULT NULL COMMENT '请求参数',
  `METHOD_DESCRIPTION` varchar(100) DEFAULT NULL COMMENT '方法描述',
  `REQUEST_IP` varchar(50) DEFAULT NULL COMMENT '访问者IP',
  `REQUEST_URI` varchar(200) DEFAULT NULL COMMENT '请求URI',
  `REQUEST_PATH` varchar(200) DEFAULT NULL COMMENT '请求PATH',
  `EXCEPTION_CODE` varchar(100) DEFAULT NULL COMMENT '异常码',
  `EXCEPTION_DETAIL` varchar(100) DEFAULT NULL COMMENT '异常描述',
  `CREATE_TIME` datetime DEFAULT NULL COMMENT '创建时间',
  `STATUS` varchar(200) DEFAULT NULL COMMENT '请求返回状态',
  `CONTENT` longtext COMMENT '请求返回内容',
  PRIMARY KEY (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

定义annotation

import java.lang.annotation.*;

/**
 * author : fengjing
 * createTime : 2016-07-25
 * description : 定义aop annotation切入点凡是方法上标注了@OperationLog的则会自动记录操作日志和异常日志
 * version : 1.0
 */
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperationLog {
    /**
     * 日志操作描述
     * @return
     */
    String value()  default "";
}

定义Aspect

package com.mopon.saas.platform.log;

import com.mopon.saas.client.component.HttpSaasRequest;
import com.mopon.saas.client.component.HttpSaasResponse;
import com.mopon.saas.common.thrift.SoaClient;
import com.mopon.saas.common.util.GsonUtil;
import com.mopon.saas.common.util.Sequence;
import com.mopon.saas.platform.vo.LogVo;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * author : fengjing
 * createTime : 2016-07-25
 * description : 使用aop实现系统操作日志
 * version : 1.0
 */
@Aspect
@Service
public class OperationLogAspect {

    /**
     * 注入soaClient用于把日志保存数据库
     */
    @Resource
    private SoaClient soaClient;

    //本地异常日志记录对象
    private static final Logger logger = LoggerFactory.getLogger(OperationLogAspect.class);

    /**
     * 定义日志切入点
     */
    @Pointcut("@annotation(com.mopon.saas.platform.log.OperationLog)")
    public void serviceAspect(){
    }

    /**
     * 后置通知 用于拦截service层记录用户的操作
     *
     * @param joinPoint 切点
     */
    @After("serviceAspect()")
    public void doAfter(JoinPoint joinPoint) {
        try {
            //*========控制台输出=========*//
            // System.out.println("=====后置通知开始=====");
            // System.out.println("请求方法:" + (joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()"));
            // System.out.println("方法描述:" + getServiceMethodDescription(joinPoint));

            // 获取注入点方法中的参数(HttpSaasRequest request, HttpSaasResponse response)
            HttpSaasRequest saasRequest = (HttpSaasRequest)joinPoint.getArgs()[0];
            String uri = saasRequest.getUri();
            String path = saasRequest.getPath();
            // 获取客户端IP
            String ip = saasRequest.getRemoteAddr();

            // 从请求中获取参数列表
            Set<String> parameterNames = saasRequest.getParameterNames();
            Map<String,String> requestParams = new HashMap<>();
            for (String parameterName : parameterNames) {
                String value = saasRequest.getParameter(parameterName);
                requestParams.put(parameterName,value);
            }

            // 读取session,获取用户信息
            // String user = (String) session.getAttribute(WebConstants.CURRENT_USER);
            String user = "admin";
            // System.out.println("请求参数:" + GsonUtil.toJson(requestParams));
            // System.out.println("请求uri:" + uri);
            // System.out.println("请求path:" + path);
            // System.out.println("请求IP:" + ip);

            // 获取response
            HttpSaasResponse saasResponse = (HttpSaasResponse)joinPoint.getArgs()[1];
            String status = saasResponse.getStatus().toString();
            String content = saasResponse.getContent();
            // System.out.println("请求返回状态:"+status);
            // System.out.println("请求返回值:"+content);

            //*========数据库日志=========*//
            LogVo log = new LogVo();
            log.setId(String.valueOf(Sequence.sequenceId()));
            log.setAppName("");
            log.setUser(user);
            log.setLogType(0);
            log.setMethodName((joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()"));
            log.setRequestParams(GsonUtil.toJson(requestParams));
            log.setMethodDescription(getServiceMethodDescription(joinPoint));

            log.setRequestIp(ip);
            log.setRequestUri(uri);
            log.setRequestPath(path);

            log.setExceptionCode(null);
            log.setExceptionDetail(null);

            log.setStatus(status);
            log.setContent(content);

            log.setCreateTime(Calendar.getInstance().getTime());
            // 保存数据库
            String result = soaClient.requestPlatform("/logService/save",GsonUtil.toJson(log));
            // System.out.println("=====后置通知结束=====");
        }  catch (Exception e) {
            //记录本地异常日志
            logger.error("==后置通知异常==");
            logger.error("异常信息:{}", e.getMessage());
        }
    }

    /**
     * 异常通知 用于拦截service层记录异常日志
     *
     * @param joinPoint
     * @param e
     */
    @AfterThrowing(pointcut = "serviceAspect()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
        Map<String,String> requestParams = new HashMap<>();
        try {
            /*========控制台输出=========*/

            // System.out.println("=====异常通知开始=====");
            // System.out.println("异常代码:" + e.getClass().getName());
            // System.out.println("异常信息:" + e.getMessage());
            // System.out.println("异常方法:" + (joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()"));
            // System.out.println("方法描述:" + getServiceMethodDescription(joinPoint));

            // 获取注入点方法中的参数(HttpSaasRequest request, HttpSaasResponse response)
            HttpSaasRequest saasRequest = (HttpSaasRequest)joinPoint.getArgs()[0];
            String uri = saasRequest.getUri();
            String path = saasRequest.getPath();
            // 获取客户端IP
            String ip = saasRequest.getRemoteAddr();

            // 从请求中获取参数列表
            Set<String> parameterNames = saasRequest.getParameterNames();
            for (String parameterName : parameterNames) {
                String value = saasRequest.getParameter(parameterName);
                requestParams.put(parameterName,value);
            }
            // System.out.println("请求参数:" + GsonUtil.toJson(requestParams));

            // 读取session,获取用户信息
            // String user = (String) session.getAttribute(WebConstants.CURRENT_USER);
            String user = "admin";
            // System.out.println("请求参数:" + GsonUtil.toJson(requestParams));
            // System.out.println("请求uri:" + uri);
            // System.out.println("请求path:" + path);
            // System.out.println("请求IP:" + ip);

            // 获取response
            HttpSaasResponse saasResponse = (HttpSaasResponse)joinPoint.getArgs()[1];
            String status = saasResponse.getStatus().toString();
            String content = saasResponse.getContent();
            // System.out.println("请求返回状态:"+status);
            // System.out.println("请求返回值:"+content);

            /*==========数据库日志=========*/
            LogVo log = new LogVo();
            log.setId(String.valueOf(Sequence.sequenceId()));
            log.setAppName("");
            log.setUser(user);
            log.setLogType(1);
            log.setMethodName((joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()"));
            log.setRequestParams(GsonUtil.toJson(requestParams));
            log.setMethodDescription(getServiceMethodDescription(joinPoint));

            log.setRequestIp(ip);
            log.setRequestUri(uri);
            log.setRequestPath(path);

            log.setExceptionCode(e.getClass().getName());
            log.setExceptionDetail(e.getMessage());

            log.setStatus(status);
            log.setContent(content);

            log.setCreateTime(Calendar.getInstance().getTime());
            //保存数据库
            String result = soaClient.requestPlatform("/logService/save",GsonUtil.toJson(log));
            // System.out.println("=====异常通知结束=====");
        }  catch (Exception ex) {
            //记录本地异常日志
            logger.error("==异常通知异常==");
            logger.error("异常信息:{}", ex.getMessage());
        }
         /*==========记录本地异常日志==========*/
        logger.error("异常方法:{}异常代码:{}异常信息:{}参数:{}", joinPoint.getTarget().getClass().getName() + joinPoint.getSignature().getName(), e.getClass().getName(), e.getMessage(), GsonUtil.toJson(requestParams));

    }

    /**
     * 获取注解中对方法的描述信息 用于service层注解
     *
     * @param joinPoint 切点
     * @return 方法描述
     * @throws Exception
     */
    private 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(OperationLog.class).value();
                    break;
                }
            }
        }
        return description;
    }

}

spring配置(xml方式配置,不是必须,因为上面使用了注解。这里仅仅只是给出xml中的配置)

<bean id="logAspect" class="com.mopon.saas.platform.log.OperationLogAspect">
    <property name="soaClient" ref="soaClient"/>
</bean>
<aop:config>
    <aop:aspect id="aspect" ref="logAspect">
        <aop:pointcut id="logService" expression="@annotation(com.mopon.saas.platform.log.OperationLog)" />
        <aop:after pointcut-ref="logService" method="doAfter"/>
        <aop:after-throwing pointcut-ref="logService" method="doAfterThrowing" throwing="e"/>
    </aop:aspect>
</aop:config>
v5tech commented 7 years ago

参考 http://tiangai.iteye.com/blog/2103708

第一步定义两个注解:

package com.annotation;  
import java.lang.annotation.*;  

/** 
 *自定义注解 拦截Controller 
 */  
@Target({ElementType.PARAMETER, ElementType.METHOD})  
@Retention(RetentionPolicy.RUNTIME)  
@Documented  
public @interface SystemControllerLog {  
    String description() default "";  
}  

package com.annotation;  
import java.lang.annotation.*;  

/** 
 *自定义注解 拦截service 
 */    
@Target({ElementType.PARAMETER, ElementType.METHOD})  
@Retention(RetentionPolicy.RUNTIME)  
@Documented  
public @interface SystemServiceLog {  
    String description() default "";  
}

第二步创建一个切点类:

package com.annotation;  

import com.model.Log;  
import com.model.User;  
import com.service.LogService;  
import com.util.DateUtil;  
import com.util.JSONUtil;  
import com.util.SpringContextHolder;  
import com.util.WebConstants;  
import org.aspectj.lang.JoinPoint;  
import org.aspectj.lang.annotation.*;  
import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;  
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.servlet.http.HttpServletRequest;  
import javax.servlet.http.HttpSession;  
import java.lang.reflect.Method;  

/** 
 * 切点类 
 * @author tiangai 
 * @since 2014-08-05 Pm 20:35 
 * @version 1.0 
 */  
@Aspect  
@Component  
public class SystemLogAspect {  
    //注入Service用于把日志保存数据库  
    @Resource  
    private LogService logService;  
    //本地异常日志记录对象  
    private static final Logger logger = LoggerFactory.getLogger(SystemLogAspect.class);  

    //Service层切点  
    @Pointcut("@annotation(com.annotation.SystemServiceLog)")  
    public void serviceAspect() {  
    }  

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

    /** 
     * 前置通知 用于拦截Controller层记录用户的操作 
     * 
     * @param joinPoint 切点 
     */  
    @Before("controllerAspect()")  
    public void doBefore(JoinPoint joinPoint) {  

        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();  
        HttpSession session = request.getSession();  
        //读取session中的用户  
        User user = (User) session.getAttribute(WebConstants.CURRENT_USER);  
        //请求的IP  
        String ip = request.getRemoteAddr();  
        try {  
            //*========控制台输出=========*//  
            System.out.println("=====前置通知开始=====");  
            System.out.println("请求方法:" + (joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()"));  
            System.out.println("方法描述:" + getControllerMethodDescription(joinPoint));  
            System.out.println("请求人:" + user.getName());  
            System.out.println("请求IP:" + ip);  
            //*========数据库日志=========*//  
            Log log = SpringContextHolder.getBean("logxx");  
            log.setDescription(getControllerMethodDescription(joinPoint));  
            log.setMethod((joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()"));  
            log.setType("0");  
            log.setRequestIp(ip);  
            log.setExceptionCode(null);  
            log.setExceptionDetail(null);  
            log.setParams(null);  
            log.setCreateBy(user);  
            log.setCreateDate(DateUtil.getCurrentDate());  
            //保存数据库  
            logService.add(log);  
            System.out.println("=====前置通知结束=====");  
        } catch (Exception e) {  
            //记录本地异常日志  
            logger.error("==前置通知异常==");  
            logger.error("异常信息:{}", e.getMessage());  
        }  
    }  

    /** 
     * 异常通知 用于拦截service层记录异常日志 
     * 
     * @param joinPoint 
     * @param e 
     */  
    @AfterThrowing(pointcut = "serviceAspect()", throwing = "e")  
    public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {  
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();  
        HttpSession session = request.getSession();  
        //读取session中的用户  
        User user = (User) session.getAttribute(WebConstants.CURRENT_USER);  
        //获取请求ip  
        String ip = request.getRemoteAddr();  
        //获取用户请求方法的参数并序列化为JSON格式字符串  
        String params = "";  
        if (joinPoint.getArgs() != null && joinPoint.getArgs().length > 0) {  
            for (int i = 0; i < joinPoint.getArgs().length; i++) {  
                params += JSONUtil.toJsonString(joinPoint.getArgs()[i]) + ";";  
            }  
        }  
        try {  
              /*========控制台输出=========*/  
            System.out.println("=====异常通知开始=====");  
            System.out.println("异常代码:" + e.getClass().getName());  
            System.out.println("异常信息:" + e.getMessage());  
            System.out.println("异常方法:" + (joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()"));  
            System.out.println("方法描述:" + getServiceMthodDescription(joinPoint));  
            System.out.println("请求人:" + user.getName());  
            System.out.println("请求IP:" + ip);  
            System.out.println("请求参数:" + params);  
               /*==========数据库日志=========*/  
            Log log = SpringContextHolder.getBean("logxx");  
            log.setDescription(getServiceMthodDescription(joinPoint));  
            log.setExceptionCode(e.getClass().getName());  
            log.setType("1");  
            log.setExceptionDetail(e.getMessage());  
            log.setMethod((joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()"));  
            log.setParams(params);  
            log.setCreateBy(user);  
            log.setCreateDate(DateUtil.getCurrentDate());  
            log.setRequestIp(ip);  
            //保存数据库  
            logService.add(log);  
            System.out.println("=====异常通知结束=====");  
        } catch (Exception ex) {  
            //记录本地异常日志  
            logger.error("==异常通知异常==");  
            logger.error("异常信息:{}", ex.getMessage());  
        }  
         /*==========记录本地异常日志==========*/  
        logger.error("异常方法:{}异常代码:{}异常信息:{}参数:{}", joinPoint.getTarget().getClass().getName() + joinPoint.getSignature().getName(), e.getClass().getName(), e.getMessage(), params);  

    }  

    /** 
     * 获取注解中对方法的描述信息 用于service层注解 
     * 
     * @param joinPoint 切点 
     * @return 方法描述 
     * @throws Exception 
     */  
    public static String getServiceMthodDescription(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;  
    }  

    /** 
     * 获取注解中对方法的描述信息 用于Controller层注解 
     * 
     * @param joinPoint 切点 
     * @return 方法描述 
     * @throws Exception 
     */  
    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;  
    }  
} 

第三步把Controller的代理权交给cglib

在实例化ApplicationContext的时候需要加上

<!-- 启动对@AspectJ注解的支持 -->  
<aop:aspectj-autoproxy/>

在调用Controller的时候AOP发挥作用所以在SpringMVC的配置文件里加上

<!-- 通知spring使用cglib而不是jdk的来生成代理方法 AOP可以拦截到Controller -->  
<aop:aspectj-autoproxy proxy-target-class="true"/> 

Controller层的使用

/**
 * 删除用户 
 * 
 * @param criteria 条件 
 * @param id       id 
 * @param model    模型 
 * @return 数据列表 
 */
@RequestMapping(value = "/delete")  
//此处为记录AOP拦截Controller记录用户操作  
@SystemControllerLog(description = "删除用户")  
public String del(Criteria criteria, String id, Model model, HttpSession session) {  
   try {  
       User user = (User) session.getAttribute(WebConstants.CURRENT_USER);  
       if (null != user) {  
           if (user.getId().equals(id)) {  
               msg = "您不可以删除自己!";  
               criteria = userService.selectByCriteriaPagination(criteria);  
           } else {  
               //删除数据并查询出数据  
               criteria = userService.delete(id, criteria);  
               msg = "删除成功!";  
           }  
       }  
   } catch (Exception e) {  
       msg = "删除失败!";  
   } finally {  
       model.addAttribute("msg", msg);  
       model.addAttribute("criteria", criteria);  
   }  
   //跳转列表页  
   return "user/list";  
}  

Service层的使用

/**
 * 按照分页查询 
 * @param criteria 
 * @return 
 */
//此处为AOP拦截Service记录异常信息。方法不需要加try-catch  
@SystemServiceLog(description = "查询用户")  
public Criteria<User> selectByCriteriaPagination(Criteria<User> criteria)  
{  
   criteria.getList().get(0).getAccount();  
   //查询总数  
   long total=userMapper.countByCriteria(criteria);  
   //设置总数  
   criteria.setRowCount(total);  
   criteria.setList(userMapper.selectByCriteriaPagination(criteria));  
   return  criteria;  
}  
qianminglang commented 7 years ago

大哥,这样有源代码吗?

qianminglang commented 7 years ago

import com.mopon.saas.client.component.HttpSaasRequest; import com.mopon.saas.client.component.HttpSaasResponse; import com.mopon.saas.common.thrift.SoaClient; import com.mopon.saas.common.util.GsonUtil; import com.mopon.saas.common.util.Sequence; import com.mopon.saas.platform.vo.LogVo; 这些jar包网上都找不到欸

changchangjie commented 5 years ago

import com.mopon.saas.client.component.HttpSaasRequest; import com.mopon.saas.client.component.HttpSaasResponse; import com.mopon.saas.common.thrift.SoaClient; import com.mopon.saas.common.util.GsonUtil; import com.mopon.saas.common.util.Sequence; import com.mopon.saas.platform.vo.LogVo; 这些jar包网上都找不到欸

这是人家的业务代码

leokongwq commented 5 years ago

这种方法打印的日志,类名和方法名称都是切面类的。不好。