Springboot自定義註解實現操作日誌管理

操作日誌的記錄

爲什麼要有日誌?
因爲我們不光要記錄代碼的運行,如(logback log4j),而且還應該記錄用戶的行爲,這叫做業務運行日誌

例如:記錄 zhangsan 在項目中 調用了哪個方法, 什麼時間調用的 。訪問的ip地址, 訪問了哪些數據,做了什麼操作,以此當程序出現問題的時候更利於我們進行錯誤的排查!

業務運行日誌的作用

  1. 記錄用戶的行爲 用於後續的分析
  2. 記錄用戶的所有的操作

業務運行日誌最常用的使用場景:記錄管理員所有的行爲操作, 可以用於業務分析,事故恢復

日誌實現的思路

1.我們需要記錄哪些數據 存入數據庫

這裏列出一個我所用的表結構,如下所示:

字段 含義
log_id 主鍵
log_date 時間
log_content 操作內容 例如:查詢全部菜單信息 添加用戶數據
log_name_id 用戶的id
log_ip 用戶的ip地址
log_type 操作類型

2.在項目中什麼位置記錄

日誌記錄是一個數據庫的添加操作 是一段代碼

通常,我們在Controller方法進行後置增強
如下圖所示,我們在需要記錄操作的controller上使用aop配置一個切入點,以此來記錄用戶所進行的操作
在這裏插入圖片描述

3.如何實現記錄功能

實現方式:AOP

4.Aop日誌記錄 具體代碼實現

aop的使用流程,這裏使用註解式aop來實現
具體步驟:

  1. 設置切入點

    1. 可以切在方法上
    2. 可以切在註解上
      @Transactional 事務註解 註解加在類上 aop 切在註解上
      
  2. 寫增強 日誌記錄增強

    1. 獲取日誌的相關信息
      用戶的id ip地址, 時間, 操作的描述, 類型等信息
    2. 將日誌對象 添加到數據庫

增強方法的編寫

增強方法中獲取session
因爲我們是通過aop來獲取用戶的請求的,所以就需要通過當前的請求拿到session,進而去獲取用戶的信息。
在這裏插入圖片描述

但是,操作的描述如何獲取呢?

比如 執行的方法不同  描述是不一樣的
login             管理員登錄
selectAllMenu  查詢了所有的菜單

解決方案:使用自定義註解:

  1. 在 目標 方法上添加自定義註解 (@Log) 如下

  2. 在增強中獲取註解(@Log)的value 和 type

代碼實現

自定義日誌註解
在這裏插入圖片描述

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 元註解:加在自定義註解上的註解
 * @Target 定義註解可以添加的位置 METHOD 方法上 type 類上
 * @Retention RUNTIME 運行時  不管編譯 還是 運行 這個註解都可以用
 *
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogAnnotation {
    /**
     * 寫法類似於接口的方法 後面可以通過default 關鍵字給默認值
     * 用法類似於屬性
     * @return
     */
    String value() default "";
    String type() default "";
}

這裏要注意什麼是元註解,和 註解屬性的定義方式

2. 在目標方法上使用註解

在這裏插入圖片描述
3. 在增強方法中獲取註解的value 和 type
在這裏插入圖片描述

        /**
         * 操作的描述
         *
         * 執行的方法不同  描述是不一樣的
         * login         管理員登錄
         * selectAllGuru 查詢了所有的上師
         *
         * 獲取註解的值
         */
//        1.通過連接點獲取方法簽名 被切入方法的所有信息
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//        2.獲取被切入方法對象
        Method method = signature.getMethod();
//        3.獲取方法上的註解
        LogAnnotation annotation = method.getAnnotation(LogAnnotation.class);
//        4.獲取註解的值
        String value = annotation.value();

完整的aop的代碼實現

package com.tourism.hu.config;

/**
 * @author 馬超偉
 * @PROJECT_NAME: fzll
 * @Description:
 * @date 15:29
 * @Copyright: All rights Reserved, Designed By Huerdai  
 * Copyright:    Copyright(C) 2019-2020
 * Company       Huerdai Henan LTD.
 */

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.tourism.hu.entity.CustomerInfo;
import com.tourism.hu.entity.CustomerLoginLog;
import com.tourism.hu.service.ICustomerInfoService;
import com.tourism.hu.service.ICustomerLoginLogService;
import com.tourism.hu.util.IpAddressUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
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;
import java.time.LocalDateTime;

/*** @Aspect 標記當前類爲功能增強類 切面類 *
 *  @Configuration 標記當前類爲配置類 這個註解包含了@Component的功能
 */
@Aspect
@Configuration
public class LogAop {

    private  Logger logger = LoggerFactory.getLogger(getClass());

    @Resource
    private ICustomerInfoService iCustomerInfoService;

    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    private ICustomerLoginLogService iCustomerLoginLogService;

    /**
     * JoinPoint 連接點 就是切入點 通過這個對象可以獲取切入點的相關所有信息 例如:被切入的方法和註解
     *
     * @param joinPoint ** 切入點的設置 切註解 @annotation *
     */
    @After("@annotation(com.tourism.hu.config.Log)")
    public void logAfter(JoinPoint joinPoint) {
    	//new 一個日誌的實體,用來保存日誌信息
        CustomerLoginLog loginLog = new CustomerLoginLog();
        // 1.獲取日誌相關的信息  用戶的id session  ip  時間  操作的描述  類型  ctrl+H
        /**
         * 獲取用戶id
         * 爲什麼不能裝配session?因爲服務器有多個session
         * 通過 ServletRequestAttributes 可以獲取當前請求
         * 當前請求可以獲取當前會話的session
         */
         //獲取用戶的請求
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        //得到session
        HttpSession session = request.getSession();
        String sessionid = session.getId();
        //通過sessionid去獲取用戶信息
        Object obj = redisTemplate.opsForValue().get(sessionid);
        String customerId = "";
        if(obj!=null) {
            customerId=obj.toString();
        }
        //拿到用戶對象
        CustomerInfo customerInfo = iCustomerInfoService.getOne(new QueryWrapper<CustomerInfo>().eq("id", customerId));
        if (customerInfo!=null){
        	//將用戶的id 存入到日誌實體中
            loginLog.setCustomerId(customerInfo.getCustomerId());
        }
         loginLog.setLoginTime(LocalDateTime.now());
        /**
         * 獲取用戶的ip
         * 通過工具類 ip
         */
        loginLog.setLoginIp(IpAddressUtil.getIp());

        /**
         * 操作的描述
         * 執行的方法不同  描述是不一樣的
         * login         管理員登錄
         * 獲取註解的值
         */
//        1.通過連接點獲取方法簽名 被切入方法的所有信息
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//        2.獲取被切入方法對象
        Method method = signature.getMethod();
//        3.獲取方法上的註解
        Log annotation = method.getAnnotation(Log.class);
//        4.獲取註解的值
        String value = annotation.value();
        loginLog.setLogContent(value);
        // 獲取註解的類型
        String type = annotation.type();
        if (type!=null){
            loginLog.setLoginType(type);
        }
//        2.將日誌對象 添加到數據庫
        System.out.println(loginLog);
        logger.debug("loginLog===="+loginLog);
        boolean save = iCustomerLoginLogService.save(loginLog);
        logger.debug("保存日誌------"+save);
    }
}

所用到的工具類

獲取ip地址的工具類IpAddressUtil

  public static String getIp() {
        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);
        }
        //使用代理,則獲取第一個IP地址
        if(StringUtils.isNotEmpty(ip) && ip.length() > 15) {
          if(ip.indexOf(",") > 0) {
              ip = ip.substring(0, ip.indexOf(","));
          }
      }
        return ip;
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章