feat:V3.1.1整合remi-comm到remi-cloud中

This commit is contained in:
dianjiu
2024-09-11 17:41:17 +08:00
parent e111c59a69
commit 28d62f6cf4
245 changed files with 20975 additions and 154 deletions

View File

@@ -0,0 +1,69 @@
# 工程简介
瑞米平台公共的日志模块
## 操作日志
主要面向用户的,要求简单易懂,反映出用户所做的动作。
### 核心功能
- 要实现以下功能:
- 自定义spring boot starter
- 定义日志注解
- AOP拦截日志注解方法
- 定义日志动态内容模板
- 模板中又需要实现:
- 动态模板表达式解析:用强大的SpEL来解析表达式
- 自定义函数:支持目标方法前置/后置的自定义函数
##
### SQL脚本
```sql
DROP TABLE IF EXISTS t_system_log;
CREATE TABLE t_system_log
(
id VARCHAR(32) NOT NULL COMMENT '日志编号',
user_id VARCHAR(32) COMMENT '用户ID',
user_name VARCHAR(255) COMMENT '用户名称',
app_code VARCHAR(255) COMMENT '应用编码',
app_name VARCHAR(255) COMMENT '应用名称',
business_no VARCHAR(255) COMMENT '业务流水号',
business_type VARCHAR(255) COMMENT '业务类型(新增、修改、删除、查询、导入、导出、其他)',
log_content LONGTEXT COMMENT '日志模板内容',
log_extras LONGTEXT COMMENT '额外的日志内容',
request_url VARCHAR(255) COMMENT '请求地址',
request_ip VARCHAR(255) COMMENT '请求IP',
request_type VARCHAR(255) COMMENT '请求类型(POST、PUT、OPTIONS、DELETE)',
content_type VARCHAR(255) COMMENT '请求报文类型(map、json)',
request_body LONGTEXT COMMENT '请求报文',
response_body LONGTEXT COMMENT '响应报文',
request_time DATETIME COMMENT '请求时间',
response_time DATETIME COMMENT '响应时间',
execute_time BIGINT COMMENT '请求耗时',
trace_id VARCHAR(255) COMMENT '链路追踪ID',
error_info LONGTEXT COMMENT '异常信息',
user_agent VARCHAR(255) COMMENT '浏览器、操作系统、UI引擎标识',
revision INTEGER(11) COMMENT '乐观锁',
is_deleted INTEGER(1) COMMENT '逻辑删除;0-未删除 1-已删除',
group_tenant_id VARCHAR(32) COMMENT '部门租户ID',
company_tenant_id VARCHAR(32) COMMENT '公司租户ID',
created_by VARCHAR(32) COMMENT '创建人',
created_at DATETIME COMMENT '创建时间',
updated_by VARCHAR(32) COMMENT '更新人',
updated_at DATETIME COMMENT '更新时间',
PRIMARY KEY (id)
) COMMENT = '系统日志表';
```
## 系统日志
主要用于开发者调试排查系统问题的,不要求固定格式和可读性。
###
## 延伸阅读
https://tech.meituan.com/2021/09/16/operational-logbook.html
https://juejin.cn/post/7075184311397253128
http://mysql.taobao.org/monthly/2017/09/08/

View File

@@ -0,0 +1,69 @@
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>remi-comm-log</artifactId>
<name>remi-comm-log</name>
<description>remi-comm-log</description>
<parent>
<groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-comm</artifactId>
<version>3.1.1</version>
</parent>
<!-- 打包上传到私服, 需要和配置文件中的server节点配合使用 -->
<distributionManagement>
<repository>
<id>maven-releases</id>
<name>release</name>
<url>https://repository.njydsz.com/repository/maven-releases/</url>
</repository>
<snapshotRepository>
<id>maven-snapshots</id>
<name>snapshot</name>
<url>https://repository.njydsz.com/repository/maven-snapshots/</url>
</snapshotRepository>
</distributionManagement>
<dependencies>
<!--spring-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--nacos-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--公共依赖-->
<dependency>
<groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-comm-core</artifactId>
</dependency>
<dependency>
<groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-comm-util</artifactId>
</dependency>
<dependency>
<groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-comm-redis</artifactId>
</dependency>
<!--项目依赖-->
<dependency>
<groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-sys-api</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,23 @@
package ai.remi.comm.log.annotation;
import ai.remi.comm.log.config.LogRecordAutoConfigure;
import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.Order;
import java.lang.annotation.*;
/**
* @author DianJiu 【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @date 2022-07-19
* @desc 日志记录开启注解
*/
@Documented
@Inherited
@Order(Integer.MIN_VALUE)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(LogRecordAutoConfigure.class)
public @interface EnableLogRecord {
}

