日志切面功能整合

This commit is contained in:
Tankaikai
2025-02-13 14:29:14 +08:00
parent cde964492c
commit 8d8e338da8
29 changed files with 802 additions and 15 deletions

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.czg</groupId>
<artifactId>cash-common</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>cash-common-log</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>com.czg</groupId>
<artifactId>cash-common-tools</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.czg</groupId>
<artifactId>cash-common-redis</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.czg</groupId>
<artifactId>cash-common-sa-token</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>com.mybatis-flex</groupId>
<artifactId>mybatis-flex-spring-boot3-starter</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
</build>
</project>

View File

@@ -0,0 +1,62 @@
package com.czg.log;
import cn.hutool.core.exceptions.ExceptionUtil;
import com.alibaba.fastjson2.JSON;
import com.czg.config.RedisCst;
import com.czg.service.RedisService;
import com.mybatisflex.core.row.Db;
import com.mybatisflex.core.row.Row;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 从Redis队列中获取Log保存到DB
*
* @author admin admin@cashier.com
* @since 1.0.0
*/
@Slf4j
@Component
public class LogConsumer implements CommandLineRunner {
@Resource
private RedisService redisService;
private final ScheduledExecutorService scheduledService = new ScheduledThreadPoolExecutor(1,
new BasicThreadFactory.Builder().namingPattern("log-consumer-schedule-pool-%d").daemon(true).build());
@Override
public void run(String... args) {
//上次任务结束后等待10秒钟再执行下次任务
scheduledService.scheduleWithFixedDelay(() -> {
try {
receiveQueue();
} catch (Exception e) {
log.error("LogConsumer Error" + ExceptionUtil.stacktraceToString(e));
}
}, 1, 10, TimeUnit.SECONDS);
}
private void receiveQueue() {
String key = RedisCst.SYS_LOG_KEY;
//每次插入100条
int count = 100;
for (int i = 0; i < count; i++) {
String log = (String) redisService.rightPop(key);
if (log == null) {
return;
}
// 操作日志入库
Row row = JSON.parseObject(log, Row.class);
Db.insert("operation_log",row);
}
}
}

View File

@@ -0,0 +1,18 @@
package com.czg.log.annotation;
import java.lang.annotation.*;
/**
* 操作日志注解
*
* @author admin admin@cashier.com
* @since 1.0.0
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogOperation {
String value() default "";
}

View File

@@ -0,0 +1,137 @@
package com.czg.log.aspect;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.JakartaServletUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.czg.log.annotation.LogOperation;
import com.czg.log.enums.LogTypeEnum;
import com.czg.log.enums.OperationStatusEnum;
import com.czg.log.producer.LogProducer;
import com.czg.sa.StpKit;
import com.czg.utils.AddressUtil;
import com.czg.utils.HttpContextUtil;
import com.mybatisflex.core.keygen.impl.SnowFlakeIDKeyGenerator;
import com.mybatisflex.core.row.Row;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.Map;
/**
* 操作日志,切面处理类
*
* @author admin admin@cashier.com
* @since 1.0.0
*/
@Aspect
@Component
public class LogOperationAspect {
@Resource
private LogProducer logProducer;
@Pointcut("@annotation(com.czg.log.annotation.LogOperation)")
public void logPointCut() {
}
@Around("logPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
long beginTime = System.currentTimeMillis();
try {
//执行方法
Object result = point.proceed();
//执行时长(毫秒)
long time = System.currentTimeMillis() - beginTime;
//保存日志
saveLog(point, time, OperationStatusEnum.SUCCESS.value());
return result;
} catch (Exception e) {
//执行时长(毫秒)
long time = System.currentTimeMillis() - beginTime;
String errorInfo = ExceptionUtil.stacktraceToString(e);
//保存日志
saveLog(point, time, OperationStatusEnum.FAIL.value(), errorInfo);
throw e;
}
}
private void saveLog(ProceedingJoinPoint joinPoint, long time, Integer status) {
saveLog(joinPoint, time, status, null);
}
private void saveLog(ProceedingJoinPoint joinPoint, long time, Integer status, String errorInfo) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
Row log = new Row();
LogOperation annotation = method.getAnnotation(LogOperation.class);
if (annotation != null) {
//注解上的描述
log.set("operation", annotation.value());
}
//登录用户信息
Long shopId = StpKit.USER.getShopId();
Long createUserId = StpKit.USER.getLoginIdAsLong();
//TODO SA-TOKEN 暂未整合当前登录人信息,此处仅为临时账号
String createUserName = "temp-account";
log.set("createUserId", createUserId);
log.set("createUserName", createUserName);
log.set("shopId", shopId);
log.set("type", LogTypeEnum.INFO.value());
if(StrUtil.isNotBlank(errorInfo)){
log.set("type", LogTypeEnum.ERROR.value());
}
log.set("status", status);
log.set("requestTime", (int) time);
log.set("createTime", LocalDateTime.now());
//请求相关信息
HttpServletRequest request = HttpContextUtil.getHttpServletRequest();
String ip = JakartaServletUtil.getClientIP(request);
log.set("ip", ip);
log.set("location", AddressUtil.getRealAddressByIp(ip));
log.set("userAgent", request.getHeader(HttpHeaders.USER_AGENT));
log.set("requestUri", request.getRequestURI());
log.set("requestMethod", request.getMethod());
log.set("errorInfo", errorInfo);
//请求参数
Object[] args = joinPoint.getArgs();
Map<String, String> param = JakartaServletUtil.getParamMap(request);
try {
String params = JSON.toJSONString(args[0]);
log.set("requestParams", params);
boolean isJsonObject = JSONUtil.isTypeJSONObject(params);
if(isJsonObject){
JSONObject reqData = JSON.parseObject(params);
param.forEach(reqData::putIfAbsent);
log.set("requestParams", reqData.toJSONString());
}
} catch (Exception ignored) {
}
log.set("id", new SnowFlakeIDKeyGenerator().nextId());
//保存到Redis队列里
logProducer.saveLog(JSON.toJSONString(log.toUnderlineKeysMap()));
}
}

View File

@@ -0,0 +1,28 @@
package com.czg.log.enums;
/**
* 日志类型枚举
*
* @author admin admin@cashier.com
* @since 1.0.0
*/
public enum LogTypeEnum {
/**
* INFO
*/
INFO("info"),
/**
* ERROR
*/
ERROR("error");
private final String value;
LogTypeEnum(String value) {
this.value = value;
}
public String value() {
return this.value;
}
}

View File

@@ -0,0 +1,28 @@
package com.czg.log.enums;
/**
* 操作状态枚举
*
* @author admin admin@cashier.com
* @since 1.0.0
*/
public enum OperationStatusEnum {
/**
* 失败
*/
FAIL(0),
/**
* 成功
*/
SUCCESS(1);
private final int value;
OperationStatusEnum(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}

View File

@@ -0,0 +1,36 @@
package com.czg.log.producer;
import cn.hutool.core.thread.ThreadFactoryBuilder;
import com.czg.config.RedisCst;
import com.czg.service.RedisService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Component;
import java.util.concurrent.*;
/**
* 日志通过redis队列异步保存到数据库
*
* @author admin admin@cashier.com
* @since 1.0.0
*/
@Component
public class LogProducer {
@Resource
private RedisService redisService;
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNamePrefix("log-producer-pool").build();
ExecutorService pool = new ThreadPoolExecutor(5, 200, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
/**
* 保存Log到Redis消息队列
*/
public void saveLog(String log) {
String key = RedisCst.SYS_LOG_KEY;
//异步保存到队列
pool.execute(() -> redisService.leftPush(key, log, RedisService.NOT_EXPIRE));
}
}