13-运用切面实现用户行为日志的添加

  1. Dao接口实现

  • 业务描述与设计实现

数据层基于业务层的持久化请求,将业务层提交的用户行为日志信息写入到数据库。

  • 关键代码设计与实现

在SysLogDao接口中添加用于实现日志信息持久化的方法。关键代码如下:

void saveObject(SysLog entity)

第二步:在SysLogServiceImpl类中添加,保存日志的方法实现。关键代码如下:

         @Override
        public void saveObject(SysLog entity) {
          sysLogDao.insertObject(entity);
}
  1. 日志切面Aspect实现

  • 业务描述与设计实现

在日志切面中,抓取用户行为信息,并将其封装到日志对象然后传递到业务,通过业务层对象对日志日志信息做进一步处理。此部分内容后续结合AOP进行实现(暂时先了解,不做具体实现)。

  • 关键代码设计与实现

定义日志切面类对象,通过环绕通知处理日志记录操作。关键代码如下:

package com.cy.pj.common.aspect;

import com.cy.pj.common.annotation.RequiredLog;
import com.cy.pj.common.util.IPUtils;
import com.cy.pj.sys.pojo.SysLog;
import com.cy.pj.sys.pojo.SysUser;
import com.cy.pj.sys.service.SysLogService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.shiro.SecurityUtils;
import org.aspectj.lang.ProceedingJoinPoint;
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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Date;

@Aspect
@Component
public class SysLogAspect {
       @Autowired
       private SysLogService sysLogService;
       @Pointcut("@annotation(com.cy.pj.common.annotation.RequiredLog)")
       public void doLog(){}

       @Around("doLog()")
       public Object doAround(ProceedingJoinPoint joinPoint)throws Throwable{
              long t1=System.currentTimeMillis();
              Object result=joinPoint.proceed();
              long t2=System.currentTimeMillis();
              doSaveLog(joinPoint,(t2-t1));
              return result;//目标方法的执行结果
       }
       /**记录用户正常行为日志:
        * username  (登录用户)
        * ip (通过工具类获取)
        * operation (一般是通过注解指定或定义)
        * method (目标类型的类全名+方法名)
        * params(执行目标方法时传入的参数)
        * time (执行目标方法时的耗时时长)
        * createdtime (日志的记录时间)
        * */
       private void doSaveLog(ProceedingJoinPoint jointPoint,long time) throws NoSuchMethodException, JsonProcessingException {
          //想要获取目标对象的操作内容,得先获取目标对象的的类型,基于类型获取目标方法,
           //1.获取用户行为日志,IPUtils是一个获取ip地址工具类,需要外界导入
          String ip= IPUtils.getIpAddr();
          //2.获取目标对象类型(为什么要获取此类型呢?要基于此类型获取目标方法)
          Class<?> targetCls=jointPoint.getTarget().getClass();
          System.out.println("targetCls="+targetCls.getName());
          //3获取目标方法,获取目标方法会携带方法名和参数类型,所以我们要先获取一个方法签名
           //3.1获取方法签名(存储了方法信息的一个对象),通过此签名获取目标方法的名字name以及参数类型ParameterTypes
          MethodSignature ms=(MethodSignature) jointPoint.getSignature();
          //3.2DeclaredMethod获取私有的方法
          Method targetMethod= targetCls.getDeclaredMethod(ms.getName(),ms.getParameterTypes());
          //4.获取目标方法上的requiredLog注解
          RequiredLog requiredLog=targetMethod.getAnnotation(RequiredLog.class);

          //5.获取注解中operation属性的值
          String operation=requiredLog.operation();;//操作名
           //6.目标方法(目标类型的类全名+方法名)
          String method=targetCls.getName()+"."+targetMethod.getName();
           //7.(执行目标方法时传入的实际参数值),拿出来的参数是一个数组,需要转换成字符串,
           // 如果传入的是pojo,会自动转成json字符串格式
          String params= new ObjectMapper().writeValueAsString(jointPoint.getArgs());

          //8.封装用户行为日志
          SysLog log=new SysLog();
          SysUser user= (SysUser)SecurityUtils.getSubject().getPrincipal();
          log.setUsername(user.getUsername());
          log.setIp(ip);
          log.setOperation(operation);
          log.setMethod(method);
          log.setParams(params);
          log.setTime(time);
          log.setCreatedTime(new Date());
          //3.保存用户行为日志
          sysLogService.saveObject(log);
       }
}

方法中用到的ip地址获取需要提供一个如下的工具类:(不用自己实现,直接用)

package com.cy.pj.common.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

public class IPUtils {
	private static Logger logger = LoggerFactory.getLogger(IPUtils.class);
	public static String getIpAddr() {
		HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
			String ip = null;
			try {
				ip = request.getHeader("x-forwarded-for");
				if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
					ip = request.getHeader("Proxy-Client-IP");
				}
			if (StringUtils.isEmpty(ip) || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
					ip = request.getHeader("WL-Proxy-Client-IP");
				}
				if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
					ip = request.getHeader("HTTP_CLIENT_IP");
				}
				if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
					ip = request.getHeader("HTTP_X_FORWARDED_FOR");
				}
				if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
					ip = request.getRemoteAddr();
				}
			} catch (Exception e) {
				logger.error("IPUtils ERROR ", e);
			}
			return ip;
		}

}

定义注解接口的实现,通过此注解作为切面的切入点

package com.cy.pj.common.annotation;

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.METHOD)
public @interface RequiredLog {
      String operation() default "";
}

例如在实现类中加注解实现:

@RequiredLog(operation = "禁用启用")//此注解描述的方法为日志切入点方法
    @RequiresPermissions("sys:user:update")//注解中的字符串为一个权限标识
    @Override
    public int validById(Integer id, Integer valid) {
        //1.参数校验
        //2.修改用户状态
        int rows=sysUserDao.validById(id,valid,"admin");//这里的admin暂时理解为登录用户
        if(rows==0)
            throw new ServiceException("记录有可能已经不存在");
        return rows;
    }

猜你喜欢

转载自blog.csdn.net/suchaoh/article/details/109892907