View File

@@ -0,0 +1,74 @@
package ai.remi.comm.log.annotation;
import ai.remi.comm.log.enums.BusinessType;
import ai.remi.comm.log.enums.OperatorType;
import org.springframework.core.annotation.Order;
import java.lang.annotation.*;
/**
* @author DianJiu 【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @date 2022-07-19
* @desc 操作日志记录
*/
@Inherited
@Documented
@Order(Integer.MIN_VALUE)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogRecord {
/**
* 接口名称
*/
String content() default "";
/**
* 功能操作类型
*/
BusinessType businessType() default BusinessType.OTHER;
/**
* 操作人类别
*/
OperatorType operatorType() default OperatorType.MANAGE;
/**
* 是否保存请求的参数
*/
boolean isSaveRequestData() default true;
/**
* 是否保存响应的参数
*/
boolean isSaveResponseData() default true;
/**
* 是否保存异常的信息
*/
boolean isSaveErrorInfo() default true;
/**
* 在方法成功执行后打点,记录方法的执行时间发送到指标系统,默认关闭
*
* @return
*/
boolean recordSuccessMetrics() default false;
/**
* 在方法成功失败后打点,记录方法的执行时间发送到指标系统,默认关闭
*/
boolean recordFailMetrics() default false;
/**
* 出现异常后忽略异常返回默认值,默认关闭
*/
boolean ignoreException() default false;
/**
* 排除指定的请求参数
*/
public String[] excludeParamNames() default {};
}

View File

