操作日志自动记录参数.

This commit is contained in:
lijiahang
2023-10-12 10:44:14 +08:00
parent 730a20a3e2
commit 1571d47bfb
21 changed files with 322 additions and 36 deletions

View File

@@ -26,6 +26,13 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,17 @@
package com.orion.ops.framework.biz.operator.log.core.annotation;
import java.lang.annotation.*;
/**
* 用于记录操作日志时候 忽略参数
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/10/11 17:45
*/
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface IgnoreParameter {
}

View File

@@ -21,6 +21,19 @@ public @interface OperatorLog {
*/
String value();
/**
* 是否记录参数
* <p>
* - {@link org.springframework.web.bind.annotation.RequestBody}
* - {@link org.springframework.web.bind.annotation.RequestParam}
* - {@link org.springframework.web.bind.annotation.RequestHeader}
* - {@link org.springframework.web.bind.annotation.PathVariable}
* <p>
* 使用 @IgnoreParameter 可以忽略参数记录 {@link IgnoreParameter}
* 如果只需要忽略某个字段可以使用 @Desensitize(toEmpty = true) 标注
*/
boolean parameter() default true;
/**
* 返回值处理
*/

View File

@@ -4,8 +4,10 @@ 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.Arrays1;
import com.orion.lang.utils.Strings;
import com.orion.lang.utils.json.matcher.ReplacementFormatters;
import com.orion.ops.framework.biz.operator.log.core.annotation.IgnoreParameter;
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;
@@ -16,6 +18,7 @@ import com.orion.ops.framework.biz.operator.log.core.service.OperatorLogFramewor
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.LoginUser;
import com.orion.ops.framework.common.security.SecurityHolder;
import com.orion.ops.framework.common.utils.IpUtils;
import com.orion.web.servlet.web.Servlets;
@@ -23,14 +26,19 @@ import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;
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 java.util.Date;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ExecutorService;
/**
@@ -71,9 +79,9 @@ public class OperatorLogAspect {
@Around("@annotation(o)")
public Object around(ProceedingJoinPoint joinPoint, OperatorLog o) throws Throwable {
long start = System.currentTimeMillis();
// 先清空上下文
OperatorLogs.clear();
try {
// 初始化参数
this.initExtra(joinPoint, o);
// 执行
Object result = joinPoint.proceed();
// 记录日志
@@ -89,6 +97,71 @@ public class OperatorLogAspect {
}
}
/**
* 初始化参数
*
* @param joinPoint joinPoint
*/
private void initExtra(ProceedingJoinPoint joinPoint, OperatorLog o) {
// 清空上下文
OperatorLogs.clear();
if (!o.parameter()) {
return;
}
// 获取方法注解
Annotation[][] methodAnnotations = Optional.ofNullable(joinPoint.getSignature())
.filter(s -> (s instanceof MethodSignature))
.map(s -> ((MethodSignature) s).getMethod())
.map(Method::getParameterAnnotations)
.orElse(null);
if (Arrays1.isEmpty(methodAnnotations)) {
return;
}
// 获取参数
Object[] args = joinPoint.getArgs();
for (int i = 0; i < methodAnnotations.length; i++) {
Annotation[] annotations = methodAnnotations[i];
if (Arrays1.isEmpty(annotations)) {
continue;
}
// 检查是否有 IgnoreParameter 注解
boolean ignore = Arrays.stream(annotations)
.anyMatch(s -> s instanceof IgnoreParameter);
if (ignore) {
continue;
}
// 获取需要记录的参数
for (Annotation annotation : annotations) {
if (annotation instanceof RequestBody) {
OperatorLogs.add(args[i]);
break;
} else if (annotation instanceof RequestParam) {
Object arg = args[i];
if (arg instanceof MultipartFile) {
break;
}
OperatorLogs.add(
orElse(((RequestParam) annotation).value(),
((RequestParam) annotation).name()),
args[i]);
break;
} else if (annotation instanceof RequestHeader) {
OperatorLogs.add(
orElse(((RequestHeader) annotation).value(),
((RequestHeader) annotation).name()),
args[i]);
break;
} else if (annotation instanceof PathVariable) {
OperatorLogs.add(
orElse(((PathVariable) annotation).value(),
((PathVariable) annotation).name()),
args[i]);
break;
}
}
}
}
/**
* 保存日志
*
@@ -97,7 +170,17 @@ public class OperatorLogAspect {
*/
private void saveLog(long start, OperatorLog o, Object ret, Throwable exception) {
try {
// 请求信息
// 获取日志类型
OperatorType type = OperatorTypeHolder.get(o.value());
if (type == null) {
return;
}
// 获取当前用户
LoginUser user = this.getUser();
if (user == null) {
return;
}
// 获取请求信息
Map<String, Object> extra = OperatorLogs.get();
if (!OperatorLogs.isSave(extra)) {
return;
@@ -106,7 +189,7 @@ public class OperatorLogAspect {
// 填充使用时间
this.fillUseTime(model, start);
// 填充用户信息
this.fillUserInfo(model);
this.fillUserInfo(model, user);
// 填充请求信息
this.fillRequest(model);
// 填充结果信息
@@ -114,7 +197,7 @@ public class OperatorLogAspect {
// 填充拓展信息
this.fillExtra(model, extra);
// 填充日志
this.fillLogInfo(model, extra, o);
this.fillLogInfo(model, extra, type);
// 插入日志
this.asyncSaveLog(model);
} catch (Exception e) {
@@ -122,6 +205,20 @@ public class OperatorLogAspect {
}
}
/**
* 获取当前用户
*
* @return user
*/
private LoginUser getUser() {
LoginUser user = OperatorLogs.getUser();
if (user != null) {
return user;
}
// 登录上下文获取
return securityHolder.getLoginUser();
}
/**
* 填充使用时间
*
@@ -139,9 +236,11 @@ public class OperatorLogAspect {
* 填充用户信息
*
* @param model model
* @param user user
*/
private void fillUserInfo(OperatorLogModel model) {
model.setUserId(securityHolder.getLoginUserId());
private void fillUserInfo(OperatorLogModel model, LoginUser user) {
model.setUserId(user.getId());
model.setUsername(user.getUsername());
}
/**
@@ -205,10 +304,9 @@ public class OperatorLogAspect {
*
* @param model model
* @param extra extra
* @param o o
* @param type type
*/
private void fillLogInfo(OperatorLogModel model, Map<String, Object> extra, OperatorLog o) {
OperatorType type = OperatorTypeHolder.get(o.value());
private void fillLogInfo(OperatorLogModel model, Map<String, Object> extra, OperatorType type) {
model.setModule(type.getModule());
model.setType(type.getType());
model.setLogInfo(ReplacementFormatters.format(type.getTemplate(), extra));
@@ -220,9 +318,18 @@ public class OperatorLogAspect {
* @param model model
*/
private void asyncSaveLog(OperatorLogModel model) {
LOG_SAVER.submit(() -> {
operatorLogFrameworkService.insert(model);
});
LOG_SAVER.submit(() -> operatorLogFrameworkService.insert(model));
}
/**
* 获取可用值
*
* @param value value
* @param name name
* @return available
*/
private String orElse(String value, String name) {
return Strings.isBlank(value) ? name : value;
}
}

View File

@@ -17,6 +17,10 @@ public interface OperatorLogKeys {
String NAME = "name";
String STATUS = "status";
String STATUS_NAME = "statusName";
String USERNAME = "username";
String TITLE = "title";

View File

@@ -19,6 +19,11 @@ public class OperatorLogModel {
*/
private Long userId;
/**
* 用户名
*/
private String username;
/**
* traceId
*/

View File

@@ -3,6 +3,7 @@ package com.orion.ops.framework.biz.operator.log.core.uitls;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.ValueFilter;
import com.orion.ops.framework.biz.operator.log.core.constant.OperatorLogKeys;
import com.orion.ops.framework.common.security.LoginUser;
import java.util.HashMap;
import java.util.Map;
@@ -20,8 +21,16 @@ public class OperatorLogs implements OperatorLogKeys {
private static ValueFilter desensitizeValueFilter;
/**
* 拓展信息
*/
private static final ThreadLocal<Map<String, Object>> EXTRA_HOLDER = new ThreadLocal<>();
/**
* 当前用户 优先于登录用户
*/
private static final ThreadLocal<LoginUser> USER_HOLDER = new ThreadLocal<>();
private OperatorLogs() {
}
@@ -93,10 +102,11 @@ public class OperatorLogs implements OperatorLogKeys {
*/
public static void clear() {
EXTRA_HOLDER.remove();
USER_HOLDER.remove();
}
/**
* 是否保存
* 设置是否保存
*
* @param map map
* @return save
@@ -105,6 +115,24 @@ public class OperatorLogs implements OperatorLogKeys {
return map == null || !map.containsKey(UN_SAVE_FLAG);
}
/**
* 设置用户信息
*
* @param user user
*/
public static void setUser(LoginUser user) {
USER_HOLDER.set(user);
}
/**
* 获取用户
*
* @return user
*/
public static LoginUser getUser() {
return USER_HOLDER.get();
}
/**
* 初始化
*

View File

@@ -23,6 +23,10 @@ import org.springframework.http.converter.json.MappingJackson2HttpMessageConvert
public class OrionDesensitizeAutoConfiguration {
/**
* 用于参数打印
* - 全局日志打印
* - 操作日志参数
*
* @return fastjson 序列化脱敏过滤器
*/
@Bean
@@ -31,13 +35,14 @@ public class OrionDesensitizeAutoConfiguration {
}
/**
* @return jackson 序列化脱敏过滤器
* 用于 HTTP 响应序列化
*
* @return jackson 序列化脱敏序列化
*/
@Bean
@ConditionalOnBean(MappingJackson2HttpMessageConverter.class)
public DesensitizeJsonSerializer desensitizeJsonSerializer(MappingJackson2HttpMessageConverter converter) {
DesensitizeJsonSerializer serializer = new DesensitizeJsonSerializer();
return serializer;
public DesensitizeJsonSerializer desensitizeJsonSerializer() {
return new DesensitizeJsonSerializer();
}
}

View File

@@ -9,7 +9,8 @@ import java.lang.annotation.*;
/**
* FastJson / Jackson 脱敏配置元注解
* <p>
* 标注在字段上则标记该字段执行 http 序列化时脱敏
* Jackson 标注在字段上则标记该字段执行 http 序列化 (返回 VO)时脱敏 (http-message-converts 用的是 jackson)
* FastJson 需要组合 {@link DesensitizeObject} 一起使用
*
* @author Jiahang Li
* @version 1.0.0
@@ -22,6 +23,13 @@ import java.lang.annotation.*;
@JsonSerialize(using = DesensitizeJsonSerializer.class)
public @interface Desensitize {
/**
* 一般用于操作日志参数脱敏
*
* @return 是否直接设置为空
*/
boolean toEmpty() default false;
/**
* @return 起始保留字符数
*/

View File

@@ -8,6 +8,7 @@ import com.orion.lang.utils.Strings;
import com.orion.lang.utils.collect.Maps;
import com.orion.lang.utils.reflect.Annotations;
import com.orion.lang.utils.reflect.Fields;
import com.orion.ops.framework.common.constant.Const;
import com.orion.ops.framework.desensitize.core.annotation.Desensitize;
import com.orion.ops.framework.desensitize.core.annotation.DesensitizeObject;
@@ -39,8 +40,13 @@ public class DesensitizeValueFilter implements ValueFilter {
if (config == null) {
return value;
}
// 脱敏
return Desensitizes.mix(Objects1.toString(value), config.keepStart(), config.keepEnd(), config.replacer());
if (config.toEmpty()) {
// 设置为空
return Const.EMPTY;
} else {
// 脱敏
return Desensitizes.mix(Objects1.toString(value), config.keepStart(), config.keepEnd(), config.replacer());
}
}
/**

View File

@@ -6,6 +6,7 @@ import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import com.orion.lang.constant.Const;
import com.orion.lang.utils.Desensitizes;
import com.orion.lang.utils.Objects1;
import com.orion.ops.framework.desensitize.core.annotation.Desensitize;
@@ -24,6 +25,10 @@ import java.util.Objects;
*/
public class DesensitizeJsonSerializer extends JsonSerializer<Object> implements ContextualSerializer {
/**
* 这里是线程安全的
* 同一个字段使用同一个 serializer 所以不需要使用 TheadLocal
*/
private Desensitize desensitize;
@Override
@@ -43,9 +48,14 @@ public class DesensitizeJsonSerializer extends JsonSerializer<Object> implements
gen.writeNull();
return;
}
// 脱敏
String mix = Desensitizes.mix(Objects1.toString(value), desensitize.keepStart(), desensitize.keepEnd(), desensitize.replacer());
gen.writeString(mix);
if (desensitize.toEmpty()) {
// 设置为空
gen.writeString(Const.EMPTY);
} else {
// 脱敏
String mix = Desensitizes.mix(Objects1.toString(value), desensitize.keepStart(), desensitize.keepEnd(), desensitize.replacer());
gen.writeString(mix);
}
}
}