-
Dao接口实现
- 业务描述与设计实现
数据层基于业务层的持久化请求,将业务层提交的用户行为日志信息写入到数据库。
- 关键代码设计与实现
在SysLogDao接口中添加用于实现日志信息持久化的方法。关键代码如下:
void saveObject(SysLog entity)
第二步:在SysLogServiceImpl类中添加,保存日志的方法实现。关键代码如下:
@Override
public void saveObject(SysLog entity) {
sysLogDao.insertObject(entity);
}
-
日志切面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;
}