@@ -0,0 +1,399 @@
package ai.remi.comm.log.aspect;
import ai.remi.comm.core.spring.SpringProperties;
import ai.remi.comm.log.annotation.LogRecord;
import ai.remi.comm.log.domain.OperateLog;
import ai.remi.comm.log.enums.BusinessStatus;
import ai.remi.comm.log.filter.PropertyPreExcludeFilter;
import ai.remi.comm.log.service.AsyncLogService;
import ai.remi.comm.redis.service.RedisService;
import ai.remi.comm.util.ip.IpAddrUtils;
import ai.remi.comm.util.ip.IpInfoUtils;
import ai.remi.comm.util.string.StringUtils;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import ai.remi.comm.log.enums.RedisKeyEnum;
import eu.bitwalker.useragentutils.UserAgent;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.MapUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.NamedThreadLocal;
import org.springframework.http.HttpMethod;
import org.springframework.validation.BindingResult;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* 操作日志记录处理
*/
@Slf4j
@Aspect
public class LogRecordAspect {
/**
* 排除敏感属性字段
*/
public static final String[] EXCLUDE_PROPERTIES = {"password", "oldPassword", "newPassword", "confirmPassword"};
private final ThreadLocal<Map<String, Object>> INFO_THREADLOCAL = new ThreadLocal<Map<String, Object>>();
/**
* 计算操作消耗时间
*/
private static final ThreadLocal<Long> TIME_THREADLOCAL = new NamedThreadLocal<>("Cost Time");
@Resource
private AsyncLogService asyncLogService;
@Resource
private RedisService redisService;
/**
* 处理请求前执行
*/
@Before(value = "@annotation(logRecord)")
public void boBefore(JoinPoint joinPoint, LogRecord logRecord) {
TIME_THREADLOCAL.set(System.currentTimeMillis());
//登出接口需要前置储存用户信息
handleLog(joinPoint, logRecord);
}
/**
* 处理完请求后执行
*
* @param joinPoint 切点
*/
@AfterReturning(pointcut = "@annotation(logRecord)", returning = "jsonResult")
public void doAfterReturning(JoinPoint joinPoint, LogRecord logRecord, Object jsonResult) {
handleLog(joinPoint, logRecord, null, jsonResult);
}
/**
* 拦截异常操作
*
* @param joinPoint 切点
* @param e 异常
*/
@AfterThrowing(value = "@annotation(logRecord)", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, LogRecord logRecord, Exception e) {
handleLog(joinPoint, logRecord, e, null);
}
protected void handleLog(final JoinPoint joinPoint, LogRecord logRecord) {
// 传递线程信息
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
RequestContextHolder.setRequestAttributes(requestAttributes, true);
HttpServletRequest request = requestAttributes.getRequest();
// 记录请求内容,threadInfo存储所有内容
Map<String, Object> threadInfo = new HashMap<>();
//该方法用于获取客户端发出请求时的完整URL,包括协议、服务器名、端口号、资源路径等信息,但不包括后面的查询参数部分。
// 注意,getRequestRUL()方法返回的时StringBuffer类型,而不是String类型。
threadInfo.put("url", request.getRequestURL());
//该方法用于获取请求行中资源名称部分,即位于URL的主机和端口之后、参数部分之前的部分
threadInfo.put("uri", request.getRequestURI());
//该方法用于获取HTTP请求消息中的请求方式(如GET、POST等)
threadInfo.put("httpMethod", request.getMethod());
//该方法用于获取请求的协议名,例如http、https或ftp
threadInfo.put("scheme", request.getScheme());
//该方法用于获取请求客户端的IP地址,其格式类似于"192.168.0.1"
//threadInfo.put("ip", request.getRemoteAddr());
threadInfo.put("ip", IpAddrUtils.getIpAddr(request));
//检查是否为内网IP
String ipAddress = IpAddrUtils.internalIp(IpAddrUtils.getIpAddr(request)) ? "内网IP" : IpInfoUtils.getInfo(IpAddrUtils.getIpAddr(request)).getProvince() + "-" + IpInfoUtils.getInfo(IpAddrUtils.getIpAddr(request)).getCity();
threadInfo.put("ipAddress", ipAddress);
//该方法用于获取Content-Type头字段的值,结果为String类型
threadInfo.put("contentType", request.getContentType());
//包含了一个特征字符串,用来让网络协议的对端来识别发起请求的用户代理软件的应用类型、操作系统、软件开发商以及版本号。
//UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
threadInfo.put("platform", userAgent.getOperatingSystem().getName());
threadInfo.put("browser", userAgent.getBrowser().getName());
//从请求头获取操作人
threadInfo.put("operator", request.getHeader("X-Unique-Id"));
//从请求头获取部门租户
threadInfo.put("deptTenantId", request.getHeader("X-Dept-Tenant-Id"));
//从请求头获取公司租户
threadInfo.put("companyTenantId", request.getHeader("X-Company-Tenant-Id"));
//从请求头获取操作人
String accessToken = request.getHeader("X-Access-Token");
threadInfo.put("token", accessToken);
threadInfo.put("userId", redisService.hget(RedisKeyEnum.USER_INFO.getKey(accessToken), "id"));
threadInfo.put("userCode", redisService.hget(RedisKeyEnum.USER_INFO.getKey(accessToken), "userCode"));
threadInfo.put("userName", redisService.hget(RedisKeyEnum.USER_INFO.getKey(accessToken), "userName"));
threadInfo.put("userNameEn", redisService.hget(RedisKeyEnum.USER_INFO.getKey(accessToken), "userNameEn"));
//从请求头获取系统语言
threadInfo.put("language", request.getHeader("X-USER-LANGUAGE"));
//从请求头获取业务流水号
threadInfo.put("businessNo", request.getHeader("X-Business-No"));
INFO_THREADLOCAL.set(threadInfo);
}
protected void handleLog(final JoinPoint joinPoint, LogRecord logRecord, final Exception e, Object jsonResult) {
try {
// *========解析请求信息=========*//
OperateLog operateLog = new OperateLog();
// *========获取注解信息=========*//
//获取方法签名
//MethodSignature signature = getSignature(joinPoint);
//获取注解方法
Method method = getMethod(joinPoint);
//获取注解对象
//LogRecord logRecord = getAnnotation(joinPoint, LogRecord.class);
// 获取连接点所在类名
String classname = joinPoint.getTarget().getClass().getSimpleName();
// 获取连接点所在方法名称
String methodName = method.getName();
String name = String.format("%s.%s()", classname, methodName);
// 保存请求方法
operateLog.setRequestMethod(name);
// 处理设置注解上的参数
getControllerMethodDescription(joinPoint, logRecord, operateLog, jsonResult);
// 是否需要保存request,参数和值
if (logRecord.isSaveRequestData()) {
// 获取参数的信息,传入到数据库中
setRequestValue(joinPoint, operateLog, logRecord.excludeParamNames());
}
// 是否需要保存response,参数和值
if (logRecord.isSaveResponseData() && ObjectUtil.isNotNull(jsonResult)) {
String result = JSON.toJSONString(jsonResult);
// 对登录日志的特殊处理
if (operateLog.getBusinessType()== 9) {
Object data = JSON.parseObject(result).get("data");
String accessToken = String.valueOf(JSON.parseObject(JSON.toJSONString(data)).get("accessToken"));
setOperateUser(accessToken, operateLog);
}
operateLog.setStatus(String.valueOf(JSON.parseObject(result).get("code")));
operateLog.setResponseBody(result);
}
//请求的成功或失败
if (e != null) {
operateLog.setStatus(BusinessStatus.FAIL.getCode());
// 是否需要保存errorMsg,参数和值
if (logRecord.isSaveErrorInfo()) {
operateLog.setStatus("111111");
operateLog.setErrorInfo(StrUtil.sub(e.getMessage(), 0, 5000));
}
}
// 保存数据库
//asyncLogService.saveOperateLog(operateLog);
} catch (Exception exp) {
// 记录本地异常日志
log.error("异常信息:{}", exp.getMessage());
exp.printStackTrace();
} finally {
TIME_THREADLOCAL.remove();
INFO_THREADLOCAL.remove();
}
}
/**
* 获取注解中对方法的描述信息 用于Controller层注解
*
* @param logRecord 日志切面
* @param operateLog 操作日志
*/
public void getControllerMethodDescription(JoinPoint joinPoint, LogRecord logRecord, OperateLog operateLog, Object jsonResult) throws Exception {
Map<String, Object> objectMap = INFO_THREADLOCAL.get();
// 业务模块
operateLog.setAppId(SpringProperties.getString("log.record.appId"));
operateLog.setAppCode(SpringProperties.getString("log.record.appCode"));
// 操作方法名称或描述
operateLog.setContent(logRecord.content());
// 业务操作类型
operateLog.setBusinessType(logRecord.businessType().getCode());
// 操作人类别
operateLog.setOperatorType(logRecord.operatorType().getCode());
// 操作人IP
operateLog.setUserIp(String.valueOf(objectMap.get("ip")));
operateLog.setIpAddress(String.valueOf(objectMap.get("ipAddress")));
// 操作人Id
//operateLog.setUserId(Objects.nonNull(objectMap.get("operator")) ? String.valueOf(objectMap.get("operator")) : null);
// 操作人信息
operateLog.setUserId(Objects.nonNull(objectMap.get("userId")) ? String.valueOf(objectMap.get("userId")) : null);
operateLog.setUserCode(Objects.nonNull(objectMap.get("userCode")) ? String.valueOf(objectMap.get("userCode")) : null);
operateLog.setUserName(Objects.nonNull(objectMap.get("userName")) ? String.valueOf(objectMap.get("userName")) : null);
operateLog.setUserNameEn(Objects.nonNull(objectMap.get("userNameEn")) ? String.valueOf(objectMap.get("userNameEn")) : null);
// 用户系统语言
operateLog.setUserLanguage(Objects.nonNull(objectMap.get("language")) ? String.valueOf(objectMap.get("language")) : null);
// 请求地址
operateLog.setRequestUrl(String.valueOf(objectMap.get("url")));
// 请求类型
operateLog.setRequestType(String.valueOf(objectMap.get("httpMethod")));
// 请求报文类型
operateLog.setContentType(Objects.nonNull(objectMap.get("contentType")) ? String.valueOf(objectMap.get("contentType")) : null);
// 业务流水号
operateLog.setBusinessNo(Objects.nonNull(objectMap.get("businessNo")) ? String.valueOf(objectMap.get("businessNo")) : null);
// 链路追踪ID
//logRecordDTO.setTraceId();
// 浏览器、操作系统、UI引擎标识
operateLog.setPlatform(String.valueOf(objectMap.get("platform")));
operateLog.setBrowser(String.valueOf(objectMap.get("browser")));
// 部门租户
operateLog.setDeptTenantId(String.valueOf(objectMap.get("deptTenantId")));
// 公司租户
operateLog.setCompanyTenantId(String.valueOf(objectMap.get("companyTenantId")));
// 设置消耗时间
operateLog.setRequestTime(LocalDateTime.ofEpochSecond(TIME_THREADLOCAL.get() / 1000, 0, ZoneOffset.ofHours(8)));
operateLog.setResponseTime(LocalDateTime.ofEpochSecond(System.currentTimeMillis() / 1000, 0, ZoneOffset.ofHours(8)));
operateLog.setExecuteTime(System.currentTimeMillis() - TIME_THREADLOCAL.get());
}
/**
* 保存操作人信息
*
* @param accessToken
* @param operateLog
*/
private void setOperateUser(String accessToken, OperateLog operateLog) {
if (StringUtils.isNotBlank(accessToken)) {
String id = String.valueOf(redisService.hget(RedisKeyEnum.USER_INFO.getKey(accessToken), "id"));
operateLog.setUserId(id);
String userCode = String.valueOf(redisService.hget(RedisKeyEnum.USER_INFO.getKey(accessToken), "userCode"));
operateLog.setUserCode(userCode);
String userName = String.valueOf(redisService.hget(RedisKeyEnum.USER_INFO.getKey(accessToken), "userName"));
operateLog.setUserName(userName);
String userNameEn = String.valueOf(redisService.hget(RedisKeyEnum.USER_INFO.getKey(accessToken), "userNameEn"));
operateLog.setUserNameEn(userNameEn);
}
}
/**
* 获取请求的参数,放到log中
*
* @param operateLog 操作日志
* @param excludeParamNames 排除指定的请求参数
* @throws Exception 异常
*/
private void setRequestValue(JoinPoint joinPoint, OperateLog operateLog, String[] excludeParamNames) throws Exception {
// 获取RequestAttributes
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
// 从获取RequestAttributes中获取HttpServletRequest的信息
HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
// 获取请求类型
// String requestType = operateLog.getRequestType();
String requestType = request.getMethod();
// 获取请求参数
Map<String, String> paramsMap = converMap(request.getParameterMap());
// 组装请求参数
if (MapUtils.isEmpty(paramsMap) && (HttpMethod.PUT.name().equals(requestType) || HttpMethod.POST.name().equals(requestType))) {
String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames);
operateLog.setRequestBody(params);
} else {
operateLog.setRequestBody(JSON.toJSONString(paramsMap, excludePropertyPreFilter(excludeParamNames)));
}
}
/**
* 请求参数 转换
*
* @param paramMap request获取的参数数组
*/
public Map<String, String> converMap(Map<String, String[]> paramMap) {
Map<String, String> returnMap = new HashMap<>();
for (String key : paramMap.keySet()) {
returnMap.put(key, paramMap.get(key)[0]);
}
return returnMap;
}
/**
* 参数拼装
*/
private String argsArrayToString(Object[] paramsArray, String[] excludeParamNames) {
StringBuilder params = new StringBuilder();
if (ArrayUtil.isNotEmpty(paramsArray)) {
for (Object o : paramsArray) {
if (ObjectUtil.isNotNull(o) && !isFilterObject(o)) {
try {
String jsonObj = JSON.toJSONString(o, excludePropertyPreFilter(excludeParamNames));
params.append(jsonObj).append(StrUtil.SPACE);
} catch (Exception ignored) {
}
}
}
}
return params.toString().trim();
}
/**
* 忽略敏感属性
*/
public PropertyPreExcludeFilter excludePropertyPreFilter(String[] excludeParamNames) {
return new PropertyPreExcludeFilter().addExcludes(ArrayUtil.addAll(EXCLUDE_PROPERTIES, excludeParamNames));
}
/**
* 判断是否需要过滤的对象
*
* @param o 对象信息。
* @return 如果是需要过滤的对象,则返回true;否则返回false。
*/
@SuppressWarnings("rawtypes")
public boolean isFilterObject(final Object o) {
Class<?> clazz = o.getClass();
if (clazz.isArray()) {
return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
} else if (Collection.class.isAssignableFrom(clazz)) {
Collection collection = (Collection) o;
for (Object value : collection) {
return value instanceof MultipartFile;
}
} else if (Map.class.isAssignableFrom(clazz)) {
Map map = (Map) o;
for (Object value : map.entrySet()) {
Map.Entry entry = (Map.Entry) value;
return entry.getValue() instanceof MultipartFile;
}
}
return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
|| o instanceof BindingResult;
}
private MethodSignature getSignature(JoinPoint joinPoint) {
//通过连接点获取方法签名
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
return signature;
}
private Method getMethod(JoinPoint joinPoint) {
//通过连接点获取方法签名
MethodSignature signature = getSignature(joinPoint);
//获取注解服务的方法
Method method = signature.getMethod();
return method;
}
private <T extends Annotation> T getAnnotation(JoinPoint joinPoint, Class<T> annotationClass) {
Method method = getMethod(joinPoint);
//获取注解类
T annotation = method.getAnnotation(annotationClass);
return annotation;
}
}

