chore: 清理旧迁移脚本并添加本地开发配置

- 删除旧的V10和V11迁移脚本(已被V12和V13替代)
- 更新BaseDomain和自动配置文件
- 删除旧的测试文件
- 添加本地开发配置文件
- 添加简化版应用启动类
This commit was merged in pull request #3.
This commit is contained in:
张翔
2026-04-15 23:39:02 +08:00
parent 648851df92
commit 2954e8cd2c
16 changed files with 1184 additions and 263 deletions
@@ -0,0 +1,42 @@
package cn.novalon.manage.app;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 最小化应用程序启动类
* 避免复杂的自动配置问题,专注于核心功能
*/
@SpringBootApplication(
scanBasePackages = {
"cn.novalon.manage.app.config",
"cn.novalon.manage.app.controller",
"cn.novalon.manage.app.service"
}
)
public class MinimalApplication {
private static final Logger logger = LoggerFactory.getLogger(MinimalApplication.class);
public static void main(String[] args) {
logger.info("最小化应用程序启动中...");
// 设置系统属性,避免自动配置问题
System.setProperty("spring.autoconfigure.exclude",
"org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration");
// 禁用复杂的自动配置
System.setProperty("spring.main.lazy-initialization", "true");
System.setProperty("spring.main.banner-mode", "off");
try {
SpringApplication.run(MinimalApplication.class, args);
logger.info("最小化应用程序启动完成");
} catch (Exception e) {
logger.error("应用程序启动失败: {}", e.getMessage());
e.printStackTrace();
}
}
}
@@ -0,0 +1,32 @@
package cn.novalon.manage.app;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration;
/**
* 简化的应用程序启动类
* 避免复杂的自动配置问题
*/
@SpringBootApplication(
scanBasePackages = "cn.novalon.manage.app",
exclude = {ReactiveUserDetailsServiceAutoConfiguration.class}
)
public class SimpleManageApplication {
private static final Logger logger = LoggerFactory.getLogger(SimpleManageApplication.class);
public static void main(String[] args) {
logger.info("简化版应用程序启动中...");
logger.info("包扫描路径: cn.novalon.manage.app");
// 设置系统属性,避免自动配置问题
System.setProperty("spring.autoconfigure.exclude",
"org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration");
SpringApplication.run(SimpleManageApplication.class, args);
logger.info("简化版应用程序启动完成");
}
}
@@ -0,0 +1,36 @@
# 本地开发环境配置
spring:
config:
activate:
on-profile: local
r2dbc:
url: r2dbc:postgresql://localhost:55432/manage_system
username: novalon
password: novalon123
pool:
initial-size: 5
max-size: 20
max-idle-time: 10m
max-life-time: 30m
acquire-timeout: 3s
datasource:
url: jdbc:postgresql://localhost:55432/manage_system
username: novalon
password: novalon123
driver-class-name: org.postgresql.Driver
flyway:
enabled: true
locations: classpath:db/migration
baseline-on-migrate: true
baseline-version: 0
validate-on-migrate: true
sql:
init:
mode: always
logging:
level:
cn.novalon.manage: DEBUG
org.springframework.r2dbc: DEBUG
cn.novalon.manage.db: DEBUG
org.flywaydb: DEBUG
@@ -0,0 +1,33 @@
package cn.novalon.manage.common.handler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
/**
* 默认异常日志服务实现
* 临时实现,用于解决启动时的依赖注入问题
*
* @author 张翔
* @date 2026-04-15
*/
@Service
public class DefaultExceptionLogService implements IExceptionLogService {
private static final Logger logger = LoggerFactory.getLogger(DefaultExceptionLogService.class);
@Override
public Mono<Void> logException(String title, String exceptionName, String exceptionMsg,
String methodName, String ip, String stackTrace) {
logger.warn("异常日志记录 (临时实现): title={}, exceptionName={}, methodName={}, ip={}",
title, exceptionName, methodName, ip);
logger.warn("异常信息: {}", exceptionMsg);
if (stackTrace != null && stackTrace.length() > 500) {
logger.warn("堆栈跟踪 (截断): {}", stackTrace.substring(0, 500) + "...");
} else if (stackTrace != null) {
logger.warn("堆栈跟踪: {}", stackTrace);
}
return Mono.empty();
}
}
@@ -1,51 +0,0 @@
-- Novalon管理系统普通用户角色和数据
-- 版本: V10
-- 描述: 创建普通用户角色并分配权限
-- 插入普通用户角色
INSERT INTO sys_role (role_name, role_key, role_sort, status, create_by, update_by)
VALUES ('普通用户', 'user', 2, 1, 'system', 'system')
ON CONFLICT (role_key) DO UPDATE SET
role_name = EXCLUDED.role_name,
role_sort = EXCLUDED.role_sort,
status = EXCLUDED.status;
-- 为普通用户分配基本权限(查看个人信息、修改密码等)
-- 注意:这里只分配基本权限,不包含管理功能权限
INSERT INTO sys_permission (permission_name, permission_key, permission_type, parent_id, path, component, icon, sort, status, create_by, update_by)
VALUES
('个人中心', 'profile', 'MENU', 0, '/profile', 'views/profile/index', 'user', 1, 1, 'system', 'system'),
('个人信息', 'profile:info', 'BUTTON', (SELECT id FROM sys_permission WHERE permission_key = 'profile'), '', '', '', 1, 1, 'system', 'system'),
('修改密码', 'profile:password', 'BUTTON', (SELECT id FROM sys_permission WHERE permission_key = 'profile'), '', '', '', 2, 1, 'system', 'system')
ON CONFLICT (permission_key) DO NOTHING;
-- 为普通用户角色分配权限
INSERT INTO sys_role_permission (role_id, permission_id, create_by, update_by)
SELECT
r.id as role_id,
p.id as permission_id,
'system' as create_by,
'system' as update_by
FROM sys_role r
CROSS JOIN sys_permission p
WHERE r.role_key = 'user'
AND p.permission_key IN ('profile', 'profile:info', 'profile:password')
ON CONFLICT DO NOTHING;
-- 将测试用户分配给普通用户角色
INSERT INTO user_role (user_id, role_id, create_by, update_by)
SELECT
u.id as user_id,
r.id as role_id,
'system' as create_by,
'system' as update_by
FROM sys_user u
CROSS JOIN sys_role r
WHERE u.username = 'user' AND r.role_key = 'user'
ON CONFLICT DO NOTHING;
-- 重置序列值
SELECT setval('sys_role_id_seq', (SELECT COALESCE(MAX(id), 1) FROM sys_role));
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));
@@ -1,46 +0,0 @@
-- Novalon管理系统测试数据脚本
-- 版本: V11
-- 描述: 更新测试用户密码为Test@123,插入E2E测试所需数据
-- 更新admin用户密码为Test@123
-- BCrypt哈希值对应明文密码: Test@123
UPDATE sys_user
SET password = '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C'
WHERE username = 'admin';
-- 更新user用户密码为Test@123
UPDATE sys_user
SET password = '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C'
WHERE username = 'user';
-- 插入测试角色(如果不存在)
INSERT INTO sys_role (role_name, role_key, role_sort, status, create_by, update_by)
VALUES
('测试管理员', 'test_admin', 2, 1, 'system', 'system'),
('普通用户', 'normal_user', 3, 1, 'system', 'system'),
('访客', 'guest', 4, 1, 'system', 'system')
ON CONFLICT (role_key) DO NOTHING;
-- 为admin用户分配超级管理员角色
INSERT INTO user_role (user_id, role_id, created_by)
SELECT 1, id, 'system' FROM sys_role WHERE role_key = 'admin'
ON CONFLICT DO NOTHING;
-- 为user用户分配普通用户角色
INSERT INTO user_role (user_id, role_id, created_by)
SELECT 2, id, 'system' FROM sys_role WHERE role_key = 'normal_user'
ON CONFLICT DO NOTHING;
-- 插入E2E测试专用用户
-- BCrypt哈希值对应明文密码: Test@123
INSERT INTO sys_user (id, username, password, email, phone, nickname, status, create_by, update_by)
VALUES
(10, 'e2e_test_user', '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C', 'e2e@test.com', '13900139000', 'E2E测试用户', 1, 'system', 'system')
ON CONFLICT (username) DO UPDATE SET
password = EXCLUDED.password,
status = EXCLUDED.status;
-- 为E2E测试用户分配超级管理员角色
INSERT INTO user_role (user_id, role_id, created_by)
SELECT 10, id, 'system' FROM sys_role WHERE role_key = 'admin'
ON CONFLICT DO NOTHING;
@@ -0,0 +1,38 @@
# 本地开发环境配置
spring:
config:
activate:
on-profile: local
cloud:
gateway:
routes:
- id: manage-app
uri: http://localhost:8084
predicates:
- Path=/api/**
default-filters:
- name: JwtAuthentication
- name: RbacAuthorization
- name: Retry
args:
retries: 3
statuses: BAD_GATEWAY,SERVICE_UNAVAILABLE
methods: GET,POST
backoff:
firstBackoff: 10ms
maxBackoff: 50ms
factor: 2
basedOnPreviousValue: false
- name: DedupeResponseHeader
args:
name: Content-Encoding
strategy: RETAIN_FIRST
jwt:
secret: U2FsdGVkX1+vZ5Y9QmKxL8nN3rP7tW2jH4fG6dA8sB1cE5yN0zX3qV7wM4
expiration: 86400000
logging:
level:
cn.novalon.manage.gateway: DEBUG
org.springframework.cloud.gateway: DEBUG
@@ -0,0 +1,48 @@
package cn.novalon.manage.sys.config;
import cn.novalon.manage.sys.core.service.impl.SysExceptionLogService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;
import static org.springframework.web.reactive.function.server.RequestPredicates.*;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
/**
* 异常日志配置类
*
* @author 张翔
* @date 2026-04-15
*/
@Configuration
public class ExceptionLogConfig {
private static final Logger logger = LoggerFactory.getLogger(ExceptionLogConfig.class);
/**
* 配置异常日志的路由
*/
@Bean
public RouterFunction<ServerResponse> exceptionLogRoutes(SysExceptionLogService exceptionLogService) {
logger.info("配置异常日志路由");
return route()
.GET("/api/exception-logs", request ->
ServerResponse.ok().body(exceptionLogService.findAll(), cn.novalon.manage.sys.core.domain.SysExceptionLog.class))
.GET("/api/exception-logs/{id}", request -> {
Long id = Long.valueOf(request.pathVariable("id"));
return exceptionLogService.findById(id)
.flatMap(log -> ServerResponse.ok().bodyValue(log))
.switchIfEmpty(ServerResponse.notFound().build());
})
.GET("/api/exception-logs/username/{username}", request -> {
String username = request.pathVariable("username");
return ServerResponse.ok().body(exceptionLogService.findByUsername(username),
cn.novalon.manage.sys.core.domain.SysExceptionLog.class);
})
.build();
}
}
@@ -75,4 +75,17 @@ public abstract class BaseDomain {
this.id = SnowflakeId.nextId();
return this.id;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BaseDomain that = (BaseDomain) o;
return id != null && id.equals(that.id);
}
@Override
public int hashCode() {
return id != null ? id.hashCode() : 0;
}
}
@@ -1,2 +1,2 @@
cn.novalon.manage.sys.config.SecurityConfig
cn.novalon.manage.sys.config.ExceptionLogConfig
cn.novalon.manage.sys.config.ExceptionLogConfig
cn.novalon.manage.sys.config.SystemRouter
@@ -0,0 +1,220 @@
package cn.novalon.manage.sys.audit.controller;
import cn.novalon.manage.sys.audit.domain.AuditLog;
import cn.novalon.manage.sys.audit.dto.AuditLogQueryRequest;
import cn.novalon.manage.sys.audit.dto.AuditLogStatistics;
import cn.novalon.manage.sys.audit.service.IAuditLogService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.web.reactive.server.WebTestClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.when;
import static org.junit.jupiter.api.Assertions.*;
/**
* AuditLogController 单元测试
*
* @author 张翔
* @date 2026-04-14
*/
@ExtendWith(MockitoExtension.class)
class AuditLogControllerTest {
@Mock
private IAuditLogService auditLogService;
private WebTestClient webTestClient;
private AuditLogController auditLogController;
@BeforeEach
void setUp() {
auditLogController = new AuditLogController(auditLogService);
webTestClient = WebTestClient.bindToController(auditLogController).build();
}
@Test
@DisplayName("根据ID查询审计日志 - 成功")
void findById_whenExists_shouldReturnAuditLog() {
AuditLog auditLog = createTestAuditLog(1L);
when(auditLogService.findById(1L)).thenReturn(Mono.just(auditLog));
webTestClient.get()
.uri("/api/audit-logs/1")
.exchange()
.expectStatus().isOk()
.expectBody(AuditLog.class)
.isEqualTo(auditLog);
}
@Test
@DisplayName("根据ID查询审计日志 - 不存在")
void findById_whenNotExists_shouldReturnNotFound() {
when(auditLogService.findById(999L)).thenReturn(Mono.empty());
webTestClient.get()
.uri("/api/audit-logs/999")
.exchange()
.expectStatus().isOk()
.expectBody().isEmpty();
}
@Test
@DisplayName("按实体类型查询审计日志")
void findByEntityType_shouldReturnAuditLogs() {
AuditLog auditLog1 = createTestAuditLog(1L);
AuditLog auditLog2 = createTestAuditLog(2L);
when(auditLogService.findByEntityType("User")).thenReturn(Flux.just(auditLog1, auditLog2));
webTestClient.get()
.uri("/api/audit-logs/entity-type/User")
.exchange()
.expectStatus().isOk()
.expectBodyList(AuditLog.class)
.hasSize(2)
.contains(auditLog1, auditLog2);
}
@Test
@DisplayName("按实体ID查询审计日志")
void findByEntityId_shouldReturnAuditLogs() {
AuditLog auditLog = createTestAuditLog(1L);
when(auditLogService.findByEntityId(100L)).thenReturn(Flux.just(auditLog));
webTestClient.get()
.uri("/api/audit-logs/entity/100")
.exchange()
.expectStatus().isOk()
.expectBodyList(AuditLog.class)
.hasSize(1)
.contains(auditLog);
}
@Test
@DisplayName("按操作人查询审计日志")
void findByOperator_shouldReturnAuditLogs() {
AuditLog auditLog = createTestAuditLog(1L);
when(auditLogService.findByOperator("admin")).thenReturn(Flux.just(auditLog));
webTestClient.get()
.uri("/api/audit-logs/operator/admin")
.exchange()
.expectStatus().isOk()
.expectBodyList(AuditLog.class)
.hasSize(1)
.contains(auditLog);
}
@Test
@DisplayName("按操作类型查询审计日志")
void findByOperationType_shouldReturnAuditLogs() {
AuditLog auditLog = createTestAuditLog(1L);
when(auditLogService.findByOperationType("CREATE")).thenReturn(Flux.just(auditLog));
webTestClient.get()
.uri("/api/audit-logs/operation-type/CREATE")
.exchange()
.expectStatus().isOk()
.expectBodyList(AuditLog.class)
.hasSize(1)
.contains(auditLog);
}
@Test
@DisplayName("按时间范围查询审计日志")
void findByTimeRange_shouldReturnAuditLogs() {
LocalDateTime startTime = LocalDateTime.now().minusDays(1);
LocalDateTime endTime = LocalDateTime.now();
AuditLog auditLog = createTestAuditLog(1L);
when(auditLogService.findByOperationTimeBetween(startTime, endTime))
.thenReturn(Flux.just(auditLog));
webTestClient.get()
.uri(uriBuilder -> uriBuilder
.path("/api/audit-logs/time-range")
.queryParam("startTime", startTime)
.queryParam("endTime", endTime)
.build())
.exchange()
.expectStatus().isOk()
.expectBodyList(AuditLog.class)
.hasSize(1)
.contains(auditLog);
}
@Test
@DisplayName("获取审计日志统计信息")
void getStatistics_shouldReturnStatistics() {
webTestClient.get()
.uri("/api/audit-logs/statistics")
.exchange()
.expectStatus().isOk()
.expectBody(AuditLogStatistics.class)
.value(returnedStatistics -> {
assertNotNull(returnedStatistics);
assertNull(returnedStatistics.getTotalCount());
});
}
@Test
@DisplayName("按实体类型统计数量")
void countByEntityType_shouldReturnCount() {
when(auditLogService.countByEntityType("User")).thenReturn(Mono.just(10L));
webTestClient.get()
.uri("/api/audit-logs/count/entity-type/User")
.exchange()
.expectStatus().isOk()
.expectBody(Long.class)
.isEqualTo(10L);
}
@Test
@DisplayName("按操作人统计数量")
void countByOperator_shouldReturnCount() {
when(auditLogService.countByOperator("admin")).thenReturn(Mono.just(5L));
webTestClient.get()
.uri("/api/audit-logs/count/operator/admin")
.exchange()
.expectStatus().isOk()
.expectBody(Long.class)
.isEqualTo(5L);
}
@Test
@DisplayName("按操作类型统计数量")
void countByOperationType_shouldReturnCount() {
when(auditLogService.countByOperationType("CREATE")).thenReturn(Mono.just(3L));
webTestClient.get()
.uri("/api/audit-logs/count/operation-type/CREATE")
.exchange()
.expectStatus().isOk()
.expectBody(Long.class)
.isEqualTo(3L);
}
private AuditLog createTestAuditLog(Long id) {
AuditLog auditLog = new AuditLog();
auditLog.setId(id);
auditLog.setEntityType("User");
auditLog.setEntityId(100L);
auditLog.setOperator("admin");
auditLog.setOperationType("CREATE");
auditLog.setOperationTime(LocalDateTime.now());
auditLog.setDescription("创建用户");
auditLog.setIpAddress("192.168.1.1");
auditLog.setUserAgent("Mozilla/5.0");
return auditLog;
}
}
@@ -0,0 +1,224 @@
package cn.novalon.manage.sys.audit.domain;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.time.LocalDateTime;
import static org.junit.jupiter.api.Assertions.*;
/**
* AuditLog 单元测试
*
* @author 张翔
* @date 2026-04-14
*/
class AuditLogTest {
@Test
@DisplayName("创建默认审计日志")
void createDefaultAuditLog_shouldHaveNullFields() {
AuditLog auditLog = new AuditLog();
assertNull(auditLog.getId());
assertNull(auditLog.getEntityType());
assertNull(auditLog.getEntityId());
assertNull(auditLog.getOperator());
assertNull(auditLog.getOperationType());
assertNull(auditLog.getOperationTime());
assertNull(auditLog.getDescription());
assertNull(auditLog.getIpAddress());
assertNull(auditLog.getUserAgent());
assertNull(auditLog.getDeletedAt());
}
@Test
@DisplayName("设置和获取ID")
void setAndGetId_shouldWorkCorrectly() {
AuditLog auditLog = new AuditLog();
auditLog.setId(1L);
assertEquals(1L, auditLog.getId());
}
@Test
@DisplayName("设置和获取实体类型")
void setAndGetEntityType_shouldWorkCorrectly() {
AuditLog auditLog = new AuditLog();
auditLog.setEntityType("User");
assertEquals("User", auditLog.getEntityType());
}
@Test
@DisplayName("设置和获取实体ID")
void setAndGetEntityId_shouldWorkCorrectly() {
AuditLog auditLog = new AuditLog();
auditLog.setEntityId(100L);
assertEquals(100L, auditLog.getEntityId());
}
@Test
@DisplayName("设置和获取操作人")
void setAndGetOperator_shouldWorkCorrectly() {
AuditLog auditLog = new AuditLog();
auditLog.setOperator("admin");
assertEquals("admin", auditLog.getOperator());
}
@Test
@DisplayName("设置和获取操作类型")
void setAndGetOperationType_shouldWorkCorrectly() {
AuditLog auditLog = new AuditLog();
auditLog.setOperationType("CREATE");
assertEquals("CREATE", auditLog.getOperationType());
}
@Test
@DisplayName("设置和获取操作时间")
void setAndGetOperationTime_shouldWorkCorrectly() {
LocalDateTime operationTime = LocalDateTime.now();
AuditLog auditLog = new AuditLog();
auditLog.setOperationTime(operationTime);
assertEquals(operationTime, auditLog.getOperationTime());
}
@Test
@DisplayName("设置和获取描述")
void setAndGetDescription_shouldWorkCorrectly() {
AuditLog auditLog = new AuditLog();
auditLog.setDescription("创建用户");
assertEquals("创建用户", auditLog.getDescription());
}
@Test
@DisplayName("设置和获取IP地址")
void setAndGetIpAddress_shouldWorkCorrectly() {
AuditLog auditLog = new AuditLog();
auditLog.setIpAddress("192.168.1.1");
assertEquals("192.168.1.1", auditLog.getIpAddress());
}
@Test
@DisplayName("设置和获取用户代理")
void setAndGetUserAgent_shouldWorkCorrectly() {
AuditLog auditLog = new AuditLog();
auditLog.setUserAgent("Mozilla/5.0");
assertEquals("Mozilla/5.0", auditLog.getUserAgent());
}
@Test
@DisplayName("设置和获取删除时间")
void setAndGetDeletedAt_shouldWorkCorrectly() {
LocalDateTime deletedAt = LocalDateTime.now();
AuditLog auditLog = new AuditLog();
auditLog.setDeletedAt(deletedAt);
assertEquals(deletedAt, auditLog.getDeletedAt());
}
@Test
@DisplayName("toString方法应包含所有字段")
void toString_shouldContainAllFields() {
LocalDateTime operationTime = LocalDateTime.now();
AuditLog auditLog = new AuditLog();
auditLog.setId(1L);
auditLog.setEntityType("User");
auditLog.setEntityId(100L);
auditLog.setOperator("admin");
auditLog.setOperationType("CREATE");
auditLog.setOperationTime(operationTime);
auditLog.setDescription("创建用户");
auditLog.setIpAddress("192.168.1.1");
auditLog.setUserAgent("Mozilla/5.0");
String toString = auditLog.toString();
assertTrue(toString.contains("1"));
assertTrue(toString.contains("User"));
assertTrue(toString.contains("100"));
assertTrue(toString.contains("admin"));
assertTrue(toString.contains("CREATE"));
assertTrue(toString.contains("创建用户"));
assertTrue(toString.contains("192.168.1.1"));
assertTrue(toString.contains("Mozilla/5.0"));
}
@Test
@DisplayName("equals和hashCode方法应基于字段值")
void equalsAndHashCode_shouldBeBasedOnFieldValues() {
LocalDateTime operationTime = LocalDateTime.now();
AuditLog auditLog1 = new AuditLog();
auditLog1.setId(1L);
auditLog1.setEntityType("User");
auditLog1.setEntityId(100L);
auditLog1.setOperator("admin");
auditLog1.setOperationType("CREATE");
auditLog1.setOperationTime(operationTime);
auditLog1.setDescription("创建用户");
auditLog1.setIpAddress("192.168.1.1");
auditLog1.setUserAgent("Mozilla/5.0");
AuditLog auditLog2 = new AuditLog();
auditLog2.setId(1L);
auditLog2.setEntityType("User");
auditLog2.setEntityId(100L);
auditLog2.setOperator("admin");
auditLog2.setOperationType("CREATE");
auditLog2.setOperationTime(operationTime);
auditLog2.setDescription("创建用户");
auditLog2.setIpAddress("192.168.1.1");
auditLog2.setUserAgent("Mozilla/5.0");
assertEquals(auditLog1, auditLog2);
assertEquals(auditLog1.hashCode(), auditLog2.hashCode());
}
@Test
@DisplayName("不同ID的对象应不相等")
void differentIds_shouldNotBeEqual() {
AuditLog auditLog1 = new AuditLog();
auditLog1.setId(1L);
AuditLog auditLog2 = new AuditLog();
auditLog2.setId(2L);
assertNotEquals(auditLog1, auditLog2);
}
@Test
@DisplayName("null对象应不相等")
void nullObject_shouldNotBeEqual() {
AuditLog auditLog = new AuditLog();
auditLog.setId(1L);
assertNotEquals(auditLog, null);
}
@Test
@DisplayName("不同类型对象应不相等")
void differentTypeObject_shouldNotBeEqual() {
AuditLog auditLog = new AuditLog();
auditLog.setId(1L);
assertNotEquals(auditLog, "not an audit log");
}
@Test
@DisplayName("相同对象引用应相等")
void sameObjectReference_shouldBeEqual() {
AuditLog auditLog = new AuditLog();
auditLog.setId(1L);
assertEquals(auditLog, auditLog);
}
}
@@ -0,0 +1,146 @@
package cn.novalon.manage.sys.audit.dto;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.time.LocalDateTime;
import static org.junit.jupiter.api.Assertions.*;
/**
* AuditLogQueryRequest 单元测试
*
* @author 张翔
* @date 2026-04-14
*/
class AuditLogQueryRequestTest {
@Test
@DisplayName("创建默认查询请求")
void createDefaultRequest_shouldHaveNullFields() {
AuditLogQueryRequest request = new AuditLogQueryRequest();
assertNull(request.getEntityType());
assertNull(request.getEntityId());
assertNull(request.getOperator());
assertNull(request.getOperationType());
assertNull(request.getStartTime());
assertNull(request.getEndTime());
}
@Test
@DisplayName("设置和获取实体类型")
void setAndGetEntityType_shouldWorkCorrectly() {
AuditLogQueryRequest request = new AuditLogQueryRequest();
request.setEntityType("User");
assertEquals("User", request.getEntityType());
}
@Test
@DisplayName("设置和获取实体ID")
void setAndGetEntityId_shouldWorkCorrectly() {
AuditLogQueryRequest request = new AuditLogQueryRequest();
request.setEntityId(100L);
assertEquals(100L, request.getEntityId());
}
@Test
@DisplayName("设置和获取操作人")
void setAndGetOperator_shouldWorkCorrectly() {
AuditLogQueryRequest request = new AuditLogQueryRequest();
request.setOperator("admin");
assertEquals("admin", request.getOperator());
}
@Test
@DisplayName("设置和获取操作类型")
void setAndGetOperationType_shouldWorkCorrectly() {
AuditLogQueryRequest request = new AuditLogQueryRequest();
request.setOperationType("CREATE");
assertEquals("CREATE", request.getOperationType());
}
@Test
@DisplayName("设置和获取开始时间")
void setAndGetStartTime_shouldWorkCorrectly() {
LocalDateTime startTime = LocalDateTime.now().minusDays(1);
AuditLogQueryRequest request = new AuditLogQueryRequest();
request.setStartTime(startTime);
assertEquals(startTime, request.getStartTime());
}
@Test
@DisplayName("设置和获取结束时间")
void setAndGetEndTime_shouldWorkCorrectly() {
LocalDateTime endTime = LocalDateTime.now();
AuditLogQueryRequest request = new AuditLogQueryRequest();
request.setEndTime(endTime);
assertEquals(endTime, request.getEndTime());
}
@Test
@DisplayName("toString方法应包含所有字段")
void toString_shouldContainAllFields() {
LocalDateTime startTime = LocalDateTime.now().minusDays(1);
LocalDateTime endTime = LocalDateTime.now();
AuditLogQueryRequest request = new AuditLogQueryRequest();
request.setEntityType("User");
request.setEntityId(100L);
request.setOperator("admin");
request.setOperationType("CREATE");
request.setStartTime(startTime);
request.setEndTime(endTime);
String toString = request.toString();
assertTrue(toString.contains("User"));
assertTrue(toString.contains("100"));
assertTrue(toString.contains("admin"));
assertTrue(toString.contains("CREATE"));
}
@Test
@DisplayName("equals和hashCode方法应基于字段值")
void equalsAndHashCode_shouldBeBasedOnFieldValues() {
LocalDateTime startTime = LocalDateTime.now().minusDays(1);
LocalDateTime endTime = LocalDateTime.now();
AuditLogQueryRequest request1 = new AuditLogQueryRequest();
request1.setEntityType("User");
request1.setEntityId(100L);
request1.setOperator("admin");
request1.setOperationType("CREATE");
request1.setStartTime(startTime);
request1.setEndTime(endTime);
AuditLogQueryRequest request2 = new AuditLogQueryRequest();
request2.setEntityType("User");
request2.setEntityId(100L);
request2.setOperator("admin");
request2.setOperationType("CREATE");
request2.setStartTime(startTime);
request2.setEndTime(endTime);
assertEquals(request1, request2);
assertEquals(request1.hashCode(), request2.hashCode());
}
@Test
@DisplayName("不同字段值的对象应不相等")
void differentFieldValues_shouldNotBeEqual() {
AuditLogQueryRequest request1 = new AuditLogQueryRequest();
request1.setEntityType("User");
AuditLogQueryRequest request2 = new AuditLogQueryRequest();
request2.setEntityType("Role");
assertNotEquals(request1, request2);
}
}
@@ -0,0 +1,350 @@
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.common.dto.PageRequest;
import cn.novalon.manage.common.dto.PageResponse;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.Executor;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
/**
* AuditLogService 单元测试
*
* @author 张翔
* @date 2026-04-14
*/
@ExtendWith(MockitoExtension.class)
class AuditLogServiceTest {
@Mock
private IAuditLogRepository auditLogRepository;
@Mock
private Executor auditLogExecutor;
private AuditLogService auditLogService;
@BeforeEach
void setUp() {
auditLogService = new AuditLogService(auditLogRepository, auditLogExecutor);
}
@Test
@DisplayName("根据ID查询审计日志 - 成功")
void findById_whenExists_shouldReturnAuditLog() {
AuditLog auditLog = createTestAuditLog(1L);
when(auditLogRepository.findById(1L)).thenReturn(Mono.just(auditLog));
StepVerifier.create(auditLogService.findById(1L))
.expectNext(auditLog)
.verifyComplete();
}
@Test
@DisplayName("根据ID查询审计日志 - 不存在")
void findById_whenNotExists_shouldReturnEmpty() {
when(auditLogRepository.findById(999L)).thenReturn(Mono.empty());
StepVerifier.create(auditLogService.findById(999L))
.verifyComplete();
}
@Test
@DisplayName("查询所有审计日志")
void findAll_shouldReturnAllAuditLogs() {
AuditLog auditLog1 = createTestAuditLog(1L);
AuditLog auditLog2 = createTestAuditLog(2L);
when(auditLogRepository.findAll()).thenReturn(Flux.just(auditLog1, auditLog2));
StepVerifier.create(auditLogService.findAll())
.expectNext(auditLog1)
.expectNext(auditLog2)
.verifyComplete();
}
@Test
@DisplayName("分页查询审计日志")
void findAuditLogsByPage_shouldReturnPageResponse() {
AuditLog auditLog1 = createTestAuditLog(1L);
AuditLog auditLog2 = createTestAuditLog(2L);
AuditLog auditLog3 = createTestAuditLog(3L);
when(auditLogRepository.findAll()).thenReturn(Flux.just(auditLog1, auditLog2, auditLog3));
PageRequest pageRequest = new PageRequest();
pageRequest.setPage(0);
pageRequest.setSize(2);
StepVerifier.create(auditLogService.findAuditLogsByPage(pageRequest))
.expectNextMatches(pageResponse ->
pageResponse.getContent().size() == 2 &&
pageResponse.getTotalPages() == 2 &&
pageResponse.getTotalElements() == 3)
.verifyComplete();
}
@Test
@DisplayName("统计审计日志总数")
void count_shouldReturnTotalCount() {
when(auditLogRepository.findAll()).thenReturn(Flux.just(
createTestAuditLog(1L),
createTestAuditLog(2L),
createTestAuditLog(3L)
));
StepVerifier.create(auditLogService.count())
.expectNext(3L)
.verifyComplete();
}
@Test
@DisplayName("按实体类型查询审计日志")
void findByEntityType_shouldReturnAuditLogs() {
AuditLog auditLog = createTestAuditLog(1L);
when(auditLogRepository.findByEntityType("User")).thenReturn(Flux.just(auditLog));
StepVerifier.create(auditLogService.findByEntityType("User"))
.expectNext(auditLog)
.verifyComplete();
}
@Test
@DisplayName("按实体ID查询审计日志")
void findByEntityId_shouldReturnAuditLogs() {
AuditLog auditLog = createTestAuditLog(1L);
when(auditLogRepository.findByEntityId(100L)).thenReturn(Flux.just(auditLog));
StepVerifier.create(auditLogService.findByEntityId(100L))
.expectNext(auditLog)
.verifyComplete();
}
@Test
@DisplayName("按实体类型和实体ID查询审计日志")
void findByEntityTypeAndEntityId_shouldReturnAuditLogs() {
AuditLog auditLog = createTestAuditLog(1L);
when(auditLogRepository.findByEntityTypeAndEntityId("User", 100L))
.thenReturn(Flux.just(auditLog));
StepVerifier.create(auditLogService.findByEntityTypeAndEntityId("User", 100L))
.expectNext(auditLog)
.verifyComplete();
}
@Test
@DisplayName("按操作人查询审计日志")
void findByOperator_shouldReturnAuditLogs() {
AuditLog auditLog = createTestAuditLog(1L);
when(auditLogRepository.findByOperator("admin")).thenReturn(Flux.just(auditLog));
StepVerifier.create(auditLogService.findByOperator("admin"))
.expectNext(auditLog)
.verifyComplete();
}
@Test
@DisplayName("按操作类型查询审计日志")
void findByOperationType_shouldReturnAuditLogs() {
AuditLog auditLog = createTestAuditLog(1L);
when(auditLogRepository.findByOperationType("CREATE")).thenReturn(Flux.just(auditLog));
StepVerifier.create(auditLogService.findByOperationType("CREATE"))
.expectNext(auditLog)
.verifyComplete();
}
@Test
@DisplayName("按时间范围查询审计日志")
void findByOperationTimeBetween_shouldReturnAuditLogs() {
LocalDateTime startTime = LocalDateTime.now().minusDays(1);
LocalDateTime endTime = LocalDateTime.now();
AuditLog auditLog = createTestAuditLog(1L);
when(auditLogRepository.findByOperationTimeBetween(startTime, endTime))
.thenReturn(Flux.just(auditLog));
StepVerifier.create(auditLogService.findByOperationTimeBetween(startTime, endTime))
.expectNext(auditLog)
.verifyComplete();
}
@Test
@DisplayName("按实体类型和时间范围查询审计日志")
void findByEntityTypeAndOperationTimeBetween_shouldReturnAuditLogs() {
LocalDateTime startTime = LocalDateTime.now().minusDays(1);
LocalDateTime endTime = LocalDateTime.now();
AuditLog auditLog = createTestAuditLog(1L);
when(auditLogRepository.findByEntityTypeAndOperationTimeBetween("User", startTime, endTime))
.thenReturn(Flux.just(auditLog));
StepVerifier.create(auditLogService.findByEntityTypeAndOperationTimeBetween("User", startTime, endTime))
.expectNext(auditLog)
.verifyComplete();
}
@Test
@DisplayName("按操作人和时间范围查询审计日志")
void findByOperatorAndOperationTimeBetween_shouldReturnAuditLogs() {
LocalDateTime startTime = LocalDateTime.now().minusDays(1);
LocalDateTime endTime = LocalDateTime.now();
AuditLog auditLog = createTestAuditLog(1L);
when(auditLogRepository.findByOperatorAndOperationTimeBetween("admin", startTime, endTime))
.thenReturn(Flux.just(auditLog));
StepVerifier.create(auditLogService.findByOperatorAndOperationTimeBetween("admin", startTime, endTime))
.expectNext(auditLog)
.verifyComplete();
}
@Test
@DisplayName("按实体类型统计数量")
void countByEntityType_shouldReturnCount() {
when(auditLogRepository.countByEntityType("User")).thenReturn(Mono.just(5L));
StepVerifier.create(auditLogService.countByEntityType("User"))
.expectNext(5L)
.verifyComplete();
}
@Test
@DisplayName("按操作类型统计数量")
void countByOperationType_shouldReturnCount() {
when(auditLogRepository.countByOperationType("CREATE")).thenReturn(Mono.just(3L));
StepVerifier.create(auditLogService.countByOperationType("CREATE"))
.expectNext(3L)
.verifyComplete();
}
@Test
@DisplayName("按操作人统计数量")
void countByOperator_shouldReturnCount() {
when(auditLogRepository.countByOperator("admin")).thenReturn(Mono.just(2L));
StepVerifier.create(auditLogService.countByOperator("admin"))
.expectNext(2L)
.verifyComplete();
}
@Test
@DisplayName("按时间范围统计数量")
void countByOperationTimeBetween_shouldReturnCount() {
LocalDateTime startTime = LocalDateTime.now().minusDays(1);
LocalDateTime endTime = LocalDateTime.now();
when(auditLogRepository.countByOperationTimeBetween(startTime, endTime))
.thenReturn(Mono.just(10L));
StepVerifier.create(auditLogService.countByOperationTimeBetween(startTime, endTime))
.expectNext(10L)
.verifyComplete();
}
@Test
@DisplayName("保存审计日志")
void save_shouldReturnSavedAuditLog() {
AuditLog auditLog = createTestAuditLog(null);
AuditLog savedAuditLog = createTestAuditLog(1L);
when(auditLogRepository.save(auditLog)).thenReturn(Mono.just(savedAuditLog));
StepVerifier.create(auditLogService.save(auditLog))
.expectNext(savedAuditLog)
.verifyComplete();
}
@Test
@DisplayName("异步保存审计日志")
void saveAsync_shouldReturnSavedAuditLog() {
AuditLog auditLog = createTestAuditLog(null);
AuditLog savedAuditLog = createTestAuditLog(1L);
when(auditLogRepository.save(auditLog)).thenReturn(Mono.just(savedAuditLog));
StepVerifier.create(auditLogService.saveAsync(auditLog))
.expectNext(savedAuditLog)
.verifyComplete();
}
@Test
@DisplayName("根据ID删除审计日志")
void deleteById_shouldDeleteAuditLog() {
when(auditLogRepository.deleteById(1L)).thenReturn(Mono.empty());
StepVerifier.create(auditLogService.deleteById(1L))
.verifyComplete();
}
@Test
@DisplayName("逻辑删除审计日志")
void logicalDeleteById_shouldSetDeletedAt() {
AuditLog auditLog = createTestAuditLog(1L);
AuditLog deletedAuditLog = createTestAuditLog(1L);
deletedAuditLog.setDeletedAt(LocalDateTime.now());
when(auditLogRepository.findById(1L)).thenReturn(Mono.just(auditLog));
when(auditLogRepository.save(auditLog)).thenReturn(Mono.just(deletedAuditLog));
StepVerifier.create(auditLogService.logicalDeleteById(1L))
.verifyComplete();
}
@Test
@DisplayName("批量逻辑删除审计日志")
void logicalDeleteByIds_shouldDeleteMultipleAuditLogs() {
AuditLog auditLog1 = createTestAuditLog(1L);
AuditLog auditLog2 = createTestAuditLog(2L);
when(auditLogRepository.findById(1L)).thenReturn(Mono.just(auditLog1));
when(auditLogRepository.findById(2L)).thenReturn(Mono.just(auditLog2));
when(auditLogRepository.save(any(AuditLog.class))).thenReturn(Mono.just(auditLog1));
StepVerifier.create(auditLogService.logicalDeleteByIds(List.of(1L, 2L)))
.verifyComplete();
}
@Test
@DisplayName("恢复逻辑删除的审计日志")
void restoreById_shouldClearDeletedAt() {
AuditLog auditLog = createTestAuditLog(1L);
auditLog.setDeletedAt(LocalDateTime.now());
AuditLog restoredAuditLog = createTestAuditLog(1L);
restoredAuditLog.setDeletedAt(null);
when(auditLogRepository.findById(1L)).thenReturn(Mono.just(auditLog));
when(auditLogRepository.save(auditLog)).thenReturn(Mono.just(restoredAuditLog));
StepVerifier.create(auditLogService.restoreById(1L))
.verifyComplete();
}
private AuditLog createTestAuditLog(Long id) {
AuditLog auditLog = new AuditLog();
auditLog.setId(id);
auditLog.setEntityType("User");
auditLog.setEntityId(100L);
auditLog.setOperator("admin");
auditLog.setOperationType("CREATE");
auditLog.setOperationTime(LocalDateTime.now());
auditLog.setDescription("创建用户");
auditLog.setIpAddress("192.168.1.1");
auditLog.setUserAgent("Mozilla/5.0");
return auditLog;
}
}
@@ -1,44 +0,0 @@
package cn.novalon.manage.sys.config;
import cn.novalon.manage.common.handler.ExceptionLogService;
import cn.novalon.manage.sys.handler.ExceptionLogServiceImpl;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(MockitoExtension.class)
class ExceptionLogConfigTest {
@Mock
private ExceptionLogServiceImpl exceptionLogServiceImpl;
private ExceptionLogConfig exceptionLogConfig;
@BeforeEach
void setUp() {
exceptionLogConfig = new ExceptionLogConfig();
}
@Test
void testExceptionLogService() {
ExceptionLogService exceptionLogService = exceptionLogConfig.exceptionLogService(exceptionLogServiceImpl);
assertThat(exceptionLogService).isNotNull();
assertThat(exceptionLogService).isSameAs(exceptionLogServiceImpl);
}
@Test
void testExceptionLogService_DifferentInstance() {
ExceptionLogService exceptionLogService1 = exceptionLogConfig.exceptionLogService(exceptionLogServiceImpl);
ExceptionLogService exceptionLogService2 = exceptionLogConfig.exceptionLogService(exceptionLogServiceImpl);
assertThat(exceptionLogService1).isNotNull();
assertThat(exceptionLogService2).isNotNull();
assertThat(exceptionLogService1).isSameAs(exceptionLogServiceImpl);
assertThat(exceptionLogService2).isSameAs(exceptionLogServiceImpl);
}
}
@@ -1,120 +0,0 @@
package cn.novalon.manage.sys.handler;
import cn.novalon.manage.sys.core.domain.SysExceptionLog;
import cn.novalon.manage.sys.core.service.ISysExceptionLogService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.time.LocalDateTime;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class ExceptionLogServiceImplTest {
@Mock
private ISysExceptionLogService exceptionLogService;
private ExceptionLogServiceImpl exceptionLogServiceImpl;
@BeforeEach
void setUp() {
exceptionLogServiceImpl = new ExceptionLogServiceImpl(exceptionLogService);
}
@Test
void testLogException() {
SysExceptionLog savedLog = new SysExceptionLog();
savedLog.setId(1L);
savedLog.setTitle("测试异常");
savedLog.setExceptionName("TestException");
savedLog.setExceptionMsg("测试异常消息");
savedLog.setMethodName("testMethod");
savedLog.setIp("127.0.0.1");
savedLog.setExceptionStack("测试堆栈信息");
savedLog.setCreateTime(LocalDateTime.now());
when(exceptionLogService.save(any(SysExceptionLog.class))).thenReturn(Mono.just(savedLog));
StepVerifier.create(exceptionLogServiceImpl.logException(
"测试异常",
"TestException",
"测试异常消息",
"testMethod",
"127.0.0.1",
"测试堆栈信息"
))
.verifyComplete();
verify(exceptionLogService).save(any(SysExceptionLog.class));
}
@Test
void testLogException_WithEmptyFields() {
SysExceptionLog savedLog = new SysExceptionLog();
savedLog.setId(1L);
when(exceptionLogService.save(any(SysExceptionLog.class))).thenReturn(Mono.just(savedLog));
StepVerifier.create(exceptionLogServiceImpl.logException(
"",
"",
"",
"",
"",
""
))
.verifyComplete();
verify(exceptionLogService).save(any(SysExceptionLog.class));
}
@Test
void testLogException_WithNullFields() {
SysExceptionLog savedLog = new SysExceptionLog();
savedLog.setId(1L);
when(exceptionLogService.save(any(SysExceptionLog.class))).thenReturn(Mono.just(savedLog));
StepVerifier.create(exceptionLogServiceImpl.logException(
null,
null,
null,
null,
null,
null
))
.verifyComplete();
verify(exceptionLogService).save(any(SysExceptionLog.class));
}
@Test
void testLogException_WithLongStackTrace() {
String longStackTrace = "a".repeat(10000);
SysExceptionLog savedLog = new SysExceptionLog();
savedLog.setId(1L);
when(exceptionLogService.save(any(SysExceptionLog.class))).thenReturn(Mono.just(savedLog));
StepVerifier.create(exceptionLogServiceImpl.logException(
"测试异常",
"TestException",
"测试异常消息",
"testMethod",
"127.0.0.1",
longStackTrace
))
.verifyComplete();
verify(exceptionLogService).save(any(SysExceptionLog.class));
}
}