首页IT科技springboot自定义注解 解析(SpringBoot自定义注解+异步+观察者模式实现业务日志保存)

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

时间2025-05-03 01:59:00分类IT科技浏览3331
导读:一、前言 我们在...

一           、前言

我们在企业级的开发中           ,必不可少的是对日志的记录                 ,实现有很多种方式     ,常见的就是基于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() {} /** * 在切点之后织入 * @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.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); } /** * 从多级反向代理中获得第一个非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请求相关 * * @param checkString 被检测的字符串 * @return 是否未知 */ 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://cdn.yuucn.cn/wp-content/uploads/2022/10/1666925884-cab93b55a231034.jpg)

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

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
win10任务栏桌面图标怎么取消(win10任务栏向上箭头怎么取消? win10任务栏图标全显示的技巧) win怎么给文件夹加密(win11文件夹怎么加密?win11系统自带文件加密的方法步骤)