View File

@@ -0,0 +1,56 @@
package ai.remi.comm.log.config;
import ai.remi.comm.log.aspect.LogRecordAspect;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
/**
* @author DianJiu 【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @date 2022-07-19
* @desc
*/
@Slf4j
@RefreshScope
@Configuration
public class LogRecordAutoConfigure {
/**
* 是否开启监控配置参数
*/
@Value("${remi-log.record.enabled}")
private Boolean enabled;
/**
* 日志监控的应用名称
*/
@Value("${remi-log.record.appName}")
private String appName;
/**
* 在方法成功执行后打点,记录方法的执行时间发送到指标系统,默认关闭
* 在方法成功失败后打点,记录方法的执行时间发送到指标系统,默认关闭
*/
@Value("${remi-log.record.pushUrl}")
private String pushUrl;
/**
* 根据运行环境决定是否开启日志记录监控
*/
@PostConstruct
protected LogRecordAspect init() {
if(!enabled){
return null;
}
if (enabled && StringUtils.isBlank(appName)) {
log.error("Please config the remi-log.record property in application.yml file to enabled log record, but appName is null.");
throw new RuntimeException("Please config the remi-log.record property in application.yml file to config log record appName.");
}
return new LogRecordAspect();
}
}

View File

@@ -0,0 +1,176 @@
package ai.remi.comm.log.domain;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.*;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* <p>
* 系统日志表
* </p>
*
* @author 点九
* @since 2022-07-20
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ToString(callSuper = true)
public class OperateLog implements Serializable {
private static final long serialVersionUID = -85509022761738038L;
/**
* 业务模块,区分不同业务模块的应用ID
*/
private String appId;
/**
* 业务模块,区分不同业务模块的应用编码
*/
private String appCode;
/**
* 操作方法名称或描述
*/
private String content;
/**
* 请求状态 成功 or 失败
*/
private String status;
/**
* 操作人类别
*/
private Integer operatorType;
/**
* 业务操作类型
*/
private Integer businessType;
/**
* 操作人Id
*/
private String userId;
/**
* 操作人编码
*/
private String userCode;
/**
* 操作人员名称
*/
private String userName;
/**
* 操作人员英文名称
*/
private String userNameEn;
/**
* 请求地址
*/
private String requestUrl;
/**
* 请求方法
*/
private String requestMethod;
/**
* 请求类型(POST、PUT、OPTIONS、DELETE)
*/
private String requestType;
/**
* 请求报文类型(map、json)
*/
private String contentType;
/**
* 请求报文
*/
private String requestBody;
/**
* 响应报文
*/
private String responseBody;
/**
* 异常信息
*/
private String errorInfo;
/**
* 请求时间
*/
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime requestTime;
/**
* 响应时间
*/
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime responseTime;
/**
* 操作花费的时间 单位:ms
*/
private Long executeTime;
/**
* 用户系统语言
*/
private String userLanguage;
/**
* 业务流水号
*/
private String businessNo;
/**
* 链路追踪ID
*/
private String traceId;
/**
* 操作系统
*/
private String platform;
/**
* 浏览器
*/
private String browser;
/**
* 操作人IP
*/
private String userIp;
/**
* IP归属地址
*/
private String ipAddress;
/**
* 公司租户ID
*/
private String companyTenantId;
/**
* 部门租户ID
*/
private String deptTenantId;
}

