添加操作日志服务.

This commit is contained in:
lijiahang
2023-10-10 16:56:56 +08:00
parent a1477e9614
commit 730fb000d1
18 changed files with 739 additions and 16 deletions

View File

@@ -41,6 +41,8 @@ public interface AutoConfigureOrderConst {
int FRAMEWORK_MONITOR = Integer.MIN_VALUE + 2200;
int FRAMEWORK_BANNER = Integer.MIN_VALUE + 2300;
int FRAMEWORK_BIZ_OPERATOR_LOG = Integer.MIN_VALUE + 3000;
int FRAMEWORK_BANNER = Integer.MIN_VALUE + 10000;
}

View File

@@ -4,13 +4,20 @@ import com.orion.ext.location.Region;
import com.orion.ext.location.region.LocationRegions;
import com.orion.ops.framework.common.constant.Const;
import java.util.HashMap;
import java.util.Map;
/**
* ip 工具类
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/7/14 16:26
*/
public class IpUtils {
private static final Map<String, String> CACHE = new HashMap<>();
private IpUtils() {
}
@@ -21,6 +28,20 @@ public class IpUtils {
* @return ip 位置
*/
public static String getLocation(String ip) {
if (ip == null) {
return Const.UNKNOWN;
}
// 查询缓存
return CACHE.computeIfAbsent(ip, IpUtils::queryLocation);
}
/**
* 查询 ip 位置
*
* @param ip ip
* @return ip 位置
*/
private static String queryLocation(String ip) {
if (ip == null) {
return Const.UNKNOWN;
}

View File

@@ -0,0 +1,54 @@
package com.orion.ops.framework.biz.operator.log.config;
import com.orion.ops.framework.biz.operator.log.core.aspect.OperatorLogAspect;
import com.orion.ops.framework.biz.operator.log.core.config.OperatorLogConfig;
import com.orion.ops.framework.biz.operator.log.core.service.OperatorLogFrameworkService;
import com.orion.ops.framework.biz.operator.log.core.service.OperatorLogFrameworkServiceDelegate;
import com.orion.ops.framework.common.constant.AutoConfigureOrderConst;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
/**
* 操作日志配置类
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/10/10 16:42
*/
@AutoConfiguration
@AutoConfigureOrder(AutoConfigureOrderConst.FRAMEWORK_BIZ_OPERATOR_LOG)
@EnableConfigurationProperties(OperatorLogConfig.class)
public class OrionOperatorLogAutoConfiguration {
/**
* 操作日志委托类
*
* @param service service
* @return delegate
*/
@Bean
@Primary
@ConditionalOnBean(OperatorLogFrameworkService.class)
public OperatorLogFrameworkServiceDelegate operatorLogFrameworkService(OperatorLogFrameworkService service) {
return new OperatorLogFrameworkServiceDelegate(service);
}
/**
* 日志切面
*
* @param operatorLogConfig operatorLogConfig
* @param service service
* @return aspect
*/
@Bean
@ConditionalOnBean(OperatorLogFrameworkServiceDelegate.class)
public OperatorLogAspect operatorLogAspect(OperatorLogConfig operatorLogConfig,
OperatorLogFrameworkService service) {
return new OperatorLogAspect(operatorLogConfig, service);
}
}

View File

@@ -1,9 +1,29 @@
package com.orion.ops.framework.biz.operator.log.core.annotation;
import com.orion.ops.framework.biz.operator.log.core.enums.ReturnType;
import java.lang.annotation.*;
/**
* 操作日志
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/10/9 18:44
*/
public interface OperatorLog {
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperatorLog {
/**
* 操作类型
*/
String value();
/**
* 返回值处理
*/
ReturnType ret() default ReturnType.JSON;
}

View File

@@ -0,0 +1,220 @@
package com.orion.ops.framework.biz.operator.log.core.aspect;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.ValueFilter;
import com.orion.lang.define.thread.ExecutorBuilder;
import com.orion.lang.define.wrapper.Ref;
import com.orion.lang.utils.Strings;
import com.orion.ops.framework.biz.operator.log.core.annotation.OperatorLog;
import com.orion.ops.framework.biz.operator.log.core.config.OperatorLogConfig;
import com.orion.ops.framework.biz.operator.log.core.enums.ReturnType;
import com.orion.ops.framework.biz.operator.log.core.holder.OperatorTypeHolder;
import com.orion.ops.framework.biz.operator.log.core.model.OperatorLogModel;
import com.orion.ops.framework.biz.operator.log.core.model.OperatorType;
import com.orion.ops.framework.biz.operator.log.core.service.OperatorLogFrameworkService;
import com.orion.ops.framework.biz.operator.log.core.uitls.OperatorLogs;
import com.orion.ops.framework.common.enums.BooleanBit;
import com.orion.ops.framework.common.meta.TraceIdHolder;
import com.orion.ops.framework.common.security.SecurityHolder;
import com.orion.ops.framework.common.utils.IpUtils;
import com.orion.web.servlet.web.Servlets;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.annotation.Resource;
import java.util.Date;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
/**
* 操作日志切面
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/10/10 10:47
*/
@Aspect
@Slf4j
public class OperatorLogAspect {
private static final ExecutorService LOG_SAVER = ExecutorBuilder.create()
.corePoolSize(1)
.maxPoolSize(1)
.useLinkedBlockingQueue()
.allowCoreThreadTimeout()
.useLinkedBlockingQueue()
.build();
private final OperatorLogConfig operatorLogConfig;
private final OperatorLogFrameworkService operatorLogFrameworkService;
@Resource
private ValueFilter desensitizeValueFilter;
@Resource
private SecurityHolder securityHolder;
public OperatorLogAspect(OperatorLogConfig operatorLogConfig,
OperatorLogFrameworkService operatorLogFrameworkService) {
this.operatorLogConfig = operatorLogConfig;
this.operatorLogFrameworkService = operatorLogFrameworkService;
}
@Around("@annotation(o)")
public Object around(ProceedingJoinPoint joinPoint, OperatorLog o) throws Throwable {
long start = System.currentTimeMillis();
try {
// 执行
Object result = joinPoint.proceed();
// 记录日志
this.saveLog(start, o, result, null);
return result;
} catch (Throwable exception) {
// 记录日志
this.saveLog(start, o, null, exception);
throw exception;
} finally {
// 清空上下文
OperatorLogs.clear();
}
}
/**
* 保存日志
*
* @param start start
* @param exception exception
*/
private void saveLog(long start, OperatorLog o, Object ret, Throwable exception) {
// 请求信息
Map<String, Object> extra = OperatorLogs.get();
if (!OperatorLogs.isSave(extra)) {
return;
}
OperatorLogModel model = new OperatorLogModel();
// 填充使用时间
this.fillUseTime(model, start);
// 填充用户信息
this.fillUserInfo(model);
// 填充请求信息
this.fillRequest(model);
// 填充结果信息
this.fillResult(model, o, ret, exception);
// 填充拓展信息
this.fillExtra(model, extra);
// 填充日志
this.fillLogInfo(model, extra, o);
// 插入日志
this.asyncSaveLog(model);
}
/**
* 填充使用时间
*
* @param model model
* @param start start
*/
private void fillUseTime(OperatorLogModel model, long start) {
long end = System.currentTimeMillis();
model.setDuration((int) (end - start));
model.setStartTime(new Date(start));
model.setEndTime(new Date(end));
}
/**
* 填充用户信息
*
* @param model model
*/
private void fillUserInfo(OperatorLogModel model) {
model.setUserId(securityHolder.getLoginUserId());
}
/**
* 填充请求信息
*
* @param model model
*/
private void fillRequest(OperatorLogModel model) {
model.setTraceId(TraceIdHolder.get());
Optional.ofNullable(RequestContextHolder.getRequestAttributes())
.map(s -> (ServletRequestAttributes) s)
.map(ServletRequestAttributes::getRequest)
.ifPresent(request -> {
String address = Servlets.getRemoteAddr(request);
model.setAddress(address);
model.setLocation(IpUtils.getLocation(address));
model.setUserAgent(Servlets.getUserAgent(request));
});
}
/**
* 填充结果
*
* @param model model
* @param exception exception
*/
private void fillResult(OperatorLogModel model, OperatorLog o, Object ret, Throwable exception) {
if (exception == null) {
model.setResult(BooleanBit.TRUE.getValue());
ReturnType retType = o.ret();
if (ret != null) {
if (ReturnType.JSON.equals(retType)) {
// 脱敏
model.setReturnValue(JSON.toJSONString(ret, desensitizeValueFilter));
} else if (ReturnType.TO_STRING.equals(retType)) {
model.setReturnValue(JSON.toJSONString(Ref.of(Objects.toString(ret))));
}
}
} else {
model.setResult(BooleanBit.FALSE.getValue());
// 错误信息
String errorMessage = Strings.retain(exception.getMessage(), operatorLogConfig.getErrorMessageLength());
model.setErrorMessage(errorMessage);
}
}
/**
* 填充拓展信息
*
* @param model model
* @param extra extra
*/
private void fillExtra(OperatorLogModel model, Map<String, Object> extra) {
// 脱敏
model.setExtra(JSON.toJSONString(extra, desensitizeValueFilter));
}
/**
* 填充日志信息
*
* @param model model
* @param extra extra
* @param o o
*/
private void fillLogInfo(OperatorLogModel model, Map<String, Object> extra, OperatorLog o) {
OperatorType type = OperatorTypeHolder.get(o.value());
model.setModule(type.getModule());
model.setType(type.getType());
model.setLogInfo(Strings.format(type.getTemplate(), extra));
}
/**
* 异步保存日志
*
* @param model model
*/
private void asyncSaveLog(OperatorLogModel model) {
LOG_SAVER.submit(() -> {
operatorLogFrameworkService.insert(model);
});
}
}

View File

@@ -0,0 +1,25 @@
package com.orion.ops.framework.biz.operator.log.core.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* 操作日志配置
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/10/10 14:08
*/
@Data
@ConfigurationProperties("orion.operator-log")
public class OperatorLogConfig {
/**
* 错误信息长度
*/
private Integer errorMessageLength;
public OperatorLogConfig() {
this.errorMessageLength = 255;
}
}

View File

@@ -0,0 +1,29 @@
package com.orion.ops.framework.biz.operator.log.core.enums;
/**
* 返回值类型
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/10/10 15:55
*/
public enum ReturnType {
/**
* 忽略
*/
IGNORE,
/**
* json
*/
JSON,
/**
* string
*/
TO_STRING,
;
}

View File

@@ -0,0 +1,42 @@
package com.orion.ops.framework.biz.operator.log.core.holder;
import com.orion.ops.framework.biz.operator.log.core.model.OperatorType;
import java.util.HashMap;
import java.util.Map;
/**
* 操作日志类型实例
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/10/10 14:43
*/
public class OperatorTypeHolder {
private static final Map<String, OperatorType> TYPES = new HashMap<>();
private OperatorTypeHolder() {
}
/**
* 获取类型
*
* @param key key
* @return type
*/
public static OperatorType get(String key) {
return TYPES.get(key);
}
/**
* 设置类型
*
* @param key key
* @param type type
*/
public static void set(String key, OperatorType type) {
TYPES.put(key, type);
}
}

View File

@@ -2,6 +2,8 @@ package com.orion.ops.framework.biz.operator.log.core.model;
import lombok.Data;
import java.util.Date;
/**
* 操作日志模型
*
@@ -11,17 +13,80 @@ import lombok.Data;
*/
@Data
public class OperatorLogModel {
// 用户操作日志
// id
// user_id
// trace_id
// log_info
// module 模块
// operator 操作
// ip
// address
// user_agent
// params
// result
// duration
/**
* userId
*/
private Long userId;
/**
* traceId
*/
private String traceId;
/**
* 请求 ip
*/
private String address;
/**
* 请求地址
*/
private String location;
/**
* user-agent
*/
private String userAgent;
/**
* 日志
*/
private String logInfo;
/**
* 模块
*/
private String module;
/**
* 操作类型
*/
private String type;
/**
* 参数
*/
private String extra;
/**
* 操作结果 0失败 1成功
*/
private Integer result;
/**
* 错误信息
*/
private String errorMessage;
/**
* 返回值
*/
private String returnValue;
/**
* 操作时间
*/
private Integer duration;
/**
* 开始时间
*/
private Date startTime;
/**
* 结束时间
*/
private Date endTime;
}

View File

@@ -0,0 +1,34 @@
package com.orion.ops.framework.biz.operator.log.core.model;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 操作类型定义
* <p>
* 因为枚举需要实现 注解中不可以使用 则需要使用对象
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/10/10 10:29
*/
@Getter
@AllArgsConstructor
public class OperatorType {
/**
* 模块
*/
private final String module;
/**
* 类型
*/
private final String type;
/**
* 模板
*/
private final String template;
}

View File

@@ -0,0 +1,21 @@
package com.orion.ops.framework.biz.operator.log.core.service;
import com.orion.ops.framework.biz.operator.log.core.model.OperatorLogModel;
/**
* 操作日志框架服务
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/10/10 11:26
*/
public interface OperatorLogFrameworkService {
/**
* 记录日志
*
* @param log log
*/
void insert(OperatorLogModel log);
}

View File

@@ -0,0 +1,25 @@
package com.orion.ops.framework.biz.operator.log.core.service;
import com.orion.ops.framework.biz.operator.log.core.model.OperatorLogModel;
/**
* 操作日志框架服务 委托类
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/10/10 14:11
*/
public class OperatorLogFrameworkServiceDelegate implements OperatorLogFrameworkService {
private final OperatorLogFrameworkService operatorLogFrameworkService;
public OperatorLogFrameworkServiceDelegate(OperatorLogFrameworkService operatorLogFrameworkService) {
this.operatorLogFrameworkService = operatorLogFrameworkService;
}
@Override
public void insert(OperatorLogModel log) {
operatorLogFrameworkService.insert(log);
}
}

View File

@@ -0,0 +1,118 @@
package com.orion.ops.framework.biz.operator.log.core.uitls;
import com.orion.lang.utils.reflect.BeanMap;
import java.util.HashMap;
import java.util.Map;
/**
* 操作日志工具类
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/10/10 11:32
*/
public class OperatorLogs {
private static final String UN_SAVE_FLAG = "__un__save__";
private static final ThreadLocal<Map<String, Object>> EXTRA_HOLDER = new ThreadLocal<>();
private OperatorLogs() {
}
/**
* 添加参数
*
* @param key key
* @param value value
*/
public static void add(String key, Object value) {
initMap().put(key, value);
}
/**
* 添加参数
*
* @param map map
*/
public static void add(Map<String, ?> map) {
initMap().putAll(map);
}
/**
* 添加参数
*
* @param obj obj
*/
@SuppressWarnings("unchecked")
public static void add(Object obj) {
if (obj == null) {
return;
}
if (obj instanceof Map) {
add((Map<String, ?>) obj);
return;
}
initMap().putAll(BeanMap.create(obj));
}
/**
* 设置不保存
*/
public static void unSave() {
setSave(false);
}
/**
* 设置是否保存
*
* @param save save
*/
public static void setSave(boolean save) {
if (!save) {
initMap().put(UN_SAVE_FLAG, UN_SAVE_FLAG);
}
}
/**
* 获取参数
*
* @return map
*/
public static Map<String, Object> get() {
return EXTRA_HOLDER.get();
}
/**
* 清空
*/
public static void clear() {
EXTRA_HOLDER.remove();
}
/**
* 是否保存
*
* @param map map
* @return save
*/
public static boolean isSave(Map<String, Object> map) {
return map == null || !map.containsKey(UN_SAVE_FLAG);
}
/**
* 初始化
*
* @return map
*/
private static Map<String, Object> initMap() {
Map<String, Object> map = EXTRA_HOLDER.get();
if (map == null) {
map = new HashMap<>();
EXTRA_HOLDER.set(map);
}
return map;
}
}

View File

@@ -0,0 +1,17 @@
{
"groups": [
{
"name": "orion.operator-log",
"type": "com.orion.ops.framework.biz.operator.log.core.config.OperatorLogConfig",
"sourceType": "com.orion.ops.framework.biz.operator.log.core.config.OperatorLogConfig"
}
],
"properties": [
{
"name": "orion.operator-log.error-message-length",
"type": "java.lang.Integer",
"description": "日志打印模型.",
"defaultValue": "255"
}
]
}

View File

@@ -0,0 +1 @@
com.orion.ops.framework.biz.operator.log.config.OrionOperatorLogAutoConfiguration

View File

@@ -16,6 +16,8 @@ import com.orion.ops.framework.log.core.annotation.IgnoreLog;
import com.orion.ops.framework.log.core.config.LogPrinterConfig;
import com.orion.ops.framework.log.core.enums.IgnoreLogMode;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.ServletRequest;
@@ -253,7 +255,10 @@ public abstract class AbstractLogPrinterInterceptor implements LogPrinterInterce
}
Object arg = args[i];
// 是否为 request / response
if (arg instanceof ServletRequest || arg instanceof ServletResponse) {
if (arg instanceof ServletRequest ||
arg instanceof ServletResponse ||
arg instanceof MultipartFile ||
arg instanceof BindingResult) {
ignored[i] = true;
continue;
}

View File

@@ -196,3 +196,5 @@ orion:
max-pool-size: 4
queue-capacity: 30
keep-alive-seconds: 180
operator-log:
error-message-length: 255

View File

@@ -0,0 +1,22 @@
package com.orion.ops.module.infra.framework.service.impl;
import com.orion.ops.framework.biz.operator.log.core.model.OperatorLogModel;
import com.orion.ops.framework.biz.operator.log.core.service.OperatorLogFrameworkService;
import org.springframework.stereotype.Service;
/**
* 操作日志包 实现类
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/10/10 16:53
*/
@Service
public class OperatorLogFrameworkServiceImpl implements OperatorLogFrameworkService {
@Override
public void insert(OperatorLogModel log) {
System.out.println(log);
}
}