溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

SpringBoot怎么通過自定義注解與異步來管理日志

發(fā)布時間:2023-05-11 15:09:53 來源:億速云 閱讀:83 作者:iii 欄目:開發(fā)技術(shù)

這篇文章主要介紹“SpringBoot怎么通過自定義注解與異步來管理日志”,在日常操作中,相信很多人在SpringBoot怎么通過自定義注解與異步來管理日志問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”SpringBoot怎么通過自定義注解與異步來管理日志”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!

一、前言

我們在企業(yè)級的開發(fā)中,必不可少的是對日志的記錄,實現(xiàn)有很多種方式,常見的就是基于AOP+注解進(jìn)行保存,同時考慮到程序的流暢和效率,我們可以使用異步進(jìn)行保存!

二、基礎(chǔ)環(huán)境

1. 導(dǎo)入依賴

我這里的springboot版本是:2.7.4

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>springboot-log</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-log</name>
    <description>springboot-log 日志 Demo</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- Druid -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.16</version>
        </dependency>
        <!--jdbc-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <!-- mysql -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!-- mybatis-plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

2. 編寫yml配置

server:
  port: 8080

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/admin?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
    druid:
      initial-size: 5
      max-active: 100
      min-idle: 5
      max-wait: 60000
      time-between-eviction-runs-millis: 60000
      min-evictable-idle-time-millis: 30000
      validation-query: SELECT 1 FROM DUAL
      test-while-idle: true
      test-on-borrow: true
      test-on-return: false

三、數(shù)據(jù)庫設(shè)計

數(shù)據(jù)庫保存日志表的設(shè)計,這里一切從簡,一般日志多的后期會進(jìn)行分庫分表,或者搭配ELK進(jìn)行分析,分庫分表一般采用根據(jù)方法類型!

DROP TABLE IF EXISTS `sys_log`;
CREATE TABLE `sys_log`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '日志主鍵',
  `title` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '模塊標(biāo)題',
  `business_type` int(2) NULL DEFAULT 0 COMMENT '業(yè)務(wù)類型(0其它 1新增 2修改 3刪除)',
  `method` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '方法名稱',
  `request_method` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '請求方式',
  `oper_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '操作人員',
  `oper_url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '請求URL',
  `oper_ip` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '主機(jī)地址',
  `oper_time` datetime(0) NULL DEFAULT NULL COMMENT '操作時間',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1585197503834284034 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '操作日志記錄' ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;

實體類:

package com.example.springbootlog.entity;
import java.util.Date;
import java.io.Serializable;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import lombok.ToString;
/**
 * 操作日志記錄(SysLog)實體類
 *
 * @author qrxm
 * @since 2023-03-26 02:09:54
 */
@Data
@ToString
@TableName("sys_log")
public class SysLog implements Serializable {
    private static final long serialVersionUID = 811241510868515068L;
    /**
    * 日志主鍵
    */
    @TableId
    private Long id;
    /**
    * 模塊標(biāo)題
    */
    private String title;
    /**
    * 業(yè)務(wù)類型(0其它 1新增 2修改 3刪除)
    */
    private Integer businessType;
    /**
    * 方法名稱
    */
    private String method;
    /**
    * 請求方式
    */
    private String requestMethod;
    /**
    * 操作人員
    */
    private String operName;
    /**
    * 請求URL
    */
    private String operUrl;
    /**
    * 主機(jī)地址
    */
    private String operIp;
    /**
    * 操作時間
    */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date operTime;
}

四、主要功能

大體思路:

  • 先手寫一個注解

  • 切面來進(jìn)行獲取要保存的數(shù)據(jù)

  • 一個發(fā)布者來發(fā)布要保存的數(shù)據(jù)

  • 一個監(jiān)聽者監(jiān)聽后保存(異步)

完整項目架構(gòu)圖如下:

SpringBoot怎么通過自定義注解與異步來管理日志

1. 編寫注解

package com.example.springbootlog.annotation;
import com.example.springbootlog.constant.BusinessTypeEnum;
import java.lang.annotation.*;
/**
 * 自定義操作日志記錄注解
 * @author qrxm
 */
