Spring Cloud Alibaba 实现操作日志

2022-06-02 00:00:00 操作 注解 自定义 日志 添加

实现技术:
1. Spring AOP

2. Spring Cloud Stream

3. RocketMQ

前提准备
Centos 7 使用docker安装命令:

1. 启动Rocketmq的依赖服务: NameServer

docker run -d -p 9876:9876 --name rmqserver foxiswho/rocketmq:server-4.7.0

2. 启动rockermq

docker run -d -p 10911:10911 -p 10909:10909\

--name rmqbroker --link rmqserver:namesrv\

-e "NAMESRV_ADDR=namesrv:9876" -e "JAVA_OPTS=-Duser.home=/opt"\

-e "JAVA_OPT_EXT=-server -Xms128m -Xmx128m"\

-v /root/ailp/rocketmq/conf/broker.conf:/etc/rocketmq/broker.conf \

foxiswho/rocketmq:broker-4.7.0

添加依赖
<!-- 我的其他依赖: Sring Boot依赖, Sring Cloud依赖, Spring Cloud Alibaba依赖 -->
...

<!-- rocketmq 消息驱动 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rocketmq</artifactId>
</dependency>
启动类添加注解
/*
1.添加自定义的Sink[消费] 或 Source类[生产者], 默认的是Sink.class和Source.class
2.如果微服务作为消费者就只添加相关的自定义Sink.class接口, 生产者端类似
3.下面代码块创建此两个接口类
*/
@EnableBinding({OperationLogSink.class, OperationLogSource.class})
application.yml 添加配置
spring:
cloud:
# 消息驱动
stream:
rocketmq:
binder:
# rocketmq的访问URL ip:port
name-server: xxx.xxx.xxx.xxx:9876
# rocketmq 必须要定义组,否则启动不起来
group: user-center-group
bindings:
# 自定义的操作日志发送source,生产者
operation-log-output:
# 相当于主题topic
destination: operation-log
content-type: application/json
# 自定义的操作日志接收sink,消费者
operation-log-input:
destination: operation-log
group: operation-log-group
写代码
说明:操作日志不用实现rocketmq的分布式事务

1. 生产者
import org.springframework.cloud.stream.annotation.Output;
import org.springframework.messaging.MessageChannel;

/**
* 自定义source
* 1. 定义source名字
* 2. 启动类@EnableBinger中添加此类
* 3. 配置文件中添加 spring.cloud.stream.rocketmq.bindings.[自定义source].xxx
* 4. 配置文件中添加 spring.cloud.stream.bindings.[自定义source].xxx
*/
public interface OperationLogSource {

String OPERATION_LOG_OUTPUT = "operation-log-output";

@Output(OPERATION_LOG_OUTPUT)
MessageChannel operationLogOutput();

// 可定义多个output结合使用
// 调用时: this.operationLogSource .output1().send(message) ;
// this.operationLogSource .output2().send(message)
// @Output("output1")
// MessageChannel output1();
// @Output("output2")
// MessageChannel output2();

}
2.消费者
import org.springframework.cloud.stream.annotation.Input;
import org.springframework.messaging.SubscribableChannel;

public interface OperationLogSink {

String OPERATION_LOG_INPUT = "operation-log-input";

@Input(OPERATION_LOG_INPUT)
SubscribableChannel operationLogInput();

}
3.消费端消息监听类
import com.kym.api.usercenter.streammessages.OperationLogMessage;
import com.kym.usercenter.rokectmq.streams.sinks.OperationLogSink;
import com.kym.usercenter.service.IOperationLogService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.stereotype.Service;

/**
* topic operation-log 的消息监听类
*/
@Service
@Slf4j
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class OperationLogReceive {

private final IOperationLogService operationLogService;

@StreamListener(OperationLogSink.OPERATION_LOG_INPUT)
public void getMessage(OperationLogMessage message) {
# 业务代码-添加操作日志,基本的save操作
this.operationLogService.addOperationLogMessage(message);
}


}
4. 操作日志切面注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OperationAnnotation {

/**
* 操作内容
* @return String
*/
String content() default "";
}
5. 注解AOP实现
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Component;
import org.springframework.util.MimeTypeUtils;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Arrays;

/**
* 操作日志AOP
* -- 保存过程由 RocketMQ 异步执行
*/
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class OperationLogAspect {

private final OperationLogSource operationLogSource;

/**
* 操作日志AOP
* @param joinPoint 切入点
*/
@AfterReturning("@annotation(com.kym.common.base.aops.logs.OperationAnnotation)")
public void OperationHandler(JoinPoint joinPoint){
log.debug("开始记录操作日志》》》》》》》》》》》》》");

// 获取主键所在方法
MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
Method method = methodSignature.getMethod();
// 获取当前会话request
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes;
HttpServletRequest request = attributes.getRequest();
// 获取注解
OperationAnnotation operationAnnotation = method.getAnnotation(OperationAnnotation.class);

// 添加参数信息
OperationLogMessage logMessage = OperationLogMessage.builder()
.methodName(method.getName())
.className(method.getDeclaringClass().getName())
.methodParams(Arrays.toString(joinPoint.getArgs()))
.content(operationAnnotation.content())
.userId(request.getHeader("userId")) # 通过request中token获取
.ip(WebRequestUtils.getIp(request)) # 获取真实IP,百度一堆
.build();


// 转换为消息体
Message<OperationLogMessage> message =
MessageBuilder
.withPayload(logMessage)
.setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON)
.build();
// 发送消息体
operationLogSource.operationLogOutput().send(message);
log.debug("结束记录操作日志》》》》》》》》》》》》》");
}

}
6. 具体使用, 使用http工具请求即可
// 插入操作日志,添加注解
@OperationAnnotation(content = "保存xxx")
@PutMapping(value = "/save")
public R save(@RequestBody XxxDTO xxxDTO){
try{
// 保存业务
...
return R.success();
} catch (VerifyDataException e) {
throw new ApiException(500, e.getMessage());
}
}

————————————————
版权声明:本文为CSDN博主「cd4479163」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/cd4479163/article/details/122700236

相关文章