用户操作日志不过就是记录访问用户的ip,访问了啥,访问时间而已。然后想想每访问一个接口就要记录一次,难道是在每个接口都要new一个日志类,然后设置值,之后就save?要是有几十个接口,就在这几十个接口里都写上日志入库逻辑?而且是日志入库逻辑都是一样的。那想想,我可不可以就写个通用的日志入库逻辑方法,然后在每个接口执行之前都先执行这个日志入库逻辑方法,那就不需要每个接口都写上日志入库逻辑类了,只需要每次执行接口之前都调用那个日志入库方法就行。
然后这种思维就是aop切面编程。切面就是日志入库逻辑方法,切点就是接口。意思就是在切点那里插入切面,就是在接口那里执行日志入库方法。下面是使用aop记录用户操作日志实例:
在xml导入aop包:
首先自定义一个注解类
然后就是Aspect了:
@Aspect @Component public class LoggerAspect { @Autowired private LoggerService loggerService; @Autowired private EmployeeService employeeService; // 定义切点 @Pointcut。是面前注解类的地址。 @Pointcut("@annotation(com.gzydt.oa.auth.utils.LoggerOperator)") public void controllerAspect() { } /** * @Description 前置通知 ,就是在有LoggerOperator注解的接口执行前执行这个doBefore()方法。 */ @Before("controllerAspect()") public void doBefore(JoinPoint joinPoint) { try { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()) .getRequest(); String employeeId = request.getHeader("employeeId"); String ip = IpUtil.getIpAdrress(request); String method = joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName(); String role = employeeService.get(employeeId).getPosition(); String desc = getControllerMethodDescription(joinPoint); Logger logger = new Logger(); logger.setIp(ip); logger.setOperator(employeeId); logger.setRemark(method); logger.setRole(role); logger.setCreateTime(new Date()); logger.setDescription(desc); loggerService.add(logger); } catch (ClassNotFoundException e) { e.printStackTrace(); } } /** * @Description 获取注解中对方法的描述信息 用于Controller层注解 */ public static String getControllerMethodDescription(JoinPoint joinPoint) throws ClassNotFoundException { 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(LoggerOperator.class).description(); break; } } } return description; } } |
下面这个是ip获取工具类:
/** * 获取用户真实的ip地址 */ public class IpUtil { public static String getIpAdrress(HttpServletRequest request) { String ip = null; //X-Forwarded-For:Squid 服务代理 String ipAddresses = request.getHeader("X-Forwarded-For"); String unknown = "unknown"; if (ipAddresses == null || ipAddresses.length() == 0 || unknown.equalsIgnoreCase(ipAddresses)) { //Proxy-Client-IP:apache 服务代理 ipAddresses = request.getHeader("Proxy-Client-IP"); } if (ipAddresses == null || ipAddresses.length() == 0 || unknown.equalsIgnoreCase(ipAddresses)) { //WL-Proxy-Client-IP:weblogic 服务代理 ipAddresses = request.getHeader("WL-Proxy-Client-IP"); } if (ipAddresses == null || ipAddresses.length() == 0 || unknown.equalsIgnoreCase(ipAddresses)) { //HTTP_CLIENT_IP:有些代理服务器 ipAddresses = request.getHeader("HTTP_CLIENT_IP"); } if (ipAddresses == null || ipAddresses.length() == 0 || unknown.equalsIgnoreCase(ipAddresses)) { //X-Real-IP:nginx服务代理 ipAddresses = request.getHeader("X-Real-IP"); } //有些网络通过多层代理,那么获取到的ip就会有多个,一般都是通过逗号(,)分割开来,并且第一个ip为客户端的真实IP if (ipAddresses != null && ipAddresses.length() != 0) { ip = ipAddresses.split(",")[0]; } //还是不能获取到,最后再通过request.getRemoteAddr();获取 if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ipAddresses)) { ip = request.getRemoteAddr(); } return ip; } } |
然后在接口上加上注解就表示在接口执行前执行这个切面了:
是不是很方便了?只在接口上加上注解就可以了,不需要每个接口都写一遍日志入库逻辑了。