View File

@@ -0,0 +1,28 @@
package ai.remi.comm.log.enums;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 操作状态
*/
@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public enum BusinessStatus {
/**
* 成功
*/
SUCCESS("000000", "成功"),
/**
* 失败
*/
FAIL("111111", "失败");
private String code;
private String desc;
}

View File

@@ -0,0 +1,98 @@
package ai.remi.comm.log.enums;
import ai.remi.comm.core.enums.TypeEnum;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 业务操作类型
*/
@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public enum BusinessType implements TypeEnum {
/**
* 新增
*/
INSERT(1, "新增"),
/**
* 修改
*/
UPDATE(2, "修改"),
/**
* 删除
*/
DELETE(3, "删除"),
/**
* 导入
*/
IMPORT(4, "导入"),
/**
* 导出
*/
EXPORT(5, "导出"),
/**
* 下载
*/
DOWNLOAD(6, "下载"),
/**
* 上传
*/
UPLOAD(7, "上传"),
/**
* 授权
*/
GRANT(8, "授权"),
/**
* 登录
*/
LOGIN(9, "登录"),
/**
* 登出
*/
LOGOUT(10, "登出"),
/**
* 强退
*/
FORCE(11, "强退"),
/**
* 修改状态
*/
STATUS(12, "修改状态"),
/**
* 生成代码
*/
GENCODE(13, "生成代码"),
/**
* 清空数据
*/
CLEAN(14, "清空数据"),
/**
* 更新缓存
*/
REFRESH(15, "更新缓存"),
/**
* 其它
*/
OTHER(99, "其它");
private Integer code;
private String desc;
}

