fix: resolve critical and important issues in OperationLogAspect
- Fix blocking call blockOptional() in reactive context - Fix double subscription issue in saveLogAsync - Fix Chinese character encoding issues in log messages - Add Flux support for reactive streams - Add parameter and result size limits - Add comprehensive JavaDoc for IpUtils
This commit is contained in:
+78
-53
@@ -12,13 +12,16 @@ import org.slf4j.LoggerFactory;
|
|||||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import reactor.core.scheduler.Schedulers;
|
|
||||||
|
|
||||||
@Aspect
|
@Aspect
|
||||||
@Component
|
@Component
|
||||||
public class OperationLogAspect {
|
public class OperationLogAspect {
|
||||||
private static final Logger logger = LoggerFactory.getLogger(OperationLogAspect.class);
|
private static final Logger logger = LoggerFactory.getLogger(OperationLogAspect.class);
|
||||||
|
private static final int MAX_PARAM_LENGTH = 2000;
|
||||||
|
private static final int MAX_RESULT_LENGTH = 5000;
|
||||||
|
|
||||||
private final IOperationLogService logService;
|
private final IOperationLogService logService;
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
@@ -31,32 +34,50 @@ public class OperationLogAspect {
|
|||||||
public Object around(ProceedingJoinPoint point, cn.novalon.manage.sys.audit.OperationLog operationLogAnnotation) throws Throwable {
|
public Object around(ProceedingJoinPoint point, cn.novalon.manage.sys.audit.OperationLog operationLogAnnotation) throws Throwable {
|
||||||
long startTime = System.currentTimeMillis();
|
long startTime = System.currentTimeMillis();
|
||||||
ServerRequest serverRequest = extractServerRequest(point.getArgs());
|
ServerRequest serverRequest = extractServerRequest(point.getArgs());
|
||||||
String username = getCurrentUsername();
|
|
||||||
String ip = IpUtils.getClientIp(serverRequest);
|
String ip = IpUtils.getClientIp(serverRequest);
|
||||||
String method = point.getSignature().toShortString();
|
String method = point.getSignature().toShortString();
|
||||||
String params = serializeParams(point.getArgs());
|
String params = serializeParams(point.getArgs());
|
||||||
Object result = null;
|
|
||||||
String status = "0";
|
|
||||||
String errorMsg = null;
|
|
||||||
try {
|
try {
|
||||||
result = point.proceed();
|
Object result = point.proceed();
|
||||||
|
|
||||||
if (result instanceof Mono) {
|
if (result instanceof Mono) {
|
||||||
return ((Mono<?>) result)
|
return getCurrentUsername()
|
||||||
.doOnSuccess(res -> {
|
.flatMap(username -> ((Mono<?>) result)
|
||||||
long duration = System.currentTimeMillis() - startTime;
|
.flatMap(res -> {
|
||||||
saveLogAsync(operationLogAnnotation, username, ip, method, params, res, duration, "0", null);
|
long duration = System.currentTimeMillis() - startTime;
|
||||||
})
|
return saveLogAsync(operationLogAnnotation, username, ip, method, params, res, duration, "0", null)
|
||||||
.doOnError(error -> {
|
.thenReturn(res);
|
||||||
long duration = System.currentTimeMillis() - startTime;
|
})
|
||||||
saveLogAsync(operationLogAnnotation, username, ip, method, params, null, duration, "1", error.getMessage());
|
.onErrorResume(error -> {
|
||||||
});
|
long duration = System.currentTimeMillis() - startTime;
|
||||||
|
return saveLogAsync(operationLogAnnotation, username, ip, method, params, null, duration, "1", error.getMessage())
|
||||||
|
.then(Mono.error(error));
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else if (result instanceof Flux) {
|
||||||
|
return getCurrentUsername()
|
||||||
|
.flatMapMany(username -> ((Flux<?>) result)
|
||||||
|
.collectList()
|
||||||
|
.flatMapMany(res -> {
|
||||||
|
long duration = System.currentTimeMillis() - startTime;
|
||||||
|
return saveLogAsync(operationLogAnnotation, username, ip, method, params, res, duration, "0", null)
|
||||||
|
.thenMany(Flux.fromIterable(res));
|
||||||
|
})
|
||||||
|
.onErrorResume(error -> {
|
||||||
|
long duration = System.currentTimeMillis() - startTime;
|
||||||
|
return saveLogAsync(operationLogAnnotation, username, ip, method, params, null, duration, "1", error.getMessage())
|
||||||
|
.thenMany(Flux.error(error));
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
} catch (Throwable error) {
|
} catch (Throwable error) {
|
||||||
status = "1";
|
|
||||||
errorMsg = error.getMessage();
|
|
||||||
long duration = System.currentTimeMillis() - startTime;
|
long duration = System.currentTimeMillis() - startTime;
|
||||||
saveLogAsync(operationLogAnnotation, username, ip, method, params, null, duration, status, errorMsg);
|
getCurrentUsername()
|
||||||
|
.flatMap(username -> saveLogAsync(operationLogAnnotation, username, ip, method, params, null, duration, "1", error.getMessage()))
|
||||||
|
.subscribe();
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -69,25 +90,24 @@ public class OperationLogAspect {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getCurrentUsername() {
|
private Mono<String> getCurrentUsername() {
|
||||||
try {
|
return ReactiveSecurityContextHolder.getContext()
|
||||||
return ReactiveSecurityContextHolder.getContext()
|
.map(ctx -> ctx.getAuthentication().getPrincipal())
|
||||||
.map(ctx -> ctx.getAuthentication().getPrincipal())
|
.map(principal -> principal instanceof String ? (String) principal : "system")
|
||||||
.cast(String.class)
|
.defaultIfEmpty("system")
|
||||||
.blockOptional()
|
.onErrorReturn("system");
|
||||||
.orElse("system");
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.warn("考试当前用命名失败: {}", e.getMessage());
|
|
||||||
return "system";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String serializeParams(Object[] args) {
|
private String serializeParams(Object[] args) {
|
||||||
try {
|
try {
|
||||||
if (args == null || args.length == 0) return null;
|
if (args == null || args.length == 0) return null;
|
||||||
return objectMapper.writeValueAsString(args);
|
String json = objectMapper.writeValueAsString(args);
|
||||||
|
if (json.length() > MAX_PARAM_LENGTH) {
|
||||||
|
return json.substring(0, MAX_PARAM_LENGTH) + "...(truncated)";
|
||||||
|
}
|
||||||
|
return json;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.warn("处的包正数失败: {}", e.getMessage());
|
logger.warn("序列化参数失败: {}", e.getMessage());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -95,31 +115,36 @@ public class OperationLogAspect {
|
|||||||
private String serializeResult(Object result) {
|
private String serializeResult(Object result) {
|
||||||
try {
|
try {
|
||||||
if (result == null) return null;
|
if (result == null) return null;
|
||||||
return objectMapper.writeValueAsString(result);
|
String json = objectMapper.writeValueAsString(result);
|
||||||
|
if (json.length() > MAX_RESULT_LENGTH) {
|
||||||
|
return json.substring(0, MAX_RESULT_LENGTH) + "...(truncated)";
|
||||||
|
}
|
||||||
|
return json;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.warn("处皅包正数失败: {}", e.getMessage());
|
logger.warn("序列化结果失败: {}", e.getMessage());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveLogAsync(cn.novalon.manage.sys.audit.OperationLog annotation, String username, String ip, String method, String params, Object result, long duration, String status, String errorMsg) {
|
private Mono<Void> saveLogAsync(cn.novalon.manage.sys.audit.OperationLog annotation,
|
||||||
Mono.fromRunnable(() -> {
|
String username, String ip, String method,
|
||||||
OperationLog log = new OperationLog();
|
String params, Object result, long duration,
|
||||||
log.setUsername(username);
|
String status, String errorMsg) {
|
||||||
log.setOperation(annotation.module() + " - " + annotation.operation());
|
OperationLog log = new OperationLog();
|
||||||
log.setMethod(method);
|
log.setUsername(username);
|
||||||
log.setParams(params);
|
log.setOperation(annotation.module() + " - " + annotation.operation());
|
||||||
log.setResult(serializeResult(result));
|
log.setMethod(method);
|
||||||
log.setIp(ip);
|
log.setParams(params);
|
||||||
log.setDuration(duration);
|
log.setResult(serializeResult(result));
|
||||||
log.setStatus(status);
|
log.setIp(ip);
|
||||||
log.setErrorMsg(errorMsg);
|
log.setDuration(duration);
|
||||||
logService.save(log)
|
log.setStatus(status);
|
||||||
.doOnSuccess(saved -> logger.debug("操作实跋信息成功退: {} - {}", annotation.module(), annotation.operation()))
|
log.setErrorMsg(errorMsg);
|
||||||
.doOnError(error -> logger.error("作实跋信息成功退: {}", error.getMessage()))
|
|
||||||
.subscribe();
|
return logService.save(log)
|
||||||
})
|
.doOnSuccess(saved -> logger.debug("操作日志保存成功: {} - {}",
|
||||||
.subscribeOn(Schedulers.boundedElastic())
|
annotation.module(), annotation.operation()))
|
||||||
.subscribe();
|
.doOnError(error -> logger.error("操作日志保存失败: {}", error.getMessage()))
|
||||||
|
.then();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,30 +4,55 @@ import org.springframework.web.reactive.function.server.ServerRequest;
|
|||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IP地址工具类
|
||||||
|
* 用于从ServerRequest中获取客户端真实IP地址
|
||||||
|
* 支持代理服务器场景(X-Forwarded-For, X-Real-IP)
|
||||||
|
*
|
||||||
|
* @author 张翔
|
||||||
|
* @date 2026-04-03
|
||||||
|
*/
|
||||||
public class IpUtils {
|
public class IpUtils {
|
||||||
|
|
||||||
private static final String UNKNOWN = "unknown";
|
private static final String UNKNOWN = "unknown";
|
||||||
private static final String LOCALHOST_IP = "127.0.0.1";
|
private static final String LOCALHOST_IP = "127.0.0.1";
|
||||||
private static final String LOCALHOST_IPV6 = "0:0:0:0:0:0:0:1";
|
private static final String LOCALHOST_IPV6 = "0:0:0:0:0:0:0:1";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从ServerRequest中获取客户端真实IP地址
|
||||||
|
* 支持代理服务器场景,优先级: X-Forwarded-For > X-Real-IP > RemoteAddress
|
||||||
|
*
|
||||||
|
* @param request ServerRequest对象
|
||||||
|
* @return 客户端IP地址,获取失败返回"unknown"
|
||||||
|
*/
|
||||||
public static String getClientIp(ServerRequest request) {
|
public static String getClientIp(ServerRequest request) {
|
||||||
if (request == null) {
|
if (request == null) {
|
||||||
return UNKNOWN;
|
return UNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
String ip = getXForwardedForIp(request);
|
String ip = getXForwardedForIp(request);
|
||||||
if (isValidIp(ip)) {
|
if (isValidIp(ip)) {
|
||||||
return ip;
|
return ip;
|
||||||
}
|
}
|
||||||
|
|
||||||
ip = getXRealIp(request);
|
ip = getXRealIp(request);
|
||||||
if (isValidIp(ip)) {
|
if (isValidIp(ip)) {
|
||||||
return ip;
|
return ip;
|
||||||
}
|
}
|
||||||
|
|
||||||
ip = getRemoteAddress(request);
|
ip = getRemoteAddress(request);
|
||||||
if (isValidIp(ip)) {
|
if (isValidIp(ip)) {
|
||||||
return ip;
|
return ip;
|
||||||
}
|
}
|
||||||
|
|
||||||
return UNKNOWN;
|
return UNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从X-Forwarded-For头获取IP地址
|
||||||
|
* X-Forwarded-For格式: client, proxy1, proxy2
|
||||||
|
* 取第一个非unknown的有效IP
|
||||||
|
*/
|
||||||
private static String getXForwardedForIp(ServerRequest request) {
|
private static String getXForwardedForIp(ServerRequest request) {
|
||||||
String ip = request.headers().firstHeader("X-Forwarded-For");
|
String ip = request.headers().firstHeader("X-Forwarded-For");
|
||||||
if (ip != null && ip.length() > 0 && !UNKNOWN.equalsIgnoreCase(ip)) {
|
if (ip != null && ip.length() > 0 && !UNKNOWN.equalsIgnoreCase(ip)) {
|
||||||
@@ -40,6 +65,9 @@ public class IpUtils {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从X-Real-IP头获取IP地址
|
||||||
|
*/
|
||||||
private static String getXRealIp(ServerRequest request) {
|
private static String getXRealIp(ServerRequest request) {
|
||||||
String ip = request.headers().firstHeader("X-Real-IP");
|
String ip = request.headers().firstHeader("X-Real-IP");
|
||||||
if (ip != null && ip.length() > 0 && !UNKNOWN.equalsIgnoreCase(ip)) {
|
if (ip != null && ip.length() > 0 && !UNKNOWN.equalsIgnoreCase(ip)) {
|
||||||
@@ -48,6 +76,10 @@ public class IpUtils {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从RemoteAddress获取IP地址
|
||||||
|
* 将IPv6本地地址转换为IPv4格式
|
||||||
|
*/
|
||||||
private static String getRemoteAddress(ServerRequest request) {
|
private static String getRemoteAddress(ServerRequest request) {
|
||||||
Optional<InetSocketAddress> remoteAddress = request.remoteAddress();
|
Optional<InetSocketAddress> remoteAddress = request.remoteAddress();
|
||||||
if (remoteAddress.isPresent()) {
|
if (remoteAddress.isPresent()) {
|
||||||
@@ -60,6 +92,9 @@ public class IpUtils {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证IP地址是否有效
|
||||||
|
*/
|
||||||
private static boolean isValidIp(String ip) {
|
private static boolean isValidIp(String ip) {
|
||||||
return ip != null && ip.length() > 0 && !UNKNOWN.equalsIgnoreCase(ip);
|
return ip != null && ip.length() > 0 && !UNKNOWN.equalsIgnoreCase(ip);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user