@Target(ElementType.METHOD) // 注解只能用于方法
@Retention(RetentionPolicy.RUNTIME) // 修飾注解的生命周期
@Documented
public @interface Log {
    String value() default "";
    /**
     * 模塊
     */
    String title() default "測試模塊";
    /**
     * 方法名稱
     */
    String method() default "測試方法";
    /**
     * 功能
     */
    BusinessTypeEnum businessType() default BusinessTypeEnum.OTHER;
}

2. 業(yè)務(wù)類型枚舉

package com.example.springbootlog.constant;
public enum BusinessTypeEnum {
    /**
     * 其它
     */
    OTHER(0,"其它"),
    /**
     * 新增
     */
    INSERT(1,"新增"),
    /**
     * 修改
     */
    UPDATE(2,"修改"),
    /**
     * 刪除
     */
    DELETE(3,"刪除");
    private Integer code;
    private String message;
    BusinessTypeEnum(Integer code, String message) {
        this.code = code;
        this.message = message;
    }
    public Integer getCode() {
        return code;
    }
    public String getMessage() {
        return message;
    }
}

3. 編寫切片

這里是以切片后進(jìn)行發(fā)起的,當(dāng)然規(guī)范流程是要加異常后的切片,這里以最簡單的進(jìn)行測試哈,大家按需進(jìn)行添加?。?/p>

package com.example.springbootlog.aspect;
import com.example.springbootlog.annotation.Log;
import com.example.springbootlog.entity.SysLog;
import com.example.springbootlog.listener.EventPubListener;
import com.example.springbootlog.utils.IpUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
@Aspect
@Component
public class SysLogAspect {
    private final Logger logger = LoggerFactory.getLogger(SysLogAspect.class);
    @Autowired
    private EventPubListener eventPubListener;
    /**
     * 以注解所標(biāo)注的方法作為切入點
     */
    @Pointcut("@annotation(com.example.springbootlog.annotation.Log)")
    public void sysLog() {}
    /**
     * 在切點之后織入
     * @throws Throwable
     */
    @After("sysLog()")
    public void doAfter(JoinPoint joinPoint) {
        Log log = ((MethodSignature) joinPoint.getSignature()).getMethod()
                .getAnnotation(Log.class);
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
                .getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String method = request.getMethod();
        String url = request.getRequestURL().toString();
        String ip = IpUtils.getIpAddr(request);
        SysLog sysLog = new SysLog();
        sysLog.setBusinessType(log.businessType().getCode());
        sysLog.setTitle(log.title());
        sysLog.setMethod(log.method());
        sysLog.setRequestMethod(method);
        sysLog.setOperIp(ip);
        sysLog.setOperUrl(url);
        // 從登錄中token獲取登錄人員信息即可
        sysLog.setOperName("我是測試人員");
        sysLog.setOperTime(new Date());
        // 發(fā)布消息
        eventPubListener.pushListener(sysLog);
        logger.info("=======日志發(fā)送成功,內(nèi)容:{}",sysLog);
    }
}

4. ip工具類

package com.example.springbootlog.utils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import javax.servlet.http.HttpServletRequest;
public class IpUtils {
    /**
     * 獲取客戶端IP
     *
     * @param request 請求對象
     * @return IP地址
     */
    public static String getIpAddr(HttpServletRequest request) {
        if (request == null) {
            return "unknown";
        }
        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("X-Forwarded-For");
        }
        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("X-Real-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : getMultistageReverseProxyIp(ip);
    }
    /**
     * 從多級反向代理中獲得第一個非unknown IP地址
     *
     * @param ip 獲得的IP地址
     * @return 第一個非unknown IP地址
     */
    public static String getMultistageReverseProxyIp(String ip) {
        // 多級反向代理檢測
        if (ip != null && ip.indexOf(",") > 0) {
            final String[] ips = ip.trim().split(",");
            for (String subIp : ips) {
                if (false == isUnknown(subIp)) {
                    ip = subIp;
                    break;
                }
            }
        }
        return ip;
    }
    /**
     * 檢測給定字符串是否為未知,多用于檢測HTTP請求相關(guān)
     *
     * @param checkString 被檢測的字符串
     * @return 是否未知
     */
    public static boolean isUnknown(String checkString) {
        return StringUtils.isBlank(checkString) || "unknown".equalsIgnoreCase(checkString);
    }
}

