From cf067dccc3cca7c531cb5c4ddc606ef8c107545c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=BF=94?= Date: Mon, 27 Apr 2026 14:06:17 +0800 Subject: [PATCH] =?UTF-8?q?feat(sys):=20=E8=BF=81=E7=A7=BB=20manage-sys=20?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=E4=B8=BB=E4=BB=A3=E7=A0=81=EF=BC=88=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1=20T2.1=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 删除 novalon manage-sys 现有 Java 源代码 - 从 gym-manage 复制所有 Java 文件并替换包名 cn.novalon.gym.manage → cn.novalon.manage - 替换 AutoConfiguration.imports - 编译验证通过 --- .../manage/sys/audit/AuditLogAspect.java | 303 +++++------------- .../manage/sys/audit/AuditLogHelper.java | 80 +++++ .../novalon/manage/sys/audit/Auditable.java | 15 + .../sys/audit/OperationLogWebFilter.java | 181 +++++++++++ .../manage/sys/audit/domain/AuditLog.java | 23 ++ .../service/impl/AuditLogArchiveService.java | 6 +- .../audit/service/impl/AuditLogService.java | 11 +- .../manage/sys/config/SecurityConfig.java | 14 +- .../manage/sys/core/domain/BaseDomain.java | 23 +- .../manage/sys/core/domain/SysPermission.java | 14 - .../manage/sys/core/domain/SysRole.java | 14 - .../manage/sys/core/domain/SysUser.java | 7 - .../core/repository/ISysMenuRepository.java | 2 + .../core/repository/ISysUserRepository.java | 2 + .../core/service/impl/DictionaryService.java | 2 - .../service/impl/OperationLogService.java | 1 - .../core/service/impl/SysConfigService.java | 27 +- .../core/service/impl/SysDictTypeService.java | 21 +- .../sys/core/service/impl/SysMenuService.java | 39 ++- .../service/impl/SysPermissionService.java | 36 ++- .../sys/core/service/impl/SysRoleService.java | 59 +++- .../sys/core/service/impl/SysUserService.java | 84 +++-- .../sys/dto/request/AssignRolesRequest.java | 16 +- .../sys/handler/user/SysUserHandler.java | 6 +- .../cn/novalon/manage/sys/util/IpUtils.java | 77 ++++- ...ot.autoconfigure.AutoConfiguration.imports | 3 +- 26 files changed, 703 insertions(+), 363 deletions(-) create mode 100644 novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/audit/AuditLogHelper.java create mode 100644 novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/audit/Auditable.java create mode 100644 novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/audit/OperationLogWebFilter.java diff --git a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/audit/AuditLogAspect.java b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/audit/AuditLogAspect.java index 2096433..d736cd0 100644 --- a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/audit/AuditLogAspect.java +++ b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/audit/AuditLogAspect.java @@ -2,171 +2,84 @@ package cn.novalon.manage.sys.audit; import cn.novalon.manage.sys.audit.domain.AuditLog; import cn.novalon.manage.sys.audit.service.IAuditLogService; -import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.data.domain.Persistable; import org.springframework.security.core.context.ReactiveSecurityContextHolder; import org.springframework.stereotype.Component; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import java.util.ArrayList; -import java.util.List; - -/** - * 审计日志切面 - * - * 文件定义:使用AOP自动拦截Repository操作,记录审计日志 - * 涉及业务:自动记录所有数据变更操作,包括变更前后对比 - * 算法:使用异步方式记录日志,不阻塞主流程 - * - * @author 张翔 - * @date 2026-04-01 - */ @Aspect @Component +@Deprecated public class AuditLogAspect { private static final Logger logger = LoggerFactory.getLogger(AuditLogAspect.class); private final IAuditLogService auditLogService; - private final ObjectMapper objectMapper; - public AuditLogAspect(IAuditLogService auditLogService, ObjectMapper objectMapper) { + public AuditLogAspect(IAuditLogService auditLogService) { this.auditLogService = auditLogService; - this.objectMapper = objectMapper; + logger.info("=== AuditLogAspect 初始化完成 ==="); } - @Around("execution(* cn.novalon.manage.db.repository.*Repository.save(..)) || " + - "execution(* cn.novalon.manage.db.repository.*Repository.delete(..)) || " + - "execution(* cn.novalon.manage.db.repository.*Repository.deleteById(..))") - public Object logAuditEvent(ProceedingJoinPoint joinPoint) throws Throwable { - String methodName = joinPoint.getSignature().getName(); + @Before("execution(* cn.novalon.manage.sys.core.service.impl.SysUserService.createUser(..))") + public void testAopWorking() { + logger.info("=== AuditLogAspect @Before 测试: SysUserService.createUser 被调用 ==="); + } + + @Around("@annotation(auditable)") + public Object logAuditEvent(ProceedingJoinPoint joinPoint, Auditable auditable) throws Throwable { + String methodName = ((MethodSignature) joinPoint.getSignature()).getName(); String className = joinPoint.getTarget().getClass().getSimpleName(); - Object[] args = joinPoint.getArgs(); - - String operationType = determineOperationType(methodName); - String entityType = extractEntityType(className); - - logger.debug("拦截审计操作: {}.{}, 操作类型: {}, 实体类型: {}", - className, methodName, operationType, entityType); - - try { - if ("save".equals(methodName) && args.length > 0) { - return handleSaveOperation(joinPoint, args[0], entityType, operationType); - } else if ("delete".equals(methodName) || "deleteById".equals(methodName)) { - return handleDeleteOperation(joinPoint, args, entityType, operationType); - } - - return joinPoint.proceed(); - } catch (Throwable error) { - logger.error("审计日志记录失败: {}", error.getMessage(), error); - throw error; - } - } + String entityType = auditable.entityType(); + String operationType = auditable.operationType(); + + logger.debug("审计切面拦截: {}.{}(), entityType={}, operationType={}", className, methodName, entityType, operationType); - private Object handleSaveOperation(ProceedingJoinPoint joinPoint, Object entity, - String entityType, String operationType) throws Throwable { try { - final String[] beforeDataHolder = {null}; - final Long[] entityIdHolder = {null}; - final String[] operationTypeHolder = {operationType}; - - if (entity instanceof Persistable) { - Persistable persistable = (Persistable) entity; - entityIdHolder[0] = persistable.getId() != null ? - ((Number) persistable.getId()).longValue() : null; - - if (entityIdHolder[0] != null) { - beforeDataHolder[0] = fetchEntityBeforeData(entityType, entityIdHolder[0]); - operationTypeHolder[0] = "UPDATE"; - } else { - operationTypeHolder[0] = "CREATE"; - } - } - Object result = joinPoint.proceed(); - + if (result instanceof Mono) { - return ((Mono) result).flatMap(savedEntity -> { - String afterData = serializeEntity(savedEntity); - Long finalEntityId = entityIdHolder[0] != null ? entityIdHolder[0] : extractEntityId(savedEntity); - String finalOperationType = operationTypeHolder[0]; - String finalBeforeData = beforeDataHolder[0]; - - logger.debug("保存操作审计日志: entityType={}, entityIdHolder={}, extractedEntityId={}, finalEntityId={}", - entityType, entityIdHolder[0], extractEntityId(savedEntity), finalEntityId); - + return ((Mono) result).flatMap(retValue -> { + Long entityId = extractIdFromResult(retValue); + String afterData = serializeEntity(retValue); return createAndSaveAuditLog( - entityType, finalEntityId, finalOperationType, - finalBeforeData, afterData, savedEntity - ).thenReturn(savedEntity); + entityType, entityId, operationType, + null, afterData + ).thenReturn(retValue); }); - } - - return result; - } catch (Throwable error) { - logger.error("保存操作审计日志记录失败", error); - throw error; - } - } - - private Object handleDeleteOperation(ProceedingJoinPoint joinPoint, Object[] args, - String entityType, String operationType) throws Throwable { - try { - Long entityId = null; - String beforeData = null; - - if (args.length > 0) { - if (args[0] instanceof Number) { - entityId = ((Number) args[0]).longValue(); - beforeData = fetchEntityBeforeData(entityType, entityId); - } else if (args[0] instanceof Persistable) { - Persistable persistable = (Persistable) args[0]; - entityId = persistable.getId() != null ? - ((Number) persistable.getId()).longValue() : null; - beforeData = serializeEntity(args[0]); - } - } - - Object result = joinPoint.proceed(); - - if (result instanceof Mono) { - Long finalEntityId = entityId; - String finalBeforeData = beforeData; - return ((Mono) result).flatMap(deleted -> - createAndSaveAuditLog( - entityType, finalEntityId, "DELETE", - finalBeforeData, null, null - ).thenReturn(deleted) - ); } else if (result instanceof Flux) { - Long finalEntityId = entityId; - String finalBeforeData = beforeData; - return ((Flux) result).flatMap(deleted -> - createAndSaveAuditLog( - entityType, finalEntityId, "DELETE", - finalBeforeData, null, null - ).thenReturn(deleted) - ); + return ((Flux) result).collectList() + .flatMapMany(list -> { + String afterData = serializeEntity(list); + return createAndSaveAuditLog( + entityType, null, operationType, + null, afterData + ).thenMany(Flux.fromIterable(list)); + }); } - + return result; } catch (Throwable error) { - logger.error("删除操作审计日志记录失败", error); + logger.error("审计日志记录失败: {}.{}()", className, methodName, error); throw error; } } - private Mono createAndSaveAuditLog(String entityType, Long entityId, - String operationType, String beforeData, - String afterData, Object entity) { + private Mono createAndSaveAuditLog(String entityType, Long entityId, + String operationType, String beforeData, + String afterData) { logger.debug("创建审计日志: entityType={}, entityId={}, operationType={}", entityType, entityId, operationType); + return ReactiveSecurityContextHolder.getContext() .map(ctx -> ctx.getAuthentication().getPrincipal()) .defaultIfEmpty("system") @@ -178,123 +91,67 @@ public class AuditLogAspect { auditLog.setOperator(principal instanceof String ? (String) principal : "system"); auditLog.setBeforeData(beforeData); auditLog.setAfterData(afterData); - - logger.debug("审计日志对象: entityId={}, entityType={}, operationType={}", - auditLog.getEntityId(), auditLog.getEntityType(), auditLog.getOperationType()); - - if (beforeData != null && afterData != null) { - String[] changedFields = extractChangedFields(beforeData, afterData); - auditLog.setChangedFields(changedFields); - } - auditLog.setDescription(generateDescription(entityType, operationType, entityId)); - - return auditLogService.save(auditLog) - .doOnSuccess(saved -> logger.debug("审计日志保存成功: {} - {}", - entityType, operationType)) - .doOnError(error -> logger.error("审计日志保存失败: {}", - error.getMessage())) + + return auditLogService.saveAsync(auditLog) + .doOnSuccess(saved -> logger.debug("审计日志保存成功: {} - {}, ID={}", + entityType, operationType, saved.getId())) + .doOnError(error -> logger.error("审计日志保存失败: {}", error.getMessage())) .then(); }) .onErrorResume(error -> { - logger.error("创建审计日志失败,但不影响主流程: {}", error.getMessage()); + logger.error("创建审计日志失败,但不影响主流程: {}", error.getMessage(), error); return Mono.empty(); }); } - private String determineOperationType(String methodName) { - if (methodName.startsWith("save")) { - return "SAVE"; - } else if (methodName.startsWith("delete")) { - return "DELETE"; + private Long extractIdFromResult(Object result) { + if (result == null) { + return null; } - return "UNKNOWN"; - } - - private String extractEntityType(String className) { - if (className.contains("User")) { - return "User"; - } else if (className.contains("Role")) { - return "Role"; - } else if (className.contains("Menu")) { - return "Menu"; - } else if (className.contains("Permission")) { - return "Permission"; + try { + var getIdMethod = result.getClass().getMethod("getId"); + Object id = getIdMethod.invoke(result); + if (id instanceof Number) { + return ((Number) id).longValue(); + } + if (id instanceof String) { + try { + return Long.parseLong((String) id); + } catch (NumberFormatException e) { + return null; + } + } + } catch (NoSuchMethodException e) { + logger.debug("结果对象没有getId方法: {}", result.getClass().getSimpleName()); + } catch (Exception e) { + logger.debug("提取结果ID失败: {}", e.getMessage()); } - return className.replace("Repository", "").replace("Impl", ""); - } - - private String fetchEntityBeforeData(String entityType, Long entityId) { return null; } private String serializeEntity(Object entity) { try { - return objectMapper.writeValueAsString(entity); + ObjectMapper mapper = new ObjectMapper() + .registerModule(new JavaTimeModule()) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .disable(SerializationFeature.FAIL_ON_SELF_REFERENCES); + return mapper.writeValueAsString(entity); } catch (Exception e) { logger.error("序列化实体失败: {}", e.getMessage()); return null; } } - private Long extractEntityId(Object entity) { - logger.debug("提取实体ID: entity class={}", entity.getClass().getName()); - if (entity instanceof Persistable) { - Persistable persistable = (Persistable) entity; - Object id = persistable.getId(); - logger.debug("Persistable实体ID: id={}, isNew={}", id, persistable.isNew()); - return id != null ? ((Number) id).longValue() : null; - } - logger.debug("实体不是Persistable类型"); - return null; - } - - private String[] extractChangedFields(String beforeData, String afterData) { - try { - JsonNode beforeNode = objectMapper.readTree(beforeData); - JsonNode afterNode = objectMapper.readTree(afterData); - - List changedFields = new ArrayList<>(); - - beforeNode.fieldNames().forEachRemaining(fieldName -> { - JsonNode beforeValue = beforeNode.get(fieldName); - JsonNode afterValue = afterNode.get(fieldName); - - if (afterValue == null || !beforeValue.equals(afterValue)) { - changedFields.add(fieldName); - } - }); - - afterNode.fieldNames().forEachRemaining(fieldName -> { - if (!beforeNode.has(fieldName)) { - changedFields.add(fieldName); - } - }); - - return changedFields.toArray(new String[0]); - } catch (Exception e) { - logger.error("提取变更字段失败: {}", e.getMessage()); - return new String[0]; - } - } - private String generateDescription(String entityType, String operationType, Long entityId) { - String operation = ""; - switch (operationType) { - case "CREATE": - operation = "创建"; - break; - case "UPDATE": - operation = "更新"; - break; - case "DELETE": - operation = "删除"; - break; - default: - operation = "操作"; - } - - return String.format("%s%s (ID: %s)", operation, entityType, - entityId != null ? entityId : "未知"); + String operation = switch (operationType) { + case "CREATE" -> "创建"; + case "UPDATE" -> "更新"; + case "DELETE" -> "删除"; + default -> "操作"; + }; + + return String.format("%s%s (ID: %s)", operation, entityType, + entityId != null ? entityId : "未知"); } } diff --git a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/audit/AuditLogHelper.java b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/audit/AuditLogHelper.java new file mode 100644 index 0000000..8bceb63 --- /dev/null +++ b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/audit/AuditLogHelper.java @@ -0,0 +1,80 @@ +package cn.novalon.manage.sys.audit; + +import cn.novalon.manage.sys.audit.domain.AuditLog; +import cn.novalon.manage.sys.audit.service.IAuditLogService; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.core.context.ReactiveSecurityContextHolder; +import reactor.core.publisher.Mono; + +public final class AuditLogHelper { + + private static final Logger logger = LoggerFactory.getLogger(AuditLogHelper.class); + private static final ObjectMapper objectMapper = new ObjectMapper() + .registerModule(new JavaTimeModule()) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .disable(SerializationFeature.FAIL_ON_SELF_REFERENCES); + + private AuditLogHelper() {} + + public static Mono record(IAuditLogService auditLogService, + String entityType, Long entityId, + String operationType, Object afterEntity) { + return record(auditLogService, entityType, entityId, operationType, null, afterEntity); + } + + public static Mono record(IAuditLogService auditLogService, + String entityType, Long entityId, + String operationType, Object beforeEntity, Object afterEntity) { + return ReactiveSecurityContextHolder.getContext() + .map(ctx -> ctx.getAuthentication().getPrincipal()) + .defaultIfEmpty("system") + .flatMap(principal -> { + AuditLog auditLog = new AuditLog(); + auditLog.generateId(); + auditLog.setEntityType(entityType); + auditLog.setEntityId(entityId != null ? entityId : 0L); + auditLog.setOperationType(operationType); + auditLog.setOperator(principal instanceof String ? (String) principal : "system"); + auditLog.setBeforeData(serializeEntity(beforeEntity)); + auditLog.setAfterData(serializeEntity(afterEntity)); + auditLog.setDescription(generateDescription(entityType, operationType, entityId)); + + logger.info("记录审计日志: {} {} ID={}, operator={}", operationType, entityType, entityId, auditLog.getOperator()); + + return auditLogService.saveAsync(auditLog) + .doOnSuccess(saved -> logger.info("审计日志保存成功: {} - {}, ID={}", + entityType, operationType, saved.getId())) + .doOnError(error -> logger.error("审计日志保存失败: {}", error.getMessage())) + .then(); + }) + .onErrorResume(error -> { + logger.error("记录审计日志失败,但不影响主流程: {}", error.getMessage(), error); + return Mono.empty(); + }); + } + + private static String serializeEntity(Object entity) { + try { + if (entity == null) return null; + return objectMapper.writeValueAsString(entity); + } catch (Exception e) { + logger.error("序列化实体失败: {}", e.getMessage()); + return null; + } + } + + private static String generateDescription(String entityType, String operationType, Long entityId) { + String operation = switch (operationType) { + case "CREATE" -> "创建"; + case "UPDATE" -> "更新"; + case "DELETE" -> "删除"; + default -> "操作"; + }; + return String.format("%s%s (ID: %s)", operation, entityType, + entityId != null ? entityId : "未知"); + } +} diff --git a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/audit/Auditable.java b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/audit/Auditable.java new file mode 100644 index 0000000..c30300f --- /dev/null +++ b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/audit/Auditable.java @@ -0,0 +1,15 @@ +package cn.novalon.manage.sys.audit; + +import java.lang.annotation.*; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Auditable { + + String entityType(); + + String operationType() default "CREATE"; + + String description() default ""; +} diff --git a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/audit/OperationLogWebFilter.java b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/audit/OperationLogWebFilter.java new file mode 100644 index 0000000..f62dd0f --- /dev/null +++ b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/audit/OperationLogWebFilter.java @@ -0,0 +1,181 @@ +package cn.novalon.manage.sys.audit; + +import cn.novalon.manage.sys.core.domain.OperationLog; +import cn.novalon.manage.sys.core.service.IOperationLogService; +import cn.novalon.manage.sys.util.IpUtils; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.annotation.PostConstruct; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.core.io.buffer.DataBufferUtils; +import org.springframework.http.HttpMethod; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.security.core.context.ReactiveSecurityContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.server.HandlerStrategies; +import org.springframework.web.reactive.function.server.RouterFunction; +import org.springframework.web.reactive.function.server.ServerResponse; +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.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Component +@Order(Ordered.LOWEST_PRECEDENCE) +public class OperationLogWebFilter implements WebFilter { + + private static final Logger logger = LoggerFactory.getLogger(OperationLogWebFilter.class); + + private final IOperationLogService operationLogService; + private final ObjectMapper objectMapper; + + private static final Map OPERATION_MAPPING = new ConcurrentHashMap<>(); + + static { + OPERATION_MAPPING.put("POST:/api/roles", new OperationInfo("角色管理", "创建角色")); + OPERATION_MAPPING.put("PUT:/api/roles/", new OperationInfo("角色管理", "更新角色")); + OPERATION_MAPPING.put("DELETE:/api/roles/", new OperationInfo("角色管理", "删除角色")); + OPERATION_MAPPING.put("POST:/api/users", new OperationInfo("用户管理", "创建用户")); + OPERATION_MAPPING.put("PUT:/api/users/", new OperationInfo("用户管理", "更新用户")); + OPERATION_MAPPING.put("DELETE:/api/users/", new OperationInfo("用户管理", "删除用户")); + OPERATION_MAPPING.put("POST:/api/users/", new OperationInfo("用户管理", "用户操作")); + OPERATION_MAPPING.put("POST:/api/menus", new OperationInfo("菜单管理", "创建菜单")); + OPERATION_MAPPING.put("PUT:/api/menus/", new OperationInfo("菜单管理", "更新菜单")); + OPERATION_MAPPING.put("DELETE:/api/menus/", new OperationInfo("菜单管理", "删除菜单")); + } + + public OperationLogWebFilter(IOperationLogService operationLogService, ObjectMapper objectMapper) { + logger.info("=== OperationLogWebFilter 构造函数被调用 ==="); + this.operationLogService = operationLogService; + this.objectMapper = objectMapper; + } + + @PostConstruct + public void init() { + logger.info("=== OperationLogWebFilter 初始化 ==="); + logger.info("操作日志映射配置数量: {}", OPERATION_MAPPING.size()); + OPERATION_MAPPING.forEach((key, value) -> { + logger.info(" {} -> {}:{}", key, value.module, value.operation); + }); + } + + @Override + public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { + ServerHttpRequest request = exchange.getRequest(); + String method = request.getMethod().name(); + String path = request.getPath().value(); + + logger.info("WebFilter 拦截请求: {} {}", method, path); + + OperationInfo operationInfo = findOperationInfo(method, path); + + if (operationInfo == null) { + logger.info("未匹配到操作日志配置,跳过: {} {}", method, path); + return chain.filter(exchange); + } + + logger.info("匹配到操作日志配置: {} {} -> {}:{}", method, path, operationInfo.module, operationInfo.operation); + + long startTime = System.currentTimeMillis(); + String ip = IpUtils.getClientIp(request); + + return Mono.deferContextual(contextView -> { + return chain.filter(exchange) + .then(Mono.defer(() -> { + long duration = System.currentTimeMillis() - startTime; + logger.info("请求处理完成,准备保存操作日志: {} {}, 耗时: {}ms", method, path, duration); + + return ReactiveSecurityContextHolder.getContext() + .flatMap(securityContext -> { + Object principal = securityContext.getAuthentication().getPrincipal(); + String username = principal instanceof String ? (String) principal : "system"; + logger.info("获取到用户名: {}", username); + return Mono.just(username); + }) + .defaultIfEmpty("system") + .flatMap(username -> { + logger.info("开始保存操作日志: 用户={}, 操作={}", username, + operationInfo.module + " - " + operationInfo.operation); + + OperationLog log = new OperationLog(); + log.setUsername(username); + log.setOperation(operationInfo.module + " - " + operationInfo.operation); + log.setMethod(method + " " + path); + log.setParams(null); + log.setIp(ip); + log.setDuration(duration); + log.setStatus("0"); + + return operationLogService.save(log) + .doOnSuccess(saved -> logger.info("操作日志保存成功: {} - {}", + operationInfo.module, operationInfo.operation)) + .doOnError(e -> logger.error("操作日志保存失败: {}", e.getMessage(), e)) + .onErrorResume(e -> Mono.empty()); + }) + .then(); + })) + .onErrorResume(error -> { + long duration = System.currentTimeMillis() - startTime; + logger.error("请求处理失败: {} {}, 错误: {}", method, path, error.getMessage()); + + return ReactiveSecurityContextHolder.getContext() + .flatMap(securityContext -> { + Object principal = securityContext.getAuthentication().getPrincipal(); + String username = principal instanceof String ? (String) principal : "system"; + return Mono.just(username); + }) + .defaultIfEmpty("system") + .flatMap(username -> { + OperationLog log = new OperationLog(); + log.setUsername(username); + log.setOperation(operationInfo.module + " - " + operationInfo.operation); + log.setMethod(method + " " + path); + log.setParams(null); + log.setIp(ip); + log.setDuration(duration); + log.setStatus("1"); + log.setErrorMsg(error.getMessage()); + + return operationLogService.save(log) + .doOnError(e -> logger.error("错误日志保存失败: {}", e.getMessage())) + .onErrorResume(e -> Mono.empty()); + }) + .then(Mono.error(error)); + }); + }); + } + + private OperationInfo findOperationInfo(String method, String path) { + String key = method + ":" + path; + if (OPERATION_MAPPING.containsKey(key)) { + return OPERATION_MAPPING.get(key); + } + + for (Map.Entry entry : OPERATION_MAPPING.entrySet()) { + String mappingKey = entry.getKey(); + if (key.startsWith(mappingKey)) { + return entry.getValue(); + } + } + + return null; + } + + private static class OperationInfo { + final String module; + final String operation; + + OperationInfo(String module, String operation) { + this.module = module; + this.operation = operation; + } + } +} diff --git a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/audit/domain/AuditLog.java b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/audit/domain/AuditLog.java index 1c25995..fb4221e 100644 --- a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/audit/domain/AuditLog.java +++ b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/audit/domain/AuditLog.java @@ -139,6 +139,29 @@ public class AuditLog extends BaseDomain { this.description = description; } + @Override + public String toString() { + return "AuditLog{" + + "id=" + id + + ", entityType='" + entityType + '\'' + + ", entityId=" + entityId + + ", operationType='" + operationType + '\'' + + ", operator='" + operator + '\'' + + ", operationTime=" + operationTime + + ", beforeData='" + beforeData + '\'' + + ", afterData='" + afterData + '\'' + + ", changedFields=" + java.util.Arrays.toString(changedFields) + + ", ipAddress='" + ipAddress + '\'' + + ", userAgent='" + userAgent + '\'' + + ", description='" + description + '\'' + + ", createBy='" + createBy + '\'' + + ", updateBy='" + updateBy + '\'' + + ", createdAt=" + createdAt + + ", updatedAt=" + updatedAt + + ", deletedAt=" + deletedAt + + '}'; + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/audit/service/impl/AuditLogArchiveService.java b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/audit/service/impl/AuditLogArchiveService.java index 7e748bc..06afcf8 100644 --- a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/audit/service/impl/AuditLogArchiveService.java +++ b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/audit/service/impl/AuditLogArchiveService.java @@ -39,7 +39,7 @@ public class AuditLogArchiveService implements IAuditLogArchiveService { } @Override - @Transactional + @Transactional(transactionManager = "connectionFactoryTransactionManager") public Mono archiveOldLogs(int daysToKeep) { LocalDateTime archiveBefore = LocalDateTime.now().minusDays(daysToKeep); @@ -53,7 +53,7 @@ public class AuditLogArchiveService implements IAuditLogArchiveService { } @Override - @Transactional + @Transactional(transactionManager = "connectionFactoryTransactionManager") public Mono archiveLog(AuditLog auditLog) { AuditLogArchive archive = convertToArchive(auditLog); @@ -99,7 +99,7 @@ public class AuditLogArchiveService implements IAuditLogArchiveService { } @Override - @Transactional + @Transactional(transactionManager = "connectionFactoryTransactionManager") public Mono deleteArchivedLogsOlderThan(LocalDateTime date) { return auditLogArchiveRepository.findByOperationTimeBetween(LocalDateTime.MIN, date) .flatMap(archive -> auditLogArchiveRepository.deleteById(archive.getId())) diff --git a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/audit/service/impl/AuditLogService.java b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/audit/service/impl/AuditLogService.java index 6d1ab6e..0f84d82 100644 --- a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/audit/service/impl/AuditLogService.java +++ b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/audit/service/impl/AuditLogService.java @@ -150,7 +150,6 @@ public class AuditLogService implements IAuditLogService { } @Override - @Async("auditLogExecutor") public Mono saveAsync(AuditLog auditLog) { logger.debug("异步保存审计日志: {} - {}", auditLog.getEntityType(), auditLog.getOperationType()); @@ -161,13 +160,13 @@ public class AuditLogService implements IAuditLogService { } @Override - @Transactional + @Transactional(transactionManager = "connectionFactoryTransactionManager") public Mono deleteById(Long id) { return auditLogRepository.deleteById(id); } @Override - @Transactional + @Transactional(transactionManager = "connectionFactoryTransactionManager") public Mono logicalDeleteById(Long id) { return auditLogRepository.findById(id) .flatMap(auditLog -> { @@ -178,7 +177,7 @@ public class AuditLogService implements IAuditLogService { } @Override - @Transactional + @Transactional(transactionManager = "connectionFactoryTransactionManager") public Mono logicalDeleteByIds(List ids) { return Flux.fromIterable(ids) .flatMap(this::logicalDeleteById) @@ -186,7 +185,7 @@ public class AuditLogService implements IAuditLogService { } @Override - @Transactional + @Transactional(transactionManager = "connectionFactoryTransactionManager") public Mono restoreById(Long id) { return auditLogRepository.findById(id) .flatMap(auditLog -> { @@ -197,7 +196,7 @@ public class AuditLogService implements IAuditLogService { } @Override - @Transactional + @Transactional(transactionManager = "connectionFactoryTransactionManager") public Mono restoreByIds(List ids) { return Flux.fromIterable(ids) .flatMap(this::restoreById) diff --git a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/config/SecurityConfig.java b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/config/SecurityConfig.java index 8d5373d..ec47728 100644 --- a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/config/SecurityConfig.java +++ b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/config/SecurityConfig.java @@ -1,5 +1,6 @@ package cn.novalon.manage.sys.config; +import cn.novalon.manage.sys.audit.OperationLogWebFilter; import cn.novalon.manage.sys.security.JwtAuthenticationFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -11,22 +12,20 @@ import org.springframework.security.config.web.server.SecurityWebFiltersOrder; import org.springframework.security.config.web.server.ServerHttpSecurity; import org.springframework.security.web.server.SecurityWebFilterChain; -/** - * 安全配置类 - * - * @author 张翔 - * @date 2026-03-13 - */ @Configuration @EnableWebFluxSecurity public class SecurityConfig { private static final Logger logger = LoggerFactory.getLogger(SecurityConfig.class); private final JwtAuthenticationFilter jwtAuthenticationFilter; + private final OperationLogWebFilter operationLogWebFilter; private final Environment environment; - public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter, Environment environment) { + public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter, + OperationLogWebFilter operationLogWebFilter, + Environment environment) { this.jwtAuthenticationFilter = jwtAuthenticationFilter; + this.operationLogWebFilter = operationLogWebFilter; this.environment = environment; } @@ -46,6 +45,7 @@ public class SecurityConfig { .httpBasic(ServerHttpSecurity.HttpBasicSpec::disable) .formLogin(ServerHttpSecurity.FormLoginSpec::disable) .addFilterBefore(jwtAuthenticationFilter, SecurityWebFiltersOrder.AUTHENTICATION) + .addFilterAfter(operationLogWebFilter, SecurityWebFiltersOrder.AUTHORIZATION) .authorizeExchange(spec -> { spec.pathMatchers("/api/auth/**").permitAll() .pathMatchers("/api/public/**").permitAll() diff --git a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/domain/BaseDomain.java b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/domain/BaseDomain.java index d318605..3ff628b 100644 --- a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/domain/BaseDomain.java +++ b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/domain/BaseDomain.java @@ -76,10 +76,29 @@ public abstract class BaseDomain { return this.id; } + /** + * 删除(幂等操作) + * 已删除的对象不会更新删除时间 + */ + public void delete() { + if (this.deletedAt == null) { + this.deletedAt = LocalDateTime.now(); + } + } + + /** + * 恢复已删除的对象 + */ + public void restore() { + this.deletedAt = null; + } + @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; BaseDomain that = (BaseDomain) o; return id != null && id.equals(that.id); } diff --git a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/domain/SysPermission.java b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/domain/SysPermission.java index a28f34a..046de39 100644 --- a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/domain/SysPermission.java +++ b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/domain/SysPermission.java @@ -77,18 +77,4 @@ public class SysPermission extends BaseDomain { public void setStatus(Integer status) { this.status = status; } - - /** - * 删除权限 - */ - public void delete() { - this.deletedAt = java.time.LocalDateTime.now(); - } - - /** - * 恢复权限 - */ - public void restore() { - this.deletedAt = null; - } } \ No newline at end of file diff --git a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/domain/SysRole.java b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/domain/SysRole.java index e4357a2..e6978c6 100644 --- a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/domain/SysRole.java +++ b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/domain/SysRole.java @@ -57,18 +57,4 @@ public class SysRole extends BaseDomain { public void setStatus(Integer status) { this.status = status; } - - /** - * 删除角色 - */ - public void delete() { - this.deletedAt = LocalDateTime.now(); - } - - /** - * 恢复角色 - */ - public void restore() { - this.deletedAt = null; - } } diff --git a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/domain/SysUser.java b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/domain/SysUser.java index e228582..57eb4b4 100644 --- a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/domain/SysUser.java +++ b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/domain/SysUser.java @@ -100,11 +100,4 @@ public class SysUser extends BaseDomain { public void setStatus(Integer status) { this.status = status; } - - /** - * 删除用户 - */ - public void delete() { - this.deletedAt = LocalDateTime.now(); - } } diff --git a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/repository/ISysMenuRepository.java b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/repository/ISysMenuRepository.java index 5f91c3d..5d61a8f 100644 --- a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/repository/ISysMenuRepository.java +++ b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/repository/ISysMenuRepository.java @@ -24,6 +24,8 @@ public interface ISysMenuRepository { Mono save(SysMenu sysMenu); + Mono update(SysMenu sysMenu); + Mono deleteById(Long id); Flux findAll(); diff --git a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/repository/ISysUserRepository.java b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/repository/ISysUserRepository.java index c55a59e..7b76b8b 100644 --- a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/repository/ISysUserRepository.java +++ b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/repository/ISysUserRepository.java @@ -28,6 +28,8 @@ public interface ISysUserRepository { Mono save(SysUser sysUser); + Mono update(SysUser sysUser); + Mono deleteById(Long id); Flux findAll(); diff --git a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/service/impl/DictionaryService.java b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/service/impl/DictionaryService.java index a31f82a..245eb7b 100644 --- a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/service/impl/DictionaryService.java +++ b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/service/impl/DictionaryService.java @@ -48,13 +48,11 @@ public class DictionaryService implements IDictionaryService { @Override public Mono save(Dictionary dictionary) { if (dictionary.getId() == null) { - dictionary.setCreatedAt(LocalDateTime.now()); return checkTypeAndCodeExists(dictionary.getType(), dictionary.getCode()) .flatMap(exists -> { if (exists) { return Mono.error(new DictionaryAlreadyExistsException(dictionary.getType(), dictionary.getCode())); } - dictionary.setUpdatedAt(LocalDateTime.now()); return repository.save(dictionary); }); } diff --git a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/service/impl/OperationLogService.java b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/service/impl/OperationLogService.java index 0806d35..6d38cf9 100644 --- a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/service/impl/OperationLogService.java +++ b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/service/impl/OperationLogService.java @@ -29,7 +29,6 @@ public class OperationLogService implements IOperationLogService { @Override public Mono save(OperationLog log) { - log.setCreatedAt(LocalDateTime.now()); return logRepository.save(log); } diff --git a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/service/impl/SysConfigService.java b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/service/impl/SysConfigService.java index 52464c7..95f67b1 100644 --- a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/service/impl/SysConfigService.java +++ b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/service/impl/SysConfigService.java @@ -1,27 +1,23 @@ package cn.novalon.manage.sys.core.service.impl; +import cn.novalon.manage.sys.audit.AuditLogHelper; +import cn.novalon.manage.sys.audit.service.IAuditLogService; import cn.novalon.manage.sys.core.domain.SysConfig; import cn.novalon.manage.sys.core.repository.ISysConfigRepository; import cn.novalon.manage.sys.core.service.ISysConfigService; -import org.springframework.cache.annotation.CacheEvict; -import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -/** - * 系统配置服务实现类 - * - * @author 张翔 - * @date 2026-03-14 - */ @Service public class SysConfigService implements ISysConfigService { private final ISysConfigRepository repository; + private final IAuditLogService auditLogService; - public SysConfigService(ISysConfigRepository repository) { + public SysConfigService(ISysConfigRepository repository, IAuditLogService auditLogService) { this.repository = repository; + this.auditLogService = auditLogService; } @Override @@ -30,27 +26,28 @@ public class SysConfigService implements ISysConfigService { } @Override - @Cacheable(value = "sysConfig", key = "#id") public Mono findById(Long id) { return repository.findById(id); } @Override - @Cacheable(value = "sysConfig", key = "#configKey") public Mono findByConfigKey(String configKey) { return repository.findByConfigKeyAndDeletedAtIsNull(configKey); } @Override - @CacheEvict(value = "sysConfig", allEntries = true) public Mono save(SysConfig config) { - return repository.save(config); + return repository.save(config) + .flatMap(saved -> AuditLogHelper.record(auditLogService, "Config", saved.getId(), "CREATE", saved) + .thenReturn(saved)); } @Override - @CacheEvict(value = "sysConfig", key = "#id") public Mono deleteById(Long id) { - return repository.deleteByIdAndDeletedAtIsNull(id); + return repository.findById(id) + .flatMap(config -> repository.deleteByIdAndDeletedAtIsNull(id) + .then(AuditLogHelper.record(auditLogService, "Config", id, "DELETE", config, null))) + .then(); } @Override diff --git a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/service/impl/SysDictTypeService.java b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/service/impl/SysDictTypeService.java index ab5c1a5..2205260 100644 --- a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/service/impl/SysDictTypeService.java +++ b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/service/impl/SysDictTypeService.java @@ -1,5 +1,7 @@ package cn.novalon.manage.sys.core.service.impl; +import cn.novalon.manage.sys.audit.AuditLogHelper; +import cn.novalon.manage.sys.audit.service.IAuditLogService; import cn.novalon.manage.sys.core.domain.SysDictType; import cn.novalon.manage.sys.core.repository.ISysDictTypeRepository; import cn.novalon.manage.sys.core.service.ISysDictTypeService; @@ -7,19 +9,15 @@ import org.springframework.stereotype.Service; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -/** - * 字典类型服务实现类 - * - * @author 张翔 - * @date 2026-03-14 - */ @Service public class SysDictTypeService implements ISysDictTypeService { private final ISysDictTypeRepository repository; + private final IAuditLogService auditLogService; - public SysDictTypeService(ISysDictTypeRepository repository) { + public SysDictTypeService(ISysDictTypeRepository repository, IAuditLogService auditLogService) { this.repository = repository; + this.auditLogService = auditLogService; } @Override @@ -39,11 +37,16 @@ public class SysDictTypeService implements ISysDictTypeService { @Override public Mono save(SysDictType dictType) { - return repository.save(dictType); + return repository.save(dictType) + .flatMap(saved -> AuditLogHelper.record(auditLogService, "Dict", saved.getId(), "CREATE", saved) + .thenReturn(saved)); } @Override public Mono deleteById(Long id) { - return repository.deleteByIdAndDeletedAtIsNull(id); + return repository.findById(id) + .flatMap(dict -> repository.deleteByIdAndDeletedAtIsNull(id) + .then(AuditLogHelper.record(auditLogService, "Dict", id, "DELETE", dict, null))) + .then(); } } diff --git a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/service/impl/SysMenuService.java b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/service/impl/SysMenuService.java index 06a222f..bf52711 100644 --- a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/service/impl/SysMenuService.java +++ b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/service/impl/SysMenuService.java @@ -6,6 +6,8 @@ import cn.novalon.manage.sys.core.service.ISysMenuService; import cn.novalon.manage.sys.core.command.CreateMenuCommand; import cn.novalon.manage.sys.core.command.UpdateMenuCommand; import cn.novalon.manage.common.util.StatusConstants; +import cn.novalon.manage.sys.audit.AuditLogHelper; +import cn.novalon.manage.sys.audit.service.IAuditLogService; import org.springframework.stereotype.Service; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -24,9 +26,11 @@ import java.util.stream.Collectors; public class SysMenuService implements ISysMenuService { private final ISysMenuRepository menuRepository; + private final IAuditLogService auditLogService; - public SysMenuService(ISysMenuRepository menuRepository) { + public SysMenuService(ISysMenuRepository menuRepository, IAuditLogService auditLogService) { this.menuRepository = menuRepository; + this.auditLogService = auditLogService; } @Override @@ -46,8 +50,9 @@ public class SysMenuService implements ISysMenuService { @Override public Mono createMenu(SysMenu menu) { - menu.setCreatedAt(LocalDateTime.now()); - return menuRepository.save(menu); + return menuRepository.save(menu) + .flatMap(saved -> AuditLogHelper.record(auditLogService, "Menu", saved.getId(), "CREATE", saved) + .thenReturn(saved)); } @Override @@ -60,14 +65,18 @@ public class SysMenuService implements ISysMenuService { menu.setComponent(command.component()); menu.setPerms(command.perms()); menu.setStatus(command.status() != null ? command.status() : StatusConstants.ENABLED); - menu.setCreatedAt(LocalDateTime.now()); - return menuRepository.save(menu); + return menuRepository.save(menu) + .flatMap(saved -> AuditLogHelper.record(auditLogService, "Menu", saved.getId(), "CREATE", saved) + .thenReturn(saved)); } @Override public Mono updateMenu(SysMenu menu) { menu.setUpdatedAt(LocalDateTime.now()); - return menuRepository.save(menu); + return menuRepository.findById(menu.getId()) + .flatMap(before -> menuRepository.update(menu) + .flatMap(saved -> AuditLogHelper.record(auditLogService, "Menu", saved.getId(), "UPDATE", before, saved) + .thenReturn(saved))); } @Override @@ -75,6 +84,15 @@ public class SysMenuService implements ISysMenuService { return menuRepository.findById(command.id()) .switchIfEmpty(Mono.error(new RuntimeException("Menu not found"))) .flatMap(menu -> { + SysMenu before = new SysMenu(); + before.setId(menu.getId()); + before.setParentId(menu.getParentId()); + before.setMenuName(menu.getMenuName()); + before.setMenuType(menu.getMenuType()); + before.setOrderNum(menu.getOrderNum()); + before.setComponent(menu.getComponent()); + before.setPerms(menu.getPerms()); + before.setStatus(menu.getStatus()); if (command.parentId() != null) { menu.setParentId(command.parentId()); } @@ -97,13 +115,18 @@ public class SysMenuService implements ISysMenuService { menu.setStatus(command.status()); } menu.setUpdatedAt(LocalDateTime.now()); - return menuRepository.save(menu); + return menuRepository.update(menu) + .flatMap(saved -> AuditLogHelper.record(auditLogService, "Menu", saved.getId(), "UPDATE", before, saved) + .thenReturn(saved)); }); } @Override public Mono deleteMenu(Long id) { - return menuRepository.deleteById(id); + return menuRepository.findById(id) + .flatMap(menu -> menuRepository.deleteById(id) + .then(AuditLogHelper.record(auditLogService, "Menu", id, "DELETE", menu, null))) + .then(); } @Override diff --git a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/service/impl/SysPermissionService.java b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/service/impl/SysPermissionService.java index d45752f..d5d5394 100644 --- a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/service/impl/SysPermissionService.java +++ b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/service/impl/SysPermissionService.java @@ -1,11 +1,15 @@ package cn.novalon.manage.sys.core.service.impl; import cn.novalon.manage.common.util.StatusConstants; +import cn.novalon.manage.sys.audit.AuditLogHelper; +import cn.novalon.manage.sys.audit.service.IAuditLogService; import cn.novalon.manage.sys.core.domain.SysPermission; import cn.novalon.manage.sys.core.domain.SysRolePermission; import cn.novalon.manage.sys.core.repository.ISysPermissionRepository; import cn.novalon.manage.sys.core.repository.ISysRolePermissionRepository; import cn.novalon.manage.sys.core.service.ISysPermissionService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -24,13 +28,18 @@ import java.util.List; @Service public class SysPermissionService implements ISysPermissionService { + private static final Logger logger = LoggerFactory.getLogger(SysPermissionService.class); + private final ISysPermissionRepository permissionRepository; private final ISysRolePermissionRepository rolePermissionRepository; + private final IAuditLogService auditLogService; public SysPermissionService(ISysPermissionRepository permissionRepository, - ISysRolePermissionRepository rolePermissionRepository) { + ISysRolePermissionRepository rolePermissionRepository, + IAuditLogService auditLogService) { this.permissionRepository = permissionRepository; this.rolePermissionRepository = rolePermissionRepository; + this.auditLogService = auditLogService; } @Override @@ -60,25 +69,41 @@ public class SysPermissionService implements ISysPermissionService { @Override public Mono createPermission(SysPermission permission) { - permission.setCreatedAt(LocalDateTime.now()); if (permission.getStatus() == null) { permission.setStatus(StatusConstants.ENABLED); } - return permissionRepository.save(permission); + return permissionRepository.save(permission) + .flatMap(saved -> AuditLogHelper.record(auditLogService, "Permission", saved.getId(), "CREATE", saved) + .doOnError(e -> logger.error("Audit log failed for Permission CREATE id={}: {}", saved.getId(), e.getMessage())) + .thenReturn(saved)); } @Override public Mono updatePermission(SysPermission permission) { permission.setUpdatedAt(LocalDateTime.now()); - return permissionRepository.updatePermission(permission); + return permissionRepository.findById(permission.getId()) + .flatMap(before -> permissionRepository.updatePermission(permission) + .flatMap(saved -> AuditLogHelper.record(auditLogService, "Permission", saved.getId(), "UPDATE", before, saved) + .thenReturn(saved))); } @Override public Mono deletePermission(Long id) { return permissionRepository.findById(id) .flatMap(permission -> { + SysPermission before = new SysPermission(); + before.setId(permission.getId()); + before.setPermissionName(permission.getPermissionName()); + before.setPermissionCode(permission.getPermissionCode()); + before.setResource(permission.getResource()); + before.setAction(permission.getAction()); + before.setStatus(permission.getStatus()); + before.setCreatedAt(permission.getCreatedAt()); + before.setUpdatedAt(permission.getUpdatedAt()); + before.setDeletedAt(permission.getDeletedAt()); permission.delete(); return permissionRepository.updatePermission(permission) + .flatMap(saved -> AuditLogHelper.record(auditLogService, "Permission", id, "DELETE", before, saved)) .then(rolePermissionRepository.deleteByPermissionId(id)); }); } @@ -99,7 +124,7 @@ public class SysPermissionService implements ISysPermissionService { } @Override - @Transactional + @Transactional(transactionManager = "connectionFactoryTransactionManager") public Mono assignPermissionsToRole(Long roleId, List permissionIds) { return rolePermissionRepository.deleteByRoleId(roleId) .then(Flux.fromIterable(permissionIds) @@ -107,7 +132,6 @@ public class SysPermissionService implements ISysPermissionService { SysRolePermission rolePermission = new SysRolePermission(); rolePermission.setRoleId(roleId); rolePermission.setPermissionId(permissionId); - rolePermission.setCreatedAt(LocalDateTime.now()); return rolePermissionRepository.save(rolePermission); }) .then()); diff --git a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/service/impl/SysRoleService.java b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/service/impl/SysRoleService.java index 6a62709..c180906 100644 --- a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/service/impl/SysRoleService.java +++ b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/service/impl/SysRoleService.java @@ -1,6 +1,8 @@ package cn.novalon.manage.sys.core.service.impl; import cn.novalon.manage.common.util.StatusConstants; +import cn.novalon.manage.sys.audit.AuditLogHelper; +import cn.novalon.manage.sys.audit.service.IAuditLogService; import cn.novalon.manage.sys.core.domain.SysRole; import cn.novalon.manage.sys.core.query.SysRoleQuery; import cn.novalon.manage.sys.core.repository.ISysRoleRepository; @@ -21,12 +23,6 @@ import reactor.core.publisher.Mono; import java.time.LocalDateTime; -/** - * 系统角色服务实现类 - * - * @author 张翔 - * @date 2026-03-14 - */ @Service public class SysRoleService implements ISysRoleService { @@ -35,13 +31,16 @@ public class SysRoleService implements ISysRoleService { private final ISysUserService userService; private final IUserRoleRepository userRoleRepository; private final ISysRolePermissionRepository rolePermissionRepository; + private final IAuditLogService auditLogService; public SysRoleService(ISysRoleRepository roleRepository, ISysUserService userService, - IUserRoleRepository userRoleRepository, ISysRolePermissionRepository rolePermissionRepository) { + IUserRoleRepository userRoleRepository, ISysRolePermissionRepository rolePermissionRepository, + IAuditLogService auditLogService) { this.roleRepository = roleRepository; this.userService = userService; this.userRoleRepository = userRoleRepository; this.rolePermissionRepository = rolePermissionRepository; + this.auditLogService = auditLogService; } @Override @@ -76,7 +75,9 @@ public class SysRoleService implements ISysRoleService { if (role.getStatus() == null) { role.setStatus(StatusConstants.ENABLED); } - return roleRepository.save(role); + return roleRepository.save(role) + .flatMap(saved -> AuditLogHelper.record(auditLogService, "Role", saved.getId(), "CREATE", saved) + .thenReturn(saved)); } @Override @@ -88,13 +89,18 @@ public class SysRoleService implements ISysRoleService { role.setRoleSort(command.roleSort()); role.setStatus(command.status() != null ? command.status() : StatusConstants.ENABLED); role.setCreatedAt(LocalDateTime.now()); - return roleRepository.save(role); + return roleRepository.save(role) + .flatMap(saved -> AuditLogHelper.record(auditLogService, "Role", saved.getId(), "CREATE", saved) + .thenReturn(saved)); } @Override public Mono updateRole(SysRole role) { role.setUpdatedAt(LocalDateTime.now()); - return roleRepository.save(role); + return roleRepository.findById(role.getId()) + .flatMap(before -> roleRepository.updateRole(role) + .flatMap(saved -> AuditLogHelper.record(auditLogService, "Role", saved.getId(), "UPDATE", before, saved) + .thenReturn(saved))); } @Override @@ -102,6 +108,15 @@ public class SysRoleService implements ISysRoleService { return roleRepository.findById(command.id()) .switchIfEmpty(Mono.error(new RuntimeException("Role not found"))) .flatMap(role -> { + SysRole before = new SysRole(); + before.setId(role.getId()); + before.setRoleName(role.getRoleName()); + before.setRoleKey(role.getRoleKey()); + before.setRoleSort(role.getRoleSort()); + before.setStatus(role.getStatus()); + before.setCreatedAt(role.getCreatedAt()); + before.setUpdatedAt(role.getUpdatedAt()); + before.setDeletedAt(role.getDeletedAt()); if (command.roleName() != null) { role.setRoleName(command.roleName()); } @@ -115,15 +130,17 @@ public class SysRoleService implements ISysRoleService { role.setStatus(command.status()); } role.setUpdatedAt(LocalDateTime.now()); - return roleRepository.save(role); + return roleRepository.updateRole(role) + .flatMap(saved -> AuditLogHelper.record(auditLogService, "Role", saved.getId(), "UPDATE", before, saved) + .thenReturn(saved)); }); } @Override - @Transactional + @Transactional(transactionManager = "connectionFactoryTransactionManager") public Mono deleteRole(Long id) { logger.debug("开始删除角色,ID: {}", id); - + return roleRepository.findById(id) .flatMap(role -> { logger.debug("找到角色,开始删除关联记录"); @@ -138,7 +155,8 @@ public class SysRoleService implements ISysRoleService { .doOnError(e -> logger.error("更新用户角色ID失败", e)) .then(roleRepository.deleteById(id)) .doOnSuccess(v -> logger.debug("成功删除角色")) - .doOnError(e -> logger.error("删除角色失败", e)); + .doOnError(e -> logger.error("删除角色失败", e)) + .then(AuditLogHelper.record(auditLogService, "Role", id, "DELETE", role, null)); }); } @@ -156,8 +174,19 @@ public class SysRoleService implements ISysRoleService { public Mono logicalDeleteRole(Long id) { return roleRepository.findByIdIncludingDeleted(id) .flatMap(role -> { + SysRole before = new SysRole(); + before.setId(role.getId()); + before.setRoleName(role.getRoleName()); + before.setRoleKey(role.getRoleKey()); + before.setRoleSort(role.getRoleSort()); + before.setStatus(role.getStatus()); + before.setCreatedAt(role.getCreatedAt()); + before.setUpdatedAt(role.getUpdatedAt()); + before.setDeletedAt(role.getDeletedAt()); role.delete(); - return roleRepository.updateRole(role); + return roleRepository.updateRole(role) + .flatMap(saved -> AuditLogHelper.record(auditLogService, "Role", saved.getId(), "DELETE", before, saved) + .thenReturn(saved)); }); } diff --git a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/service/impl/SysUserService.java b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/service/impl/SysUserService.java index 399b38d..e6b5a01 100644 --- a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/service/impl/SysUserService.java +++ b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/service/impl/SysUserService.java @@ -1,9 +1,12 @@ package cn.novalon.manage.sys.core.service.impl; import cn.novalon.manage.common.util.StatusConstants; +import cn.novalon.manage.sys.audit.AuditLogHelper; +import cn.novalon.manage.sys.audit.service.IAuditLogService; import cn.novalon.manage.sys.core.domain.SysUser; import cn.novalon.manage.sys.core.domain.SysRole; import cn.novalon.manage.sys.core.domain.UserRole; +import cn.novalon.manage.sys.core.query.SysUserQuery; import cn.novalon.manage.common.dto.PageRequest; import cn.novalon.manage.common.dto.PageResponse; import cn.novalon.manage.sys.core.repository.ISysUserRepository; @@ -25,16 +28,6 @@ import reactor.core.publisher.Mono; import java.time.LocalDateTime; import java.util.List; -/** - * 用户服务实现类 - * - * 文件定义:实现用户管理的核心业务逻辑 - * 涉及业务:用户注册、登录、信息修改、删除、密码修改、逻辑删除等用户生命周期管理 - * 算法:使用R2DBC进行响应式数据库操作,支持分页查询、条件查询、批量操作 - * - * @author 张翔 - * @date 2026-03-13 - */ @Service public class SysUserService implements ISysUserService { @@ -43,15 +36,18 @@ public class SysUserService implements ISysUserService { private final ISysRoleRepository roleRepository; private final IUserRoleRepository userRoleRepository; private final PasswordEncoder passwordEncoder; + private final IAuditLogService auditLogService; public SysUserService(ISysUserRepository userRepository, ISysRoleRepository roleRepository, IUserRoleRepository userRoleRepository, - @Qualifier("passwordEncoder") PasswordEncoder passwordEncoder) { + @Qualifier("passwordEncoder") PasswordEncoder passwordEncoder, + IAuditLogService auditLogService) { this.userRepository = userRepository; this.roleRepository = roleRepository; this.userRoleRepository = userRoleRepository; this.passwordEncoder = passwordEncoder; + this.auditLogService = auditLogService; logger.info("使用的密码编码器类型: {}", passwordEncoder.getClass().getName()); } @@ -80,7 +76,9 @@ public class SysUserService implements ISysUserService { @Override public Mono> findUsersByPage(PageRequest pageRequest) { - return userRepository.findByQueryWithPagination(null, pageRequest); + SysUserQuery query = new SysUserQuery(); + query.setKeyword(pageRequest.getKeyword()); + return userRepository.findByQueryWithPagination(query, pageRequest); } @Override @@ -110,7 +108,9 @@ public class SysUserService implements ISysUserService { if (user.getStatus() == null) { user.setStatus(StatusConstants.ENABLED); } - return userRepository.save(user); + return userRepository.save(user) + .flatMap(saved -> AuditLogHelper.record(auditLogService, "User", saved.getId(), "CREATE", saved) + .thenReturn(saved)); } @Override @@ -124,13 +124,18 @@ public class SysUserService implements ISysUserService { user.setPhone(command.phone()); user.setRoleId(command.roleId()); user.setStatus(command.status() != null ? command.status() : StatusConstants.ENABLED); - return userRepository.save(user); + return userRepository.save(user) + .flatMap(saved -> AuditLogHelper.record(auditLogService, "User", saved.getId(), "CREATE", saved) + .thenReturn(saved)); } @Override public Mono updateUser(SysUser user) { user.setUpdatedAt(LocalDateTime.now()); - return userRepository.save(user); + return userRepository.findById(user.getId()) + .flatMap(before -> userRepository.update(user) + .flatMap(saved -> AuditLogHelper.record(auditLogService, "User", saved.getId(), "UPDATE", before, saved) + .thenReturn(saved))); } @Override @@ -138,6 +143,17 @@ public class SysUserService implements ISysUserService { return userRepository.findById(command.id()) .switchIfEmpty(Mono.error(new RuntimeException("User not found"))) .flatMap(user -> { + SysUser before = new SysUser(); + before.setId(user.getId()); + before.setUsername(user.getUsername()); + before.setEmail(user.getEmail()); + before.setNickname(user.getNickname()); + before.setPhone(user.getPhone()); + before.setRoleId(user.getRoleId()); + before.setStatus(user.getStatus()); + before.setCreatedAt(user.getCreatedAt()); + before.setUpdatedAt(user.getUpdatedAt()); + before.setDeletedAt(user.getDeletedAt()); if (command.username() != null) { user.setUsername(command.username()); } @@ -156,12 +172,15 @@ public class SysUserService implements ISysUserService { user.setStatus(command.status()); } user.setUpdatedAt(LocalDateTime.now()); - return userRepository.save(user); + return userRepository.update(user) + .flatMap(saved -> AuditLogHelper + .record(auditLogService, "User", saved.getId(), "UPDATE", before, saved) + .thenReturn(saved)); }); } @Override - @Transactional + @Transactional(transactionManager = "connectionFactoryTransactionManager") public Mono deleteUser(Long id) { logger.debug("开始删除用户,ID: {}", id); @@ -174,7 +193,8 @@ public class SysUserService implements ISysUserService { .doOnError(e -> logger.error("删除用户角色关联记录失败", e)) .then(userRepository.deleteById(id)) .doOnSuccess(v -> logger.debug("成功删除用户")) - .doOnError(e -> logger.error("删除用户失败", e)); + .doOnError(e -> logger.error("删除用户失败", e)) + .then(AuditLogHelper.record(auditLogService, "User", id, "DELETE", user, null)); }); } @@ -192,7 +212,10 @@ public class SysUserService implements ISysUserService { } user.setPassword(passwordEncoder.encode(newPassword)); user.setUpdatedAt(LocalDateTime.now()); - return userRepository.save(user); + return userRepository.update(user) + .flatMap(saved -> AuditLogHelper + .record(auditLogService, "User", saved.getId(), "UPDATE", saved) + .thenReturn(saved)); }); } @@ -214,8 +237,20 @@ public class SysUserService implements ISysUserService { public Mono logicalDeleteUser(Long id) { return userRepository.findByIdIncludingDeleted(id) .flatMap(user -> { + SysUser before = new SysUser(); + before.setId(user.getId()); + before.setUsername(user.getUsername()); + before.setEmail(user.getEmail()); + before.setNickname(user.getNickname()); + before.setPhone(user.getPhone()); + before.setRoleId(user.getRoleId()); + before.setStatus(user.getStatus()); + before.setCreatedAt(user.getCreatedAt()); + before.setUpdatedAt(user.getUpdatedAt()); + before.setDeletedAt(user.getDeletedAt()); user.setDeletedAt(LocalDateTime.now()); - return userRepository.save(user); + return userRepository.save(user) + .flatMap(saved -> AuditLogHelper.record(auditLogService, "User", saved.getId(), "DELETE", before, saved)); }) .then(); } @@ -241,7 +276,7 @@ public class SysUserService implements ISysUserService { } @Override - @Transactional + @Transactional(transactionManager = "connectionFactoryTransactionManager") public Mono assignRolesToUser(Long userId, List roleIds) { logger.debug("开始为用户分配角色,用户ID: {}, 角色IDs: {}", userId, roleIds); @@ -249,7 +284,8 @@ public class SysUserService implements ISysUserService { logger.debug("角色列表为空,删除用户的所有角色关联"); return userRoleRepository.deleteByUserId(userId) .doOnSuccess(v -> logger.debug("成功删除用户的所有角色关联")) - .doOnError(e -> logger.error("删除用户角色关联失败", e)); + .doOnError(e -> logger.error("删除用户角色关联失败", e)) + .then(AuditLogHelper.record(auditLogService, "User", userId, "UPDATE", null)); } return userRoleRepository.deleteByUserId(userId) @@ -262,12 +298,12 @@ public class SysUserService implements ISysUserService { UserRole userRole = new UserRole(); userRole.setUserId(userId); userRole.setRoleId(roleId); - userRole.setCreatedAt(LocalDateTime.now()); return userRoleRepository.save(userRole) .doOnSuccess(v -> logger.debug("成功保存用户角色关联")) .doOnError(e -> logger.error("保存用户角色关联失败", e)); }) - .then()); + .then()) + .then(AuditLogHelper.record(auditLogService, "User", userId, "UPDATE", null)); } @Override diff --git a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/dto/request/AssignRolesRequest.java b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/dto/request/AssignRolesRequest.java index cb32f1d..0559476 100644 --- a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/dto/request/AssignRolesRequest.java +++ b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/dto/request/AssignRolesRequest.java @@ -1,15 +1,25 @@ package cn.novalon.manage.sys.dto.request; import java.util.List; +import java.util.stream.Collectors; public class AssignRolesRequest { - private List roleIds; + private List roleIds; - public List getRoleIds() { + public List getRoleIds() { return roleIds; } - public void setRoleIds(List roleIds) { + public void setRoleIds(List roleIds) { this.roleIds = roleIds; } + + public List getRoleIdsAsLong() { + if (roleIds == null) { + return null; + } + return roleIds.stream() + .map(Long::valueOf) + .collect(Collectors.toList()); + } } diff --git a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/handler/user/SysUserHandler.java b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/handler/user/SysUserHandler.java index 07e6fcd..2880951 100644 --- a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/handler/user/SysUserHandler.java +++ b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/handler/user/SysUserHandler.java @@ -63,6 +63,10 @@ public class SysUserHandler { String order = request.queryParam("order").orElse("asc"); String keyword = request.queryParam("keyword").orElse(null); + System.out.println("=== SysUserHandler.getUsersByPage ==="); + System.out.println("page: " + page + ", size: " + size + ", sort: " + sort + ", order: " + order); + System.out.println("keyword: " + keyword); + PageRequest pageRequest = new PageRequest(); pageRequest.setPage(page); pageRequest.setSize(size); @@ -259,7 +263,7 @@ public class SysUserHandler { public Mono assignRoles(ServerRequest request) { Long id = Long.valueOf(request.pathVariable("id")); return request.bodyToMono(AssignRolesRequest.class) - .flatMap(req -> userService.assignRolesToUser(id, req.getRoleIds())) + .flatMap(req -> userService.assignRolesToUser(id, req.getRoleIdsAsLong())) .then(ServerResponse.ok().build()) .onErrorResume(error -> { logger.error("分配角色失败", error); diff --git a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/util/IpUtils.java b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/util/IpUtils.java index f37a2a6..0ce286b 100644 --- a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/util/IpUtils.java +++ b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/util/IpUtils.java @@ -1,12 +1,13 @@ package cn.novalon.manage.sys.util; +import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.web.reactive.function.server.ServerRequest; import java.net.InetSocketAddress; import java.util.Optional; /** * IP地址工具类 - * 用于从ServerRequest中获取客户端真实IP地址 + * 用于从ServerRequest或ServerHttpRequest中获取客户端真实IP地址 * 支持代理服务器场景(X-Forwarded-For, X-Real-IP) * * @author 张翔 @@ -48,6 +49,36 @@ public class IpUtils { return UNKNOWN; } + /** + * 从ServerHttpRequest中获取客户端真实IP地址 + * 支持代理服务器场景,优先级: X-Forwarded-For > X-Real-IP > RemoteAddress + * + * @param request ServerHttpRequest对象 + * @return 客户端IP地址,获取失败返回"unknown" + */ + public static String getClientIp(ServerHttpRequest request) { + if (request == null) { + return UNKNOWN; + } + + String ip = getXForwardedForIp(request); + if (isValidIp(ip)) { + return ip; + } + + ip = getXRealIp(request); + if (isValidIp(ip)) { + return ip; + } + + ip = getRemoteAddress(request); + if (isValidIp(ip)) { + return ip; + } + + return UNKNOWN; + } + /** * 从X-Forwarded-For头获取IP地址 * X-Forwarded-For格式: client, proxy1, proxy2 @@ -98,4 +129,48 @@ public class IpUtils { private static boolean isValidIp(String ip) { return ip != null && ip.length() > 0 && !UNKNOWN.equalsIgnoreCase(ip); } + + /** + * 从X-Forwarded-For头获取IP地址(ServerHttpRequest版本) + * X-Forwarded-For格式: client, proxy1, proxy2 + * 取第一个非unknown的有效IP + */ + private static String getXForwardedForIp(ServerHttpRequest request) { + String ip = request.getHeaders().getFirst("X-Forwarded-For"); + if (ip != null && ip.length() > 0 && !UNKNOWN.equalsIgnoreCase(ip)) { + int index = ip.indexOf(","); + if (index != -1) { + return ip.substring(0, index); + } + return ip; + } + return null; + } + + /** + * 从X-Real-IP头获取IP地址(ServerHttpRequest版本) + */ + private static String getXRealIp(ServerHttpRequest request) { + String ip = request.getHeaders().getFirst("X-Real-IP"); + if (ip != null && ip.length() > 0 && !UNKNOWN.equalsIgnoreCase(ip)) { + return ip; + } + return null; + } + + /** + * 从RemoteAddress获取IP地址(ServerHttpRequest版本) + * 将IPv6本地地址转换为IPv4格式 + */ + private static String getRemoteAddress(ServerHttpRequest request) { + InetSocketAddress remoteAddress = request.getRemoteAddress(); + if (remoteAddress != null) { + String ip = remoteAddress.getAddress().getHostAddress(); + if (LOCALHOST_IPV6.equals(ip)) { + ip = LOCALHOST_IP; + } + return ip; + } + return null; + } } diff --git a/novalon-manage-api/manage-sys/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/novalon-manage-api/manage-sys/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 65c915b..7238aba 100644 --- a/novalon-manage-api/manage-sys/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/novalon-manage-api/manage-sys/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1,2 +1 @@ -cn.novalon.manage.sys.config.ExceptionLogConfig -cn.novalon.manage.sys.config.SystemRouter \ No newline at end of file +cn.novalon.manage.sys.config.ExceptionLogConfig \ No newline at end of file