feat: 实现登录日志和操作日志的分页查询功能
refactor: 重构日志服务层代码,将分页逻辑移至Repository层 test: 添加日志分页查询的单元测试和组件测试 docs: 更新README文档,记录API响应格式修复过程 chore: 清理无用文件,更新.gitignore配置 build: 添加Jacoco代码覆盖率插件配置 ci: 添加测试环境配置文件application-h2-test.yml style: 统一日志服务代码格式,添加必要的日志输出
This commit is contained in:
+4
@@ -1,6 +1,8 @@
|
||||
package cn.novalon.manage.sys.core.repository;
|
||||
|
||||
import cn.novalon.manage.sys.core.domain.SysExceptionLog;
|
||||
import cn.novalon.manage.common.dto.PageRequest;
|
||||
import cn.novalon.manage.common.dto.PageResponse;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@@ -25,4 +27,6 @@ public interface ISysExceptionLogRepository {
|
||||
Mono<SysExceptionLog> findById(Long id);
|
||||
|
||||
Mono<Long> count();
|
||||
|
||||
Mono<PageResponse<SysExceptionLog>> findExceptionLogsByPage(PageRequest pageRequest);
|
||||
}
|
||||
+4
@@ -1,6 +1,8 @@
|
||||
package cn.novalon.manage.sys.core.repository;
|
||||
|
||||
import cn.novalon.manage.sys.core.domain.SysLoginLog;
|
||||
import cn.novalon.manage.common.dto.PageRequest;
|
||||
import cn.novalon.manage.common.dto.PageResponse;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@@ -27,4 +29,6 @@ public interface ISysLoginLogRepository {
|
||||
Mono<Long> count();
|
||||
|
||||
Mono<Long> countToday();
|
||||
|
||||
Mono<PageResponse<SysLoginLog>> findLoginLogsByPage(PageRequest pageRequest);
|
||||
}
|
||||
+1
@@ -23,4 +23,5 @@ public interface ISysLoginLogService {
|
||||
Mono<PageResponse<SysLoginLog>> findLoginLogsByPage(PageRequest pageRequest);
|
||||
Mono<Long> count();
|
||||
Mono<Long> countToday();
|
||||
Flux<SysLoginLog> findRecent(int limit);
|
||||
}
|
||||
|
||||
+5
-69
@@ -5,12 +5,13 @@ import cn.novalon.manage.sys.core.repository.ISysExceptionLogRepository;
|
||||
import cn.novalon.manage.sys.core.service.ISysExceptionLogService;
|
||||
import cn.novalon.manage.common.dto.PageRequest;
|
||||
import cn.novalon.manage.common.dto.PageResponse;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 异常日志服务实现类
|
||||
@@ -21,6 +22,7 @@ import java.util.List;
|
||||
@Service
|
||||
public class SysExceptionLogService implements ISysExceptionLogService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(SysExceptionLogService.class);
|
||||
private final ISysExceptionLogRepository repository;
|
||||
|
||||
public SysExceptionLogService(ISysExceptionLogRepository repository) {
|
||||
@@ -54,74 +56,8 @@ public class SysExceptionLogService implements ISysExceptionLogService {
|
||||
|
||||
@Override
|
||||
public Mono<PageResponse<SysExceptionLog>> findExceptionLogsByPage(PageRequest pageRequest) {
|
||||
Flux<SysExceptionLog> allLogs = repository.findAllByOrderByCreateTimeDesc();
|
||||
|
||||
if (pageRequest.getKeyword() != null && !pageRequest.getKeyword().isEmpty()) {
|
||||
String keyword = pageRequest.getKeyword().toLowerCase();
|
||||
allLogs = allLogs
|
||||
.filter(log -> (log.getUsername() != null && log.getUsername().toLowerCase().contains(keyword)) ||
|
||||
(log.getTitle() != null && log.getTitle().toLowerCase().contains(keyword)) ||
|
||||
(log.getExceptionName() != null && log.getExceptionName().toLowerCase().contains(keyword)));
|
||||
}
|
||||
|
||||
return allLogs
|
||||
.collectList()
|
||||
.map(list -> {
|
||||
if (pageRequest.getSort() != null && !pageRequest.getSort().isEmpty()) {
|
||||
list.sort((a, b) -> {
|
||||
int comparison = 0;
|
||||
if ("username".equals(pageRequest.getSort())) {
|
||||
comparison = compareStrings(a.getUsername(), b.getUsername());
|
||||
} else if ("title".equals(pageRequest.getSort())) {
|
||||
comparison = compareStrings(a.getTitle(), b.getTitle());
|
||||
} else if ("createTime".equals(pageRequest.getSort())) {
|
||||
comparison = compareLocalDateTimes(a.getCreateTime(), b.getCreateTime());
|
||||
}
|
||||
return "desc".equalsIgnoreCase(pageRequest.getOrder()) ? -comparison : comparison;
|
||||
});
|
||||
}
|
||||
return list;
|
||||
})
|
||||
.zipWith(repository.count())
|
||||
.map(tuple -> {
|
||||
List<SysExceptionLog> all = tuple.getT1();
|
||||
long totalCount = tuple.getT2();
|
||||
int totalPages = (int) Math.ceil((double) totalCount / pageRequest.getSize());
|
||||
|
||||
int fromIndex = pageRequest.getPage() * pageRequest.getSize();
|
||||
int toIndex = Math.min(fromIndex + pageRequest.getSize(), all.size());
|
||||
|
||||
List<SysExceptionLog> pageData = fromIndex < all.size()
|
||||
? all.subList(fromIndex, toIndex)
|
||||
: List.of();
|
||||
|
||||
return new PageResponse<SysExceptionLog>(
|
||||
pageData,
|
||||
totalPages,
|
||||
totalCount,
|
||||
pageRequest.getPage(),
|
||||
pageRequest.getSize());
|
||||
});
|
||||
}
|
||||
|
||||
private int compareStrings(String a, String b) {
|
||||
if (a == null && b == null)
|
||||
return 0;
|
||||
if (a == null)
|
||||
return -1;
|
||||
if (b == null)
|
||||
return 1;
|
||||
return a.compareTo(b);
|
||||
}
|
||||
|
||||
private int compareLocalDateTimes(LocalDateTime a, LocalDateTime b) {
|
||||
if (a == null && b == null)
|
||||
return 0;
|
||||
if (a == null)
|
||||
return -1;
|
||||
if (b == null)
|
||||
return 1;
|
||||
return a.compareTo(b);
|
||||
logger.info("分页查询异常日志: page={}, size={}", pageRequest.getPage(), pageRequest.getSize());
|
||||
return repository.findExceptionLogsByPage(pageRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+13
-72
@@ -5,13 +5,13 @@ import cn.novalon.manage.sys.core.repository.ISysLoginLogRepository;
|
||||
import cn.novalon.manage.sys.core.service.ISysLoginLogService;
|
||||
import cn.novalon.manage.common.dto.PageRequest;
|
||||
import cn.novalon.manage.common.dto.PageResponse;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 登录日志服务实现类
|
||||
@@ -22,6 +22,7 @@ import java.util.List;
|
||||
@Service
|
||||
public class SysLoginLogService implements ISysLoginLogService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(SysLoginLogService.class);
|
||||
private final ISysLoginLogRepository repository;
|
||||
|
||||
public SysLoginLogService(ISysLoginLogRepository repository) {
|
||||
@@ -55,72 +56,8 @@ public class SysLoginLogService implements ISysLoginLogService {
|
||||
|
||||
@Override
|
||||
public Mono<PageResponse<SysLoginLog>> findLoginLogsByPage(PageRequest pageRequest) {
|
||||
Flux<SysLoginLog> allLogs = repository.findAllByOrderByLoginTimeDesc();
|
||||
|
||||
if (pageRequest.getKeyword() != null && !pageRequest.getKeyword().isEmpty()) {
|
||||
String keyword = pageRequest.getKeyword().toLowerCase();
|
||||
allLogs = allLogs.filter(log ->
|
||||
(log.getUsername() != null && log.getUsername().toLowerCase().contains(keyword)) ||
|
||||
(log.getIp() != null && log.getIp().toLowerCase().contains(keyword)) ||
|
||||
(log.getMessage() != null && log.getMessage().toLowerCase().contains(keyword))
|
||||
);
|
||||
}
|
||||
|
||||
return allLogs
|
||||
.collectList()
|
||||
.flatMap(list -> {
|
||||
List<SysLoginLog> sortedList = new ArrayList<>(list);
|
||||
|
||||
if (pageRequest.getSort() != null && !pageRequest.getSort().isEmpty()) {
|
||||
sortedList.sort((a, b) -> {
|
||||
int comparison = 0;
|
||||
if ("username".equals(pageRequest.getSort())) {
|
||||
comparison = compareStrings(a.getUsername(), b.getUsername());
|
||||
} else if ("ip".equals(pageRequest.getSort())) {
|
||||
comparison = compareStrings(a.getIp(), b.getIp());
|
||||
} else if ("loginTime".equals(pageRequest.getSort())) {
|
||||
comparison = compareLocalDateTimes(a.getLoginTime(), b.getLoginTime());
|
||||
}
|
||||
return "desc".equalsIgnoreCase(pageRequest.getOrder()) ? -comparison : comparison;
|
||||
});
|
||||
}
|
||||
|
||||
return Mono.just(sortedList);
|
||||
})
|
||||
.zipWith(repository.count())
|
||||
.map(tuple -> {
|
||||
List<SysLoginLog> all = tuple.getT1();
|
||||
long totalCount = tuple.getT2();
|
||||
int totalPages = (int) Math.ceil((double) totalCount / pageRequest.getSize());
|
||||
|
||||
int fromIndex = pageRequest.getPage() * pageRequest.getSize();
|
||||
int toIndex = Math.min(fromIndex + pageRequest.getSize(), all.size());
|
||||
|
||||
List<SysLoginLog> pageData = fromIndex < all.size()
|
||||
? all.subList(fromIndex, toIndex)
|
||||
: List.of();
|
||||
|
||||
return new PageResponse<SysLoginLog>(
|
||||
pageData,
|
||||
totalPages,
|
||||
totalCount,
|
||||
pageRequest.getPage(),
|
||||
pageRequest.getSize());
|
||||
});
|
||||
}
|
||||
|
||||
private int compareStrings(String a, String b) {
|
||||
if (a == null && b == null) return 0;
|
||||
if (a == null) return -1;
|
||||
if (b == null) return 1;
|
||||
return a.compareTo(b);
|
||||
}
|
||||
|
||||
private int compareLocalDateTimes(LocalDateTime a, LocalDateTime b) {
|
||||
if (a == null && b == null) return 0;
|
||||
if (a == null) return -1;
|
||||
if (b == null) return 1;
|
||||
return a.compareTo(b);
|
||||
logger.info("分页查询登录日志: page={}, size={}", pageRequest.getPage(), pageRequest.getSize());
|
||||
return repository.findLoginLogsByPage(pageRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -130,9 +67,13 @@ public class SysLoginLogService implements ISysLoginLogService {
|
||||
|
||||
@Override
|
||||
public Mono<Long> countToday() {
|
||||
LocalDateTime todayStart = LocalDateTime.now().withHour(0).withMinute(0).withSecond(0).withNano(0);
|
||||
LocalDateTime todayEnd = todayStart.plusDays(1);
|
||||
return repository.findByLoginTimeBetweenOrderByLoginTimeDesc(todayStart, todayEnd)
|
||||
.count();
|
||||
return repository.countToday();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<SysLoginLog> findRecent(int limit) {
|
||||
logger.info("获取最近{}条登录日志", limit);
|
||||
return repository.findAllByOrderByLoginTimeDesc()
|
||||
.take(limit);
|
||||
}
|
||||
}
|
||||
+8
@@ -6,6 +6,7 @@ import cn.novalon.manage.sys.core.service.ISysLoginLogService;
|
||||
import cn.novalon.manage.sys.core.service.ISysExceptionLogService;
|
||||
import cn.novalon.manage.common.dto.PageRequest;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -83,6 +84,13 @@ public class SysLogHandler {
|
||||
.flatMap(count -> ServerResponse.ok().bodyValue(count));
|
||||
}
|
||||
|
||||
@Operation(summary = "获取最近登录日志", description = "获取最近N条登录日志记录")
|
||||
public Mono<ServerResponse> getRecentLoginLogs(ServerRequest request) {
|
||||
int limit = Integer.parseInt(request.queryParam("limit").orElse("10"));
|
||||
return ServerResponse.ok()
|
||||
.body(loginLogService.findRecent(limit), SysLoginLog.class);
|
||||
}
|
||||
|
||||
@Operation(summary = "获取所有异常日志", description = "获取系统中所有异常日志列表")
|
||||
public Mono<ServerResponse> getAllExceptionLogs(ServerRequest request) {
|
||||
return ServerResponse.ok()
|
||||
|
||||
+88
@@ -72,6 +72,50 @@ class SysLogHandlerTest {
|
||||
verify(loginLogService).findAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetAllLoginLogs_WithPagination() {
|
||||
PageResponse<SysLoginLog> pageResponse = new PageResponse<>();
|
||||
pageResponse.setContent(java.util.Collections.singletonList(testLoginLog));
|
||||
pageResponse.setTotalElements(1L);
|
||||
pageResponse.setTotalPages(1);
|
||||
|
||||
when(loginLogService.findLoginLogsByPage(any())).thenReturn(Mono.just(pageResponse));
|
||||
|
||||
ServerRequest request = MockServerRequest.builder()
|
||||
.queryParam("page", "0")
|
||||
.queryParam("size", "10")
|
||||
.build();
|
||||
Mono<ServerResponse> response = logHandler.getAllLoginLogs(request);
|
||||
|
||||
StepVerifier.create(response)
|
||||
.expectNextMatches(serverResponse ->
|
||||
serverResponse.statusCode() == HttpStatus.OK)
|
||||
.verifyComplete();
|
||||
|
||||
verify(loginLogService).findLoginLogsByPage(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetAllLoginLogs_WithOnlyPageParam() {
|
||||
PageResponse<SysLoginLog> pageResponse = new PageResponse<>();
|
||||
pageResponse.setContent(java.util.Collections.singletonList(testLoginLog));
|
||||
pageResponse.setTotalElements(1L);
|
||||
|
||||
when(loginLogService.findLoginLogsByPage(any())).thenReturn(Mono.just(pageResponse));
|
||||
|
||||
ServerRequest request = MockServerRequest.builder()
|
||||
.queryParam("page", "0")
|
||||
.build();
|
||||
Mono<ServerResponse> response = logHandler.getAllLoginLogs(request);
|
||||
|
||||
StepVerifier.create(response)
|
||||
.expectNextMatches(serverResponse ->
|
||||
serverResponse.statusCode() == HttpStatus.OK)
|
||||
.verifyComplete();
|
||||
|
||||
verify(loginLogService).findLoginLogsByPage(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetLoginLogById() {
|
||||
when(loginLogService.findById(1L)).thenReturn(Mono.just(testLoginLog));
|
||||
@@ -203,6 +247,50 @@ class SysLogHandlerTest {
|
||||
verify(exceptionLogService).findAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetAllExceptionLogs_WithPagination() {
|
||||
PageResponse<SysExceptionLog> pageResponse = new PageResponse<>();
|
||||
pageResponse.setContent(java.util.Collections.singletonList(testExceptionLog));
|
||||
pageResponse.setTotalElements(1L);
|
||||
pageResponse.setTotalPages(1);
|
||||
|
||||
when(exceptionLogService.findExceptionLogsByPage(any())).thenReturn(Mono.just(pageResponse));
|
||||
|
||||
ServerRequest request = MockServerRequest.builder()
|
||||
.queryParam("page", "0")
|
||||
.queryParam("size", "10")
|
||||
.build();
|
||||
Mono<ServerResponse> response = logHandler.getAllExceptionLogs(request);
|
||||
|
||||
StepVerifier.create(response)
|
||||
.expectNextMatches(serverResponse ->
|
||||
serverResponse.statusCode() == HttpStatus.OK)
|
||||
.verifyComplete();
|
||||
|
||||
verify(exceptionLogService).findExceptionLogsByPage(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetAllExceptionLogs_WithOnlySizeParam() {
|
||||
PageResponse<SysExceptionLog> pageResponse = new PageResponse<>();
|
||||
pageResponse.setContent(java.util.Collections.singletonList(testExceptionLog));
|
||||
pageResponse.setTotalElements(1L);
|
||||
|
||||
when(exceptionLogService.findExceptionLogsByPage(any())).thenReturn(Mono.just(pageResponse));
|
||||
|
||||
ServerRequest request = MockServerRequest.builder()
|
||||
.queryParam("size", "10")
|
||||
.build();
|
||||
Mono<ServerResponse> response = logHandler.getAllExceptionLogs(request);
|
||||
|
||||
StepVerifier.create(response)
|
||||
.expectNextMatches(serverResponse ->
|
||||
serverResponse.statusCode() == HttpStatus.OK)
|
||||
.verifyComplete();
|
||||
|
||||
verify(exceptionLogService).findExceptionLogsByPage(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetExceptionLogById() {
|
||||
when(exceptionLogService.findById(1L)).thenReturn(Mono.just(testExceptionLog));
|
||||
|
||||
+45
@@ -7,6 +7,7 @@ import cn.novalon.manage.sys.dto.request.UserRegisterRequest;
|
||||
import cn.novalon.manage.sys.dto.request.UserUpdateRequest;
|
||||
import cn.novalon.manage.sys.core.command.CreateUserCommand;
|
||||
import cn.novalon.manage.sys.core.command.UpdateUserCommand;
|
||||
import cn.novalon.manage.common.dto.PageResponse;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
@@ -74,6 +75,50 @@ class SysUserHandlerTest {
|
||||
verify(userService).findAll(anyBoolean());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetAllUsers_WithPagination() {
|
||||
PageResponse<SysUser> pageResponse = new PageResponse<>();
|
||||
pageResponse.setContent(java.util.Collections.singletonList(testUser));
|
||||
pageResponse.setTotalElements(1L);
|
||||
pageResponse.setTotalPages(1);
|
||||
|
||||
when(userService.findUsersByPage(any())).thenReturn(Mono.just(pageResponse));
|
||||
|
||||
ServerRequest request = MockServerRequest.builder()
|
||||
.queryParam("page", "0")
|
||||
.queryParam("size", "10")
|
||||
.build();
|
||||
Mono<ServerResponse> response = userHandler.getAllUsers(request);
|
||||
|
||||
StepVerifier.create(response)
|
||||
.expectNextMatches(serverResponse ->
|
||||
serverResponse.statusCode() == HttpStatus.OK)
|
||||
.verifyComplete();
|
||||
|
||||
verify(userService).findUsersByPage(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetAllUsers_WithOnlyPageParam() {
|
||||
PageResponse<SysUser> pageResponse = new PageResponse<>();
|
||||
pageResponse.setContent(java.util.Collections.singletonList(testUser));
|
||||
pageResponse.setTotalElements(1L);
|
||||
|
||||
when(userService.findUsersByPage(any())).thenReturn(Mono.just(pageResponse));
|
||||
|
||||
ServerRequest request = MockServerRequest.builder()
|
||||
.queryParam("page", "0")
|
||||
.build();
|
||||
Mono<ServerResponse> response = userHandler.getAllUsers(request);
|
||||
|
||||
StepVerifier.create(response)
|
||||
.expectNextMatches(serverResponse ->
|
||||
serverResponse.statusCode() == HttpStatus.OK)
|
||||
.verifyComplete();
|
||||
|
||||
verify(userService).findUsersByPage(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetUserCount() {
|
||||
when(userService.count()).thenReturn(Mono.just(10L));
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package cn.novalon.manage.sys.util;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
|
||||
public class PasswordHashGenerator {
|
||||
|
||||
@Test
|
||||
public void generatePasswordHash() {
|
||||
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(12);
|
||||
|
||||
String password = "Test@123";
|
||||
String hash = passwordEncoder.encode(password);
|
||||
|
||||
System.out.println("========================================");
|
||||
System.out.println("密码: " + password);
|
||||
System.out.println("哈希: " + hash);
|
||||
System.out.println("========================================");
|
||||
|
||||
boolean matches = passwordEncoder.matches(password, hash);
|
||||
System.out.println("验证结果: " + matches);
|
||||
|
||||
String hash2b = "$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/X4.VTtYA/7.J6LlZy";
|
||||
boolean matches2b = passwordEncoder.matches(password, hash2b);
|
||||
System.out.println("验证$2b$哈希结果: " + matches2b);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user