View File

@@ -0,0 +1,34 @@
package ai.remi.comm.log.enums;
import ai.remi.comm.core.enums.TypeEnum;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 操作人类别
*/
@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public enum OperatorType implements TypeEnum {
/**
* 后台用户
*/
MANAGE(1, "管理端用户"),
/**
* 手机端用户
*/
MOBILE(2, "移动端用户"),
/**
* 其它
*/
OTHER(99, "其它");
private Integer code;
private String desc;
}

View File

@@ -0,0 +1,39 @@
package ai.remi.comm.log.enums;
import cn.hutool.core.util.StrUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* Redis key枚举类
*
* @author dianjiu
* @date 2021-12-7 16:10:35
*/
@Getter
@AllArgsConstructor
public enum RedisKeyEnum {
/**
* 客户登录token,24小时失效 1:{} = customerId 2:{} = token 使用方式 RedisKeysEnum.CUSTOMER_LOGIN_TOKEN.getKey(customerId,token)
*/
APP_CODE("sdt-saas-iam:app-code:{}:{}"),
USER_INFO("sdt-saas-iam:user-info:{}"),
TICKET("sdt-saas-iam:ticket:{}"),
AUTH_CODE("sdt-saas-iam:auth-code:{}"),
ACCESS_TOKEN("sdt-saas-iam:access-token:{}"),
REFRESH_TOKEN("sdt-saas-iam:refresh-token:{}");
/**
* redis key
*/
private String key;
/**
* key失效时间,单位秒
*/
//private Long expireAt;
public String getKey(String... key) {
return StrUtil.format(this.key, key);
}
}

View File

@@ -0,0 +1,18 @@
package ai.remi.comm.log.filter;
import com.alibaba.fastjson2.filter.SimplePropertyPreFilter;
import lombok.NoArgsConstructor;
/**
* 排除JSON敏感属性
*/
@NoArgsConstructor
public class PropertyPreExcludeFilter extends SimplePropertyPreFilter {
public PropertyPreExcludeFilter addExcludes(String... filters) {
for (String filter : filters) {
this.getExcludes().add(filter);
}
return this;
}
}

View File

@@ -0,0 +1,29 @@
package ai.remi.comm.log.service;
import org.springframework.stereotype.Service;
/**
* 异步调用日志服务
*/
@Service
public class AsyncLogService {
/**
* 通过OpenFeign客户端注入
*/
//@Resource
//private SysClientApi sysClientApi;
/**
* 保存系统日志记录
*/
//@Async
//public void saveOperateLog(OperateLog operateLog) {
//CompanyAuthInfo companyAuthInfo = new CompanyAuthInfo();
//companyAuthInfo.setDeptTenantId(operateLog.getDeptTenantId());
//companyAuthInfo.setCompanyTenantId(operateLog.getCompanyTenantId());
//RequestHolder.add(companyAuthInfo);
//LogRecordPostDTO logRecordPostDTO = BeanCopyUtils.copyPropertiesThird(operateLog, LogRecordPostDTO.class);
//sysClientApi.addLogRecord(logRecordPostDTO);
//}
}

View File

@@ -0,0 +1,4 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
ai.remi.comm.log.config.LogRecordAutoConfigure,\
ai.remi.comm.log.aspect.LogRecordAspect,\
ai.remi.comm.log.service.AsyncLogService