Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
v5tech opened this issue Jul 26, 2016 · 5 comments
Open

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

v5tech opened this issue Jul 26, 2016 · 5 comments

Comments

@v5tech
Copy link
Owner

v5tech commented Jul 26, 2016

数据模型

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
Copy link
Owner Author

v5tech commented Jul 26, 2016

参考 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
Copy link

大哥,这样有源代码吗?

@qianminglang
Copy link

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
Copy link

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
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants