This commit is contained in:
clay
2024-03-06 17:44:09 +08:00
commit adaec0eadd
1493 changed files with 219939 additions and 0 deletions

60
common/common-log/pom.xml Normal file
View File

@@ -0,0 +1,60 @@
<?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">
<parent>
<artifactId>common</artifactId>
<groupId>cn.fateverse</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>common-log</artifactId>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!--安全依赖获取上下文信息-->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<optional>true</optional>
</dependency>
<!-- 解析客户端操作系统、浏览器等 -->
<dependency>
<groupId>eu.bitwalker</groupId>
<artifactId>UserAgentUtils</artifactId>
</dependency>
<!-- Common Core 核心依赖 -->
<dependency>
<groupId>cn.fateverse</groupId>
<artifactId>common-core</artifactId>
</dependency>
<dependency>
<groupId>cn.fateverse</groupId>
<artifactId>log-api</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>cn.fateverse</groupId>
<artifactId>common-dubbo</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,4 @@
# Log
- 后置对此处的内容进行自定配置,对应
- org.springframework.boot.autoconfigure.AutoConfiguration.imports配置文件

View File

@@ -0,0 +1,29 @@
package cn.fateverse.common.log;
import cn.fateverse.common.log.aspect.LogAspect;
import cn.fateverse.common.log.config.OperationProperties;
import cn.fateverse.common.log.service.OperationService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
/**
* @author Clay
* @date 2023-05-21
*/
@ConditionalOnWebApplication
@EnableConfigurationProperties({OperationProperties.class})
public class LogAutoConfiguration {
@Bean
public LogAspect logAspect(){
return new LogAspect();
}
@Bean
public OperationService operationService(OperationProperties properties) {
return new OperationService(properties);
}
}

View File

@@ -0,0 +1,38 @@
package cn.fateverse.common.log.annotation;
import cn.fateverse.common.log.enums.BusinessType;
import cn.fateverse.common.log.enums.OperateType;
import java.lang.annotation.*;
/**
* 自定义操作日志记录注解
*
* @author Clay
* @date 2022/11/1
*/
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
/**
* 日志记录名称
*/
String title() default "";
/**
* 功能
*/
BusinessType businessType() default BusinessType.OTHER;
/**
* 操作人类别
*/
OperateType operatorType() default OperateType.MANAGE;
/**
* 是否保存请求的参数
*/
boolean isSaveRequestData() default true;
}

View File

@@ -0,0 +1,217 @@
package cn.fateverse.common.log.aspect;
import cn.fateverse.common.core.utils.HttpServletUtils;
import cn.fateverse.common.core.utils.IpUtils;
import cn.fateverse.common.log.annotation.Log;
import cn.fateverse.common.log.config.OperationProperties;
import cn.hutool.core.util.StrUtil;
import cn.fateverse.common.core.constant.UserConstants;
import cn.fateverse.common.core.utils.ReflectUserUtils;
import cn.fateverse.common.log.enums.LogLeve;
import cn.fateverse.common.log.enums.OperateType;
import cn.fateverse.common.log.service.OperationService;
import cn.fateverse.log.entity.OperationLog;
import com.alibaba.fastjson2.JSON;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.HandlerMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Map;
/**
* @author Clay
* @date 2022/11/1
*/
@Slf4j
@Aspect
public class LogAspect {
@Autowired
private OperationService operationService;
@Autowired
private OperationProperties operationProperties;
private final ThreadLocal<Long> startTime = new ThreadLocal<>();
@Around("@within(log) || @annotation(log)")
public Object before(ProceedingJoinPoint point, Log log) throws Throwable {
long startTime = System.currentTimeMillis();
try {
Object proceed = point.proceed(point.getArgs());
HttpServletRequest request = HttpServletUtils.getRequest();
handleLog(point, proceed, request, null, log, true, startTime);
return proceed;
}catch (Throwable e){
HttpServletRequest request = HttpServletUtils.getRequest();
handleLog(point, null, request, e, log, false, startTime);
throw e;
}
}
protected Boolean check(Boolean success) {
if (!operationProperties.getEnabled()) {
return false;
}
if (operationProperties.getLevel().equals(LogLeve.ALL)) {
return true;
}
if (success & operationProperties.getLevel().equals(LogLeve.SUCCESS)) {
return true;
}
return !success & operationProperties.getLevel().equals(LogLeve.ERROR);
}
protected void handleLog(JoinPoint point, Object jsonResult, HttpServletRequest request, Throwable e, Log controllerLog, Boolean success, Long time) {
if (!check(success)) {
return;
}
try {
controllerLog = chickLog(point, controllerLog);
OperationLog operationLog = new OperationLog();
//请求地址
operationLog.setOperIp(IpUtils.getIpAdder(request));
operationLog.setOperUrl(request.getRequestURI());
// 设置请求方式
operationLog.setRequestMethod(request.getMethod());
//处理设置注解上的参数
getControllerMethodDescription(point, controllerLog, operationLog, request);
// 设置操作人
setOperName(operationLog);
// 设置方法名称
String className = point.getTarget().getClass().getName();
String methodName = point.getSignature().getName();
operationLog.setMethod(className + "." + methodName + "()");
operationService.asyncExecute(operationLog,jsonResult,e,time);
} catch (Exception exp) {
// 记录本地异常日志
log.error("异常信息:{}", exp.getMessage());
exp.printStackTrace();
}
}
/**
* 设置操作人
*
* @param operationLog
*/
public void setOperName(OperationLog operationLog) {
try {
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (null != principal && !UserConstants.ANONYMOUS_USER.equals(principal)) {
String userName = ReflectUserUtils.getUsername(principal);
operationLog.setOperName(userName);
String userId = ReflectUserUtils.getUserId(principal);
operationLog.setUserId(Long.valueOf(userId));
}
} catch (Exception e) {
log.info("当前接口不是由登录后的用户触发的!");
operationLog.setOperatorType(OperateType.OTHER.ordinal());
}
}
/**
* 获取控制前方法上的描述
*
* @param point
* @param log
* @param operationLog
* @param request
* @throws Exception
*/
public void getControllerMethodDescription(JoinPoint point, Log log, OperationLog operationLog, HttpServletRequest request) throws Exception {
//设置action动作
operationLog.setBusinessType(log.businessType().ordinal());
//设置标题
operationLog.setTitle(log.title());
//设置操作人类型
operationLog.setOperatorType(log.operatorType().ordinal());
// 是否需要保存request,参数和值
if (log.isSaveRequestData()) {
// 获取参数的信息,传入到数据库中
setRequestValue(point, operationLog, request);
}
}
/**
* 获取请求的参数放到log中
*
* @param operationLog 操作日志
* @param request
* @throws Exception 异常
*/
private void setRequestValue(JoinPoint joinPoint, OperationLog operationLog, HttpServletRequest request) throws Exception {
String requestMethod = operationLog.getRequestMethod();
if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) {
String params = argsArrayToString(joinPoint.getArgs());
operationLog.setOperParam(StrUtil.sub(params, 0, 2000));
} else {
Map<?, ?> paramsMap = (Map<?, ?>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
operationLog.setOperParam(StrUtil.sub(paramsMap.toString(), 0, 2000));
}
}
/**
* 参数拼装
*/
private String argsArrayToString(Object[] paramsArray) {
StringBuilder params = new StringBuilder();
if (paramsArray != null && paramsArray.length > 0) {
for (Object object : paramsArray) {
if (!isFilterObject(object)) {
Object jsonObj = JSON.toJSON(object);
params.append(jsonObj.toString()).append(" ");
}
}
}
return params.toString().trim();
}
/**
* 判断是否需要过滤的对象。
*
* @param o 对象信息。
* @return 如果是需要过滤的对象则返回true否则返回false。
*/
public boolean isFilterObject(final Object o) {
return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse;
}
/**
* 检查Log相关信息
*
* @param point
* @param log
* @return
*/
public Log chickLog(JoinPoint point, Log log) {
if (log == null) {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
if (method != null) {
log = method.getAnnotation(Log.class);
}
}
return log;
}
}

View File

@@ -0,0 +1,52 @@
package cn.fateverse.common.log.config;
import cn.fateverse.common.log.enums.LogLeve;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author Clay
* @date 2023-05-25
*/
@ConfigurationProperties(prefix = "operation-log")
public class OperationProperties {
private Boolean enabled;
// all success error
private LogLeve level;
private Integer cacheSize;
public Boolean getEnabled() {
return enabled == null || enabled;
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
public LogLeve getLevel() {
if (null == level){
return LogLeve.ALL;
}
return level;
}
public void setLevel(LogLeve level) {
this.level = level;
}
public Integer getCacheSize() {
if (null == cacheSize){
cacheSize = 8;
}
return cacheSize;
}
public void setCacheSize(Integer cacheSize) {
this.cacheSize = cacheSize;
}
}

View File

@@ -0,0 +1,20 @@
package cn.fateverse.common.log.enums;
/**
* 操作状态
*
* @author Clay
*/
public enum BusinessState {
/**
* 成功
*/
SUCCESS,
/**
* 失败
*/
FAIL,
}

View File

@@ -0,0 +1,67 @@
package cn.fateverse.common.log.enums;
/**
* 业务操作类型
* @author Clay
* @date 2022/11/1
*/
public enum BusinessType {
/**
* 其它
*/
OTHER,
/**
* 新增
*/
INSERT,
/**
* 修改
*/
UPDATE,
/**
* 删除
*/
DELETE,
/**
* 授权
*/
GRANT,
/**
* 导出
*/
EXPORT,
/**
* 导入
*/
IMPORT,
/**
* 强退
*/
FORCE,
/**
* 执行代码
*/
EXECUTE_CODE,
/**
* 生成代码
*/
GENCODE,
/**
* 清空数据
*/
CLEAN,
/**
* 解除绑定
*/
BIND,
}

View File

@@ -0,0 +1,19 @@
package cn.fateverse.common.log.enums;
/**
* @author Clay
* @date 2023-05-25
*/
public enum LogLeve {
ALL("all"),
SUCCESS("success"),
ERROR("error"),
;
final String value;
LogLeve(String value) {
this.value = value;
}
}

View File

@@ -0,0 +1,24 @@
package cn.fateverse.common.log.enums;
/**
* 操作人类别
*
* @author Clay
* @date 2022/11/1
*/
public enum OperateType {
/**
* 其它
*/
OTHER,
/**
* 后台用户
*/
MANAGE,
/**
* 手机端用户
*/
MOBILE
}

View File

@@ -0,0 +1,111 @@
package cn.fateverse.common.log.service;
import cn.fateverse.common.core.enums.ResultEnum;
import cn.fateverse.common.core.exception.BaseException;
import cn.fateverse.common.core.result.Result;
import cn.fateverse.common.log.config.OperationProperties;
import cn.fateverse.common.log.enums.BusinessState;
import cn.fateverse.log.dubbo.DubboLogService;
import cn.fateverse.log.entity.OperationLog;
import com.alibaba.fastjson2.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.scheduling.annotation.Async;
import org.springframework.util.ObjectUtils;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @author Clay
* @date 2023-08-03
*/
@Slf4j
public class OperationService {
private final ScheduledExecutorService service = new ScheduledThreadPoolExecutor(1);
@DubboReference
private DubboLogService logService;
private final OperationProperties properties;
private final List<OperationLog> operationLogListCache;
public OperationService(OperationProperties properties) {
this.properties = properties;
this.operationLogListCache = new ArrayList<>(properties.getCacheSize());
}
@PostConstruct
public void init() {
service.scheduleAtFixedRate(() -> {
synchronized (operationLogListCache) {
if (!operationLogListCache.isEmpty()) {
logService.batchSaveLog(operationLogListCache);
operationLogListCache.clear();
}
}
}, 1, 1, TimeUnit.MINUTES);
}
@Async
public void asyncExecute(OperationLog operationLog, Object jsonResult, Throwable e, Long time) {
operationLog.setState(BusinessState.SUCCESS.ordinal());
// 返回参数
if (jsonResult instanceof Result) {
Result<Object> result = (Result<Object>) jsonResult;
operationLog.setJsonResult(JSON.toJSONString(jsonResult));
if (result.getCode() != ResultEnum.SUCCESS.code) {
operationLog.setState(1);
}
operationLog.setJsonResult(JSON.toJSONString(result));
} else {
operationLog.setState(1);
operationLog.setJsonResult(JSON.toJSONString(jsonResult));
}
if (null != e) {
operationLog.setState(BusinessState.FAIL.ordinal());
if (!(e instanceof BaseException)) {
//将错误栈打印到错误信息中
String trace = ExceptionUtils.getStackTrace(e);
if (!ObjectUtils.isEmpty(trace)) {
operationLog.setErrorStackTrace(trace);
}
}
operationLog.setErrorMsg(e.getMessage());
}
if (null != time) {
operationLog.setConsumeTime(System.currentTimeMillis() - time);
}
operationLog.setOperTime(new Date());
saveLog(operationLog);
}
/**
* 保存日志,先存入到缓存之中
*
* @param operationLog 操作日志
*/
private void saveLog(OperationLog operationLog) {
synchronized (operationLogListCache) {
operationLogListCache.add(operationLog);
if (operationLogListCache.size() >= properties.getCacheSize()) {
logService.batchSaveLog(operationLogListCache);
operationLogListCache.clear();
}
}
}
}

View File

@@ -0,0 +1,21 @@
{
"groups": [
{
"name": "operation-log",
"type": "cn.fateverse.log.config.OperationProperties",
"sourceType": "cn.fateverse.log.config.OperationProperties"
}
],
"properties": [
{
"name": "operation-log.enabled",
"type": "java.lang.Boolean",
"sourceType": "cn.fateverse.log.config.OperationProperties"
},
{
"name": "operation-log.level",
"type": "cn.fateverse.common.log.enums.LogLeve",
"sourceType": "cn.fateverse.log.config.OperationProperties"
}
]
}

View File

@@ -0,0 +1 @@
cn.fateverse.common.log.LogAutoConfiguration

View File

@@ -0,0 +1,4 @@
log4j.rootLogger=WARN, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8" ?>
<configuration scan="false">
<property name="LOG_HOME" value="/home/clay/logs"/><!-- 上线阶段修改 -->
<!-- 日志输出到控制台 -->
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>%green(%d{yyyy-MM-dd HH:mm:ss.SSS}) %highlight(%-5level) %cyan([%thread] %60.60logger) -
%highlight(%msg%n)
</pattern>
</layout>
</appender>
<!-- 日志输出到文件 -->
<appender name="alllog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/spring.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/backup/%d{yyyy-MM-dd}/Loyalty.%d{yyyy-MM-dd}.log</fileNamePattern>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 根Logger -->
<root level="debug">
<appender-ref ref="stdout"/>
<appender-ref ref="alllog"/>
</root>
</configuration>

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8" ?>
<configuration scan="false">
<property name="LOG_HOME" value="/home/clay/logs"/><!-- 上线阶段修改 -->
<!-- 日志输出到控制台 -->
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>%green(%d{yyyy-MM-dd HH:mm:ss.SSS}) %highlight(%-5level) %cyan([%thread] %60.60logger) -
%highlight(%msg%n)
</pattern>
</layout>
</appender>
<!-- 日志输出到文件 -->
<appender name="alllog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/spring.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/backup/%d{yyyy-MM}/%d{yyyy-MM-dd}.log</fileNamePattern>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 根Logger -->
<root level="info">
<appender-ref ref="alllog"/>
</root>
</configuration>