• SpringCloud使用注解+AOP+MQ来实现日志管理模块


    简介

    无论在什么系统中,日志管理模块都属于十分重要的部分,接下来会通过注解+AOP+MQ的方式实现一个简易的日志管理系统

    思路

    • 注解: 标记需要记录日志的方法

    • AOP: 通过AOP增强代码,利用后置/异常通知的方式获取相关日志信息,最后使用MQ将日志信息发送到专门处理日志的系统

    • RabbitMQ: 利用解耦、异步的特性,协调完成各个微服务系统之间的通信

    1、日志表结构

    表结构(sys_log):

    1. CREATE TABLE `sys_log` (
    2.   `id` int(11NOT NULL AUTO_INCREMENT COMMENT '唯一ID',
    3.   `opt_id` int(11DEFAULT NULL COMMENT '操作用户id',
    4.   `opt_name` varchar(50DEFAULT NULL COMMENT '操作用户名',
    5.   `log_type` varchar(20DEFAULT NULL COMMENT '日志类型',
    6.   `log_message` varchar(255DEFAULT NULL COMMENT '日志信息(具体方法名)',
    7.   `create_time` datetime DEFAULT NULL COMMENT '创建时间',
    8.   PRIMARY KEY (`id`)
    9. ) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8 COMMENT='系统日志表';

    实体类(SysLog):

    1. @Data
    2. public class SysLog  {
    3.  
    4.     private static final long serialVersionUID = 1L;
    5.  
    6.     /**
    7.      * 唯一ID
    8.      */
    9.     @TableId(value = "id", type = IdType.AUTO)
    10.     private Integer id;
    11.     /**
    12.      * 操作用户id
    13.      */
    14.     private Integer optId;
    15.     /**
    16.      * 操作用户名
    17.      */
    18.     private String optName;
    19.     /**
    20.      * 日志类型
    21.      */
    22.     private String logType;
    23.     /**
    24.      * 日志信息(具体方法名)
    25.      */
    26.     private String logMessage;
    27.     /**
    28.      * 创建时间
    29.      */
    30.     private Date createTime;
    31.  
    32. }

    2、注解

    注解(SystemLog):

    仅作为标记的作用,目的让JVM可以识别,然后可以从中获取相关信息

    • @Target: 定义注解作用的范围,这里是方法

    • @Retention: 定义注解生命周期,这里是运行时

    1. @Target(ElementType.METHOD)
    2. @Retention(RetentionPolicy.RUNTIME)
    3. public @interface SystemLog {
    4.  
    5.     SystemLogEnum type();
    6.  
    7. }

    枚举(SystemLogEnum):

    限定日志类型范围

    1. public enum SystemLogEnum {
    2.  
    3.     SAVE_LOG("保存"),
    4.     DELETE_LOG("删除"),
    5.     REGISTER_LOG("注册"),
    6.     LOGIN_LOG("登录"),
    7.     LAUD_LOG("点赞"),
    8.     COLLECT_LOG("收藏"),
    9.     THROW_LOG("异常"),
    10.     ;
    11.     private String type;
    12.  
    13.     SystemLogEnum(String type) {
    14.         this.type = type;
    15.     }
    16.  
    17.     public String getType() {
    18.         return type;
    19.     }
    20. }

    3、AOP切面

    AOP(SysLogAspect):

    实现代码的增强,主要通过动态代理方式实现的代码增强。拦截注解,并获取拦截到的相关信息,封装成日志对象发送到MQ队列(生产端)

    1. Component
    2. @Aspect
    3. @Slf4j
    4. public class SysLogAspect {
    5.  
    6.     @Autowired
    7.     MqStream stream;
    8.  
    9.     //切点
    10.     @Pointcut("@annotation(cn.zdxh.commons.utils.SystemLog)")
    11.     public void logPointcut(){}
    12.  
    13.     //后置通知
    14.     @After("logPointcut()")
    15.     public void afterLog(JoinPoint joinPoint) {
    16.         //一般日志
    17.         SysLog sysLog = wrapSysLog(joinPoint);
    18.  
    19.         log.info("Log值:"+sysLog);
    20.  
    21.         //发送mq消息
    22.         stream.logOutput().send(MessageBuilder.withPayload(sysLog).build());
    23.  
    24.     }
    25.  
    26.     //异常通知
    27.     @AfterThrowing(value = "logPointcut()", throwing = "e")
    28.     public void throwingLog(JoinPoint joinPoint, Exception e) {
    29.         //异常日志
    30.         SysLog sysLog = wrapSysLog(joinPoint);
    31.         sysLog.setLogType(SystemLogEnum.THROW_LOG.getType());
    32.         sysLog.setLogMessage(sysLog.getLogMessage()+"==="+e);
    33.  
    34.         log.info("异常Log值:"+sysLog);
    35.  
    36.         //发送mq消息
    37.         stream.logOutput().send(MessageBuilder.withPayload(sysLog).build());
    38.     }
    39.  
    40.     /**
    41.      * 封装SysLog对象
    42.      * @param joinPoint
    43.      * @return
    44.      */
    45.     public SysLog wrapSysLog(JoinPoint joinPoint){
    46.         //获取请求响应对象
    47.         ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    48.         HttpServletRequest request = attributes.getRequest();
    49.         MethodSignature signature = (MethodSignature)joinPoint.getSignature();
    50.         SysLog sysLog = new SysLog();
    51.  
    52.         //获取方法全路径
    53.         String methodName = signature.getDeclaringTypeName()+"."+signature.getName();
    54.         //获取注解参数值
    55.         SystemLog systemLog = signature.getMethod().getAnnotation(SystemLog.class);
    56.         //从header取出token
    57.         String token = request.getHeader("token");
    58.         if (!StringUtils.isEmpty(token)) {
    59.             //操作人信息
    60.             Integer userId = JwtUtils.getUserId(token);
    61.             String username = JwtUtils.getUsername(token);
    62.             sysLog.setOptId(userId);
    63.             sysLog.setOptName(username);
    64.         }
    65.         if (!StringUtils.isEmpty(systemLog.type())){
    66.             sysLog.setLogType(systemLog.type().getType());
    67.         }
    68.         sysLog.setLogMessage(methodName);
    69.         sysLog.setCreateTime(new Date());
    70.         return sysLog;
    71.     }
    72.  
    73. }

    3、RabbitMQ消息队列

    MQ:

    这里主要是通过Spring Cloud Stream集成的RabbitMQ

    Spring Cloud Stream:

    作为MQ的抽象层,已屏蔽各种MQ的各自名词,统称为input、output两大块。可以更方便灵活地切换各种MQ,如 kafka、RocketMQ等

    (1)定义Input/Ouput接口(MqStream)

    1. @Component
    2. public interface MqStream {
    3.  
    4.     String LOG_INPUT = "log_input";
    5.  
    6.     String LOG_OUTPUT = "log_output";
    7.   
    8.     @Input(LOG_INPUT)
    9.     SubscribableChannel logInput();
    10.  
    11.     @Output(LOG_OUTPUT)
    12.     MessageChannel logOutput();
    13.  
    14. }

    (2)MQ生产者

    注:这里使用到AOP切面的微服务,都属于MQ生产者服务

    引入依赖:

    这里没有版本号的原因是spring cloud已经帮我们管理好各个版本号,已无需手动定义版本号

    1. <!--Spring Cloud Stream-->
    2. <dependency>
    3.     <groupId>org.springframework.cloud</groupId>
    4.      <artifactId>spring-cloud-stream</artifactId>
    5. </dependency>
    6. <dependency>
    7.     <groupId>org.springframework.cloud</groupId>
    8.     <artifactId>spring-cloud-stream-binder-rabbit</artifactId>
    9. </dependency>

    在程序入口开启MQ的Input/Output绑定:

    1. @SpringBootApplication(scanBasePackages = {"cn.zdxh.user","cn.zdxh.commons"})
    2. @EnableEurekaClient
    3. @MapperScan("cn.zdxh.user.mapper")
    4. @EnableBinding(MqStream.class) //开启绑定
    5. @EnableFeignClients 
    6. public class YouquServiceProviderUserApplication {
    7.  
    8.     public static void main(String[] args) {
    9.         SpringApplication.run(YouquServiceProviderUserApplication.class, args);
    10.     }
    11.  
    12. }

    yml配置:

    在生产者端设置output

    • destination: 相当于rabbitmqexchange

    • group: 相当于rabbitmq的queue,不过是和destination一起组合成的queue名。另外,搜索公众号Linux中文社区后台回复“私房菜”,获取一份惊喜礼包。

    • binder: 需要绑定的MQ

    1. #Spring Cloud Stream相关配置
    2. spring:
    3.   cloud:
    4.     stream:
    5.       bindings: # exchange与queue绑定
    6.         log_output: # 日志生产者设置output
    7.           destination: log.exchange
    8.           content-type: application/json
    9.           group: log.queue
    10.           binder: youqu_rabbit #自定义名称
    11.       binders:
    12.         youqu_rabbit:  #自定义名称
    13.           type: rabbit
    14.           environment:
    15.             spring:
    16.               rabbitmq:
    17.                 host: localhost
    18.                 port: 5672
    19.                 username: guest
    20.                 password: 25802580

    注:完成以上操作,即完成MQ生产端的所有工作

    (3)MQ消费者

    引入依赖、开启Input/Output绑定:均和生产者的设置一致

    yml配置:

    在生产者端设置input

    1. spring:
    2.   cloud:  # Spring Cloud Stream 相关配置
    3.     stream:
    4.       bindings: # exchange与queue绑定
    5.         log_input: # 日志消费者设置input
    6.           destination: log.exchange
    7.           content-type: application/json
    8.           group: log.queue
    9.           binder: youqu_rabbit
    10.       binders:
    11.         youqu_rabbit:
    12.           type: rabbit
    13.           environment:
    14.             spring:
    15.               rabbitmq:
    16.                 host: localhost
    17.                 port5672
    18.                 username: guest
    19.                 password25802580

    消费者监听(LogMqListener):

    监听生产者发过来的日志信息,将信息添加到数据库即可

    1. @Service
    2. @Slf4j
    3. public class LogMqListener {
    4.  
    5.     @Autowired
    6.     SysLogService sysLogService;
    7.  
    8.     @StreamListener(MqStream.LOG_INPUT)
    9.     public void input(SysLog sysLog)  {
    10.         log.info("开始记录日志========================");
    11.  
    12.         sysLogService.save(sysLog);
    13.  
    14.         log.info("结束记录日志========================");
    15.  
    16.     }
    17. }

    注:完成以上操作,即完成MQ消费端的所有工作

    4、应用

    简述:

    只需将@SystemLog(type = SystemLogEnum.REGISTER_LOG),标记在需要记录的方法上,当有客户端访问该方法时,就可以自动完成日志的记录

    5、总结

    流程:

    注解标记--->AOP拦截--->日志发送到MQ--->专门处理日志的系统监听MQ消息 --->日志插入到数据库

    来源:https://blog.csdn.net/m0_71777195/article/details/126539664
  • 相关阅读:
    [转]wcf系列学习——服务托管
    [转]十五天精通WCF——终结篇 那些你需要注意的坑
    [转]十五天精通WCF——第十四天 一起聊聊FaultException
    [转]十五天精通WCF——第十三天 用WCF来玩Rest
    [转]十五天精通WCF——第十二天 说说wcf中的那几种序列化
    [转]十五天精通WCF——第十一天 如何对wcf进行全程监控
    [转]十五天精通WCF——第十天 学会用SvcConfigEditor来简化配置
    [转]十五天精通WCF——第九天 高级玩法之自定义Behavior
    [转]十五天精通WCF——第八天 对“绑定”的最后一点理解
    [转]十五天精通WCF——第七天 Close和Abort到底该怎么用才对得起观众
  • 原文地址:https://www.cnblogs.com/konglxblog/p/16725110.html
Copyright © 2020-2023  润新知