5. 事件發(fā)布

事件發(fā)布是由ApplicationContext對象進(jìn)行發(fā)布的,直接注入使用即可!

使用觀察者模式的目的:為了業(yè)務(wù)邏輯之間的解耦,提高可擴(kuò)展性。

這種模式在spring和springboot底層是經(jīng)常出現(xiàn)的,大家可以去看看。

發(fā)布者只需要關(guān)注發(fā)布消息,監(jiān)聽者只需要監(jiān)聽自己需要的,不管誰發(fā)的,符合自己監(jiān)聽條件即可。

package com.example.springbootlog.listener;
import com.example.springbootlog.entity.SysLog;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
@Component
public class EventPubListener {
    @Autowired
    private ApplicationContext applicationContext;
    /**
     * 事件發(fā)布方法
     * @param sysLogEvent
     */
    public void pushListener(SysLog sysLogEvent) {
        applicationContext.publishEvent(sysLogEvent);
    }
}

6. 監(jiān)聽者

@Async:單獨開啟一個新線程去保存,提高效率!

@EventListener:監(jiān)聽

package com.example.springbootlog.listener;
import com.example.springbootlog.entity.SysLog;
import com.example.springbootlog.service.SysLogService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class MyEventListener {
    @Autowired
    private SysLogService sysLogService;
    // 開啟異步
    @Async
    // 開啟監(jiān)聽
    @EventListener(SysLog.class)
    public void saveSysLog(SysLog event) {
        log.info("=====即將異步保存到數(shù)據(jù)庫======");
        sysLogService.saveLog(event);
    }
}

五、測試

1. controller

package com.example.springbootlog.controller;
import com.example.springbootlog.annotation.Log;
import com.example.springbootlog.constant.BusinessTypeEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
/**
 * 操作日志記錄(SysLog)表控制層
 *
 */
@Slf4j
@RestController
@RequestMapping("sysLog")
public class SysLogController {
    @Log(method = "測試添加方法", title = "測試呢", businessType = BusinessTypeEnum.INSERT)
    @GetMapping("/saveLog")
    public void saveLog() {
        log.info("我就是來測試一下是否成功!");
    }
}

2. service

package com.example.springbootlog.service;
import com.example.springbootlog.entity.SysLog;
import com.baomidou.mybatisplus.extension.service.IService;
/**
 * 操作日志記錄(SysLog)表服務(wù)接口
 */
public interface SysLogService  extends IService<SysLog> {
    Integer saveLog(SysLog sysLog);
}
package com.example.springbootlog.service.impl;
import com.example.springbootlog.entity.SysLog;
import com.example.springbootlog.service.SysLogService;
import com.example.springbootlog.dao.SysLogDao;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
 * 操作日志記錄(SysLog)表服務(wù)實現(xiàn)類
 */
@Service("sysLogService")
public class SysLogServiceImpl  extends ServiceImpl<SysLogDao, SysLog>  implements SysLogService {
    @Autowired
    private SysLogDao sysLogDao;
    @Override
    public Integer saveLog(SysLog sysLog) {
        return sysLogDao.insert(sysLog);
    }
}

3. dao

這里使用mybatis-plus進(jìn)行保存

package com.example.springbootlog.dao;
import com.example.springbootlog.entity.SysLog;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
 * 操作日志記錄(SysLog)表數(shù)據(jù)庫訪問層
 */
public interface SysLogDao extends BaseMapper<SysLog> {
}

4. 測試

訪問地址:http://localhost:8080/sysLog/saveLog

SpringBoot怎么通過自定義注解與異步來管理日志

5. 數(shù)據(jù)庫

SpringBoot怎么通過自定義注解與異步來管理日志

到此,關(guān)于“SpringBoot怎么通過自定義注解與異步來管理日志”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注億速云網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI