SpringBoot整合aop日志管理并写入数据库

SpringBoot整合aop日志管理

1.前期准备

1.1 配置
  • 导入依赖
  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-aop</artifactId>
  </dependency>
  <dependency>
       <groupId>org.springframework</groupId>
       <artifactId>spring-aop</artifactId>
  </dependency>
1.2 涉及知识点
  • Java基础自定义注解、反射
  • Spring AOP
  • SpringBoot

2.aop(Aspect Oriented Programming)

可以对程序进行扩展,而不需要修改源代码。

使用代理完成面向切面的功能。(注意代理不是Spring的代理,只是使用代理。)

日志、登录权限、事务处理等等功能都可以使用AOP解决

2.1 aop的五种通知
  1. 前置通知(Before):在目标方法或者说连接点被调用前执行的通知;
  2. 后置通知(After):指在某个连接点完成后执行的通知;
  3. 返回通知(After-returning):指在某个连接点成功执行之后执行的通知;
  4. 异常通知(After-throwing):指在方法抛出异常后执行的通知;
  5. 环绕通知(Around):指包围一个连接点通知,在被通知的方法调用之前和之后执行自定义的方法。
2.2 使用注解
//在AOP类上添加注解
@Aspect
@Component
//添加切入点
/*  
	切入表达式execution(* com.mf.core..*.*(..)) 
	切入点是一个方法 
	第一个*表示切入点的返回类型,如果为*,代表所有返回类和无返回值 
	第一个..表示该包下所有的子包。如果写一个.那么表示只在根包下。 
	第二个*表示类,可以写类名、如果写*表示所有类。 
	第三个*表示方法名,如果写*表示所有方法 
	第二个..表示方法的参数类型,如果写..表示所有参数类型,可以写参数类型名 
*/
@Pointcut("execution(* com.woniu..*.*(..))")
//添加通知
@Before
@AfterReturning
@After
@AfterThrowing
@Around
@AfterReturning@AfterThrowing互斥
2.3 实现aop日志功能

实现效果:用户在浏览器登录web页面,对应的操作会被记录到数据库中。
实现思路:自定义一个注解,将注解加到登录操作上,使用aop环绕通知代理带有注解的方法,在环绕前进行日志准备,执行完方法后进行日志入库。

自定义注解类
package com.woniu.core.annotation;

import java.lang.annotation.*;

/**
 * 自定义注解类
 */
@Target(ElementType.METHOD) //注解放置的目标位置,METHOD是可注解在方法级别上
@Retention(RetentionPolicy.RUNTIME) //注解在哪个阶段执行
@Documented //生成文档
public @interface SystemControllerLog {
    String descrption() default "";
}
切面处理类
  • 拦截带有注解的方法
  • 使用Log日志类将日志录入数据库
package com.woniu.core.aop;

import com.woniu.core.annotation.SystemControllerLog;
import com.woniu.core.common.HttpContextUtil;
import com.woniu.dao.LogDao;
import com.woniu.pojo.Log;
import com.woniu.pojo.User;
import com.woniu.service.LogService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
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.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 java.lang.reflect.Method;
import java.sql.SQLException;
import java.util.Date;

@Aspect
@Component
public class SystemLogAspect {

    @Resource
    private LogService logService;
    @Resource
    private LogDao logDao;
    /***
     * 定义controller切入点拦截规则,拦截SystemControllerLog注解的方法
     */
    @Pointcut("@annotation(com.woniu.core.annotation.SystemControllerLog)")
    public void controllerAspect(){}

    /***
     * 拦截控制层的操作日志
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("controllerAspect()")
    public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable {

        // 获取方法签名
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        // 获取方法
        Method method = methodSignature.getMethod();
        //获取方法的注解
        SystemControllerLog systemControllerLog
            =method.getAnnotation(SystemControllerLog.class);
        
        String operateType = systemControllerLog.descrption();
        Log systemLog = new Log();
        systemLog.setOp(operateType);

        //获取session中的用户
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        Subject subject = SecurityUtils.getSubject();
        User loginUser = (User) subject.getSession().getAttribute("loginUser");
        systemLog.setUid(loginUser.getUid());
        systemLog.setUname(loginUser.getUname());
        //获取ip地址
        String ip = HttpContextUtil.getIpAddress();
        systemLog.setIp(ip);

        Object result = null;
        try {
            result=joinPoint.proceed();
            systemLog.setOp("正常");
        } catch (SQLException e) {
            // 相当于异常通知部分
            systemLog.setOp("失败");// 设置操作结果
        } finally {
            // 相当于最终通知
            systemLog.setLtime(new Date());//获取时间
            logDao.insertSelective(systemLog);// 添加日志记录
        }
        return result ;
    }
}

将注解加到需要记录的方法体上
  • 这个登录后跳转页面的controller层的方法,也可以在其他方法体上加注解。
    @SystemControllerLog(descrption = "用户登录")
    @RequestMapping("main")
    public String main() {
        return "main";
    }
获取用户ip
  • 首先我们需要获取HttpServletRequest
  • Controller层通过RequestContextHolder.getRequestAttributes()获取HttpServletRequest
  • 非controller层可能会出现空指针问题
package com.woniu.core.common;

import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

public class HttpContextUtil {
    /**
     * 为了获取 HttpServletRequest
     * @return
     */
    public static HttpServletRequest getRequest(){

        return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
    }

    /**
     * 获取ip地址
     * @return
     */
    public static String getIpAddress(){
        HttpServletRequest request=getRequest();
        String ip = request.getHeader("x-forwarded-for");
        if (ip==null|| ip.length()==0||"unknown".equalsIgnoreCase(ip)){
            ip=request.getHeader("Proxy-Client-IP");
        }if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
     return ip;
    }
}

结语

获取用户ip注意的点:
  • X-Forwarded-For 这是一个 Squid 开发的字段,只有在通过了 HTTP 代理或者负载均衡服务器时才会添加该项。格式为X-Forwarded-For: client1, proxy1, proxy2,一般情况下,第一个ip为客户端真实ip,后面的为经过的代理服务器ip。现在大部分的代理都会加上这个请求头。
  • Proxy-Client-IP/WL- Proxy-Client-IP 这个一般是经过apache http服务器的请求才会有,用apache http做代理时一般会加上Proxy-Client-IP请求头,而WL- Proxy-Client-IP是他的weblogic插件加上的头。
  • HTTP_CLIENT_IP 有些代理服务器会加上此请求头。
  • X-Real-IP nginx代理一般会加上此请求头。
  • 获取客户端ip,直接使用ip = request.getRemoteAddr (),虽然获取到的可能是代理的ip不是客户端的ip,但这个获取到的ip基本上是不可能伪造的。
1024!!快乐快乐

在这里插入图片描述

  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值