SpringBoot自定义注解+异步+观察者模式实现业务日志保存()

  本篇文章为你整理了SpringBoot自定义注解+异步+观察者模式实现业务日志保存()的详细内容,包含有 SpringBoot自定义注解+异步+观察者模式实现业务日志保存,希望能帮助你了解 SpringBoot自定义注解+异步+观察者模式实现业务日志保存。

  我们在企业级的开发中,必不可少的是对日志的记录,实现有很多种方式,常见的就是基于AOP+注解进行保存,但是考虑到程序的流畅和效率,我们可以使用异步进行保存,小编最近在spring和springboot源码中看到有很多的监听处理贯穿前后:这就是著名的观察者模式!!

  二、基础环境

  项目这里小编就不带大家创建了,直接开始!!

  1. 导入依赖

  小编这里的springboot版本是:2.7.4

  

 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

  

 

  2. 编写yml配置

  

server:

 

   port: 8088

  spring:

   datasource:

   #使用阿里的Druid

   type: com.alibaba.druid.pool.DruidDataSource

   driver-class-name: com.mysql.cj.jdbc.Driver

   url: jdbc:mysql://192.168.239.131:3306/test?serverTimezone=UTC

   username: root

   password: root

  

 

  三、数据库设计

  数据库保存日志表的设计,小编一切从简,一般日志多的后期会进行分库分表,或者搭配ELK进行分析,分库分表一般采用根据方法类型,这需要开发人员遵循rest风格,不然肯定都是post,纯属个人见解哈!!大家可以根据自己的公司的要求进行补充哈!!

  

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 模块标题,

   `business_type` int(2) NULL DEFAULT 0 COMMENT 业务类型(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 主机地址,

   `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;

  

 

  实体类:

  

import com.baomidou.mybatisplus.annotation.TableId;

 

  import com.baomidou.mybatisplus.annotation.TableName;

  import com.fasterxml.jackson.annotation.JsonFormat;

  import lombok.Data;

  import java.time.LocalDateTime;

   * 操作日志记录表 sys_log

  @Data

  @TableName("sys_log")

  public class SysLog {

   private static final long serialVersionUID = 1L;

   * 日志主键

   @TableId

   private Long id;

   * 操作模块

   private String title;

   * 业务类型(0其它 1新增 2修改 3删除)

   private Integer businessType;

   * 请求方式

   private String requestMethod;

   * 操作人员

   private String operName;

   * 请求url

   private String operUrl;

   * 操作地址

   private String operIp;

   * 操作时间

   @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")

   private LocalDateTime operTime;

  

 

  四、主要功能

  大体思路:
 

  先手写一个注解--- 切面来进行获取要保存的数据--- 一个发布者来发布要保存的数据--- 一个监听者监听后保存(异步)

  完整项目架构图如下:
 

  1. 编写注解

  

import com.example.demo.constant.BusinessTypeEnum;

 

  import java.lang.annotation.*;

   * 自定义操作日志记录注解

   * @author wangzhenjun

   * @date 2022/10/26 15:37

  @Target(ElementType.METHOD) // 注解只能用于方法

  @Retention(RetentionPolicy.RUNTIME) // 修饰注解的生命周期

  @Documented

  public @interface Log {

   String value() default "";

   * 模块

   String title() default "测试模块";

   * 功能

   BusinessTypeEnum businessType() default BusinessTypeEnum.OTHER;

  

 

  2. 业务类型枚举

  

/**

 

   * @author wangzhenjun

   * @date 2022/10/26 11:22

  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. 编写切片

  这里小编是以切片后进行发起的,当然规范流程是要加异常后的切片,这里以最简单的进行测试哈,大家按需进行添加!!

  

import com.example.demo.annotation.Log;

 

  import com.example.demo.entity.SysLog;

  import com.example.demo.listener.EventPubListener;

  import com.example.demo.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.time.LocalDateTime;

   * @author wangzhenjun

   * @date 2022/10/26 15:39

  @Aspect

  @Component

  public class SysLogAspect {

   private final Logger logger = LoggerFactory.getLogger(SysLogAspect.class);

   @Autowired

   private EventPubListener eventPubListener;

   * 以注解所标注的方法作为切入点

   @Pointcut("@annotation(com.example.demo.annotation.Log)")

   public void 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.setRequestMethod(method);

   sysLog.setOperIp(ip);

   sysLog.setOperUrl(url);

   // 从登录中token获取登录人员信息即可

   sysLog.setOperName("我是测试人员");

   sysLog.setOperTime(LocalDateTime.now());

   // 发布消息

   eventPubListener.pushListener(sysLog);

   logger.info("=======日志发送成功,内容:{}",sysLog);

  

 

  4. ip工具类

  

import com.baomidou.mybatisplus.core.toolkit.StringUtils;

 

  import javax.servlet.http.HttpServletRequest;

   * @author wangzhenjun

   * @date 2022/10/26 16:27

   * 获取IP方法

   * @author jw

  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);

  
public static boolean isUnknown(String checkString) {

   return StringUtils.isBlank(checkString) "unknown".equalsIgnoreCase(checkString);

  

 

  5. 事件发布

  事件发布是由ApplicationContext对象进行发布的,直接注入使用即可!
 

  使用观察者模式的目的:为了业务逻辑之间的解耦,提高可扩展性。
 

  这种模式在spring和springboot底层是经常出现的,大家可以去看看。
 

  发布者只需要关注发布消息,监听者只需要监听自己需要的,不管谁发的,符合自己监听条件即可。

  

import com.example.demo.entity.SysLog;

 

  import org.springframework.beans.factory.annotation.Autowired;

  import org.springframework.context.ApplicationContext;

  import org.springframework.stereotype.Component;

   * @author wangzhenjun

   * @date 2022/10/26 16:38

  @Component

  public class EventPubListener {

   @Autowired

   private ApplicationContext applicationContext;

   // 事件发布方法

   public void pushListener(SysLog sysLogEvent) {

   applicationContext.publishEvent(sysLogEvent);

  

 

  6. 监听者

  @Async:单独开启一个新线程去保存,提高效率!
 

  @EventListener:监听

  

/**

 

   * @author wangzhenjun

   * @date 2022/10/25 15:22

  @Slf4j

  @Component

  public class MyEventListener {

   @Autowired

   private TestService testService;

   // 开启异步

   @Async

   // 开启监听

   @EventListener(SysLog.class)

   public void saveSysLog(SysLog event) {

   log.info("=====即将异步保存到数据库======");

   testService.saveLog(event);

  

 

  1. controller

  

/**

 

   * @author wangzhenjun

   * @date 2022/10/26 16:51

  @Slf4j

  @RestController

  @RequestMapping("/test")

  public class TestController {

   @Log(title = "测试呢",businessType = BusinessTypeEnum.INSERT)

   @GetMapping("/saveLog")

   public void saveLog(){

   log.info("我就是来测试一下是否成功!");

  

 

  2. service

  

/**

 

   * @author wangzhenjun

   * @date 2022/10/26 16:55

  public interface TestService {

   int saveLog(SysLog sysLog);

  

 

  

/**

 

   * @author wangzhenjun

   * @date 2022/10/26 16:56

  @Service

  public class TestServiceImpl implements TestService {

   @Autowired

   private TestMapper testMapper;

   @Override

   public int saveLog(SysLog sysLog) {

   return testMapper.insert(sysLog);

  

 

  3. mapper

  这里使用mybatis-plus进行保存

  

/**

 

   * @author wangzhenjun

   * @date 2022/10/26 17:07

  public interface TestMapper extends BaseMapper SysLog {

  

 

  4. 测试

  5. 数据库

  铛铛铛,终于完成了!这个实战在企业级必不可少的,每个项目搭建人不同,但是结果都是一样的,保存日志到数据,这样可以进行按钮的点击进行统计,分析那个功能是否经常使用,那些东西需要优化。只要是有数据的东西,分析一下总会有收获的!后面日志多了就行分库分表,ELK搭建。知道的越多不知道的就越多,这一次下来,知道下面要学什么了嘛!!

  可以看下一小编的微信公众号,和网站文章首发看,欢迎关注,一起交流哈!!

  ![](https://img2022.cnblogs.com/blog/2471401/202210/2471401-20221028085553420-1506161649.jpg)

  点击访问!小编自己的网站,里面也是有很多好的文章哦!

  以上就是SpringBoot自定义注解+异步+观察者模式实现业务日志保存()的详细内容,想要了解更多 SpringBoot自定义注解+异步+观察者模式实现业务日志保存的内容,请持续关注盛行IT软件开发工作室。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: