diff --git a/gym-manage-api/manage-app/src/main/resources/application.yml b/gym-manage-api/manage-app/src/main/resources/application.yml index 0173263..9305b16 100644 --- a/gym-manage-api/manage-app/src/main/resources/application.yml +++ b/gym-manage-api/manage-app/src/main/resources/application.yml @@ -2,6 +2,8 @@ server: port: 8084 spring: + aop: + proxy-target-class: true application: name: gym-manage-api main: diff --git a/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/entity/BaseEntity.java b/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/entity/BaseEntity.java index 0b6fc70..24de278 100644 --- a/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/entity/BaseEntity.java +++ b/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/entity/BaseEntity.java @@ -5,17 +5,12 @@ import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.Id; import org.springframework.data.annotation.LastModifiedBy; import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.annotation.Transient; import org.springframework.data.domain.Persistable; import org.springframework.data.relational.core.mapping.Column; import java.time.LocalDateTime; -/** - * 数据库实体基类 - * - * @author 张翔 - * @date 2026-03-13 - */ public abstract class BaseEntity implements Persistable { @Id @@ -40,6 +35,9 @@ public abstract class BaseEntity implements Persistable { @Column("deleted_at") private LocalDateTime deletedAt; + @Transient + private boolean newEntity = true; + @Override public Long getId() { return id; @@ -89,12 +87,16 @@ public abstract class BaseEntity implements Persistable { this.deletedAt = deletedAt; } - /** - * 判断实体是否为新的 - * 如果createdAt为null,则认为是新实体 - */ @Override public boolean isNew() { - return createdAt == null; + return newEntity; + } + + public void markNotNew() { + this.newEntity = false; + } + + public void markNew() { + this.newEntity = true; } } diff --git a/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/repository/AuditLogRepository.java b/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/repository/AuditLogRepository.java index 8dfa07e..049b670 100644 --- a/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/repository/AuditLogRepository.java +++ b/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/repository/AuditLogRepository.java @@ -7,6 +7,7 @@ import cn.novalon.gym.manage.db.dao.AuditLogDao; import cn.novalon.gym.manage.db.entity.AuditLogEntity; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.data.r2dbc.core.R2dbcEntityTemplate; import org.springframework.stereotype.Repository; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -26,10 +27,12 @@ public class AuditLogRepository implements IAuditLogRepository { private final AuditLogDao auditLogDao; private final AuditLogConverter auditLogConverter; + private final R2dbcEntityTemplate r2dbcEntityTemplate; - public AuditLogRepository(AuditLogDao auditLogDao, AuditLogConverter auditLogConverter) { + public AuditLogRepository(AuditLogDao auditLogDao, AuditLogConverter auditLogConverter, R2dbcEntityTemplate r2dbcEntityTemplate) { this.auditLogDao = auditLogDao; this.auditLogConverter = auditLogConverter; + this.r2dbcEntityTemplate = r2dbcEntityTemplate; } @Override @@ -41,6 +44,12 @@ public class AuditLogRepository implements IAuditLogRepository { @Override public Mono save(AuditLog auditLog) { AuditLogEntity entity = auditLogConverter.toEntity(auditLog); + if (entity.isNew()) { + return r2dbcEntityTemplate.insert(AuditLogEntity.class) + .using(entity) + .doOnNext(e -> e.markNotNew()) + .map(auditLogConverter::toDomain); + } return auditLogDao.save(entity) .map(auditLogConverter::toDomain); } diff --git a/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/repository/OperationLogRepository.java b/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/repository/OperationLogRepository.java index 8270c32..905006f 100644 --- a/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/repository/OperationLogRepository.java +++ b/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/repository/OperationLogRepository.java @@ -49,6 +49,12 @@ public class OperationLogRepository implements IOperationLogRepository { @Override public Mono save(OperationLog operationLog) { OperationLogEntity entity = operationLogConverter.toEntity(operationLog); + if (entity.isNew()) { + return r2dbcEntityTemplate.insert(OperationLogEntity.class) + .using(entity) + .doOnNext(e -> e.markNotNew()) + .map(operationLogConverter::toDomain); + } return operationLogDao.save(entity) .map(operationLogConverter::toDomain); } diff --git a/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/repository/SysMenuRepository.java b/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/repository/SysMenuRepository.java index faab4f9..127caa3 100644 --- a/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/repository/SysMenuRepository.java +++ b/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/repository/SysMenuRepository.java @@ -60,6 +60,20 @@ public class SysMenuRepository implements ISysMenuRepository { @Override public Mono save(SysMenu sysMenu) { SysMenuEntity entity = sysMenuConverter.toEntity(sysMenu); + if (entity.isNew()) { + return r2dbcEntityTemplate.insert(SysMenuEntity.class) + .using(entity) + .doOnNext(e -> e.markNotNew()) + .map(sysMenuConverter::toDomain); + } + return sysMenuDao.save(entity) + .map(sysMenuConverter::toDomain); + } + + @Override + public Mono update(SysMenu sysMenu) { + SysMenuEntity entity = sysMenuConverter.toEntity(sysMenu); + entity.markNotNew(); return sysMenuDao.save(entity) .map(sysMenuConverter::toDomain); } diff --git a/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/repository/SysPermissionRepository.java b/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/repository/SysPermissionRepository.java index 5776946..c5d40ef 100644 --- a/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/repository/SysPermissionRepository.java +++ b/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/repository/SysPermissionRepository.java @@ -4,7 +4,9 @@ import cn.novalon.gym.manage.sys.core.domain.SysPermission; import cn.novalon.gym.manage.sys.core.repository.ISysPermissionRepository; import cn.novalon.gym.manage.db.converter.SysPermissionConverter; import cn.novalon.gym.manage.db.dao.SysPermissionDao; +import cn.novalon.gym.manage.db.entity.SysPermissionEntity; import org.springframework.data.domain.Sort; +import org.springframework.data.r2dbc.core.R2dbcEntityTemplate; import org.springframework.stereotype.Repository; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -20,10 +22,12 @@ public class SysPermissionRepository implements ISysPermissionRepository { private final SysPermissionDao sysPermissionDao; private final SysPermissionConverter sysPermissionConverter; + private final R2dbcEntityTemplate r2dbcEntityTemplate; - public SysPermissionRepository(SysPermissionDao sysPermissionDao, SysPermissionConverter sysPermissionConverter) { + public SysPermissionRepository(SysPermissionDao sysPermissionDao, SysPermissionConverter sysPermissionConverter, R2dbcEntityTemplate r2dbcEntityTemplate) { this.sysPermissionDao = sysPermissionDao; this.sysPermissionConverter = sysPermissionConverter; + this.r2dbcEntityTemplate = r2dbcEntityTemplate; } @Override @@ -40,7 +44,14 @@ public class SysPermissionRepository implements ISysPermissionRepository { @Override public Mono save(SysPermission sysPermission) { - return sysPermissionDao.save(sysPermissionConverter.toEntity(sysPermission)) + SysPermissionEntity entity = sysPermissionConverter.toEntity(sysPermission); + if (entity.isNew()) { + return r2dbcEntityTemplate.insert(SysPermissionEntity.class) + .using(entity) + .doOnNext(e -> e.markNotNew()) + .map(sysPermissionConverter::toDomain); + } + return sysPermissionDao.save(entity) .map(sysPermissionConverter::toDomain); } diff --git a/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/repository/SysRolePermissionRepository.java b/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/repository/SysRolePermissionRepository.java index e5773e6..d40c742 100644 --- a/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/repository/SysRolePermissionRepository.java +++ b/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/repository/SysRolePermissionRepository.java @@ -4,6 +4,8 @@ import cn.novalon.gym.manage.sys.core.domain.SysRolePermission; import cn.novalon.gym.manage.sys.core.repository.ISysRolePermissionRepository; import cn.novalon.gym.manage.db.converter.SysRolePermissionConverter; import cn.novalon.gym.manage.db.dao.SysRolePermissionDao; +import cn.novalon.gym.manage.db.entity.SysRolePermissionEntity; +import org.springframework.data.r2dbc.core.R2dbcEntityTemplate; import org.springframework.stereotype.Repository; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -19,15 +21,24 @@ public class SysRolePermissionRepository implements ISysRolePermissionRepository private final SysRolePermissionDao sysRolePermissionDao; private final SysRolePermissionConverter sysRolePermissionConverter; + private final R2dbcEntityTemplate r2dbcEntityTemplate; - public SysRolePermissionRepository(SysRolePermissionDao sysRolePermissionDao, SysRolePermissionConverter sysRolePermissionConverter) { + public SysRolePermissionRepository(SysRolePermissionDao sysRolePermissionDao, SysRolePermissionConverter sysRolePermissionConverter, R2dbcEntityTemplate r2dbcEntityTemplate) { this.sysRolePermissionDao = sysRolePermissionDao; this.sysRolePermissionConverter = sysRolePermissionConverter; + this.r2dbcEntityTemplate = r2dbcEntityTemplate; } @Override public Mono save(SysRolePermission rolePermission) { - return sysRolePermissionDao.save(sysRolePermissionConverter.toEntity(rolePermission)) + SysRolePermissionEntity entity = sysRolePermissionConverter.toEntity(rolePermission); + if (entity.isNew()) { + return r2dbcEntityTemplate.insert(SysRolePermissionEntity.class) + .using(entity) + .doOnNext(e -> e.markNotNew()) + .map(sysRolePermissionConverter::toDomain); + } + return sysRolePermissionDao.save(entity) .map(sysRolePermissionConverter::toDomain); } diff --git a/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/repository/SysRoleRepository.java b/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/repository/SysRoleRepository.java index f947e8e..7deb100 100644 --- a/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/repository/SysRoleRepository.java +++ b/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/repository/SysRoleRepository.java @@ -53,6 +53,12 @@ public class SysRoleRepository implements ISysRoleRepository { @Override public Mono save(SysRole sysRole) { SysRoleEntity entity = sysRoleConverter.toEntity(sysRole); + if (entity.isNew()) { + return r2dbcEntityTemplate.insert(SysRoleEntity.class) + .using(entity) + .doOnNext(e -> e.markNotNew()) + .map(sysRoleConverter::toDomain); + } return sysRoleDao.save(entity) .map(sysRoleConverter::toDomain); } @@ -156,6 +162,7 @@ public class SysRoleRepository implements ISysRoleRepository { @Override public Mono updateRole(SysRole role) { SysRoleEntity entity = sysRoleConverter.toEntity(role); + entity.markNotNew(); return sysRoleDao.save(entity) .map(sysRoleConverter::toDomain); } diff --git a/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/repository/SysUserRepository.java b/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/repository/SysUserRepository.java index 6e0314e..c0feccc 100644 --- a/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/repository/SysUserRepository.java +++ b/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/repository/SysUserRepository.java @@ -70,6 +70,20 @@ public class SysUserRepository implements ISysUserRepository { @Override public Mono save(SysUser sysUser) { SysUserEntity entity = sysUserConverter.toEntity(sysUser); + if (entity.isNew()) { + return r2dbcEntityTemplate.insert(SysUserEntity.class) + .using(entity) + .doOnNext(e -> e.markNotNew()) + .map(sysUserConverter::toDomain); + } + return sysUserDao.save(entity) + .map(sysUserConverter::toDomain); + } + + @Override + public Mono update(SysUser sysUser) { + SysUserEntity entity = sysUserConverter.toEntity(sysUser); + entity.markNotNew(); return sysUserDao.save(entity) .map(sysUserConverter::toDomain); } @@ -176,6 +190,7 @@ public class SysUserRepository implements ISysUserRepository { public Mono logicalDeleteById(Long id) { return sysUserDao.findById(id) .flatMap(entity -> { + entity.markNotNew(); entity.setDeletedAt(java.time.LocalDateTime.now()); return sysUserDao.save(entity).then(); }); @@ -192,6 +207,7 @@ public class SysUserRepository implements ISysUserRepository { public Mono restoreById(Long id) { return sysUserDao.findById(id) .flatMap(entity -> { + entity.markNotNew(); entity.setDeletedAt(null); return sysUserDao.save(entity).then(); }); diff --git a/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/audit/AuditLogAspect.java b/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/audit/AuditLogAspect.java new file mode 100644 index 0000000..e67341c --- /dev/null +++ b/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/audit/AuditLogAspect.java @@ -0,0 +1,157 @@ +package cn.novalon.gym.manage.sys.audit; + +import cn.novalon.gym.manage.sys.audit.domain.AuditLog; +import cn.novalon.gym.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.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.security.core.context.ReactiveSecurityContextHolder; +import org.springframework.stereotype.Component; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +@Aspect +@Component +@Deprecated +public class AuditLogAspect { + + private static final Logger logger = LoggerFactory.getLogger(AuditLogAspect.class); + + private final IAuditLogService auditLogService; + + public AuditLogAspect(IAuditLogService auditLogService) { + this.auditLogService = auditLogService; + logger.info("=== AuditLogAspect 初始化完成 ==="); + } + + @Before("execution(* cn.novalon.gym.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(); + String entityType = auditable.entityType(); + String operationType = auditable.operationType(); + + logger.debug("审计切面拦截: {}.{}(), entityType={}, operationType={}", className, methodName, entityType, operationType); + + try { + Object result = joinPoint.proceed(); + + if (result instanceof Mono) { + return ((Mono) result).flatMap(retValue -> { + Long entityId = extractIdFromResult(retValue); + String afterData = serializeEntity(retValue); + return createAndSaveAuditLog( + entityType, entityId, operationType, + null, afterData + ).thenReturn(retValue); + }); + } else if (result instanceof Flux) { + 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("审计日志记录失败: {}.{}()", className, methodName, error); + throw error; + } + } + + 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") + .flatMap(principal -> { + AuditLog auditLog = new AuditLog(); + auditLog.setEntityType(entityType); + auditLog.setEntityId(entityId != null ? entityId : 0L); + auditLog.setOperationType(operationType); + auditLog.setOperator(principal instanceof String ? (String) principal : "system"); + auditLog.setBeforeData(beforeData); + auditLog.setAfterData(afterData); + auditLog.setDescription(generateDescription(entityType, operationType, entityId)); + + 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(), error); + return Mono.empty(); + }); + } + + private Long extractIdFromResult(Object result) { + if (result == null) { + return null; + } + 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 null; + } + + private String serializeEntity(Object entity) { + try { + 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 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/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/audit/AuditLogHelper.java b/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/audit/AuditLogHelper.java new file mode 100644 index 0000000..05c01a6 --- /dev/null +++ b/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/audit/AuditLogHelper.java @@ -0,0 +1,80 @@ +package cn.novalon.gym.manage.sys.audit; + +import cn.novalon.gym.manage.sys.audit.domain.AuditLog; +import cn.novalon.gym.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/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/audit/Auditable.java b/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/audit/Auditable.java new file mode 100644 index 0000000..8642001 --- /dev/null +++ b/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/audit/Auditable.java @@ -0,0 +1,15 @@ +package cn.novalon.gym.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/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/core/repository/ISysMenuRepository.java b/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/core/repository/ISysMenuRepository.java index 4a41958..7856fc7 100644 --- a/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/core/repository/ISysMenuRepository.java +++ b/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/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/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/core/repository/ISysUserRepository.java b/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/core/repository/ISysUserRepository.java index ad77010..0852841 100644 --- a/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/core/repository/ISysUserRepository.java +++ b/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/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/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/core/service/impl/DictionaryService.java b/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/core/service/impl/DictionaryService.java index 2ca9fdb..7e2dd52 100644 --- a/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/core/service/impl/DictionaryService.java +++ b/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/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/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/core/service/impl/SysConfigService.java b/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/core/service/impl/SysConfigService.java index b3e8d1d..214a1fd 100644 --- a/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/core/service/impl/SysConfigService.java +++ b/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/core/service/impl/SysConfigService.java @@ -1,5 +1,7 @@ package cn.novalon.gym.manage.sys.core.service.impl; +import cn.novalon.gym.manage.sys.audit.AuditLogHelper; +import cn.novalon.gym.manage.sys.audit.service.IAuditLogService; import cn.novalon.gym.manage.sys.core.domain.SysConfig; import cn.novalon.gym.manage.sys.core.repository.ISysConfigRepository; import cn.novalon.gym.manage.sys.core.service.ISysConfigService; @@ -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 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 @@ -28,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/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/core/service/impl/SysDictTypeService.java b/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/core/service/impl/SysDictTypeService.java index 8bc8ed8..9bb7af1 100644 --- a/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/core/service/impl/SysDictTypeService.java +++ b/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/core/service/impl/SysDictTypeService.java @@ -1,5 +1,7 @@ package cn.novalon.gym.manage.sys.core.service.impl; +import cn.novalon.gym.manage.sys.audit.AuditLogHelper; +import cn.novalon.gym.manage.sys.audit.service.IAuditLogService; import cn.novalon.gym.manage.sys.core.domain.SysDictType; import cn.novalon.gym.manage.sys.core.repository.ISysDictTypeRepository; import cn.novalon.gym.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/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/core/service/impl/SysMenuService.java b/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/core/service/impl/SysMenuService.java index 997005f..9c02e10 100644 --- a/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/core/service/impl/SysMenuService.java +++ b/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/core/service/impl/SysMenuService.java @@ -6,6 +6,8 @@ import cn.novalon.gym.manage.sys.core.service.ISysMenuService; import cn.novalon.gym.manage.sys.core.command.CreateMenuCommand; import cn.novalon.gym.manage.sys.core.command.UpdateMenuCommand; import cn.novalon.gym.manage.common.util.StatusConstants; +import cn.novalon.gym.manage.sys.audit.AuditLogHelper; +import cn.novalon.gym.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/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/core/service/impl/SysPermissionService.java b/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/core/service/impl/SysPermissionService.java index 78eb28c..de1879e 100644 --- a/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/core/service/impl/SysPermissionService.java +++ b/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/core/service/impl/SysPermissionService.java @@ -1,11 +1,15 @@ package cn.novalon.gym.manage.sys.core.service.impl; import cn.novalon.gym.manage.common.util.StatusConstants; +import cn.novalon.gym.manage.sys.audit.AuditLogHelper; +import cn.novalon.gym.manage.sys.audit.service.IAuditLogService; import cn.novalon.gym.manage.sys.core.domain.SysPermission; import cn.novalon.gym.manage.sys.core.domain.SysRolePermission; import cn.novalon.gym.manage.sys.core.repository.ISysPermissionRepository; import cn.novalon.gym.manage.sys.core.repository.ISysRolePermissionRepository; import cn.novalon.gym.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)); }); } @@ -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/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/core/service/impl/SysRoleService.java b/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/core/service/impl/SysRoleService.java index 334b658..2d40566 100644 --- a/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/core/service/impl/SysRoleService.java +++ b/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/core/service/impl/SysRoleService.java @@ -1,6 +1,8 @@ package cn.novalon.gym.manage.sys.core.service.impl; import cn.novalon.gym.manage.common.util.StatusConstants; +import cn.novalon.gym.manage.sys.audit.AuditLogHelper; +import cn.novalon.gym.manage.sys.audit.service.IAuditLogService; import cn.novalon.gym.manage.sys.core.domain.SysRole; import cn.novalon.gym.manage.sys.core.query.SysRoleQuery; import cn.novalon.gym.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,7 +130,9 @@ 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)); }); } @@ -123,7 +140,7 @@ public class SysRoleService implements ISysRoleService { @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/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/core/service/impl/SysUserService.java b/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/core/service/impl/SysUserService.java index 168a4c5..3d7bf64 100644 --- a/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/core/service/impl/SysUserService.java +++ b/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/core/service/impl/SysUserService.java @@ -1,6 +1,8 @@ package cn.novalon.gym.manage.sys.core.service.impl; import cn.novalon.gym.manage.common.util.StatusConstants; +import cn.novalon.gym.manage.sys.audit.AuditLogHelper; +import cn.novalon.gym.manage.sys.audit.service.IAuditLogService; import cn.novalon.gym.manage.sys.core.domain.SysUser; import cn.novalon.gym.manage.sys.core.domain.SysRole; import cn.novalon.gym.manage.sys.core.domain.UserRole; @@ -26,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 { @@ -44,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()); } @@ -113,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 @@ -127,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 @@ -141,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()); } @@ -159,7 +172,10 @@ 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)); }); } @@ -177,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)); }); } @@ -195,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)); }); } @@ -217,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(); } @@ -252,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) @@ -265,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/gym-manage-api/manage-sys/src/test/java/cn/novalon/gym/manage/sys/core/service/impl/OperationLogServiceTest.java b/gym-manage-api/manage-sys/src/test/java/cn/novalon/gym/manage/sys/core/service/impl/OperationLogServiceTest.java index 86eeadc..70ad84c 100644 --- a/gym-manage-api/manage-sys/src/test/java/cn/novalon/gym/manage/sys/core/service/impl/OperationLogServiceTest.java +++ b/gym-manage-api/manage-sys/src/test/java/cn/novalon/gym/manage/sys/core/service/impl/OperationLogServiceTest.java @@ -43,6 +43,7 @@ class OperationLogServiceTest { testLog.setDuration(100L); testLog.setIp("192.168.1.1"); testLog.setStatus("1"); + testLog.setCreatedAt(LocalDateTime.now()); } @Test diff --git a/gym-manage-api/manage-sys/src/test/java/cn/novalon/gym/manage/sys/core/service/impl/SysConfigServiceTest.java b/gym-manage-api/manage-sys/src/test/java/cn/novalon/gym/manage/sys/core/service/impl/SysConfigServiceTest.java index 489937c..0c802c9 100644 --- a/gym-manage-api/manage-sys/src/test/java/cn/novalon/gym/manage/sys/core/service/impl/SysConfigServiceTest.java +++ b/gym-manage-api/manage-sys/src/test/java/cn/novalon/gym/manage/sys/core/service/impl/SysConfigServiceTest.java @@ -1,5 +1,6 @@ package cn.novalon.gym.manage.sys.core.service.impl; +import cn.novalon.gym.manage.sys.audit.service.IAuditLogService; import cn.novalon.gym.manage.sys.core.domain.SysConfig; import cn.novalon.gym.manage.sys.core.repository.ISysConfigRepository; import org.junit.jupiter.api.BeforeEach; @@ -26,13 +27,16 @@ class SysConfigServiceTest { @Mock private ISysConfigRepository repository; + @Mock + private IAuditLogService auditLogService; + private SysConfigService configService; private SysConfig testConfig; @BeforeEach void setUp() { - configService = new SysConfigService(repository); + configService = new SysConfigService(repository, auditLogService); testConfig = new SysConfig(); testConfig.setId(1L); @@ -110,11 +114,13 @@ class SysConfigServiceTest { @Test void testDeleteById() { + when(repository.findById(1L)).thenReturn(Mono.just(testConfig)); when(repository.deleteByIdAndDeletedAtIsNull(1L)).thenReturn(Mono.empty()); StepVerifier.create(configService.deleteById(1L)) .verifyComplete(); + verify(repository).findById(1L); verify(repository).deleteByIdAndDeletedAtIsNull(1L); } diff --git a/gym-manage-api/manage-sys/src/test/java/cn/novalon/gym/manage/sys/core/service/impl/SysDictTypeServiceTest.java b/gym-manage-api/manage-sys/src/test/java/cn/novalon/gym/manage/sys/core/service/impl/SysDictTypeServiceTest.java index 53a99a6..cb211a7 100644 --- a/gym-manage-api/manage-sys/src/test/java/cn/novalon/gym/manage/sys/core/service/impl/SysDictTypeServiceTest.java +++ b/gym-manage-api/manage-sys/src/test/java/cn/novalon/gym/manage/sys/core/service/impl/SysDictTypeServiceTest.java @@ -1,5 +1,6 @@ package cn.novalon.gym.manage.sys.core.service.impl; +import cn.novalon.gym.manage.sys.audit.service.IAuditLogService; import cn.novalon.gym.manage.sys.core.domain.SysDictType; import cn.novalon.gym.manage.sys.core.repository.ISysDictTypeRepository; import org.junit.jupiter.api.BeforeEach; @@ -23,12 +24,15 @@ class SysDictTypeServiceTest { @Mock private ISysDictTypeRepository repository; + @Mock + private IAuditLogService auditLogService; + private SysDictTypeService dictTypeService; private SysDictType testDictType; @BeforeEach void setUp() { - dictTypeService = new SysDictTypeService(repository); + dictTypeService = new SysDictTypeService(repository, auditLogService); testDictType = new SysDictType(); testDictType.setId(1L); @@ -93,6 +97,7 @@ class SysDictTypeServiceTest { @Test void testDeleteById() { + when(repository.findById(1L)).thenReturn(Mono.just(testDictType)); when(repository.deleteByIdAndDeletedAtIsNull(1L)).thenReturn(Mono.empty()); Mono result = dictTypeService.deleteById(1L); @@ -100,6 +105,7 @@ class SysDictTypeServiceTest { StepVerifier.create(result) .verifyComplete(); + verify(repository).findById(1L); verify(repository).deleteByIdAndDeletedAtIsNull(1L); } } \ No newline at end of file diff --git a/gym-manage-api/manage-sys/src/test/java/cn/novalon/gym/manage/sys/core/service/impl/SysMenuServiceTest.java b/gym-manage-api/manage-sys/src/test/java/cn/novalon/gym/manage/sys/core/service/impl/SysMenuServiceTest.java index c11ae7a..8d8cc4a 100644 --- a/gym-manage-api/manage-sys/src/test/java/cn/novalon/gym/manage/sys/core/service/impl/SysMenuServiceTest.java +++ b/gym-manage-api/manage-sys/src/test/java/cn/novalon/gym/manage/sys/core/service/impl/SysMenuServiceTest.java @@ -4,6 +4,7 @@ import cn.novalon.gym.manage.sys.core.domain.SysMenu; import cn.novalon.gym.manage.sys.core.repository.ISysMenuRepository; import cn.novalon.gym.manage.sys.core.command.CreateMenuCommand; import cn.novalon.gym.manage.sys.core.command.UpdateMenuCommand; +import cn.novalon.gym.manage.sys.audit.service.IAuditLogService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -25,12 +26,15 @@ class SysMenuServiceTest { @Mock private ISysMenuRepository menuRepository; + @Mock + private IAuditLogService auditLogService; + private SysMenuService menuService; private SysMenu testMenu; @BeforeEach void setUp() { - menuService = new SysMenuService(menuRepository); + menuService = new SysMenuService(menuRepository, auditLogService); testMenu = new SysMenu(); testMenu.setId(1L); @@ -129,7 +133,8 @@ class SysMenuServiceTest { @Test void testUpdateMenu() { - when(menuRepository.save(any(SysMenu.class))).thenReturn(Mono.just(testMenu)); + when(menuRepository.findById(1L)).thenReturn(Mono.just(testMenu)); + when(menuRepository.update(any(SysMenu.class))).thenReturn(Mono.just(testMenu)); Mono result = menuService.updateMenu(testMenu); @@ -138,7 +143,8 @@ class SysMenuServiceTest { menu.getUpdatedAt() != null) .verifyComplete(); - verify(menuRepository).save(any(SysMenu.class)); + verify(menuRepository).findById(1L); + verify(menuRepository).update(any(SysMenu.class)); } @Test @@ -147,7 +153,7 @@ class SysMenuServiceTest { 1L, 0L, "系统管理(更新)", "M", 1, "system", "system:manage", 1); when(menuRepository.findById(1L)).thenReturn(Mono.just(testMenu)); - when(menuRepository.save(any(SysMenu.class))).thenReturn(Mono.just(testMenu)); + when(menuRepository.update(any(SysMenu.class))).thenReturn(Mono.just(testMenu)); Mono result = menuService.updateMenu(command); @@ -157,7 +163,7 @@ class SysMenuServiceTest { .verifyComplete(); verify(menuRepository).findById(1L); - verify(menuRepository).save(any(SysMenu.class)); + verify(menuRepository).update(any(SysMenu.class)); } @Test @@ -200,7 +206,7 @@ class SysMenuServiceTest { updatedMenu.setUpdatedAt(LocalDateTime.now()); when(menuRepository.findById(1L)).thenReturn(Mono.just(existingMenu)); - when(menuRepository.save(any(SysMenu.class))).thenReturn(Mono.just(updatedMenu)); + when(menuRepository.update(any(SysMenu.class))).thenReturn(Mono.just(updatedMenu)); UpdateMenuCommand command = new UpdateMenuCommand( 1L, null, null, null, null, null, null, null); @@ -210,7 +216,7 @@ class SysMenuServiceTest { .verifyComplete(); verify(menuRepository).findById(1L); - verify(menuRepository).save(any(SysMenu.class)); + verify(menuRepository).update(any(SysMenu.class)); } @Test @@ -237,7 +243,7 @@ class SysMenuServiceTest { updatedMenu.setUpdatedAt(LocalDateTime.now()); when(menuRepository.findById(1L)).thenReturn(Mono.just(existingMenu)); - when(menuRepository.save(any(SysMenu.class))).thenReturn(Mono.just(updatedMenu)); + when(menuRepository.update(any(SysMenu.class))).thenReturn(Mono.just(updatedMenu)); UpdateMenuCommand command = new UpdateMenuCommand( 1L, 2L, "系统管理(更新)", "C", 2, "system_updated", "system:manage_updated", 0); @@ -247,11 +253,12 @@ class SysMenuServiceTest { .verifyComplete(); verify(menuRepository).findById(1L); - verify(menuRepository).save(any(SysMenu.class)); + verify(menuRepository).update(any(SysMenu.class)); } @Test void testDeleteMenu() { + when(menuRepository.findById(1L)).thenReturn(Mono.just(testMenu)); when(menuRepository.deleteById(1L)).thenReturn(Mono.empty()); Mono result = menuService.deleteMenu(1L); @@ -259,6 +266,7 @@ class SysMenuServiceTest { StepVerifier.create(result) .verifyComplete(); + verify(menuRepository).findById(1L); verify(menuRepository).deleteById(1L); } diff --git a/gym-manage-api/manage-sys/src/test/java/cn/novalon/gym/manage/sys/core/service/impl/SysRoleServiceTest.java b/gym-manage-api/manage-sys/src/test/java/cn/novalon/gym/manage/sys/core/service/impl/SysRoleServiceTest.java index 77ffdb5..8f3543b 100644 --- a/gym-manage-api/manage-sys/src/test/java/cn/novalon/gym/manage/sys/core/service/impl/SysRoleServiceTest.java +++ b/gym-manage-api/manage-sys/src/test/java/cn/novalon/gym/manage/sys/core/service/impl/SysRoleServiceTest.java @@ -1,5 +1,6 @@ package cn.novalon.gym.manage.sys.core.service.impl; +import cn.novalon.gym.manage.sys.audit.service.IAuditLogService; import cn.novalon.gym.manage.common.util.StatusConstants; import cn.novalon.gym.manage.sys.core.domain.SysRole; import cn.novalon.gym.manage.sys.core.query.SysRoleQuery; @@ -46,13 +47,16 @@ class SysRoleServiceTest { @Mock private ISysRolePermissionRepository rolePermissionRepository; + @Mock + private IAuditLogService auditLogService; + private SysRoleService roleService; private SysRole testRole; @BeforeEach void setUp() { - roleService = new SysRoleService(roleRepository, userService, userRoleRepository, rolePermissionRepository); + roleService = new SysRoleService(roleRepository, userService, userRoleRepository, rolePermissionRepository, auditLogService); testRole = new SysRole(); testRole.setId(1L); @@ -206,7 +210,7 @@ class SysRoleServiceTest { existingRole.setStatus(StatusConstants.ENABLED); when(roleRepository.findById(1L)).thenReturn(Mono.just(existingRole)); - when(roleRepository.save(any(SysRole.class))).thenReturn(Mono.just(testRole)); + when(roleRepository.updateRole(any(SysRole.class))).thenReturn(Mono.just(testRole)); cn.novalon.gym.manage.sys.core.command.UpdateRoleCommand command = new cn.novalon.gym.manage.sys.core.command.UpdateRoleCommand( @@ -218,7 +222,7 @@ class SysRoleServiceTest { .verifyComplete(); verify(roleRepository).findById(1L); - verify(roleRepository).save(any(SysRole.class)); + verify(roleRepository).updateRole(any(SysRole.class)); } @Test @@ -231,7 +235,7 @@ class SysRoleServiceTest { existingRole.setStatus(StatusConstants.ENABLED); when(roleRepository.findById(1L)).thenReturn(Mono.just(existingRole)); - when(roleRepository.save(any(SysRole.class))).thenReturn(Mono.just(testRole)); + when(roleRepository.updateRole(any(SysRole.class))).thenReturn(Mono.just(testRole)); cn.novalon.gym.manage.sys.core.command.UpdateRoleCommand command = new cn.novalon.gym.manage.sys.core.command.UpdateRoleCommand( @@ -243,7 +247,7 @@ class SysRoleServiceTest { .verifyComplete(); verify(roleRepository).findById(1L); - verify(roleRepository).save(any(SysRole.class)); + verify(roleRepository).updateRole(any(SysRole.class)); } @Test @@ -252,13 +256,15 @@ class SysRoleServiceTest { updateRole.setId(1L); updateRole.setRoleName("updated_admin"); - when(roleRepository.save(any(SysRole.class))).thenReturn(Mono.just(testRole)); + when(roleRepository.findById(1L)).thenReturn(Mono.just(testRole)); + when(roleRepository.updateRole(any(SysRole.class))).thenReturn(Mono.just(testRole)); StepVerifier.create(roleService.updateRole(updateRole)) .expectNextMatches(role -> role.getUpdatedAt() != null) .verifyComplete(); - verify(roleRepository).save(any(SysRole.class)); + verify(roleRepository).findById(1L); + verify(roleRepository).updateRole(any(SysRole.class)); } @Test diff --git a/gym-manage-api/manage-sys/src/test/java/cn/novalon/gym/manage/sys/core/service/impl/SysUserServiceIntegrationTest.java b/gym-manage-api/manage-sys/src/test/java/cn/novalon/gym/manage/sys/core/service/impl/SysUserServiceIntegrationTest.java index 27c693a..f591b83 100644 --- a/gym-manage-api/manage-sys/src/test/java/cn/novalon/gym/manage/sys/core/service/impl/SysUserServiceIntegrationTest.java +++ b/gym-manage-api/manage-sys/src/test/java/cn/novalon/gym/manage/sys/core/service/impl/SysUserServiceIntegrationTest.java @@ -1,6 +1,7 @@ package cn.novalon.gym.manage.sys.core.service.impl; import cn.novalon.gym.manage.common.util.StatusConstants; +import cn.novalon.gym.manage.sys.audit.service.IAuditLogService; import cn.novalon.gym.manage.sys.config.IntegrationTestConfig; import cn.novalon.gym.manage.sys.core.domain.SysUser; import cn.novalon.gym.manage.sys.core.domain.SysRole; @@ -76,6 +77,9 @@ class SysUserServiceIntegrationTest { @Autowired private IUserRoleRepository userRoleRepository; + @Autowired + private IAuditLogService auditLogService; + @Autowired private R2dbcEntityTemplate r2dbcEntityTemplate; @@ -85,7 +89,7 @@ class SysUserServiceIntegrationTest { @BeforeEach void setUp() { passwordEncoder = new BCryptPasswordEncoder(12); - userService = new SysUserService(userRepository, roleRepository, userRoleRepository, passwordEncoder); + userService = new SysUserService(userRepository, roleRepository, userRoleRepository, passwordEncoder, auditLogService); r2dbcEntityTemplate.delete(SysUser.class).all().block(); r2dbcEntityTemplate.delete(SysRole.class).all().block(); diff --git a/gym-manage-api/manage-sys/src/test/java/cn/novalon/gym/manage/sys/core/service/impl/SysUserServiceTest.java b/gym-manage-api/manage-sys/src/test/java/cn/novalon/gym/manage/sys/core/service/impl/SysUserServiceTest.java index 0ff9008..76da23b 100644 --- a/gym-manage-api/manage-sys/src/test/java/cn/novalon/gym/manage/sys/core/service/impl/SysUserServiceTest.java +++ b/gym-manage-api/manage-sys/src/test/java/cn/novalon/gym/manage/sys/core/service/impl/SysUserServiceTest.java @@ -1,6 +1,8 @@ package cn.novalon.gym.manage.sys.core.service.impl; import cn.novalon.gym.manage.common.util.StatusConstants; +import cn.novalon.gym.manage.sys.audit.service.IAuditLogService; +import cn.novalon.gym.manage.sys.audit.domain.AuditLog; import cn.novalon.gym.manage.sys.core.domain.SysUser; import cn.novalon.gym.manage.sys.core.domain.UserRole; import cn.novalon.gym.manage.sys.core.repository.ISysUserRepository; @@ -45,11 +47,14 @@ class SysUserServiceTest { @Mock private PasswordEncoder passwordEncoder; + @Mock + private IAuditLogService auditLogService; + private SysUserService userService; @BeforeEach void setUp() { - userService = new SysUserService(userRepository, roleRepository, userRoleRepository, passwordEncoder); + userService = new SysUserService(userRepository, roleRepository, userRoleRepository, passwordEncoder, auditLogService); } @Test @@ -164,7 +169,8 @@ class SysUserServiceTest { user.setUsername("testuser"); user.setEmail("updated@example.com"); - when(userRepository.save(any(SysUser.class))).thenReturn(Mono.just(user)); + when(userRepository.findById(1L)).thenReturn(Mono.just(user)); + when(userRepository.update(any(SysUser.class))).thenReturn(Mono.just(user)); StepVerifier.create(userService.updateUser(user)) .expectNextMatches(updatedUser -> @@ -173,7 +179,8 @@ class SysUserServiceTest { ) .verifyComplete(); - verify(userRepository, times(1)).save(any(SysUser.class)); + verify(userRepository, times(1)).findById(1L); + verify(userRepository, times(1)).update(any(SysUser.class)); } @Test @@ -218,7 +225,7 @@ class SysUserServiceTest { when(userRepository.findById(1L)).thenReturn(Mono.just(user)); when(passwordEncoder.matches("oldPassword", "$2b$12$oldPassword")).thenReturn(true); when(passwordEncoder.encode("newPassword")).thenReturn("$2b$12$newPassword"); - when(userRepository.save(any(SysUser.class))).thenAnswer(invocation -> Mono.just(invocation.getArgument(0))); + when(userRepository.update(any(SysUser.class))).thenAnswer(invocation -> Mono.just(invocation.getArgument(0))); StepVerifier.create(userService.changePassword(1L, "oldPassword", "newPassword")) .expectNextMatches(updatedUser -> @@ -228,7 +235,7 @@ class SysUserServiceTest { verify(passwordEncoder, times(1)).matches("oldPassword", "$2b$12$oldPassword"); verify(passwordEncoder, times(1)).encode("newPassword"); - verify(userRepository, times(1)).save(any(SysUser.class)); + verify(userRepository, times(1)).update(any(SysUser.class)); } @Test diff --git a/gym-manage-api/manage-sys/src/test/java/cn/novalon/gym/manage/sys/integration/SystemConfigRegressionTest.java b/gym-manage-api/manage-sys/src/test/java/cn/novalon/gym/manage/sys/integration/SystemConfigRegressionTest.java index 7675b11..f5c72c5 100644 --- a/gym-manage-api/manage-sys/src/test/java/cn/novalon/gym/manage/sys/integration/SystemConfigRegressionTest.java +++ b/gym-manage-api/manage-sys/src/test/java/cn/novalon/gym/manage/sys/integration/SystemConfigRegressionTest.java @@ -10,6 +10,7 @@ import cn.novalon.gym.manage.sys.core.service.ISysMenuService; import cn.novalon.gym.manage.sys.core.service.ISysRoleService; import cn.novalon.gym.manage.sys.core.service.ISysUserService; import cn.novalon.gym.manage.sys.core.service.impl.SysMenuService; +import cn.novalon.gym.manage.sys.audit.service.IAuditLogService; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -61,6 +62,9 @@ class SystemConfigRegressionTest { @Mock private ISysMenuRepository menuRepository; + @Mock + private IAuditLogService auditLogService; + private SysUser adminUser; private SysUser normalUser; private SysUser guestUser; @@ -374,7 +378,7 @@ class SystemConfigRegressionTest { void testAdminUser_MenuManagement() { /* unused */ - ISysMenuService menuService = new SysMenuService(menuRepository); + ISysMenuService menuService = new SysMenuService(menuRepository, auditLogService); StepVerifier.create(menuService.findAll()) .expectNextCount(0) @@ -384,7 +388,7 @@ class SystemConfigRegressionTest { @Test @DisplayName("3.2 普通用户 - 菜单访问控制") void testNormalUser_MenuAccess() { - ISysMenuService menuService = new SysMenuService(menuRepository); + ISysMenuService menuService = new SysMenuService(menuRepository, auditLogService); StepVerifier.create(menuService.findAll()) .expectNextCount(0) @@ -394,7 +398,7 @@ class SystemConfigRegressionTest { @Test @DisplayName("3.3 访客用户 - 菜单访问控制") void testGuestUser_MenuAccess() { - ISysMenuService menuService = new SysMenuService(menuRepository); + ISysMenuService menuService = new SysMenuService(menuRepository, auditLogService); StepVerifier.create(menuService.findAll()) .expectNextCount(0) @@ -404,7 +408,7 @@ class SystemConfigRegressionTest { @Test @DisplayName("3.4 菜单树构建 - 管理员视图") void testMenuTree_Build_Admin() { - ISysMenuService menuService = new SysMenuService(menuRepository); + ISysMenuService menuService = new SysMenuService(menuRepository, auditLogService); StepVerifier.create(menuService.findAll()) .verifyComplete(); @@ -413,7 +417,7 @@ class SystemConfigRegressionTest { @Test @DisplayName("3.5 权限菜单过滤 - 普通用户视图") void testMenuFilter_NormalUser() { - ISysMenuService menuService = new SysMenuService(menuRepository); + ISysMenuService menuService = new SysMenuService(menuRepository, auditLogService); StepVerifier.create(menuService.findAll()) .expectNextCount(0) @@ -423,7 +427,7 @@ class SystemConfigRegressionTest { @Test @DisplayName("3.6 权限菜单过滤 - 访客视图") void testMenuFilter_Guest() { - ISysMenuService menuService = new SysMenuService(menuRepository); + ISysMenuService menuService = new SysMenuService(menuRepository, auditLogService); StepVerifier.create(menuService.findAll()) .expectNextCount(0) @@ -472,7 +476,7 @@ class SystemConfigRegressionTest { @Test @DisplayName("5.2 大量菜单加载性能测试") void testLargeMenuLoadPerformance() { - ISysMenuService menuService = new SysMenuService(menuRepository); + ISysMenuService menuService = new SysMenuService(menuRepository, auditLogService); long startTime = System.currentTimeMillis(); @@ -516,7 +520,7 @@ class SystemConfigRegressionTest { @Test @DisplayName("6.3 菜单层级结构完整性") void testMenuHierarchy_Integrity() { - ISysMenuService menuService = new SysMenuService(menuRepository); + ISysMenuService menuService = new SysMenuService(menuRepository, auditLogService); StepVerifier.create(menuService.findAll()) .verifyComplete(); diff --git a/gym-manage-web/playwright/.auth/user.json b/gym-manage-web/playwright/.auth/user.json index 38696df..3154550 100644 --- a/gym-manage-web/playwright/.auth/user.json +++ b/gym-manage-web/playwright/.auth/user.json @@ -6,7 +6,7 @@ "localStorage": [ { "name": "token", - "value": "eyJhbGciOiJIUzM4NCJ9.eyJyb2xlcyI6WyJhZG1pbiJdLCJ1c2VySWQiOjEsInVzZXJuYW1lIjoiYWRtaW4iLCJzdWIiOiJhZG1pbiIsImlhdCI6MTc3NjkxMDg2NCwiZXhwIjoxNzc2OTk3MjY0fQ.ejRf_vt3oUCP_KlV9yafkYHusTK3zR0DQNR2sXA0r8A54UlzKHy0c3GgIEUq8av-" + "value": "eyJhbGciOiJIUzM4NCJ9.eyJyb2xlcyI6WyJhZG1pbiJdLCJ1c2VySWQiOjEsInVzZXJuYW1lIjoiYWRtaW4iLCJzdWIiOiJhZG1pbiIsImlhdCI6MTc3NzAxMTQ2OSwiZXhwIjoxNzc3MDk3ODY5fQ.F6Q44miMHrG7vPlthDNPbnx31CSGpBdbPRpJSDJDAyoy6y-vJvlDGd10xiPgnZhj" }, { "name": "permission", diff --git a/scripts/seed_initial_data.sql b/scripts/seed_initial_data.sql new file mode 100644 index 0000000..8e00777 --- /dev/null +++ b/scripts/seed_initial_data.sql @@ -0,0 +1,151 @@ +INSERT INTO sys_role (id, role_name, role_key, role_sort, status, create_by, update_by, created_at, updated_at) +VALUES +(1, '超级管理员', 'admin', 1, 1, 'system', 'system', NOW(), NOW()), +(2, '测试管理员', 'test_admin', 2, 1, 'system', 'system', NOW(), NOW()), +(3, '普通用户', 'normal_user', 3, 1, 'system', 'system', NOW(), NOW()), +(4, '访客', 'guest', 4, 1, 'system', 'system', NOW(), NOW()); +SELECT setval('sys_role_id_seq', 4); + +INSERT INTO sys_user (id, username, password, email, phone, nickname, status, create_by, update_by, created_at, updated_at) +VALUES +(1, 'admin', '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C', 'admin@novalon.com', '13800138000', '超级管理员', 1, 'system', 'system', NOW(), NOW()), +(2, 'user', '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C', 'user@novalon.com', '13800138001', '普通用户', 1, 'system', 'system', NOW(), NOW()), +(10, 'e2e_test_user', '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C', 'e2e@test.com', '13900139000', 'E2E测试用户', 1, 'system', 'system', NOW(), NOW()) +ON CONFLICT (username) DO UPDATE SET + password = EXCLUDED.password, + status = EXCLUDED.status; +SELECT setval('sys_user_id_seq', 10); + +INSERT INTO user_role (user_id, role_id, created_by, created_at) +VALUES +(1, 1, 'system', NOW()), +(2, 3, 'system', NOW()), +(10, 1, 'system', NOW()) +ON CONFLICT (user_id, role_id) DO NOTHING; + +INSERT INTO sys_permission (permission_name, permission_code, resource, action, description, status, create_by, update_by, created_at, updated_at) VALUES +('用户查看', 'system:user:view', '/api/users', 'GET', '查看用户列表', 1, 'system', 'system', NOW(), NOW()), +('用户创建', 'system:user:create', '/api/users', 'POST', '创建用户', 1, 'system', 'system', NOW(), NOW()), +('用户编辑', 'system:user:edit', '/api/users', 'PUT', '编辑用户', 1, 'system', 'system', NOW(), NOW()), +('用户删除', 'system:user:delete', '/api/users', 'DELETE', '删除用户', 1, 'system', 'system', NOW(), NOW()), +('角色查看', 'system:role:view', '/api/roles', 'GET', '查看角色列表', 1, 'system', 'system', NOW(), NOW()), +('角色创建', 'system:role:create', '/api/roles', 'POST', '创建角色', 1, 'system', 'system', NOW(), NOW()), +('角色编辑', 'system:role:edit', '/api/roles', 'PUT', '编辑角色', 1, 'system', 'system', NOW(), NOW()), +('角色删除', 'system:role:delete', '/api/roles', 'DELETE', '删除角色', 1, 'system', 'system', NOW(), NOW()), +('角色分配权限', 'system:role:assign', '/api/roles/*/permissions', 'POST', '为角色分配权限', 1, 'system', 'system', NOW(), NOW()), +('权限查看', 'system:permission:view', '/api/permissions', 'GET', '查看权限列表', 1, 'system', 'system', NOW(), NOW()), +('权限创建', 'system:permission:create', '/api/permissions', 'POST', '创建权限', 1, 'system', 'system', NOW(), NOW()), +('权限编辑', 'system:permission:edit', '/api/permissions', 'PUT', '编辑权限', 1, 'system', 'system', NOW(), NOW()), +('权限删除', 'system:permission:delete', '/api/permissions', 'DELETE', '删除权限', 1, 'system', 'system', NOW(), NOW()), +('菜单查看', 'system:menu:view', '/api/menus', 'GET', '查看菜单列表', 1, 'system', 'system', NOW(), NOW()), +('菜单创建', 'system:menu:create', '/api/menus', 'POST', '创建菜单', 1, 'system', 'system', NOW(), NOW()), +('菜单编辑', 'system:menu:edit', '/api/menus', 'PUT', '编辑菜单', 1, 'system', 'system', NOW(), NOW()), +('菜单删除', 'system:menu:delete', '/api/menus', 'DELETE', '删除菜单', 1, 'system', 'system', NOW(), NOW()), +('字典查看', 'system:dict:view', '/api/dict', 'GET', '查看字典列表', 1, 'system', 'system', NOW(), NOW()), +('字典创建', 'system:dict:create', '/api/dict', 'POST', '创建字典', 1, 'system', 'system', NOW(), NOW()), +('字典编辑', 'system:dict:edit', '/api/dict', 'PUT', '编辑字典', 1, 'system', 'system', NOW(), NOW()), +('字典删除', 'system:dict:delete', '/api/dict', 'DELETE', '删除字典', 1, 'system', 'system', NOW(), NOW()), +('配置查看', 'system:config:view', '/api/config', 'GET', '查看系统配置', 1, 'system', 'system', NOW(), NOW()), +('配置创建', 'system:config:create', '/api/config', 'POST', '创建系统配置', 1, 'system', 'system', NOW(), NOW()), +('配置编辑', 'system:config:edit', '/api/config', 'PUT', '编辑系统配置', 1, 'system', 'system', NOW(), NOW()), +('配置删除', 'system:config:delete', '/api/config', 'DELETE', '删除系统配置', 1, 'system', 'system', NOW(), NOW()), +('日志查看', 'system:log:view', '/api/logs', 'GET', '查看日志', 1, 'system', 'system', NOW(), NOW()), +('文件上传', 'system:file:upload', '/api/files/upload', 'POST', '上传文件', 1, 'system', 'system', NOW(), NOW()), +('文件下载', 'system:file:download', '/api/files/download', 'GET', '下载文件', 1, 'system', 'system', NOW(), NOW()), +('文件删除', 'system:file:delete', '/api/files', 'DELETE', '删除文件', 1, 'system', 'system', NOW(), NOW()), +('公告查看', 'system:notice:view', '/api/notices', 'GET', '查看公告', 1, 'system', 'system', NOW(), NOW()), +('公告创建', 'system:notice:create', '/api/notices', 'POST', '创建公告', 1, 'system', 'system', NOW(), NOW()), +('公告编辑', 'system:notice:edit', '/api/notices', 'PUT', '编辑公告', 1, 'system', 'system', NOW(), NOW()), +('公告删除', 'system:notice:delete', '/api/notices', 'DELETE', '删除公告', 1, 'system', 'system', NOW(), NOW()); + +INSERT INTO sys_role_permission (role_id, permission_id, create_by, update_by, created_at, updated_at) +SELECT 1, id, 'system', 'system', NOW(), NOW() FROM sys_permission WHERE status = 1; + +INSERT INTO sys_menu (id, menu_name, parent_id, order_num, menu_type, perms, component, status, created_at, updated_at) VALUES +(1, '系统管理', 0, 1, 'M', NULL, NULL, 1, NOW(), NOW()), +(2, '审计日志', 0, 2, 'M', NULL, NULL, 1, NOW(), NOW()), +(3, '系统监控', 0, 3, 'M', NULL, NULL, 1, NOW(), NOW()), +(11, '用户管理', 1, 1, 'C', 'system:user:list', 'system/user/index', 1, NOW(), NOW()), +(12, '角色管理', 1, 2, 'C', 'system:role:list', 'system/role/index', 1, NOW(), NOW()), +(13, '菜单管理', 1, 3, 'C', 'system:menu:list', 'system/menu/index', 1, NOW(), NOW()), +(14, '部门管理', 1, 4, 'C', 'system:dept:list', 'system/dept/index', 1, NOW(), NOW()), +(15, '字典管理', 1, 5, 'C', 'system:dict:list', 'system/dict/index', 1, NOW(), NOW()), +(16, '参数管理', 1, 6, 'C', 'system:config:list', 'system/config/index', 1, NOW(), NOW()), +(17, '通知公告', 1, 7, 'C', 'system:notice:list', 'system/notice/index', 1, NOW(), NOW()), +(18, '文件管理', 1, 8, 'C', 'system:file:list', 'system/file/index', 1, NOW(), NOW()), +(111, '用户查询', 11, 1, 'F', 'system:user:query', NULL, 1, NOW(), NOW()), +(112, '用户新增', 11, 2, 'F', 'system:user:add', NULL, 1, NOW(), NOW()), +(113, '用户修改', 11, 3, 'F', 'system:user:edit', NULL, 1, NOW(), NOW()), +(114, '用户删除', 11, 4, 'F', 'system:user:remove', NULL, 1, NOW(), NOW()), +(115, '用户导出', 11, 5, 'F', 'system:user:export', NULL, 1, NOW(), NOW()), +(116, '用户导入', 11, 6, 'F', 'system:user:import', NULL, 1, NOW(), NOW()), +(117, '重置密码', 11, 7, 'F', 'system:user:resetPwd', NULL, 1, NOW(), NOW()), +(121, '角色查询', 12, 1, 'F', 'system:role:query', NULL, 1, NOW(), NOW()), +(122, '角色新增', 12, 2, 'F', 'system:role:add', NULL, 1, NOW(), NOW()), +(123, '角色修改', 12, 3, 'F', 'system:role:edit', NULL, 1, NOW(), NOW()), +(124, '角色删除', 12, 4, 'F', 'system:role:remove', NULL, 1, NOW(), NOW()), +(125, '角色导出', 12, 5, 'F', 'system:role:export', NULL, 1, NOW(), NOW()), +(131, '菜单查询', 13, 1, 'F', 'system:menu:query', NULL, 1, NOW(), NOW()), +(132, '菜单新增', 13, 2, 'F', 'system:menu:add', NULL, 1, NOW(), NOW()), +(133, '菜单修改', 13, 3, 'F', 'system:menu:edit', NULL, 1, NOW(), NOW()), +(134, '菜单删除', 13, 4, 'F', 'system:menu:remove', NULL, 1, NOW(), NOW()), +(21, '操作日志', 2, 1, 'C', 'audit:operation:list', 'audit/operation/index', 1, NOW(), NOW()), +(22, '登录日志', 2, 2, 'C', 'audit:login:list', 'audit/login/index', 1, NOW(), NOW()), +(23, '异常日志', 2, 3, 'C', 'audit:exception:list', 'audit/exception/index', 1, NOW(), NOW()), +(211, '操作查询', 21, 1, 'F', 'audit:operation:query', NULL, 1, NOW(), NOW()), +(212, '操作删除', 21, 2, 'F', 'audit:operation:remove', NULL, 1, NOW(), NOW()), +(213, '操作导出', 21, 3, 'F', 'audit:operation:export', NULL, 1, NOW(), NOW()), +(221, '登录查询', 22, 1, 'F', 'audit:login:query', NULL, 1, NOW(), NOW()), +(222, '登录删除', 22, 2, 'F', 'audit:login:remove', NULL, 1, NOW(), NOW()), +(223, '登录导出', 22, 3, 'F', 'audit:login:export', NULL, 1, NOW(), NOW()), +(231, '异常查询', 23, 1, 'F', 'audit:exception:query', NULL, 1, NOW(), NOW()), +(232, '异常删除', 23, 2, 'F', 'audit:exception:remove', NULL, 1, NOW(), NOW()), +(233, '异常导出', 23, 3, 'F', 'audit:exception:export', NULL, 1, NOW(), NOW()), +(31, '在线用户', 3, 1, 'C', 'monitor:online:list', 'monitor/online/index', 1, NOW(), NOW()), +(32, '定时任务', 3, 2, 'C', 'monitor:job:list', 'monitor/job/index', 1, NOW(), NOW()), +(33, '数据监控', 3, 3, 'C', 'monitor:data:list', 'monitor/data/index', 1, NOW(), NOW()), +(34, '服务监控', 3, 4, 'C', 'monitor:server:list', 'monitor/server/index', 1, NOW(), NOW()), +(35, '缓存监控', 3, 5, 'C', 'monitor:cache:list', 'monitor/cache/index', 1, NOW(), NOW()), +(311, '在线查询', 31, 1, 'F', 'monitor:online:query', NULL, 1, NOW(), NOW()), +(312, '在线强退', 31, 2, 'F', 'monitor:online:forceLogout', NULL, 1, NOW(), NOW()), +(321, '任务查询', 32, 1, 'F', 'monitor:job:query', NULL, 1, NOW(), NOW()), +(322, '任务新增', 32, 2, 'F', 'monitor:job:add', NULL, 1, NOW(), NOW()), +(323, '任务修改', 32, 3, 'F', 'monitor:job:edit', NULL, 1, NOW(), NOW()), +(324, '任务删除', 32, 4, 'F', 'monitor:job:remove', NULL, 1, NOW(), NOW()), +(325, '任务执行', 32, 5, 'F', 'monitor:job:execute', NULL, 1, NOW(), NOW()); +SELECT setval('sys_menu_id_seq', 400); + +INSERT INTO sys_dict_type (dict_name, dict_type, status, remark, create_by, update_by, created_at, updated_at) +VALUES +('用户状态', 'user_status', '0', '用户状态列表', 'system', 'system', NOW(), NOW()), +('菜单状态', 'menu_status', '0', '菜单状态列表', 'system', 'system', NOW(), NOW()), +('角色状态', 'role_status', '0', '角色状态列表', 'system', 'system', NOW(), NOW()), +('系统开关', 'sys_normal_disable', '0', '系统开关列表', 'system', 'system', NOW(), NOW()) +ON CONFLICT (dict_type) DO NOTHING; + +INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, update_by, created_at, updated_at) +VALUES +(1, '正常', '1', 'user_status', '', 'primary', 'Y', '0', 'system', 'system', NOW(), NOW()), +(2, '停用', '0', 'user_status', '', 'danger', 'N', '0', 'system', 'system', NOW(), NOW()), +(1, '正常', '0', 'menu_status', '', 'primary', 'Y', '0', 'system', 'system', NOW(), NOW()), +(2, '停用', '1', 'menu_status', '', 'danger', 'N', '0', 'system', 'system', NOW(), NOW()), +(1, '正常', '0', 'role_status', '', 'primary', 'Y', '0', 'system', 'system', NOW(), NOW()), +(2, '停用', '1', 'role_status', '', 'danger', 'N', '0', 'system', 'system', NOW(), NOW()), +(1, '正常', '0', 'sys_normal_disable', '', 'primary', 'Y', '0', 'system', 'system', NOW(), NOW()), +(2, '停用', '1', 'sys_normal_disable', '', 'danger', 'N', '0', 'system', 'system', NOW(), NOW()); + +INSERT INTO sys_config (config_name, config_key, config_value, config_type, create_by, update_by, created_at, updated_at) +VALUES +('用户管理-用户初始密码', 'sys.user.initPassword', '123456', 'Y', 'system', 'system', NOW(), NOW()), +('主框架页-默认皮肤样式名称', 'sys.index.skinName', 'skin-blue', 'Y', 'system', 'system', NOW(), NOW()), +('用户自助-验证码开关', 'sys.account.captchaEnabled', 'true', 'Y', 'system', 'system', NOW(), NOW()), +('用户自助-是否开启用户注册功能', 'sys.account.registerUser', 'false', 'Y', 'system', 'system', NOW(), NOW()), +('账号自助-密码验证码', 'sys.account.pwdCaptchaEnabled', 'true', 'Y', 'system', 'system', NOW(), NOW()) +ON CONFLICT (config_key) DO NOTHING; + +SELECT setval('sys_dict_type_id_seq', (SELECT COALESCE(MAX(id), 1) FROM sys_dict_type)); +SELECT setval('sys_dict_data_id_seq', (SELECT COALESCE(MAX(id), 1) FROM sys_dict_data)); +SELECT setval('sys_config_id_seq', (SELECT COALESCE(MAX(id), 1) FROM sys_config)); +SELECT setval('sys_permission_id_seq', (SELECT COALESCE(MAX(id), 1) FROM sys_permission)); +SELECT setval('sys_role_permission_id_seq', (SELECT COALESCE(MAX(id), 1) FROM sys_role_permission)); +SELECT setval('user_role_id_seq', (SELECT COALESCE(MAX(id), 1) FROM user_role)); diff --git a/scripts/truncate_all.sql b/scripts/truncate_all.sql new file mode 100644 index 0000000..f5b4cf9 --- /dev/null +++ b/scripts/truncate_all.sql @@ -0,0 +1,41 @@ +TRUNCATE TABLE + audit_log_archive, + audit_log, + operation_log, + sys_operation_log, + sys_exception_log, + sys_login_log, + sys_user_message, + sys_role_permission, + user_role, + sys_permission, + sys_file, + sys_notice, + sys_user, + sys_role, + sys_menu, + sys_dict_data, + sys_dict_type, + sys_dictionary, + sys_config, + oauth2_client +CASCADE; + +SELECT setval('sys_user_id_seq', 1, false); +SELECT setval('sys_role_id_seq', 1, false); +SELECT setval('sys_menu_id_seq', 1, false); +SELECT setval('sys_permission_id_seq', 1, false); +SELECT setval('sys_role_permission_id_seq', 1, false); +SELECT setval('user_role_id_seq', 1, false); +SELECT setval('sys_dict_type_id_seq', 1, false); +SELECT setval('sys_dict_data_id_seq', 1, false); +SELECT setval('sys_config_id_seq', 1, false); +SELECT setval('sys_file_id_seq', 1, false); +SELECT setval('sys_notice_id_seq', 1, false); +SELECT setval('operation_log_id_seq', 1, false); +SELECT setval('audit_log_id_seq', 1, false); +SELECT setval('sys_login_log_id_seq', 1, false); +SELECT setval('sys_exception_log_id_seq', 1, false); +SELECT setval('sys_user_message_id_seq', 1, false); +SELECT setval('oauth2_client_id_seq', 1, false); +SELECT setval('audit_log_archive_id_seq', 1, false);