From fd1c7004128182d4efc0d426c1cfbd97866e1111 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=BF=94?= Date: Wed, 18 Mar 2026 22:36:40 +0800 Subject: [PATCH] feat: add operation log filter for automatic logging --- .../sys/interceptor/OperationLogFilter.java | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/interceptor/OperationLogFilter.java diff --git a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/interceptor/OperationLogFilter.java b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/interceptor/OperationLogFilter.java new file mode 100644 index 0000000..6370226 --- /dev/null +++ b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/interceptor/OperationLogFilter.java @@ -0,0 +1,120 @@ +package cn.novalon.manage.sys.interceptor; + +import cn.novalon.manage.sys.core.domain.OperationLog; +import cn.novalon.manage.sys.core.service.IOperationLogService; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.security.core.context.ReactiveSecurityContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.WebFilter; +import org.springframework.web.server.WebFilterChain; +import reactor.core.publisher.Mono; + +import java.time.Duration; + +/** + * 操作日志过滤器 + * + * 文件定义:拦截HTTP请求,自动记录操作日志 + * 涉及业务:操作日志的自动记录和持久化 + * 算法:使用WebFlux的WebFilter机制拦截请求,异步记录日志 + * + * @author 张翔 + * @date 2026-03-18 + */ +@Component +public class OperationLogFilter implements WebFilter { + + private static final Logger logger = LoggerFactory.getLogger(OperationLogFilter.class); + + private final IOperationLogService logService; + private final ObjectMapper objectMapper; + + public OperationLogFilter(IOperationLogService logService, ObjectMapper objectMapper) { + this.logService = logService; + this.objectMapper = objectMapper; + } + + @Override + public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { + long startTime = System.currentTimeMillis(); + ServerHttpRequest request = exchange.getRequest(); + + String path = request.getPath().value(); + String method = request.getMethod().name(); + String ip = getClientIp(request); + + return chain.filter(exchange) + .doOnSuccess(v -> { + long duration = System.currentTimeMillis() - startTime; + recordLog(exchange, path, method, ip, duration, null); + }) + .doOnError(error -> { + long duration = System.currentTimeMillis() - startTime; + recordLog(exchange, path, method, ip, duration, error.getMessage()); + }); + } + + private void recordLog(ServerWebExchange exchange, String path, String method, String ip, long duration, String errorMsg) { + try { + OperationLog log = new OperationLog(); + log.setOperation(path); + log.setMethod(method); + log.setIp(ip); + log.setDuration(duration); + + if (errorMsg != null) { + log.setStatus("1"); + log.setErrorMsg(errorMsg); + log.setResult("Failed"); + } else { + log.setStatus("0"); + log.setResult("Success"); + } + + String queryParams = exchange.getRequest().getQueryParams().toSingleValueMap().toString(); + log.setParams(queryParams); + + ReactiveSecurityContextHolder.getContext() + .flatMap(securityContext -> { + Object principal = securityContext.getAuthentication().getPrincipal(); + if (principal instanceof String) { + log.setUsername((String) principal); + } + return Mono.empty(); + }) + .then(Mono.fromRunnable(() -> { + logService.save(log) + .doOnSuccess(saved -> logger.debug("操作日志记录成功: {}", log.getOperation())) + .doOnError(error -> logger.error("操作日志记录失败: {}", error.getMessage())) + .subscribe(); + })) + .subscribe(); + } catch (Exception e) { + logger.error("记录操作日志时发生异常: {}", e.getMessage()); + } + } + + private String getClientIp(ServerHttpRequest request) { + String ip = request.getHeaders().getFirst("X-Forwarded-For"); + if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeaders().getFirst("X-Real-IP"); + } + if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeaders().getFirst("Proxy-Client-IP"); + } + if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeaders().getFirst("WL-Proxy-Client-IP"); + } + if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { + ip = request.getRemoteAddress() != null ? request.getRemoteAddress().getAddress().getHostAddress() : ""; + } + if (ip != null && ip.contains(",")) { + ip = ip.split(",")[0].trim(); + } + return ip; + } +} \ No newline at end of file