refactor(审计日志): 优化审计日志架构和 E2E 测试质量

架构改进:
- 引入审计日志服务层,实现业务逻辑与数据访问分离
- 添加 Spring Data 审计注解,自动填充创建人、创建时间等字段
- 修复切面范围,避免 Repository 和 Dao 层重复记录

代码优化:
- 移除构造函数中的冗余 info 日志,降低生产环境日志量
- 恢复 SQL 文件格式,提高可读性
- 优化 E2E 测试等待策略,移除硬编码等待时间,提高测试稳定性

影响范围:
- 后端:审计日志模块(Service、Repository、Aspect、Entity)
- 前端:E2E 测试文件(4 个 workflow 测试)
- 数据库:审计日志表结构
This commit was merged in pull request #2.
This commit is contained in:
张翔
2026-04-08 19:49:55 +08:00
parent 7e534f3049
commit 7e54d7fb46
16 changed files with 766 additions and 252 deletions
@@ -1,7 +1,7 @@
package cn.novalon.manage.sys.audit;
import cn.novalon.manage.sys.audit.domain.AuditLog;
import cn.novalon.manage.sys.audit.repository.IAuditLogRepository;
import cn.novalon.manage.sys.audit.service.IAuditLogService;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.aspectj.lang.ProceedingJoinPoint;
@@ -34,11 +34,11 @@ public class AuditLogAspect {
private static final Logger logger = LoggerFactory.getLogger(AuditLogAspect.class);
private final IAuditLogRepository auditLogRepository;
private final IAuditLogService auditLogService;
private final ObjectMapper objectMapper;
public AuditLogAspect(IAuditLogRepository auditLogRepository, ObjectMapper objectMapper) {
this.auditLogRepository = auditLogRepository;
public AuditLogAspect(IAuditLogService auditLogService, ObjectMapper objectMapper) {
this.auditLogService = auditLogService;
this.objectMapper = objectMapper;
}
@@ -99,6 +99,9 @@ public class AuditLogAspect {
String finalOperationType = operationTypeHolder[0];
String finalBeforeData = beforeDataHolder[0];
logger.debug("保存操作审计日志: entityType={}, entityIdHolder={}, extractedEntityId={}, finalEntityId={}",
entityType, entityIdHolder[0], extractEntityId(savedEntity), finalEntityId);
return createAndSaveAuditLog(
entityType, finalEntityId, finalOperationType,
finalBeforeData, afterData, savedEntity
@@ -163,6 +166,7 @@ public class AuditLogAspect {
private Mono<Void> createAndSaveAuditLog(String entityType, Long entityId,
String operationType, String beforeData,
String afterData, Object entity) {
logger.debug("创建审计日志: entityType={}, entityId={}, operationType={}", entityType, entityId, operationType);
return ReactiveSecurityContextHolder.getContext()
.map(ctx -> ctx.getAuthentication().getPrincipal())
.defaultIfEmpty("system")
@@ -175,6 +179,9 @@ public class AuditLogAspect {
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);
@@ -182,7 +189,7 @@ public class AuditLogAspect {
auditLog.setDescription(generateDescription(entityType, operationType, entityId));
return auditLogRepository.save(auditLog)
return auditLogService.save(auditLog)
.doOnSuccess(saved -> logger.debug("审计日志保存成功: {} - {}",
entityType, operationType))
.doOnError(error -> logger.error("审计日志保存失败: {}",
@@ -231,11 +238,14 @@ public class AuditLogAspect {
}
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;
}
@@ -49,7 +49,6 @@ public class AuditLog extends BaseDomain {
public AuditLog() {
this.operationTime = LocalDateTime.now();
this.createdAt = LocalDateTime.now();
}
public String getEntityType() {
@@ -0,0 +1,30 @@
package cn.novalon.manage.sys.audit.service;
import cn.novalon.manage.sys.audit.domain.AuditLog;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* 审计日志服务接口
*
* @author 张翔
* @date 2026-04-08
*/
public interface IAuditLogService {
Mono<AuditLog> save(AuditLog auditLog);
Mono<AuditLog> findById(Long id);
Flux<AuditLog> findAll();
Flux<AuditLog> findByEntityType(String entityType);
Flux<AuditLog> findByEntityId(Long entityId);
Flux<AuditLog> findByEntityTypeAndEntityId(String entityType, Long entityId);
Flux<AuditLog> findByOperator(String operator);
Flux<AuditLog> findByOperationType(String operationType);
}
@@ -0,0 +1,68 @@
package cn.novalon.manage.sys.audit.service.impl;
import cn.novalon.manage.sys.audit.domain.AuditLog;
import cn.novalon.manage.sys.audit.repository.IAuditLogRepository;
import cn.novalon.manage.sys.audit.service.IAuditLogService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* 审计日志服务实现类
*
* @author 张翔
* @date 2026-04-08
*/
@Service
public class AuditLogService implements IAuditLogService {
private static final Logger logger = LoggerFactory.getLogger(AuditLogService.class);
private final IAuditLogRepository auditLogRepository;
public AuditLogService(IAuditLogRepository auditLogRepository) {
this.auditLogRepository = auditLogRepository;
}
@Override
public Mono<AuditLog> save(AuditLog auditLog) {
return auditLogRepository.save(auditLog);
}
@Override
public Mono<AuditLog> findById(Long id) {
return auditLogRepository.findById(id);
}
@Override
public Flux<AuditLog> findAll() {
return auditLogRepository.findAll();
}
@Override
public Flux<AuditLog> findByEntityType(String entityType) {
return auditLogRepository.findByEntityType(entityType);
}
@Override
public Flux<AuditLog> findByEntityId(Long entityId) {
return auditLogRepository.findByEntityId(entityId);
}
@Override
public Flux<AuditLog> findByEntityTypeAndEntityId(String entityType, Long entityId) {
return auditLogRepository.findByEntityTypeAndEntityId(entityType, entityId);
}
@Override
public Flux<AuditLog> findByOperator(String operator) {
return auditLogRepository.findByOperator(operator);
}
@Override
public Flux<AuditLog> findByOperationType(String operationType) {
return auditLogRepository.findByOperationType(operationType);
}
}