feat: extend operation log service and repository with pagination support
This commit is contained in:
+87
-1
@@ -1,5 +1,7 @@
|
||||
package cn.novalon.manage.db.repository;
|
||||
|
||||
import cn.novalon.manage.common.dto.PageRequest;
|
||||
import cn.novalon.manage.common.dto.PageResponse;
|
||||
import cn.novalon.manage.sys.core.domain.OperationLog;
|
||||
import cn.novalon.manage.sys.core.repository.IOperationLogRepository;
|
||||
import cn.novalon.manage.db.converter.OperationLogConverter;
|
||||
@@ -10,6 +12,8 @@ import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 操作日志仓储实现类
|
||||
@@ -48,7 +52,7 @@ public class OperationLogRepository implements IOperationLogRepository {
|
||||
|
||||
@Override
|
||||
public Flux<OperationLog> findAll() {
|
||||
return operationLogDao.findAll()
|
||||
return operationLogDao.findByDeletedAtIsNull()
|
||||
.map(operationLogConverter::toDomain);
|
||||
}
|
||||
|
||||
@@ -58,6 +62,88 @@ public class OperationLogRepository implements IOperationLogRepository {
|
||||
.map(operationLogConverter::toDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<PageResponse<OperationLog>> findOperationLogsByPage(PageRequest pageRequest) {
|
||||
Flux<OperationLog> allLogs = operationLogDao.findByDeletedAtIsNull()
|
||||
.map(operationLogConverter::toDomain);
|
||||
|
||||
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.getOperation() != null && log.getOperation().toLowerCase().contains(keyword)) ||
|
||||
(log.getIp() != null && log.getIp().toLowerCase().contains(keyword))
|
||||
);
|
||||
}
|
||||
|
||||
return allLogs
|
||||
.collectList()
|
||||
.flatMap(list -> {
|
||||
List<OperationLog> 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 ("operation".equals(pageRequest.getSort())) {
|
||||
comparison = compareStrings(a.getOperation(), b.getOperation());
|
||||
} else if ("duration".equals(pageRequest.getSort())) {
|
||||
comparison = compareLongs(a.getDuration(), b.getDuration());
|
||||
} else if ("status".equals(pageRequest.getSort())) {
|
||||
comparison = compareStrings(a.getStatus(), b.getStatus());
|
||||
} else {
|
||||
comparison = compareLocalDateTimes(a.getCreatedAt(), b.getCreatedAt());
|
||||
}
|
||||
return "desc".equalsIgnoreCase(pageRequest.getOrder()) ? -comparison : comparison;
|
||||
});
|
||||
}
|
||||
|
||||
return Mono.just(sortedList);
|
||||
})
|
||||
.zipWith(operationLogDao.countByDeletedAtIsNull())
|
||||
.map(tuple -> {
|
||||
List<OperationLog> 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<OperationLog> pageData = fromIndex < all.size()
|
||||
? all.subList(fromIndex, toIndex)
|
||||
: List.of();
|
||||
|
||||
return new PageResponse<OperationLog>(
|
||||
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 compareLongs(Long a, Long 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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Long> count() {
|
||||
return operationLogDao.countByDeletedAtIsNull();
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
cn.novalon.manage.db.config.RepositoryScanConfig
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
-- Novalon管理系统E2E测试数据初始化脚本
|
||||
-- 版本: V3
|
||||
-- 描述: 为E2E测试准备测试数据
|
||||
|
||||
-- 清理测试数据(保留管理员)
|
||||
DELETE FROM sys_user_message WHERE user_id > 1;
|
||||
DELETE FROM users WHERE id > 1;
|
||||
DELETE FROM sys_notice WHERE id > 0;
|
||||
DELETE FROM sys_file WHERE id > 0;
|
||||
DELETE FROM sys_exception_log WHERE id > 0;
|
||||
DELETE FROM sys_login_log WHERE id > 0;
|
||||
DELETE FROM sys_dict_data WHERE dict_type NOT IN ('user_status');
|
||||
DELETE FROM sys_dict_type WHERE dict_type NOT IN ('user_status');
|
||||
DELETE FROM sys_config WHERE id > 0;
|
||||
DELETE FROM menus WHERE id > 0;
|
||||
DELETE FROM roles WHERE id > 1;
|
||||
|
||||
-- 插入测试角色
|
||||
INSERT INTO roles (role_name, role_key, role_sort, status, create_by, update_by)
|
||||
VALUES
|
||||
('普通用户', 'user', 2, 1, 'system', 'system'),
|
||||
('测试角色', 'test_role', 3, 1, 'system', 'system'),
|
||||
('受限角色', 'limited_role', 4, 1, 'system', 'system');
|
||||
|
||||
-- 插入测试用户
|
||||
INSERT INTO users (username, password, email, phone, role_id, status, create_by, update_by)
|
||||
VALUES
|
||||
('testuser', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iKTVKIUi', 'test@example.com', '13800138001', 2, 1, 'system', 'system'),
|
||||
('limiteduser', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iKTVKIUi', 'limited@example.com', '13800138002', 4, 1, 'system', 'system'),
|
||||
('normaluser', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iKTVKIUi', 'normal@example.com', '13800138003', 2, 1, 'system', 'system');
|
||||
|
||||
-- 插入测试菜单
|
||||
INSERT INTO menus (menu_name, parent_id, order_num, menu_type, perms, component, status, create_by, update_by)
|
||||
VALUES
|
||||
('系统管理', 0, 1, 'M', '', '', 1, 'system', 'system'),
|
||||
('用户管理', 1, 1, 'C', 'system:user:list', 'system/user/index', 1, 'system', 'system'),
|
||||
('角色管理', 1, 2, 'C', 'system:role:list', 'system/role/index', 1, 'system', 'system'),
|
||||
('菜单管理', 1, 3, 'C', 'system:menu:list', 'system/menu/index', 1, 'system', 'system'),
|
||||
('系统配置', 1, 4, 'C', 'system:config:list', 'system/config/index', 1, 'system', 'system'),
|
||||
('监控中心', 0, 2, 'M', '', '', 1, 'system', 'system'),
|
||||
('在线用户', 6, 1, 'C', 'monitor:online:list', 'monitor/online/index', 1, 'system', 'system'),
|
||||
('登录日志', 6, 2, 'C', 'monitor:loginlog:list', 'monitor/loginlog/index', 1, 'system', 'system');
|
||||
|
||||
-- 插入测试字典类型
|
||||
INSERT INTO sys_dict_type (dict_name, dict_type, status, remark, create_by, update_by)
|
||||
VALUES
|
||||
('菜单状态', 'menu_status', '0', '菜单状态列表', 'system', 'system'),
|
||||
('角色状态', 'role_status', '0', '角色状态列表', 'system', 'system'),
|
||||
('系统开关', 'sys_normal_disable', '0', '系统开关列表', 'system', 'system'),
|
||||
('任务状态', 'job_status', '0', '任务状态列表', 'system', 'system'),
|
||||
('任务分组', 'job_group', '0', '任务分组列表', 'system', 'system');
|
||||
|
||||
-- 插入测试字典数据
|
||||
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, update_by)
|
||||
VALUES
|
||||
-- 菜单状态
|
||||
(1, '正常', '0', 'menu_status', '', 'primary', 'N', '0', 'system', 'system'),
|
||||
(2, '停用', '1', 'menu_status', '', 'danger', 'N', '0', 'system', 'system'),
|
||||
-- 角色状态
|
||||
(1, '正常', '0', 'role_status', '', 'primary', 'N', '0', 'system', 'system'),
|
||||
(2, '停用', '1', 'role_status', '', 'danger', 'N', '0', 'system', 'system'),
|
||||
-- 系统开关
|
||||
(1, '正常', '0', 'sys_normal_disable', '', 'primary', 'Y', '0', 'system', 'system'),
|
||||
(2, '停用', '1', 'sys_normal_disable', '', 'danger', 'N', '0', 'system', 'system'),
|
||||
-- 任务状态
|
||||
(1, '正常', '0', 'job_status', '', 'primary', 'Y', '0', 'system', 'system'),
|
||||
(2, '暂停', '1', 'job_status', '', 'danger', 'N', '0', 'system', 'system'),
|
||||
-- 任务分组
|
||||
(1, '默认', 'DEFAULT', 'job_group', '', '', 'Y', '0', 'system', 'system'),
|
||||
(2, '系统', 'SYSTEM', 'job_group', '', '', 'N', '0', 'system', 'system');
|
||||
|
||||
-- 插入测试系统配置
|
||||
INSERT INTO sys_config (config_name, config_key, config_value, config_type, create_by, update_by)
|
||||
VALUES
|
||||
('用户管理-用户初始密码', 'sys.user.initPassword', '123456', 'Y', 'system', 'system'),
|
||||
('主框架页-默认皮肤样式名称', 'sys.index.skinName', 'skin-blue', 'Y', 'system', 'system'),
|
||||
('用户自助-验证码开关', 'sys.account.captchaEnabled', 'true', 'Y', 'system', 'system'),
|
||||
('用户自助-是否开启用户注册功能', 'sys.account.registerUser', 'false', 'Y', 'system', 'system'),
|
||||
('账号自助-密码验证码', 'sys.account.pwdCaptchaEnabled', 'true', 'Y', 'system', 'system');
|
||||
|
||||
-- 插入测试系统公告
|
||||
INSERT INTO sys_notice (notice_title, notice_type, notice_content, status, create_by, update_by)
|
||||
VALUES
|
||||
('系统维护通知', '1', '系统将于今晚22:00-23:00进行维护,请提前做好准备。', '0', 'admin', 'admin'),
|
||||
('新功能上线通知', '2', '系统新增了用户管理功能,欢迎大家使用!', '0', 'admin', 'admin'),
|
||||
('安全提醒', '1', '请定期修改密码,确保账户安全。', '0', 'admin', 'admin');
|
||||
|
||||
-- 插入测试文件
|
||||
INSERT INTO sys_file (file_name, file_path, file_size, file_type, file_extension, create_by, update_by)
|
||||
VALUES
|
||||
('test-image.jpg', '/uploads/images/test-image.jpg', 102400, 'image/jpeg', 'jpg', 'system', 'system'),
|
||||
('test-document.pdf', '/uploads/documents/test-document.pdf', 204800, 'application/pdf', 'pdf', 'system', 'system'),
|
||||
('test-data.xlsx', '/uploads/data/test-data.xlsx', 51200, 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'xlsx', 'system', 'system');
|
||||
|
||||
-- 插入测试登录日志
|
||||
INSERT INTO sys_login_log (username, ip, location, browser, os, status, message, login_time)
|
||||
VALUES
|
||||
('admin', '127.0.0.1', '内网IP', 'Chrome', 'Windows 10', '0', '登录成功', NOW() - INTERVAL '1 day'),
|
||||
('admin', '127.0.0.1', '内网IP', 'Chrome', 'Windows 10', '0', '登录成功', NOW() - INTERVAL '2 hours'),
|
||||
('testuser', '127.0.0.1', '内网IP', 'Firefox', 'Mac OS', '0', '登录成功', NOW() - INTERVAL '3 hours'),
|
||||
('testuser', '127.0.0.1', '内网IP', 'Firefox', 'Mac OS', '1', '密码错误', NOW() - INTERVAL '4 hours');
|
||||
|
||||
-- 插入测试用户消息
|
||||
INSERT INTO sys_user_message (user_id, notice_id, message_title, message_content, is_read, create_by, update_by)
|
||||
VALUES
|
||||
(2, 1, '系统维护通知', '系统将于今晚22:00-23:00进行维护,请提前做好准备。', '0', 'admin', 'admin'),
|
||||
(2, 2, '新功能上线通知', '系统新增了用户管理功能,欢迎大家使用!', '0', 'admin', 'admin'),
|
||||
(3, 3, '安全提醒', '请定期修改密码,确保账户安全。', '0', 'admin', 'admin');
|
||||
|
||||
-- 插入测试OAuth2客户端
|
||||
INSERT INTO oauth2_client (client_id, client_secret, client_name, web_server_redirect_uri, scope, authorized_grant_types, access_token_validity_seconds, refresh_token_validity_seconds, auto_approve, enabled, create_by, update_by)
|
||||
VALUES
|
||||
('test_client', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iKTVKIUi', '测试客户端', 'http://localhost:3001/callback', 'read,write', 'password,refresh_token', 3600, 7200, 'true', 'true', 'system', 'system');
|
||||
|
||||
-- 更新序列值
|
||||
SELECT setval('users_id_seq', (SELECT MAX(id) FROM users));
|
||||
SELECT setval('roles_id_seq', (SELECT MAX(id) FROM roles));
|
||||
SELECT setval('menus_id_seq', (SELECT MAX(id) FROM menus));
|
||||
SELECT setval('sys_dict_type_id_seq', (SELECT MAX(id) FROM sys_dict_type));
|
||||
SELECT setval('sys_dict_data_id_seq', (SELECT MAX(id) FROM sys_dict_data));
|
||||
SELECT setval('sys_config_id_seq', (SELECT MAX(id) FROM sys_config));
|
||||
SELECT setval('sys_notice_id_seq', (SELECT MAX(id) FROM sys_notice));
|
||||
SELECT setval('sys_file_id_seq', (SELECT MAX(id) FROM sys_file));
|
||||
SELECT setval('sys_login_log_id_seq', (SELECT MAX(id) FROM sys_login_log));
|
||||
SELECT setval('sys_user_message_id_seq', (SELECT MAX(id) FROM sys_user_message));
|
||||
SELECT setval('oauth2_client_id_seq', (SELECT MAX(id) FROM oauth2_client));
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
-- 更新管理员密码为已知密码
|
||||
-- BCrypt哈希值对应明文密码: admin123
|
||||
UPDATE users
|
||||
SET password = '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iKTVKIUi'
|
||||
WHERE username = 'admin';
|
||||
|
||||
-- 确保管理员用户状态为启用
|
||||
UPDATE users
|
||||
SET status = 1
|
||||
WHERE username = 'admin';
|
||||
Reference in New Issue
Block a user