feat: extend operation log service and repository with pagination support

This commit is contained in:
张翔
2026-03-18 22:34:43 +08:00
parent 157aee2ffc
commit 8a0cd64829
81 changed files with 8842 additions and 509 deletions
@@ -1,5 +1,7 @@
package cn.novalon.manage.sys.core.repository;
import cn.novalon.manage.common.dto.PageRequest;
import cn.novalon.manage.common.dto.PageResponse;
import cn.novalon.manage.sys.core.domain.OperationLog;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -24,6 +26,8 @@ public interface IOperationLogRepository {
Flux<OperationLog> findByUsername(String username);
Mono<PageResponse<OperationLog>> findOperationLogsByPage(PageRequest pageRequest);
Mono<Long> count();
Mono<Long> countByCreatedAtAfter(LocalDateTime dateTime);
@@ -1,5 +1,7 @@
package cn.novalon.manage.sys.core.service;
import cn.novalon.manage.common.dto.PageRequest;
import cn.novalon.manage.common.dto.PageResponse;
import cn.novalon.manage.sys.core.domain.OperationLog;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -13,7 +15,9 @@ import reactor.core.publisher.Mono;
public interface IOperationLogService {
Mono<OperationLog> save(OperationLog log);
Flux<OperationLog> findAll();
Mono<OperationLog> findById(Long id);
Flux<OperationLog> findByUsername(String username);
Mono<PageResponse<OperationLog>> findOperationLogsByPage(PageRequest pageRequest);
Mono<Long> count();
Mono<Long> countToday();
}
@@ -1,5 +1,7 @@
package cn.novalon.manage.sys.core.service.impl;
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.sys.core.service.IOperationLogService;
@@ -35,11 +37,21 @@ public class OperationLogService implements IOperationLogService {
return logRepository.findAll();
}
@Override
public Mono<OperationLog> findById(Long id) {
return logRepository.findById(id);
}
@Override
public Flux<OperationLog> findByUsername(String username) {
return logRepository.findByUsername(username);
}
@Override
public Mono<PageResponse<OperationLog>> findOperationLogsByPage(PageRequest pageRequest) {
return logRepository.findOperationLogsByPage(pageRequest);
}
@Override
public Mono<Long> count() {
return logRepository.count();
@@ -0,0 +1,79 @@
package cn.novalon.manage.sys.handler.log;
import cn.novalon.manage.sys.core.domain.OperationLog;
import cn.novalon.manage.sys.core.service.IOperationLogService;
import cn.novalon.manage.common.dto.PageRequest;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
/**
* 操作日志处理器
*
* 文件定义:处理操作日志相关的HTTP请求
* 涉及业务:操作日志查询、分页、统计
* 算法:使用WebFlux函数式编程模型处理响应式请求
*
* @author 张翔
* @date 2026-03-18
*/
@Component
@Tag(name = "操作日志", description = "操作日志相关操作")
public class OperationLogHandler {
private final IOperationLogService logService;
public OperationLogHandler(IOperationLogService logService) {
this.logService = logService;
}
@Operation(summary = "获取所有操作日志", description = "获取系统中所有操作日志列表")
public Mono<ServerResponse> getAllOperationLogs(ServerRequest request) {
return ServerResponse.ok()
.body(logService.findAll(), OperationLog.class);
}
@Operation(summary = "根据ID获取操作日志", description = "根据操作日志ID获取详细信息")
public Mono<ServerResponse> getOperationLogById(ServerRequest request) {
Long id = Long.valueOf(request.pathVariable("id"));
return logService.findById(id)
.flatMap(log -> ServerResponse.ok().bodyValue(log))
.switchIfEmpty(ServerResponse.notFound().build());
}
@Operation(summary = "分页获取操作日志", description = "根据分页参数获取操作日志列表")
public Mono<ServerResponse> getOperationLogsByPage(ServerRequest request) {
int page = Integer.parseInt(request.queryParam("page").orElse("0"));
int size = Integer.parseInt(request.queryParam("size").orElse("10"));
String sort = request.queryParam("sort").orElse("created_at");
String order = request.queryParam("order").orElse("desc");
String keyword = request.queryParam("keyword").orElse(null);
PageRequest pageRequest = new PageRequest();
pageRequest.setPage(page);
pageRequest.setSize(size);
pageRequest.setSort(sort);
pageRequest.setOrder(order);
pageRequest.setKeyword(keyword);
return logService.findOperationLogsByPage(pageRequest)
.flatMap(response -> ServerResponse.ok().bodyValue(response));
}
@Operation(summary = "获取操作日志总数", description = "获取系统中操作日志总数")
public Mono<ServerResponse> getOperationLogCount(ServerRequest request) {
return logService.count()
.flatMap(count -> ServerResponse.ok().bodyValue(count));
}
@Operation(summary = "创建操作日志", description = "手动创建操作日志")
public Mono<ServerResponse> createOperationLog(ServerRequest request) {
return request.bodyToMono(OperationLog.class)
.flatMap(logService::save)
.flatMap(log -> ServerResponse.status(HttpStatus.CREATED).bodyValue(log));
}
}
@@ -0,0 +1,2 @@
cn.novalon.manage.sys.config.SecurityConfig
cn.novalon.manage.sys.config.ExceptionLogConfig
@@ -1,68 +0,0 @@
server:
port: 8084
netty:
connection-timeout: 60s
idle-timeout: 300s
spring:
application:
name: novalon-manage-api
servlet:
multipart:
enabled: true
max-file-size: 10MB
max-request-size: 10MB
datasource:
url: jdbc:postgresql://localhost:55432/manage_system
username: postgres
password: postgres
driver-class-name: org.postgresql.Driver
r2dbc:
url: r2dbc:pool:postgresql://localhost:55432/manage_system
username: postgres
password: postgres
flyway:
enabled: true
url: jdbc:postgresql://localhost:55432/manage_system
user: postgres
password: postgres
locations: classpath:db/migration
baseline-on-migrate: true
jwt:
secret: novalon-manage-secret-key-change-in-production
expiration: 86400000
websocket:
enabled: true
heartbeat-interval: 30s
idle-timeout: 300s
max-text-message-buffer-size: 8192
max-binary-message-buffer-size: 8192
resilience4j:
ratelimiter:
instances:
apiRateLimiter:
limit-for-period: 100
limit-refresh-period: 1s
timeout-duration: 0
logging:
level:
cn.novalon.manage: DEBUG
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
endpoint:
health:
show-details: always
metrics:
access: read-only
prometheus:
metrics:
export:
enabled: true
@@ -1,209 +0,0 @@
-- Novalon管理系统数据库初始化脚本
-- 版本: V1
-- 描述: 创建所有核心表
-- 用户表
CREATE TABLE IF NOT EXISTS users (
id BIGSERIAL PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
email VARCHAR(100),
phone VARCHAR(20),
role_id BIGINT,
status INTEGER DEFAULT 1,
create_by VARCHAR(50),
update_by VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
-- 角色表
CREATE TABLE IF NOT EXISTS roles (
id BIGSERIAL PRIMARY KEY,
role_name VARCHAR(100) NOT NULL,
role_key VARCHAR(100) NOT NULL UNIQUE,
role_sort INTEGER DEFAULT 0,
status INTEGER DEFAULT 1,
create_by VARCHAR(50),
update_by VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
-- 菜单表
CREATE TABLE IF NOT EXISTS menus (
id BIGSERIAL PRIMARY KEY,
menu_name VARCHAR(50) NOT NULL,
parent_id BIGINT DEFAULT 0,
order_num INTEGER DEFAULT 0,
menu_type VARCHAR(1) DEFAULT 'C',
perms VARCHAR(100),
component VARCHAR(200),
status INTEGER DEFAULT 1,
create_by VARCHAR(50),
update_by VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
-- 字典类型表
CREATE TABLE IF NOT EXISTS sys_dict_type (
id BIGSERIAL PRIMARY KEY,
dict_name VARCHAR(100) NOT NULL,
dict_type VARCHAR(100) NOT NULL UNIQUE,
status VARCHAR(1) DEFAULT '0',
remark VARCHAR(500),
create_by VARCHAR(50),
update_by VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
-- 字典数据表
CREATE TABLE IF NOT EXISTS sys_dict_data (
id BIGSERIAL PRIMARY KEY,
dict_sort INTEGER DEFAULT 0,
dict_label VARCHAR(100) NOT NULL,
dict_value VARCHAR(100) NOT NULL,
dict_type VARCHAR(100) NOT NULL,
css_class VARCHAR(100),
list_class VARCHAR(100),
is_default VARCHAR(1) DEFAULT 'N',
status VARCHAR(1) DEFAULT '0',
create_by VARCHAR(50),
update_by VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
-- 系统配置表
CREATE TABLE IF NOT EXISTS sys_config (
id BIGSERIAL PRIMARY KEY,
config_name VARCHAR(100) NOT NULL,
config_key VARCHAR(100) NOT NULL UNIQUE,
config_value VARCHAR(500) NOT NULL,
config_type VARCHAR(1) DEFAULT 'N',
create_by VARCHAR(50),
update_by VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
-- 登录日志表
CREATE TABLE IF NOT EXISTS sys_login_log (
id BIGSERIAL PRIMARY KEY,
username VARCHAR(50),
ip VARCHAR(50),
location VARCHAR(255),
browser VARCHAR(50),
os VARCHAR(50),
status VARCHAR(1),
message VARCHAR(255),
login_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 异常日志表
CREATE TABLE IF NOT EXISTS sys_exception_log (
id BIGSERIAL PRIMARY KEY,
username VARCHAR(50),
ip VARCHAR(50),
location VARCHAR(255),
browser VARCHAR(50),
os VARCHAR(50),
status VARCHAR(1),
message VARCHAR(255),
exception_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 系统公告表
CREATE TABLE IF NOT EXISTS sys_notice (
id BIGSERIAL PRIMARY KEY,
notice_title VARCHAR(50) NOT NULL,
notice_type VARCHAR(1) NOT NULL,
notice_content TEXT,
status VARCHAR(1) DEFAULT '0',
create_by VARCHAR(50),
update_by VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
-- 用户消息表
CREATE TABLE IF NOT EXISTS sys_user_message (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL,
notice_id BIGINT,
message_title VARCHAR(255),
message_content TEXT,
is_read VARCHAR(1) DEFAULT '0',
read_time TIMESTAMP,
create_by VARCHAR(50),
update_by VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
-- 文件管理表
CREATE TABLE IF NOT EXISTS sys_file (
id BIGSERIAL PRIMARY KEY,
file_name VARCHAR(255) NOT NULL,
file_path VARCHAR(500) NOT NULL,
file_size BIGINT,
file_type VARCHAR(100),
file_extension VARCHAR(10),
create_by VARCHAR(50),
update_by VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
-- OAuth2客户端表
CREATE TABLE IF NOT EXISTS oauth2_client (
id BIGSERIAL PRIMARY KEY,
client_id VARCHAR(100) NOT NULL UNIQUE,
client_secret VARCHAR(255) NOT NULL,
client_name VARCHAR(100),
web_server_redirect_uri VARCHAR(500),
scope VARCHAR(500),
authorized_grant_types VARCHAR(500),
access_token_validity_seconds INTEGER,
refresh_token_validity_seconds INTEGER,
auto_approve VARCHAR(1) DEFAULT 'false',
enabled VARCHAR(1) DEFAULT 'true',
create_by VARCHAR(50),
update_by VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
-- 插入初始管理员用户
INSERT INTO users (username, password, email, role_id, status, create_by, update_by)
VALUES ('admin', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iKTVKIUi', 'admin@novalon.com', 1, 1, 'system', 'system')
ON CONFLICT (username) DO NOTHING;
-- 插入初始角色
INSERT INTO roles (role_name, role_key, role_sort, status, create_by, update_by)
VALUES ('超级管理员', 'admin', 1, 1, 'system', 'system')
ON CONFLICT (role_key) DO NOTHING;
-- 插入初始字典类型
INSERT INTO sys_dict_type (dict_name, dict_type, status, remark, create_by, update_by)
VALUES ('用户状态', 'user_status', '0', '用户状态列表', 'system', 'system')
ON CONFLICT (dict_type) DO NOTHING;
-- 插入初始字典数据
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, status, create_by, update_by)
VALUES
(1, '正常', '1', 'user_status', '0', 'system', 'system'),
(2, '停用', '0', 'user_status', '0', 'system', 'system')
ON CONFLICT DO NOTHING;
@@ -1,18 +0,0 @@
-- 创建字典表
CREATE TABLE IF NOT EXISTS sys_dictionary (
id BIGSERIAL PRIMARY KEY,
type VARCHAR(100) NOT NULL,
code VARCHAR(100) NOT NULL,
name VARCHAR(100) NOT NULL,
value VARCHAR(500),
remark VARCHAR(500),
sort INTEGER DEFAULT 0,
create_by VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
-- 创建索引
CREATE INDEX IF NOT EXISTS idx_sys_dictionary_type ON sys_dictionary(type);
CREATE INDEX IF NOT EXISTS idx_sys_dictionary_type_code ON sys_dictionary(type, code);