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

3552 篇文章 114 订阅

简介

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

思路

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

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

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

1、日志表结构

表结构(sys_log):

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

实体类(SysLog):

@Data
public class SysLog  {
 
    private static final long serialVersionUID = 1L;
 
    /**
     * 唯一ID
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;
    /**
     * 操作用户id
     */
    private Integer optId;
    /**
     * 操作用户名
     */
    private String optName;
    /**
     * 日志类型
     */
    private String logType;
    /**
     * 日志信息(具体方法名)
     */
    private String logMessage;
    /**
     * 创建时间
     */
    private Date createTime;
 
}

2、注解

注解(SystemLog):

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

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

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

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SystemLog {
 
    SystemLogEnum type();
 
}

枚举(SystemLogEnum):

限定日志类型范围

public enum SystemLogEnum {
 
    SAVE_LOG("保存"),
    DELETE_LOG("删除"),
    REGISTER_LOG("注册"),
    LOGIN_LOG("登录"),
    LAUD_LOG("点赞"),
    COLLECT_LOG("收藏"),
    THROW_LOG("异常"),
    ;
    private String type;
 
    SystemLogEnum(String type) {
        this.type = type;
    }
 
    public String getType() {
        return type;
    }
}

3、AOP切面

AOP(SysLogAspect):

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

Component
@Aspect
@Slf4j
public class SysLogAspect {
 
    @Autowired
    MqStream stream;
 
    //切点
    @Pointcut("@annotation(cn.zdxh.commons.utils.SystemLog)")
    public void logPointcut(){}
 
    //后置通知
    @After("logPointcut()")
    public void afterLog(JoinPoint joinPoint) {
        //一般日志
        SysLog sysLog = wrapSysLog(joinPoint);
 
        log.info("Log值:"+sysLog);
 
        //发送mq消息
        stream.logOutput().send(MessageBuilder.withPayload(sysLog).build());
 
    }
 
    //异常通知
    @AfterThrowing(value = "logPointcut()", throwing = "e")
    public void throwingLog(JoinPoint joinPoint, Exception e) {
        //异常日志
        SysLog sysLog = wrapSysLog(joinPoint);
        sysLog.setLogType(SystemLogEnum.THROW_LOG.getType());
        sysLog.setLogMessage(sysLog.getLogMessage()+"==="+e);
 
        log.info("异常Log值:"+sysLog);
 
        //发送mq消息
        stream.logOutput().send(MessageBuilder.withPayload(sysLog).build());
    }
 
    /**
     * 封装SysLog对象
     * @param joinPoint
     * @return
     */
    public SysLog wrapSysLog(JoinPoint joinPoint){
        //获取请求响应对象
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        MethodSignature signature = (MethodSignature)joinPoint.getSignature();
        SysLog sysLog = new SysLog();
 
        //获取方法全路径
        String methodName = signature.getDeclaringTypeName()+"."+signature.getName();
        //获取注解参数值
        SystemLog systemLog = signature.getMethod().getAnnotation(SystemLog.class);
        //从header取出token
        String token = request.getHeader("token");
        if (!StringUtils.isEmpty(token)) {
            //操作人信息
            Integer userId = JwtUtils.getUserId(token);
            String username = JwtUtils.getUsername(token);
            sysLog.setOptId(userId);
            sysLog.setOptName(username);
        }
        if (!StringUtils.isEmpty(systemLog.type())){
            sysLog.setLogType(systemLog.type().getType());
        }
        sysLog.setLogMessage(methodName);
        sysLog.setCreateTime(new Date());
        return sysLog;
    }
 
}

3、RabbitMQ消息队列

MQ:

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

Spring Cloud Stream:

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

 

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

@Component
public interface MqStream {
 
    String LOG_INPUT = "log_input";
 
    String LOG_OUTPUT = "log_output";
  
    @Input(LOG_INPUT)
    SubscribableChannel logInput();
 
    @Output(LOG_OUTPUT)
    MessageChannel logOutput();
 
}

(2)MQ生产者

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

引入依赖:

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

<!--Spring Cloud Stream-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-stream</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>

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

@SpringBootApplication(scanBasePackages = {"cn.zdxh.user","cn.zdxh.commons"})
@EnableEurekaClient
@MapperScan("cn.zdxh.user.mapper")
@EnableBinding(MqStream.class) //开启绑定
@EnableFeignClients 
public class YouquServiceProviderUserApplication {
 
    public static void main(String[] args) {
        SpringApplication.run(YouquServiceProviderUserApplication.class, args);
    }
 
}

yml配置:

在生产者端设置output

  • destination: 相当于rabbitmqexchange

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

  • binder: 需要绑定的MQ

#Spring Cloud Stream相关配置
spring:
  cloud:
    stream:
      bindings: # exchange与queue绑定
        log_output: # 日志生产者设置output
          destination: log.exchange
          content-type: application/json
          group: log.queue
          binder: youqu_rabbit #自定义名称
      binders:
        youqu_rabbit:  #自定义名称
          type: rabbit
          environment:
            spring:
              rabbitmq:
                host: localhost
                port: 5672
                username: guest
                password: 25802580

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

(3)MQ消费者

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

yml配置:

在生产者端设置input

spring:
  cloud:  # Spring Cloud Stream 相关配置
    stream:
      bindings: # exchange与queue绑定
        log_input: # 日志消费者设置input
          destination: log.exchange
          content-type: application/json
          group: log.queue
          binder: youqu_rabbit
      binders:
        youqu_rabbit:
          type: rabbit
          environment:
            spring:
              rabbitmq:
                host: localhost
                port: 5672
                username: guest
                password: 25802580

消费者监听(LogMqListener):

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

@Service
@Slf4j
public class LogMqListener {
 
    @Autowired
    SysLogService sysLogService;
 
    @StreamListener(MqStream.LOG_INPUT)
    public void input(SysLog sysLog)  {
        log.info("开始记录日志========================");
 
        sysLogService.save(sysLog);
 
        log.info("结束记录日志========================");
 
    }
}

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

4、应用

简述:

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

5、总结

流程:

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要通过自定义注解AOP实现Spring Security配置指定接口不需要Token才能访问,可以按照以下步骤进行操作: 1. 创建一个自定义注解,例如`@NoTokenRequired`,用于标识不需要Token的接口。 ```java @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface NoTokenRequired { } ``` 2. 创建一个切面类,用于拦截带有`@NoTokenRequired`注解的方法,并跳过Spring Security的Token验证。 ```java @Aspect @Component public class TokenValidationAspect { @Before("@annotation(com.example.NoTokenRequired)") public void skipTokenValidation(JoinPoint joinPoint) { // 跳过Spring Security的Token验证逻辑 SecurityContextHolder.getContext().setAuthentication(null); } } ``` 3. 配置Spring Security,将AOP切面类添加到Spring Security的配置中。 ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private TokenValidationAspect tokenValidationAspect; @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() // 配置需要Token验证的接口 .anyRequest().authenticated() .and() .csrf().disable(); // 将AOP切面类添加到Spring Security的配置中 http.addFilterBefore(tokenValidationAspect, UsernamePasswordAuthenticationFilter.class); } } ``` 4. 在需要不需要Token验证的接口上,添加`@NoTokenRequired`注解。 ```java @RestController public class ExampleController { @NoTokenRequired @GetMapping("/example") public String example() { return "This API does not require Token"; } } ``` 这样配置之后,带有`@NoTokenRequired`注解的接口将不会进行Spring Security的Token验证,即可在没有Token的情况下访问该接口。其他接口仍然需要进行Token验证。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值