feat: 添加测试框架和覆盖率报告功能
feat(测试): 新增Playwright和Vitest测试配置 feat(测试): 添加测试覆盖率报告生成功能 feat(测试): 实现前后端测试脚本集成 fix(测试): 修复测试密码不匹配问题 fix(测试): 修正URL等待策略 fix(测试): 调整错误消息选择器 refactor(测试): 重构测试目录结构 refactor(测试): 优化测试用例组织方式 docs: 更新测试报告文档 docs: 添加测试覆盖率报告模板 ci: 添加Docker测试环境配置 ci: 实现测试自动化脚本 chore: 更新依赖版本 chore: 添加测试相关配置文件
This commit is contained in:
+18
-1
@@ -8,6 +8,7 @@ import cn.novalon.manage.sys.handler.log.SysLogHandler;
|
||||
import cn.novalon.manage.sys.handler.log.OperationLogHandler;
|
||||
import cn.novalon.manage.sys.handler.menu.MenuHandler;
|
||||
import cn.novalon.manage.sys.handler.role.SysRoleHandler;
|
||||
import cn.novalon.manage.sys.handler.permission.SysPermissionHandler;
|
||||
import cn.novalon.manage.sys.handler.stats.StatsHandler;
|
||||
import cn.novalon.manage.sys.handler.user.SysUserHandler;
|
||||
import cn.novalon.manage.notify.handler.SysNoticeHandler;
|
||||
@@ -80,7 +81,7 @@ public class SystemRouter {
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> roleRoutes(SysRoleHandler roleHandler) {
|
||||
public RouterFunction<ServerResponse> roleRoutes(SysRoleHandler roleHandler, SysPermissionHandler permissionHandler) {
|
||||
return route()
|
||||
.GET("/api/roles", roleHandler::getAllRoles)
|
||||
.GET("/api/roles/page", roleHandler::getRolesByPage)
|
||||
@@ -92,6 +93,8 @@ public class SystemRouter {
|
||||
.PUT("/api/roles/{id}", roleHandler::updateRole)
|
||||
.DELETE("/api/roles/{id}", roleHandler::deleteRole)
|
||||
.POST("/api/roles/{id}/restore", roleHandler::restoreRole)
|
||||
.GET("/api/roles/{id}/permissions", permissionHandler::getPermissionsByRoleId)
|
||||
.POST("/api/roles/{id}/permissions", permissionHandler::assignPermissionsToRole)
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -206,4 +209,18 @@ public class SystemRouter {
|
||||
.DELETE("/api/files/{id}", fileHandler::deleteFile)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> permissionRoutes(SysPermissionHandler permissionHandler) {
|
||||
return route()
|
||||
.GET("/api/permissions", permissionHandler::getAllPermissions)
|
||||
.GET("/api/permissions/{id}", permissionHandler::getPermissionById)
|
||||
.GET("/api/permissions/code/{code}", permissionHandler::getPermissionByCode)
|
||||
.GET("/api/permissions/check-code", permissionHandler::checkCodeExists)
|
||||
.GET("/api/permissions/count", permissionHandler::getPermissionCount)
|
||||
.POST("/api/permissions", permissionHandler::createPermission)
|
||||
.PUT("/api/permissions/{id}", permissionHandler::updatePermission)
|
||||
.DELETE("/api/permissions/{id}", permissionHandler::deletePermission)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
-- 测试数据初始化脚本
|
||||
-- 用于E2E测试和UAT测试的测试数据生成
|
||||
|
||||
-- 1. 清理现有测试数据
|
||||
DELETE FROM sys_user_role WHERE user_id IN (SELECT id FROM sys_user WHERE username LIKE 'test_%' OR username = 'admin');
|
||||
DELETE FROM sys_role_menu WHERE role_id IN (SELECT id FROM sys_role WHERE role_key LIKE 'test_%' OR role_key = 'admin');
|
||||
DELETE FROM sys_login_log WHERE username IN ('admin', 'test_user', 'test_admin');
|
||||
DELETE FROM sys_user WHERE username IN ('admin', 'test_user', 'test_admin');
|
||||
DELETE FROM sys_role WHERE role_key LIKE 'test_%' OR role_key = 'admin';
|
||||
DELETE FROM sys_menu WHERE menu_name LIKE '测试%' OR menu_name = '系统管理';
|
||||
|
||||
-- 2. 插入测试角色
|
||||
INSERT INTO sys_role (role_name, role_key, role_sort, status, create_by, create_time, update_by, update_time, remark) VALUES
|
||||
('超级管理员', 'admin', 1, 1, 'system', NOW(), 'system', NOW(), '系统超级管理员,拥有所有权限'),
|
||||
('普通用户', 'user', 2, 1, 'system', NOW(), 'system', NOW(), '普通用户,拥有基本权限'),
|
||||
('测试管理员', 'test_admin', 3, 1, 'system', NOW(), 'system', NOW(), '测试用管理员角色'),
|
||||
('测试普通用户', 'test_user', 4, 1, 'system', NOW(), 'system', NOW(), '测试用普通用户角色');
|
||||
|
||||
-- 3. 插入测试菜单
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES
|
||||
('系统管理', 0, 1, 'system', NULL, 'M', '0', '0', '', 'system', 'system', NOW(), 'system', NOW(), '系统管理目录'),
|
||||
('用户管理', 1, 1, 'user', 'system/user/index', 'C', '0', '0', 'system:user:list', 'user', 'system', NOW(), 'system', NOW(), '用户管理菜单'),
|
||||
('角色管理', 1, 2, 'role', 'system/role/index', 'C', '0', '0', 'system:role:list', 'role', 'system', NOW(), 'system', NOW(), '角色管理菜单'),
|
||||
('菜单管理', 1, 3, 'menu', 'system/menu/index', 'C', '0', '0', 'system:menu:list', 'menu', 'system', NOW(), 'system', NOW(), '菜单管理菜单'),
|
||||
('审计日志', 0, 2, 'audit', NULL, 'M', '0', '0', '', 'audit', 'system', NOW(), 'system', NOW(), '审计日志目录'),
|
||||
('登录日志', 5, 1, 'loginlog', 'audit/loginlog/index', 'C', '0', '0', 'audit:loginlog:list', 'loginlog', 'system', NOW(), 'system', NOW(), '登录日志菜单'),
|
||||
('系统监控', 0, 3, 'monitor', NULL, 'M', '0', '0', '', 'monitor', 'system', NOW(), 'system', NOW(), '系统监控目录'),
|
||||
('在线用户', 7, 1, 'online', 'monitor/online/index', 'C', '0', '0', 'monitor:online:list', 'online', 'system', NOW(), 'system', NOW(), '在线用户菜单');
|
||||
|
||||
-- 4. 插入测试用户
|
||||
INSERT INTO sys_user (username, password, email, phone, status, create_by, create_time, update_by, update_time, remark) VALUES
|
||||
('admin', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iAt6Z5EH', 'admin@novalon.com', '13800138000', 1, 'system', NOW(), 'system', NOW(), '系统管理员'),
|
||||
('test_user', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iAt6Z5EH', 'testuser@novalon.com', '13800138001', 1, 'system', NOW(), 'system', NOW(), '测试普通用户'),
|
||||
('test_admin', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iAt6Z5EH', 'testadmin@novalon.com', '13800138002', 1, 'system', NOW(), 'system', NOW(), '测试管理员');
|
||||
|
||||
-- 5. 分配用户角色关系
|
||||
INSERT INTO sys_user_role (user_id, role_id) VALUES
|
||||
((SELECT id FROM sys_user WHERE username = 'admin'), (SELECT id FROM sys_role WHERE role_key = 'admin')),
|
||||
((SELECT id FROM sys_user WHERE username = 'test_user'), (SELECT id FROM sys_role WHERE role_key = 'test_user')),
|
||||
((SELECT id FROM sys_user WHERE username = 'test_admin'), (SELECT id FROM sys_role WHERE role_key = 'test_admin'));
|
||||
|
||||
-- 6. 分配角色菜单关系
|
||||
-- 超级管理员拥有所有菜单权限
|
||||
INSERT INTO sys_role_menu (role_id, menu_id)
|
||||
SELECT (SELECT id FROM sys_role WHERE role_key = 'admin'), id FROM sys_menu;
|
||||
|
||||
-- 普通用户只拥有用户查看权限
|
||||
INSERT INTO sys_role_menu (role_id, menu_id) VALUES
|
||||
((SELECT id FROM sys_role WHERE role_key = 'user'), (SELECT id FROM sys_menu WHERE menu_name = '系统管理')),
|
||||
((SELECT id FROM sys_role WHERE role_key = 'user'), (SELECT id FROM sys_menu WHERE menu_name = '用户管理'));
|
||||
|
||||
-- 测试管理员拥有系统管理权限
|
||||
INSERT INTO sys_role_menu (role_id, menu_id)
|
||||
SELECT (SELECT id FROM sys_role WHERE role_key = 'test_admin'), id FROM sys_menu WHERE menu_name IN ('系统管理', '用户管理', '角色管理', '菜单管理', '审计日志', '登录日志');
|
||||
|
||||
-- 测试普通用户拥有基本查看权限
|
||||
INSERT INTO sys_role_menu (role_id, menu_id) VALUES
|
||||
((SELECT id FROM sys_role WHERE role_key = 'test_user'), (SELECT id FROM sys_menu WHERE menu_name = '系统管理')),
|
||||
((SELECT id FROM sys_role WHERE role_key = 'test_user'), (SELECT id FROM sys_menu WHERE menu_name = '用户管理'));
|
||||
|
||||
-- 7. 插入测试登录日志
|
||||
INSERT INTO sys_login_log (username, ipaddr, login_location, browser, os, status, msg, login_time, create_by, create_time) VALUES
|
||||
('admin', '127.0.0.1', '本地', 'Chrome 120.0', 'Mac OS X', 1, '登录成功', NOW(), 'system', NOW()),
|
||||
('test_user', '127.0.0.1', '本地', 'Firefox 121.0', 'Windows 10', 1, '登录成功', NOW(), 'system', NOW()),
|
||||
('test_admin', '127.0.0.1', '本地', 'Safari 17.0', 'Mac OS X', 1, '登录成功', NOW(), 'system', NOW()),
|
||||
('admin', '192.168.1.100', '内网', 'Chrome 119.0', 'Windows 11', 1, '登录成功', NOW() - INTERVAL '1 hour', 'system', NOW() - INTERVAL '1 hour'),
|
||||
('test_user', '192.168.1.101', '内网', 'Edge 120.0', 'Windows 10', 1, '登录成功', NOW() - INTERVAL '2 hours', 'system', NOW() - INTERVAL '2 hours');
|
||||
|
||||
-- 8. 验证测试数据
|
||||
SELECT '测试用户数据' as data_type, COUNT(*) as count FROM sys_user WHERE username IN ('admin', 'test_user', 'test_admin')
|
||||
UNION ALL
|
||||
SELECT '测试角色数据', COUNT(*) FROM sys_role WHERE role_key IN ('admin', 'user', 'test_admin', 'test_user')
|
||||
UNION ALL
|
||||
SELECT '测试菜单数据', COUNT(*) FROM sys_menu WHERE menu_name IN ('系统管理', '用户管理', '角色管理', '菜单管理', '审计日志', '登录日志', '系统监控', '在线用户')
|
||||
UNION ALL
|
||||
SELECT '用户角色关系', COUNT(*) FROM sys_user_role WHERE user_id IN (SELECT id FROM sys_user WHERE username IN ('admin', 'test_user', 'test_admin'))
|
||||
UNION ALL
|
||||
SELECT '角色菜单关系', COUNT(*) FROM sys_role_menu WHERE role_id IN (SELECT id FROM sys_role WHERE role_key IN ('admin', 'user', 'test_admin', 'test_user'))
|
||||
UNION ALL
|
||||
SELECT '登录日志数据', COUNT(*) FROM sys_login_log WHERE username IN ('admin', 'test_user', 'test_admin');
|
||||
|
||||
-- 提交事务
|
||||
COMMIT;
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
package cn.novalon.manage.db.converter;
|
||||
|
||||
import cn.novalon.manage.sys.core.domain.SysPermission;
|
||||
import cn.novalon.manage.db.entity.SysPermissionEntity;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 权限实体转换器
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-25
|
||||
*/
|
||||
@Component
|
||||
public class SysPermissionConverter {
|
||||
|
||||
public SysPermission toDomain(SysPermissionEntity entity) {
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
SysPermission domain = new SysPermission();
|
||||
domain.setId(entity.getId());
|
||||
domain.setPermissionName(entity.getPermissionName());
|
||||
domain.setPermissionCode(entity.getPermissionCode());
|
||||
domain.setResource(entity.getResource());
|
||||
domain.setAction(entity.getAction());
|
||||
domain.setDescription(entity.getDescription());
|
||||
domain.setStatus(entity.getStatus());
|
||||
domain.setCreatedAt(entity.getCreatedAt());
|
||||
domain.setUpdatedAt(entity.getUpdatedAt());
|
||||
domain.setDeletedAt(entity.getDeletedAt());
|
||||
return domain;
|
||||
}
|
||||
|
||||
public SysPermissionEntity toEntity(SysPermission domain) {
|
||||
if (domain == null) {
|
||||
return null;
|
||||
}
|
||||
SysPermissionEntity entity = new SysPermissionEntity();
|
||||
entity.setId(domain.getId());
|
||||
entity.setPermissionName(domain.getPermissionName());
|
||||
entity.setPermissionCode(domain.getPermissionCode());
|
||||
entity.setResource(domain.getResource());
|
||||
entity.setAction(domain.getAction());
|
||||
entity.setDescription(domain.getDescription());
|
||||
entity.setStatus(domain.getStatus());
|
||||
entity.setCreatedAt(domain.getCreatedAt());
|
||||
entity.setUpdatedAt(domain.getUpdatedAt());
|
||||
entity.setDeletedAt(domain.getDeletedAt());
|
||||
return entity;
|
||||
}
|
||||
|
||||
public List<SysPermission> toDomainList(List<SysPermissionEntity> entities) {
|
||||
if (entities == null) {
|
||||
return null;
|
||||
}
|
||||
return entities.stream()
|
||||
.map(this::toDomain)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<SysPermissionEntity> toEntityList(List<SysPermission> domains) {
|
||||
if (domains == null) {
|
||||
return null;
|
||||
}
|
||||
return domains.stream()
|
||||
.map(this::toEntity)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
package cn.novalon.manage.db.converter;
|
||||
|
||||
import cn.novalon.manage.sys.core.domain.SysRolePermission;
|
||||
import cn.novalon.manage.db.entity.SysRolePermissionEntity;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 角色权限关联实体转换器
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-25
|
||||
*/
|
||||
@Component
|
||||
public class SysRolePermissionConverter {
|
||||
|
||||
public SysRolePermission toDomain(SysRolePermissionEntity entity) {
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
SysRolePermission domain = new SysRolePermission();
|
||||
domain.setId(entity.getId());
|
||||
domain.setRoleId(entity.getRoleId());
|
||||
domain.setPermissionId(entity.getPermissionId());
|
||||
domain.setCreatedAt(entity.getCreatedAt());
|
||||
domain.setUpdatedAt(entity.getUpdatedAt());
|
||||
return domain;
|
||||
}
|
||||
|
||||
public SysRolePermissionEntity toEntity(SysRolePermission domain) {
|
||||
if (domain == null) {
|
||||
return null;
|
||||
}
|
||||
SysRolePermissionEntity entity = new SysRolePermissionEntity();
|
||||
entity.setId(domain.getId());
|
||||
entity.setRoleId(domain.getRoleId());
|
||||
entity.setPermissionId(domain.getPermissionId());
|
||||
entity.setCreatedAt(domain.getCreatedAt());
|
||||
entity.setUpdatedAt(domain.getUpdatedAt());
|
||||
return entity;
|
||||
}
|
||||
|
||||
public List<SysRolePermission> toDomainList(List<SysRolePermissionEntity> entities) {
|
||||
if (entities == null) {
|
||||
return null;
|
||||
}
|
||||
return entities.stream()
|
||||
.map(this::toDomain)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<SysRolePermissionEntity> toEntityList(List<SysRolePermission> domains) {
|
||||
if (domains == null) {
|
||||
return null;
|
||||
}
|
||||
return domains.stream()
|
||||
.map(this::toEntity)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
+2
@@ -22,4 +22,6 @@ public interface SysLoginLogDao extends R2dbcRepository<SysLoginLogEntity, Long>
|
||||
Mono<Long> count();
|
||||
|
||||
Mono<Long> countByUsername(String username);
|
||||
|
||||
Mono<Long> countByLoginTimeBetween(LocalDateTime startTime, LocalDateTime endTime);
|
||||
}
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package cn.novalon.manage.db.dao;
|
||||
|
||||
import cn.novalon.manage.db.entity.SysPermissionEntity;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.r2dbc.repository.R2dbcRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@Repository
|
||||
public interface SysPermissionDao extends R2dbcRepository<SysPermissionEntity, Long> {
|
||||
|
||||
Mono<SysPermissionEntity> findByIdAndDeletedAtIsNull(Long id);
|
||||
|
||||
Mono<SysPermissionEntity> findByPermissionCodeAndDeletedAtIsNull(String permissionCode);
|
||||
|
||||
Flux<SysPermissionEntity> findByDeletedAtIsNull();
|
||||
|
||||
Flux<SysPermissionEntity> findByDeletedAtIsNull(Sort sort);
|
||||
|
||||
Mono<Long> countByDeletedAtIsNull();
|
||||
|
||||
Mono<Boolean> existsByPermissionCodeAndDeletedAtIsNull(String permissionCode);
|
||||
|
||||
@org.springframework.data.r2dbc.repository.Query("""
|
||||
SELECT p.* FROM sys_permission p
|
||||
INNER JOIN sys_role_permission rp ON p.id = rp.permission_id
|
||||
WHERE rp.role_id = :roleId AND p.deleted_at IS NULL
|
||||
""")
|
||||
Flux<SysPermissionEntity> findByRoleId(Long roleId);
|
||||
|
||||
@org.springframework.data.r2dbc.repository.Query("""
|
||||
SELECT DISTINCT p.* FROM sys_permission p
|
||||
INNER JOIN sys_role_permission rp ON p.id = rp.permission_id
|
||||
WHERE rp.role_id IN (:roleIds) AND p.deleted_at IS NULL
|
||||
""")
|
||||
Flux<SysPermissionEntity> findByRoleIds(java.util.List<Long> roleIds);
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package cn.novalon.manage.db.dao;
|
||||
|
||||
import cn.novalon.manage.db.entity.SysRolePermissionEntity;
|
||||
import org.springframework.data.r2dbc.repository.R2dbcRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@Repository
|
||||
public interface SysRolePermissionDao extends R2dbcRepository<SysRolePermissionEntity, Long> {
|
||||
|
||||
Flux<SysRolePermissionEntity> findByRoleId(Long roleId);
|
||||
|
||||
Flux<SysRolePermissionEntity> findByPermissionId(Long permissionId);
|
||||
|
||||
Flux<Long> findPermissionIdsByRoleId(Long roleId);
|
||||
|
||||
Flux<Long> findRoleIdsByPermissionId(Long permissionId);
|
||||
|
||||
@org.springframework.data.r2dbc.repository.Query("""
|
||||
DELETE FROM sys_role_permission
|
||||
WHERE role_id = :roleId AND permission_id IN (:permissionIds)
|
||||
""")
|
||||
Mono<Void> deleteByRoleIdAndPermissionIds(Long roleId, java.util.List<Long> permissionIds);
|
||||
|
||||
@org.springframework.data.r2dbc.repository.Query("""
|
||||
DELETE FROM sys_role_permission
|
||||
WHERE permission_id = :permissionId AND role_id IN (:roleIds)
|
||||
""")
|
||||
Mono<Void> deleteByPermissionIdAndRoleIds(Long permissionId, java.util.List<Long> roleIds);
|
||||
|
||||
@org.springframework.data.r2dbc.repository.Query("""
|
||||
DELETE FROM sys_role_permission WHERE role_id = :roleId
|
||||
""")
|
||||
Mono<Void> deleteByRoleId(Long roleId);
|
||||
|
||||
@org.springframework.data.r2dbc.repository.Query("""
|
||||
DELETE FROM sys_role_permission WHERE permission_id = :permissionId
|
||||
""")
|
||||
Mono<Void> deleteByPermissionId(Long permissionId);
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
package cn.novalon.manage.db.entity;
|
||||
|
||||
import org.springframework.data.relational.core.mapping.Column;
|
||||
import org.springframework.data.relational.core.mapping.Table;
|
||||
|
||||
/**
|
||||
* 权限数据库实体类
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-25
|
||||
*/
|
||||
@Table("sys_permission")
|
||||
public class SysPermissionEntity extends BaseEntity {
|
||||
|
||||
@Column("permission_name")
|
||||
private String permissionName;
|
||||
|
||||
@Column("permission_code")
|
||||
private String permissionCode;
|
||||
|
||||
@Column("resource")
|
||||
private String resource;
|
||||
|
||||
@Column("action")
|
||||
private String action;
|
||||
|
||||
@Column("description")
|
||||
private String description;
|
||||
|
||||
@Column("status")
|
||||
private Integer status;
|
||||
|
||||
public String getPermissionName() {
|
||||
return permissionName;
|
||||
}
|
||||
|
||||
public void setPermissionName(String permissionName) {
|
||||
this.permissionName = permissionName;
|
||||
}
|
||||
|
||||
public String getPermissionCode() {
|
||||
return permissionCode;
|
||||
}
|
||||
|
||||
public void setPermissionCode(String permissionCode) {
|
||||
this.permissionCode = permissionCode;
|
||||
}
|
||||
|
||||
public String getResource() {
|
||||
return resource;
|
||||
}
|
||||
|
||||
public void setResource(String resource) {
|
||||
this.resource = resource;
|
||||
}
|
||||
|
||||
public String getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
public void setAction(String action) {
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public Integer getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(Integer status) {
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package cn.novalon.manage.db.entity;
|
||||
|
||||
import org.springframework.data.relational.core.mapping.Column;
|
||||
import org.springframework.data.relational.core.mapping.Table;
|
||||
|
||||
/**
|
||||
* 角色权限关联数据库实体类
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-25
|
||||
*/
|
||||
@Table("sys_role_permission")
|
||||
public class SysRolePermissionEntity extends BaseEntity {
|
||||
|
||||
@Column("role_id")
|
||||
private Long roleId;
|
||||
|
||||
@Column("permission_id")
|
||||
private Long permissionId;
|
||||
|
||||
public Long getRoleId() {
|
||||
return roleId;
|
||||
}
|
||||
|
||||
public void setRoleId(Long roleId) {
|
||||
this.roleId = roleId;
|
||||
}
|
||||
|
||||
public Long getPermissionId() {
|
||||
return permissionId;
|
||||
}
|
||||
|
||||
public void setPermissionId(Long permissionId) {
|
||||
this.permissionId = permissionId;
|
||||
}
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
package cn.novalon.manage.db.repository;
|
||||
|
||||
import cn.novalon.manage.sys.core.domain.SysPermission;
|
||||
import cn.novalon.manage.sys.core.repository.ISysPermissionRepository;
|
||||
import cn.novalon.manage.db.converter.SysPermissionConverter;
|
||||
import cn.novalon.manage.db.dao.SysPermissionDao;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* 权限仓储实现类
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-25
|
||||
*/
|
||||
@Repository
|
||||
public class SysPermissionRepository implements ISysPermissionRepository {
|
||||
|
||||
private final SysPermissionDao sysPermissionDao;
|
||||
private final SysPermissionConverter sysPermissionConverter;
|
||||
|
||||
public SysPermissionRepository(SysPermissionDao sysPermissionDao, SysPermissionConverter sysPermissionConverter) {
|
||||
this.sysPermissionDao = sysPermissionDao;
|
||||
this.sysPermissionConverter = sysPermissionConverter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<SysPermission> findById(Long id) {
|
||||
return sysPermissionDao.findByIdAndDeletedAtIsNull(id)
|
||||
.map(sysPermissionConverter::toDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<SysPermission> findByIdIncludingDeleted(Long id) {
|
||||
return sysPermissionDao.findById(id)
|
||||
.map(sysPermissionConverter::toDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<SysPermission> save(SysPermission sysPermission) {
|
||||
return sysPermissionDao.save(sysPermissionConverter.toEntity(sysPermission))
|
||||
.map(sysPermissionConverter::toDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> deleteById(Long id) {
|
||||
return sysPermissionDao.deleteById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<SysPermission> findAll() {
|
||||
return sysPermissionDao.findByDeletedAtIsNull()
|
||||
.map(sysPermissionConverter::toDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<SysPermission> findAll(Sort sort) {
|
||||
return sysPermissionDao.findByDeletedAtIsNull(sort)
|
||||
.map(sysPermissionConverter::toDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<SysPermission> findByPermissionCode(String permissionCode) {
|
||||
return sysPermissionDao.findByPermissionCodeAndDeletedAtIsNull(permissionCode)
|
||||
.map(sysPermissionConverter::toDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Long> count() {
|
||||
return sysPermissionDao.countByDeletedAtIsNull();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> existsByPermissionCode(String permissionCode) {
|
||||
return sysPermissionDao.existsByPermissionCodeAndDeletedAtIsNull(permissionCode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<SysPermission> updatePermission(SysPermission permission) {
|
||||
return sysPermissionDao.save(sysPermissionConverter.toEntity(permission))
|
||||
.map(sysPermissionConverter::toDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<SysPermission> findByRoleId(Long roleId) {
|
||||
return sysPermissionDao.findByRoleId(roleId)
|
||||
.map(sysPermissionConverter::toDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<SysPermission> findByRoleIds(java.util.List<Long> roleIds) {
|
||||
return sysPermissionDao.findByRoleIds(roleIds)
|
||||
.map(sysPermissionConverter::toDomain);
|
||||
}
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
package cn.novalon.manage.db.repository;
|
||||
|
||||
import cn.novalon.manage.sys.core.domain.SysRolePermission;
|
||||
import cn.novalon.manage.sys.core.repository.ISysRolePermissionRepository;
|
||||
import cn.novalon.manage.db.converter.SysRolePermissionConverter;
|
||||
import cn.novalon.manage.db.dao.SysRolePermissionDao;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* 角色权限关联仓储实现类
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-25
|
||||
*/
|
||||
@Repository
|
||||
public class SysRolePermissionRepository implements ISysRolePermissionRepository {
|
||||
|
||||
private final SysRolePermissionDao sysRolePermissionDao;
|
||||
private final SysRolePermissionConverter sysRolePermissionConverter;
|
||||
|
||||
public SysRolePermissionRepository(SysRolePermissionDao sysRolePermissionDao, SysRolePermissionConverter sysRolePermissionConverter) {
|
||||
this.sysRolePermissionDao = sysRolePermissionDao;
|
||||
this.sysRolePermissionConverter = sysRolePermissionConverter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<SysRolePermission> save(SysRolePermission rolePermission) {
|
||||
return sysRolePermissionDao.save(sysRolePermissionConverter.toEntity(rolePermission))
|
||||
.map(sysRolePermissionConverter::toDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> deleteById(Long id) {
|
||||
return sysRolePermissionDao.deleteById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> deleteByRoleId(Long roleId) {
|
||||
return sysRolePermissionDao.deleteByRoleId(roleId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> deleteByPermissionId(Long permissionId) {
|
||||
return sysRolePermissionDao.deleteByPermissionId(permissionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<SysRolePermission> findByRoleId(Long roleId) {
|
||||
return sysRolePermissionDao.findByRoleId(roleId)
|
||||
.map(sysRolePermissionConverter::toDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<SysRolePermission> findByPermissionId(Long permissionId) {
|
||||
return sysRolePermissionDao.findByPermissionId(permissionId)
|
||||
.map(sysRolePermissionConverter::toDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<Long> findPermissionIdsByRoleId(Long roleId) {
|
||||
return sysRolePermissionDao.findPermissionIdsByRoleId(roleId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<Long> findRoleIdsByPermissionId(Long permissionId) {
|
||||
return sysRolePermissionDao.findRoleIdsByPermissionId(permissionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> deleteByRoleIdAndPermissionIds(Long roleId, java.util.List<Long> permissionIds) {
|
||||
return sysRolePermissionDao.deleteByRoleIdAndPermissionIds(roleId, permissionIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> deleteByPermissionIdAndRoleIds(Long permissionId, java.util.List<Long> roleIds) {
|
||||
return sysRolePermissionDao.deleteByPermissionIdAndRoleIds(permissionId, roleIds);
|
||||
}
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
-- Novalon管理系统权限功能数据库迁移脚本
|
||||
-- 版本: V4
|
||||
-- 描述: 创建权限管理相关表结构
|
||||
|
||||
-- 权限表
|
||||
CREATE TABLE IF NOT EXISTS sys_permission (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
permission_name VARCHAR(100) NOT NULL,
|
||||
permission_code VARCHAR(100) NOT NULL UNIQUE,
|
||||
resource VARCHAR(200) NOT NULL,
|
||||
action VARCHAR(50) NOT NULL,
|
||||
description VARCHAR(500),
|
||||
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_role_permission (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
role_id BIGINT NOT NULL,
|
||||
permission_id BIGINT NOT NULL,
|
||||
create_by VARCHAR(50),
|
||||
update_by VARCHAR(50),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (permission_id) REFERENCES sys_permission(id) ON DELETE CASCADE,
|
||||
UNIQUE (role_id, permission_id)
|
||||
);
|
||||
|
||||
-- 表注释
|
||||
COMMENT ON TABLE sys_permission IS '系统权限表';
|
||||
COMMENT ON COLUMN sys_permission.id IS '主键ID';
|
||||
COMMENT ON COLUMN sys_permission.permission_name IS '权限名称';
|
||||
COMMENT ON COLUMN sys_permission.permission_code IS '权限编码';
|
||||
COMMENT ON COLUMN sys_permission.resource IS '资源路径';
|
||||
COMMENT ON COLUMN sys_permission.action IS '操作类型';
|
||||
COMMENT ON COLUMN sys_permission.description IS '权限描述';
|
||||
COMMENT ON COLUMN sys_permission.status IS '状态:0-禁用,1-正常';
|
||||
COMMENT ON COLUMN sys_permission.create_by IS '创建者';
|
||||
COMMENT ON COLUMN sys_permission.update_by IS '更新者';
|
||||
COMMENT ON COLUMN sys_permission.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN sys_permission.updated_at IS '更新时间';
|
||||
COMMENT ON COLUMN sys_permission.deleted_at IS '删除时间';
|
||||
|
||||
COMMENT ON TABLE sys_role_permission IS '角色权限关联表';
|
||||
COMMENT ON COLUMN sys_role_permission.id IS '主键ID';
|
||||
COMMENT ON COLUMN sys_role_permission.role_id IS '角色ID';
|
||||
COMMENT ON COLUMN sys_role_permission.permission_id IS '权限ID';
|
||||
COMMENT ON COLUMN sys_role_permission.create_by IS '创建者';
|
||||
COMMENT ON COLUMN sys_role_permission.update_by IS '更新者';
|
||||
COMMENT ON COLUMN sys_role_permission.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN sys_role_permission.updated_at IS '更新时间';
|
||||
|
||||
-- 创建索引
|
||||
CREATE INDEX IF NOT EXISTS idx_permission_code ON sys_permission(permission_code);
|
||||
CREATE INDEX IF NOT EXISTS idx_permission_resource ON sys_permission(resource);
|
||||
CREATE INDEX IF NOT EXISTS idx_permission_status ON sys_permission(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_role_permission_role_id ON sys_role_permission(role_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_role_permission_permission_id ON sys_role_permission(permission_id);
|
||||
|
||||
-- 插入初始权限数据
|
||||
INSERT INTO sys_permission (permission_name, permission_code, resource, action, description, status) VALUES
|
||||
('用户查看', 'system:user:view', '/api/users', 'GET', '查看用户列表', 1),
|
||||
('用户创建', 'system:user:create', '/api/users', 'POST', '创建用户', 1),
|
||||
('用户编辑', 'system:user:edit', '/api/users', 'PUT', '编辑用户', 1),
|
||||
('用户删除', 'system:user:delete', '/api/users', 'DELETE', '删除用户', 1),
|
||||
('角色查看', 'system:role:view', '/api/roles', 'GET', '查看角色列表', 1),
|
||||
('角色创建', 'system:role:create', '/api/roles', 'POST', '创建角色', 1),
|
||||
('角色编辑', 'system:role:edit', '/api/roles', 'PUT', '编辑角色', 1),
|
||||
('角色删除', 'system:role:delete', '/api/roles', 'DELETE', '删除角色', 1),
|
||||
('角色分配权限', 'system:role:assign', '/api/roles/*/permissions', 'POST', '为角色分配权限', 1),
|
||||
('权限查看', 'system:permission:view', '/api/permissions', 'GET', '查看权限列表', 1),
|
||||
('权限创建', 'system:permission:create', '/api/permissions', 'POST', '创建权限', 1),
|
||||
('权限编辑', 'system:permission:edit', '/api/permissions', 'PUT', '编辑权限', 1),
|
||||
('权限删除', 'system:permission:delete', '/api/permissions', 'DELETE', '删除权限', 1),
|
||||
('菜单查看', 'system:menu:view', '/api/menus', 'GET', '查看菜单列表', 1),
|
||||
('菜单创建', 'system:menu:create', '/api/menus', 'POST', '创建菜单', 1),
|
||||
('菜单编辑', 'system:menu:edit', '/api/menus', 'PUT', '编辑菜单', 1),
|
||||
('菜单删除', 'system:menu:delete', '/api/menus', 'DELETE', '删除菜单', 1),
|
||||
('字典查看', 'system:dict:view', '/api/dict', 'GET', '查看字典列表', 1),
|
||||
('字典创建', 'system:dict:create', '/api/dict', 'POST', '创建字典', 1),
|
||||
('字典编辑', 'system:dict:edit', '/api/dict', 'PUT', '编辑字典', 1),
|
||||
('字典删除', 'system:dict:delete', '/api/dict', 'DELETE', '删除字典', 1),
|
||||
('配置查看', 'system:config:view', '/api/config', 'GET', '查看系统配置', 1),
|
||||
('配置创建', 'system:config:create', '/api/config', 'POST', '创建系统配置', 1),
|
||||
('配置编辑', 'system:config:edit', '/api/config', 'PUT', '编辑系统配置', 1),
|
||||
('配置删除', 'system:config:delete', '/api/config', 'DELETE', '删除系统配置', 1),
|
||||
('日志查看', 'system:log:view', '/api/logs', 'GET', '查看日志', 1),
|
||||
('文件上传', 'system:file:upload', '/api/files/upload', 'POST', '上传文件', 1),
|
||||
('文件下载', 'system:file:download', '/api/files/download', 'GET', '下载文件', 1),
|
||||
('文件删除', 'system:file:delete', '/api/files', 'DELETE', '删除文件', 1),
|
||||
('公告查看', 'system:notice:view', '/api/notices', 'GET', '查看公告', 1),
|
||||
('公告创建', 'system:notice:create', '/api/notices', 'POST', '创建公告', 1),
|
||||
('公告编辑', 'system:notice:edit', '/api/notices', 'PUT', '编辑公告', 1),
|
||||
('公告删除', 'system:notice:delete', '/api/notices', 'DELETE', '删除公告', 1);
|
||||
|
||||
-- 为管理员角色分配所有权限
|
||||
INSERT INTO sys_role_permission (role_id, permission_id)
|
||||
SELECT 1, id FROM sys_permission WHERE status = 1;
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
package cn.novalon.manage.sys.core.domain;
|
||||
|
||||
import cn.novalon.manage.common.util.SnowflakeId;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
/**
|
||||
* 权限领域对象
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-25
|
||||
*/
|
||||
@Schema(description = "系统权限实体")
|
||||
public class SysPermission extends BaseDomain {
|
||||
|
||||
@Schema(description = "权限名称", example = "用户管理")
|
||||
private String permissionName;
|
||||
|
||||
@Schema(description = "权限编码", example = "system:user:view")
|
||||
private String permissionCode;
|
||||
|
||||
@Schema(description = "资源路径", example = "/api/users")
|
||||
private String resource;
|
||||
|
||||
@Schema(description = "操作类型", example = "GET")
|
||||
private String action;
|
||||
|
||||
@Schema(description = "描述", example = "查看用户列表")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "状态:0-禁用,1-正常", example = "1")
|
||||
private Integer status;
|
||||
|
||||
public String getPermissionName() {
|
||||
return permissionName;
|
||||
}
|
||||
|
||||
public void setPermissionName(String permissionName) {
|
||||
this.permissionName = permissionName;
|
||||
}
|
||||
|
||||
public String getPermissionCode() {
|
||||
return permissionCode;
|
||||
}
|
||||
|
||||
public void setPermissionCode(String permissionCode) {
|
||||
this.permissionCode = permissionCode;
|
||||
}
|
||||
|
||||
public String getResource() {
|
||||
return resource;
|
||||
}
|
||||
|
||||
public void setResource(String resource) {
|
||||
this.resource = resource;
|
||||
}
|
||||
|
||||
public String getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
public void setAction(String action) {
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public Integer getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(Integer status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成主键ID
|
||||
*
|
||||
* @return 主键ID
|
||||
*/
|
||||
public Long generateId() {
|
||||
this.id = SnowflakeId.nextId();
|
||||
return this.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除权限
|
||||
*/
|
||||
public void delete() {
|
||||
this.deletedAt = java.time.LocalDateTime.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复权限
|
||||
*/
|
||||
public void restore() {
|
||||
this.deletedAt = null;
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package cn.novalon.manage.sys.core.domain;
|
||||
|
||||
import cn.novalon.manage.common.util.SnowflakeId;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
/**
|
||||
* 角色权限关联领域对象
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-25
|
||||
*/
|
||||
@Schema(description = "角色权限关联实体")
|
||||
public class SysRolePermission extends BaseDomain {
|
||||
|
||||
@Schema(description = "角色ID", example = "1")
|
||||
private Long roleId;
|
||||
|
||||
@Schema(description = "权限ID", example = "1")
|
||||
private Long permissionId;
|
||||
|
||||
public Long getRoleId() {
|
||||
return roleId;
|
||||
}
|
||||
|
||||
public void setRoleId(Long roleId) {
|
||||
this.roleId = roleId;
|
||||
}
|
||||
|
||||
public Long getPermissionId() {
|
||||
return permissionId;
|
||||
}
|
||||
|
||||
public void setPermissionId(Long permissionId) {
|
||||
this.permissionId = permissionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成主键ID
|
||||
*
|
||||
* @return 主键ID
|
||||
*/
|
||||
public Long generateId() {
|
||||
this.id = SnowflakeId.nextId();
|
||||
return this.id;
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package cn.novalon.manage.sys.core.repository;
|
||||
|
||||
import cn.novalon.manage.sys.core.domain.SysPermission;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* 权限仓储接口
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-25
|
||||
*/
|
||||
public interface ISysPermissionRepository {
|
||||
|
||||
Mono<SysPermission> findById(Long id);
|
||||
|
||||
Mono<SysPermission> findByIdIncludingDeleted(Long id);
|
||||
|
||||
Mono<SysPermission> save(SysPermission sysPermission);
|
||||
|
||||
Mono<Void> deleteById(Long id);
|
||||
|
||||
Flux<SysPermission> findAll();
|
||||
|
||||
Flux<SysPermission> findAll(Sort sort);
|
||||
|
||||
Mono<SysPermission> findByPermissionCode(String permissionCode);
|
||||
|
||||
Mono<Long> count();
|
||||
|
||||
Mono<Boolean> existsByPermissionCode(String permissionCode);
|
||||
|
||||
Mono<SysPermission> updatePermission(SysPermission permission);
|
||||
|
||||
Flux<SysPermission> findByRoleId(Long roleId);
|
||||
|
||||
Flux<SysPermission> findByRoleIds(java.util.List<Long> roleIds);
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package cn.novalon.manage.sys.core.repository;
|
||||
|
||||
import cn.novalon.manage.sys.core.domain.SysRolePermission;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* 角色权限关联仓储接口
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-25
|
||||
*/
|
||||
public interface ISysRolePermissionRepository {
|
||||
|
||||
Mono<SysRolePermission> save(SysRolePermission rolePermission);
|
||||
|
||||
Mono<Void> deleteById(Long id);
|
||||
|
||||
Mono<Void> deleteByRoleId(Long roleId);
|
||||
|
||||
Mono<Void> deleteByPermissionId(Long permissionId);
|
||||
|
||||
Flux<SysRolePermission> findByRoleId(Long roleId);
|
||||
|
||||
Flux<SysRolePermission> findByPermissionId(Long permissionId);
|
||||
|
||||
Flux<Long> findPermissionIdsByRoleId(Long roleId);
|
||||
|
||||
Flux<Long> findRoleIdsByPermissionId(Long permissionId);
|
||||
|
||||
Mono<Void> deleteByRoleIdAndPermissionIds(Long roleId, java.util.List<Long> permissionIds);
|
||||
|
||||
Mono<Void> deleteByPermissionIdAndRoleIds(Long permissionId, java.util.List<Long> roleIds);
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package cn.novalon.manage.sys.core.service;
|
||||
|
||||
import cn.novalon.manage.sys.core.domain.SysPermission;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* 权限服务接口
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-25
|
||||
*/
|
||||
public interface ISysPermissionService {
|
||||
Mono<SysPermission> findById(Long id);
|
||||
Flux<SysPermission> findAll();
|
||||
Flux<SysPermission> findAll(Sort sort);
|
||||
Mono<SysPermission> findByPermissionCode(String permissionCode);
|
||||
Mono<Long> count();
|
||||
Mono<SysPermission> createPermission(SysPermission permission);
|
||||
Mono<SysPermission> updatePermission(SysPermission permission);
|
||||
Mono<Void> deletePermission(Long id);
|
||||
Mono<Boolean> existsByPermissionCode(String permissionCode);
|
||||
Flux<SysPermission> findByRoleId(Long roleId);
|
||||
Flux<SysPermission> findByRoleIds(java.util.List<Long> roleIds);
|
||||
Mono<Void> assignPermissionsToRole(Long roleId, java.util.List<Long> permissionIds);
|
||||
Flux<SysPermission> getPermissionsByRoleId(Long roleId);
|
||||
}
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
package cn.novalon.manage.sys.core.service.impl;
|
||||
|
||||
import cn.novalon.manage.common.util.StatusConstants;
|
||||
import cn.novalon.manage.sys.core.domain.SysPermission;
|
||||
import cn.novalon.manage.sys.core.domain.SysRolePermission;
|
||||
import cn.novalon.manage.sys.core.repository.ISysPermissionRepository;
|
||||
import cn.novalon.manage.sys.core.repository.ISysRolePermissionRepository;
|
||||
import cn.novalon.manage.sys.core.service.ISysPermissionService;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 系统权限服务实现类
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-25
|
||||
*/
|
||||
@Service
|
||||
public class SysPermissionService implements ISysPermissionService {
|
||||
|
||||
private final ISysPermissionRepository permissionRepository;
|
||||
private final ISysRolePermissionRepository rolePermissionRepository;
|
||||
|
||||
public SysPermissionService(ISysPermissionRepository permissionRepository,
|
||||
ISysRolePermissionRepository rolePermissionRepository) {
|
||||
this.permissionRepository = permissionRepository;
|
||||
this.rolePermissionRepository = rolePermissionRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<SysPermission> findById(Long id) {
|
||||
return permissionRepository.findById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<SysPermission> findAll() {
|
||||
return permissionRepository.findAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<SysPermission> findAll(Sort sort) {
|
||||
return permissionRepository.findAll(sort);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<SysPermission> findByPermissionCode(String permissionCode) {
|
||||
return permissionRepository.findByPermissionCode(permissionCode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Long> count() {
|
||||
return permissionRepository.count();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<SysPermission> createPermission(SysPermission permission) {
|
||||
permission.setCreatedAt(LocalDateTime.now());
|
||||
if (permission.getStatus() == null) {
|
||||
permission.setStatus(StatusConstants.ENABLED);
|
||||
}
|
||||
return permissionRepository.save(permission);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<SysPermission> updatePermission(SysPermission permission) {
|
||||
permission.setUpdatedAt(LocalDateTime.now());
|
||||
return permissionRepository.updatePermission(permission);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> deletePermission(Long id) {
|
||||
return permissionRepository.findById(id)
|
||||
.flatMap(permission -> {
|
||||
permission.delete();
|
||||
return permissionRepository.updatePermission(permission)
|
||||
.then(rolePermissionRepository.deleteByPermissionId(id));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> existsByPermissionCode(String permissionCode) {
|
||||
return permissionRepository.existsByPermissionCode(permissionCode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<SysPermission> findByRoleId(Long roleId) {
|
||||
return permissionRepository.findByRoleId(roleId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<SysPermission> findByRoleIds(List<Long> roleIds) {
|
||||
return permissionRepository.findByRoleIds(roleIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Mono<Void> assignPermissionsToRole(Long roleId, List<Long> permissionIds) {
|
||||
return rolePermissionRepository.deleteByRoleId(roleId)
|
||||
.then(Flux.fromIterable(permissionIds)
|
||||
.flatMap(permissionId -> {
|
||||
SysRolePermission rolePermission = new SysRolePermission();
|
||||
rolePermission.setRoleId(roleId);
|
||||
rolePermission.setPermissionId(permissionId);
|
||||
rolePermission.setCreatedAt(LocalDateTime.now());
|
||||
return rolePermissionRepository.save(rolePermission);
|
||||
})
|
||||
.then());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<SysPermission> getPermissionsByRoleId(Long roleId) {
|
||||
return permissionRepository.findByRoleId(roleId);
|
||||
}
|
||||
}
|
||||
+58
-1
@@ -5,7 +5,10 @@ import cn.novalon.manage.sys.dto.request.UserRegisterRequest;
|
||||
import cn.novalon.manage.sys.dto.response.AuthResponse;
|
||||
import cn.novalon.manage.sys.security.JwtTokenProvider;
|
||||
import cn.novalon.manage.sys.core.domain.SysUser;
|
||||
import cn.novalon.manage.sys.core.domain.SysLoginLog;
|
||||
import cn.novalon.manage.sys.core.service.ISysUserService;
|
||||
import cn.novalon.manage.sys.core.service.ISysLoginLogService;
|
||||
import cn.novalon.manage.sys.util.UserAgentParser;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.slf4j.Logger;
|
||||
@@ -42,12 +45,17 @@ public class SysAuthHandler {
|
||||
private final ISysUserService userService;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
private final JwtTokenProvider jwtTokenProvider;
|
||||
private final ISysLoginLogService loginLogService;
|
||||
private final UserAgentParser userAgentParser;
|
||||
|
||||
public SysAuthHandler(ISysUserService userService, PasswordEncoder passwordEncoder,
|
||||
JwtTokenProvider jwtTokenProvider) {
|
||||
JwtTokenProvider jwtTokenProvider, ISysLoginLogService loginLogService,
|
||||
UserAgentParser userAgentParser) {
|
||||
this.userService = userService;
|
||||
this.passwordEncoder = passwordEncoder;
|
||||
this.jwtTokenProvider = jwtTokenProvider;
|
||||
this.loginLogService = loginLogService;
|
||||
this.userAgentParser = userAgentParser;
|
||||
}
|
||||
|
||||
@Operation(summary = "用户登录", description = "使用用户名和密码登录系统")
|
||||
@@ -61,18 +69,22 @@ public class SysAuthHandler {
|
||||
.switchIfEmpty(Mono.error(new IllegalArgumentException("密码不能为空")))
|
||||
.flatMap(loginRequest -> {
|
||||
logger.info("用户登录请求: username={}", loginRequest.getUsername());
|
||||
String clientIp = getClientIp(request);
|
||||
String userAgent = request.headers().firstHeader("User-Agent");
|
||||
return userService.findByUsername(loginRequest.getUsername())
|
||||
.flatMap(user -> {
|
||||
if (!passwordEncoder.matches(loginRequest.getPassword(),
|
||||
user.getPassword())) {
|
||||
logger.warn("用户登录失败: username={}, reason=密码错误",
|
||||
loginRequest.getUsername());
|
||||
recordLoginLog(loginRequest.getUsername(), clientIp, "1", "密码错误", userAgent);
|
||||
return Mono.error(new RuntimeException(
|
||||
"用户名或密码错误"));
|
||||
}
|
||||
if (user.getStatus() != 1) {
|
||||
logger.warn("用户登录失败: username={}, reason=用户已禁用",
|
||||
loginRequest.getUsername());
|
||||
recordLoginLog(loginRequest.getUsername(), clientIp, "1", "用户已禁用", userAgent);
|
||||
return Mono.error(new RuntimeException(
|
||||
"用户名或密码错误"));
|
||||
}
|
||||
@@ -80,6 +92,7 @@ public class SysAuthHandler {
|
||||
user.getUsername(), user.getId());
|
||||
logger.info("用户登录成功: username={}, userId={}",
|
||||
user.getUsername(), user.getId());
|
||||
recordLoginLog(loginRequest.getUsername(), clientIp, "0", "登录成功", userAgent);
|
||||
AuthResponse response = new AuthResponse(token,
|
||||
user.getId(), user.getUsername());
|
||||
return ServerResponse.ok().bodyValue(response);
|
||||
@@ -87,6 +100,7 @@ public class SysAuthHandler {
|
||||
.switchIfEmpty(Mono.defer(() -> {
|
||||
logger.warn("用户登录失败: username={}, reason=用户不存在",
|
||||
loginRequest.getUsername());
|
||||
recordLoginLog(loginRequest.getUsername(), clientIp, "1", "用户不存在", userAgent);
|
||||
return Mono.error(new RuntimeException("用户名或密码错误"));
|
||||
}));
|
||||
})
|
||||
@@ -121,6 +135,49 @@ public class SysAuthHandler {
|
||||
});
|
||||
}
|
||||
|
||||
private void recordLoginLog(String username, String ip, String status, String message, String userAgent) {
|
||||
try {
|
||||
SysLoginLog loginLog = new SysLoginLog();
|
||||
loginLog.setUsername(username);
|
||||
loginLog.setIp(ip);
|
||||
loginLog.setStatus(status);
|
||||
loginLog.setMessage(message);
|
||||
loginLog.setLoginTime(LocalDateTime.now());
|
||||
|
||||
if (userAgent != null && !userAgent.isEmpty()) {
|
||||
loginLog.setBrowser(userAgentParser.parseBrowser(userAgent));
|
||||
loginLog.setOs(userAgentParser.parseOS(userAgent));
|
||||
}
|
||||
|
||||
loginLogService.save(loginLog)
|
||||
.doOnSuccess(saved -> logger.debug("登录日志记录成功: username={}, status={}", username, status))
|
||||
.doOnError(error -> logger.error("登录日志记录失败: {}", error.getMessage()))
|
||||
.subscribe();
|
||||
} catch (Exception e) {
|
||||
logger.error("记录登录日志时发生异常: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private String getClientIp(ServerRequest request) {
|
||||
String ip = request.headers().firstHeader("X-Forwarded-For");
|
||||
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.headers().firstHeader("X-Real-IP");
|
||||
}
|
||||
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.headers().firstHeader("Proxy-Client-IP");
|
||||
}
|
||||
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.headers().firstHeader("WL-Proxy-Client-IP");
|
||||
}
|
||||
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.remoteAddress().map(addr -> addr.getAddress().getHostAddress()).orElse("");
|
||||
}
|
||||
if (ip != null && ip.contains(",")) {
|
||||
ip = ip.split(",")[0].trim();
|
||||
}
|
||||
return ip;
|
||||
}
|
||||
|
||||
@Operation(summary = "用户注册", description = "注册新用户")
|
||||
public Mono<ServerResponse> register(ServerRequest request) {
|
||||
return request.bodyToMono(UserRegisterRequest.class)
|
||||
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
package cn.novalon.manage.sys.handler.permission;
|
||||
|
||||
import cn.novalon.manage.sys.core.domain.SysPermission;
|
||||
import cn.novalon.manage.sys.core.service.ISysPermissionService;
|
||||
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;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 系统权限处理器
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-25
|
||||
*/
|
||||
@Component
|
||||
@Tag(name = "权限管理", description = "权限相关操作")
|
||||
public class SysPermissionHandler {
|
||||
|
||||
private final ISysPermissionService permissionService;
|
||||
|
||||
public SysPermissionHandler(ISysPermissionService permissionService) {
|
||||
this.permissionService = permissionService;
|
||||
}
|
||||
|
||||
@Operation(summary = "获取所有权限", description = "获取系统中所有权限列表")
|
||||
public Mono<ServerResponse> getAllPermissions(ServerRequest request) {
|
||||
return ServerResponse.ok()
|
||||
.body(permissionService.findAll(), SysPermission.class);
|
||||
}
|
||||
|
||||
@Operation(summary = "根据ID获取权限", description = "根据权限ID获取权限详细信息")
|
||||
public Mono<ServerResponse> getPermissionById(ServerRequest request) {
|
||||
Long id = Long.valueOf(request.pathVariable("id"));
|
||||
return permissionService.findById(id)
|
||||
.flatMap(permission -> ServerResponse.ok().bodyValue(permission))
|
||||
.switchIfEmpty(ServerResponse.notFound().build());
|
||||
}
|
||||
|
||||
@Operation(summary = "检查权限编码是否存在", description = "检查指定权限编码是否已存在")
|
||||
public Mono<ServerResponse> checkCodeExists(ServerRequest request) {
|
||||
String code = request.queryParam("code").orElse(null);
|
||||
return permissionService.existsByPermissionCode(code)
|
||||
.flatMap(exists -> ServerResponse.ok().bodyValue(exists));
|
||||
}
|
||||
|
||||
@Operation(summary = "获取权限总数", description = "获取系统中权限总数")
|
||||
public Mono<ServerResponse> getPermissionCount(ServerRequest request) {
|
||||
return permissionService.count()
|
||||
.flatMap(count -> ServerResponse.ok().bodyValue(count));
|
||||
}
|
||||
|
||||
@Operation(summary = "根据权限编码获取权限", description = "根据权限编码获取权限详细信息")
|
||||
public Mono<ServerResponse> getPermissionByCode(ServerRequest request) {
|
||||
String code = request.pathVariable("code");
|
||||
return permissionService.findByPermissionCode(code)
|
||||
.flatMap(permission -> ServerResponse.ok().bodyValue(permission))
|
||||
.switchIfEmpty(ServerResponse.notFound().build());
|
||||
}
|
||||
|
||||
@Operation(summary = "创建权限", description = "创建新权限")
|
||||
public Mono<ServerResponse> createPermission(ServerRequest request) {
|
||||
return request.bodyToMono(SysPermission.class)
|
||||
.flatMap(permissionService::createPermission)
|
||||
.flatMap(permission -> ServerResponse.status(HttpStatus.CREATED).bodyValue(permission));
|
||||
}
|
||||
|
||||
@Operation(summary = "更新权限", description = "更新权限信息")
|
||||
public Mono<ServerResponse> updatePermission(ServerRequest request) {
|
||||
Long id = Long.valueOf(request.pathVariable("id"));
|
||||
return request.bodyToMono(SysPermission.class)
|
||||
.flatMap(permission -> {
|
||||
permission.setId(id);
|
||||
return permissionService.updatePermission(permission);
|
||||
})
|
||||
.flatMap(updatedPermission -> ServerResponse.ok().bodyValue(updatedPermission))
|
||||
.switchIfEmpty(ServerResponse.notFound().build());
|
||||
}
|
||||
|
||||
@Operation(summary = "删除权限", description = "逻辑删除权限")
|
||||
public Mono<ServerResponse> deletePermission(ServerRequest request) {
|
||||
Long id = Long.valueOf(request.pathVariable("id"));
|
||||
return permissionService.deletePermission(id)
|
||||
.then(ServerResponse.ok().build())
|
||||
.switchIfEmpty(ServerResponse.notFound().build());
|
||||
}
|
||||
|
||||
@Operation(summary = "获取角色的权限", description = "根据角色ID获取该角色拥有的所有权限")
|
||||
public Mono<ServerResponse> getPermissionsByRoleId(ServerRequest request) {
|
||||
Long roleId = Long.valueOf(request.pathVariable("id"));
|
||||
return ServerResponse.ok()
|
||||
.body(permissionService.getPermissionsByRoleId(roleId), SysPermission.class);
|
||||
}
|
||||
|
||||
@Operation(summary = "为角色分配权限", description = "为指定角色分配权限列表")
|
||||
public Mono<ServerResponse> assignPermissionsToRole(ServerRequest request) {
|
||||
Long roleId = Long.valueOf(request.pathVariable("id"));
|
||||
return request.bodyToMono(AssignPermissionsRequest.class)
|
||||
.flatMap(req -> permissionService.assignPermissionsToRole(roleId, req.permissionIds()))
|
||||
.then(ServerResponse.ok().build());
|
||||
}
|
||||
|
||||
private record AssignPermissionsRequest(List<Long> permissionIds) {}
|
||||
}
|
||||
+28
-23
@@ -46,25 +46,41 @@ public class OperationLogFilter implements WebFilter {
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
|
||||
return chain.filter(exchange)
|
||||
.doOnSuccess(v -> {
|
||||
long duration = System.currentTimeMillis() - startTime;
|
||||
recordLog(exchange, path, method, ip, duration, null);
|
||||
return ReactiveSecurityContextHolder.getContext()
|
||||
.flatMap(securityContext -> {
|
||||
Object principal = securityContext.getAuthentication().getPrincipal();
|
||||
String username = principal instanceof String ? (String) principal : null;
|
||||
|
||||
return chain.filter(exchange)
|
||||
.doOnSuccess(v -> {
|
||||
long duration = System.currentTimeMillis() - startTime;
|
||||
recordLog(exchange, path, method, ip, duration, null, username);
|
||||
})
|
||||
.doOnError(error -> {
|
||||
long duration = System.currentTimeMillis() - startTime;
|
||||
recordLog(exchange, path, method, ip, duration, error.getMessage(), username);
|
||||
});
|
||||
})
|
||||
.doOnError(error -> {
|
||||
long duration = System.currentTimeMillis() - startTime;
|
||||
recordLog(exchange, path, method, ip, duration, error.getMessage());
|
||||
});
|
||||
.switchIfEmpty(chain.filter(exchange)
|
||||
.doOnSuccess(v -> {
|
||||
long duration = System.currentTimeMillis() - startTime;
|
||||
recordLog(exchange, path, method, ip, duration, null, null);
|
||||
})
|
||||
.doOnError(error -> {
|
||||
long duration = System.currentTimeMillis() - startTime;
|
||||
recordLog(exchange, path, method, ip, duration, error.getMessage(), null);
|
||||
}));
|
||||
}
|
||||
|
||||
private void recordLog(ServerWebExchange exchange, String path, String method, String ip, long duration,
|
||||
String errorMsg) {
|
||||
String errorMsg, String username) {
|
||||
try {
|
||||
OperationLog log = new OperationLog();
|
||||
log.setOperation(path);
|
||||
log.setMethod(method);
|
||||
log.setIp(ip);
|
||||
log.setDuration(duration);
|
||||
log.setUsername(username);
|
||||
|
||||
if (errorMsg != null) {
|
||||
log.setStatus("1");
|
||||
@@ -78,20 +94,9 @@ public class OperationLogFilter implements WebFilter {
|
||||
String queryParams = exchange.getRequest().getQueryParams().toSingleValueMap().toString();
|
||||
log.setParams(queryParams);
|
||||
|
||||
ReactiveSecurityContextHolder.getContext()
|
||||
.flatMap(securityContext -> {
|
||||
Object principal = securityContext.getAuthentication().getPrincipal();
|
||||
if (principal instanceof String) {
|
||||
log.setUsername((String) principal);
|
||||
}
|
||||
return Mono.empty();
|
||||
})
|
||||
.then(Mono.fromRunnable(() -> {
|
||||
logService.save(log)
|
||||
.doOnSuccess(saved -> logger.debug("操作日志记录成功: {}", log.getOperation()))
|
||||
.doOnError(error -> logger.error("操作日志记录失败: {}", error.getMessage()))
|
||||
.subscribe();
|
||||
}))
|
||||
logService.save(log)
|
||||
.doOnSuccess(saved -> logger.debug("操作日志记录成功: {}", log.getOperation()))
|
||||
.doOnError(error -> logger.error("操作日志记录失败: {}", error.getMessage()))
|
||||
.subscribe();
|
||||
} catch (Exception e) {
|
||||
logger.error("记录操作日志时发生异常: {}", e.getMessage());
|
||||
|
||||
+2
-1
@@ -34,10 +34,11 @@ public class JwtAuthenticationFilter implements WebFilter {
|
||||
|
||||
if (token != null && jwtTokenProvider.validateToken(token)) {
|
||||
Long userId = jwtTokenProvider.getUserIdFromToken(token);
|
||||
String username = jwtTokenProvider.getUsernameFromToken(token);
|
||||
|
||||
UsernamePasswordAuthenticationToken authentication =
|
||||
new UsernamePasswordAuthenticationToken(
|
||||
userId,
|
||||
username,
|
||||
null,
|
||||
Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"))
|
||||
);
|
||||
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
package cn.novalon.manage.sys.util;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* User-Agent解析工具类
|
||||
*
|
||||
* 用于解析HTTP请求头中的User-Agent信息,提取浏览器类型、版本和操作系统信息
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-24
|
||||
*/
|
||||
@Component
|
||||
public class UserAgentParser {
|
||||
|
||||
private static final Pattern BROWSER_PATTERN = Pattern.compile(
|
||||
"(Chrome|Firefox|Safari|Edge|MSIE|Trident|Opera)[/\\s]([\\d.]+)"
|
||||
);
|
||||
|
||||
private static final Pattern OS_PATTERN = Pattern.compile(
|
||||
"(Windows NT|Mac OS X|Linux|Android|iPhone|iPad|iPod)[\\s/_-]?([\\d._]+)?"
|
||||
);
|
||||
|
||||
/**
|
||||
* 解析User-Agent字符串,返回浏览器信息
|
||||
*
|
||||
* @param userAgent User-Agent字符串
|
||||
* @return 浏览器名称和版本,如"Chrome 120.0"
|
||||
*/
|
||||
public String parseBrowser(String userAgent) {
|
||||
if (userAgent == null || userAgent.isEmpty()) {
|
||||
return "未知浏览器";
|
||||
}
|
||||
|
||||
Matcher matcher = BROWSER_PATTERN.matcher(userAgent);
|
||||
if (matcher.find()) {
|
||||
return matcher.group(1) + " " + matcher.group(2);
|
||||
}
|
||||
|
||||
return "未知浏览器";
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析User-Agent字符串,返回操作系统信息
|
||||
*
|
||||
* @param userAgent User-Agent字符串
|
||||
* @return 操作系统名称和版本,如"Windows 10"或"Mac OS X"
|
||||
*/
|
||||
public String parseOS(String userAgent) {
|
||||
if (userAgent == null || userAgent.isEmpty()) {
|
||||
return "未知系统";
|
||||
}
|
||||
|
||||
String ua = userAgent;
|
||||
|
||||
if (ua.contains("Windows NT 10.0")) {
|
||||
return "Windows 10";
|
||||
} else if (ua.contains("Windows NT 6.3")) {
|
||||
return "Windows 8.1";
|
||||
} else if (ua.contains("Windows NT 6.2")) {
|
||||
return "Windows 8";
|
||||
} else if (ua.contains("Windows NT 6.1")) {
|
||||
return "Windows 7";
|
||||
} else if (ua.contains("Windows NT")) {
|
||||
return "Windows";
|
||||
} else if (ua.contains("Mac OS X")) {
|
||||
return "Mac OS X";
|
||||
} else if (ua.contains("Linux")) {
|
||||
return "Linux";
|
||||
} else if (ua.contains("Android")) {
|
||||
return "Android";
|
||||
} else if (ua.contains("iPhone")) {
|
||||
return "iPhone";
|
||||
} else if (ua.contains("iPad")) {
|
||||
return "iPad";
|
||||
} else if (ua.contains("iPod")) {
|
||||
return "iPod";
|
||||
}
|
||||
|
||||
return "未知系统";
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析User-Agent字符串,返回浏览器和操作系统信息
|
||||
*
|
||||
* @param userAgent User-Agent字符串
|
||||
* @return 格式化的浏览器和操作系统信息
|
||||
*/
|
||||
public String parseUserAgent(String userAgent) {
|
||||
if (userAgent == null || userAgent.isEmpty()) {
|
||||
return "未知浏览器 / 未知系统";
|
||||
}
|
||||
return parseBrowser(userAgent) + " / " + parseOS(userAgent);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
-- 系统菜单初始化数据
|
||||
-- @author 张翔
|
||||
-- @date 2026-03-24
|
||||
|
||||
-- 清空现有菜单数据
|
||||
DELETE FROM sys_menu WHERE id > 0;
|
||||
|
||||
-- 一级菜单
|
||||
INSERT INTO sys_menu (id, parent_id, menu_name, 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());
|
||||
|
||||
-- 系统管理子菜单
|
||||
INSERT INTO sys_menu (id, parent_id, menu_name, order_num, menu_type, perms, component, status, created_at, updated_at) VALUES
|
||||
(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());
|
||||
|
||||
-- 用户管理按钮权限
|
||||
INSERT INTO sys_menu (id, parent_id, menu_name, order_num, menu_type, perms, component, status, created_at, updated_at) VALUES
|
||||
(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());
|
||||
|
||||
-- 角色管理按钮权限
|
||||
INSERT INTO sys_menu (id, parent_id, menu_name, order_num, menu_type, perms, component, status, created_at, updated_at) VALUES
|
||||
(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());
|
||||
|
||||
-- 菜单管理按钮权限
|
||||
INSERT INTO sys_menu (id, parent_id, menu_name, order_num, menu_type, perms, component, status, created_at, updated_at) VALUES
|
||||
(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());
|
||||
|
||||
-- 审计日志子菜单
|
||||
INSERT INTO sys_menu (id, parent_id, menu_name, order_num, menu_type, perms, component, status, created_at, updated_at) VALUES
|
||||
(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());
|
||||
|
||||
-- 操作日志按钮权限
|
||||
INSERT INTO sys_menu (id, parent_id, menu_name, order_num, menu_type, perms, component, status, created_at, updated_at) VALUES
|
||||
(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());
|
||||
|
||||
-- 登录日志按钮权限
|
||||
INSERT INTO sys_menu (id, parent_id, menu_name, order_num, menu_type, perms, component, status, created_at, updated_at) VALUES
|
||||
(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());
|
||||
|
||||
-- 异常日志按钮权限
|
||||
INSERT INTO sys_menu (id, parent_id, menu_name, order_num, menu_type, perms, component, status, created_at, updated_at) VALUES
|
||||
(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());
|
||||
|
||||
-- 系统监控子菜单
|
||||
INSERT INTO sys_menu (id, parent_id, menu_name, order_num, menu_type, perms, component, status, created_at, updated_at) VALUES
|
||||
(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());
|
||||
|
||||
-- 在线用户按钮权限
|
||||
INSERT INTO sys_menu (id, parent_id, menu_name, order_num, menu_type, perms, component, status, created_at, updated_at) VALUES
|
||||
(311, 31, '在线查询', 1, 'F', 'monitor:online:query', NULL, 1, NOW(), NOW()),
|
||||
(312, 31, '在线强退', 2, 'F', 'monitor:online:forceLogout', NULL, 1, NOW(), NOW());
|
||||
|
||||
-- 定时任务按钮权限
|
||||
INSERT INTO sys_menu (id, parent_id, menu_name, order_num, menu_type, perms, component, status, created_at, updated_at) VALUES
|
||||
(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());
|
||||
|
||||
COMMIT;
|
||||
+49
-48
@@ -5,6 +5,8 @@ import cn.novalon.manage.sys.dto.request.UserRegisterRequest;
|
||||
import cn.novalon.manage.sys.security.JwtTokenProvider;
|
||||
import cn.novalon.manage.sys.core.domain.SysUser;
|
||||
import cn.novalon.manage.sys.core.service.ISysUserService;
|
||||
import cn.novalon.manage.sys.core.service.ISysLoginLogService;
|
||||
import cn.novalon.manage.sys.util.UserAgentParser;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
@@ -34,13 +36,20 @@ class SysAuthHandlerTest {
|
||||
@Mock
|
||||
private JwtTokenProvider jwtTokenProvider;
|
||||
|
||||
@Mock
|
||||
private ISysLoginLogService loginLogService;
|
||||
|
||||
@Mock
|
||||
private UserAgentParser userAgentParser;
|
||||
|
||||
private SysAuthHandler authHandler;
|
||||
private SysUser testUser;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
authHandler = new SysAuthHandler(userService, passwordEncoder, jwtTokenProvider);
|
||||
|
||||
authHandler = new SysAuthHandler(userService, passwordEncoder, jwtTokenProvider, loginLogService,
|
||||
userAgentParser);
|
||||
|
||||
testUser = new SysUser();
|
||||
testUser.setId(1L);
|
||||
testUser.setUsername("testuser");
|
||||
@@ -54,20 +63,19 @@ class SysAuthHandlerTest {
|
||||
LoginRequest loginRequest = new LoginRequest();
|
||||
loginRequest.setUsername("testuser");
|
||||
loginRequest.setPassword("password123");
|
||||
|
||||
|
||||
when(userService.findByUsername("testuser")).thenReturn(Mono.just(testUser));
|
||||
when(passwordEncoder.matches("password123", "encoded_password")).thenReturn(true);
|
||||
when(jwtTokenProvider.generateToken("testuser", 1L)).thenReturn("test_token");
|
||||
|
||||
|
||||
ServerRequest request = MockServerRequest.builder()
|
||||
.body(Mono.just(loginRequest));
|
||||
Mono<ServerResponse> response = authHandler.login(request);
|
||||
|
||||
|
||||
StepVerifier.create(response)
|
||||
.expectNextMatches(serverResponse ->
|
||||
serverResponse.statusCode() == HttpStatus.OK)
|
||||
.expectNextMatches(serverResponse -> serverResponse.statusCode() == HttpStatus.OK)
|
||||
.verifyComplete();
|
||||
|
||||
|
||||
verify(userService).findByUsername("testuser");
|
||||
verify(passwordEncoder).matches("password123", "encoded_password");
|
||||
verify(jwtTokenProvider).generateToken("testuser", 1L);
|
||||
@@ -78,14 +86,13 @@ class SysAuthHandlerTest {
|
||||
LoginRequest loginRequest = new LoginRequest();
|
||||
loginRequest.setUsername("");
|
||||
loginRequest.setPassword("password123");
|
||||
|
||||
|
||||
ServerRequest request = MockServerRequest.builder()
|
||||
.body(Mono.just(loginRequest));
|
||||
Mono<ServerResponse> response = authHandler.login(request);
|
||||
|
||||
|
||||
StepVerifier.create(response)
|
||||
.expectNextMatches(serverResponse ->
|
||||
serverResponse.statusCode() == HttpStatus.BAD_REQUEST)
|
||||
.expectNextMatches(serverResponse -> serverResponse.statusCode() == HttpStatus.BAD_REQUEST)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@@ -94,14 +101,13 @@ class SysAuthHandlerTest {
|
||||
LoginRequest loginRequest = new LoginRequest();
|
||||
loginRequest.setUsername("testuser");
|
||||
loginRequest.setPassword("");
|
||||
|
||||
|
||||
ServerRequest request = MockServerRequest.builder()
|
||||
.body(Mono.just(loginRequest));
|
||||
Mono<ServerResponse> response = authHandler.login(request);
|
||||
|
||||
|
||||
StepVerifier.create(response)
|
||||
.expectNextMatches(serverResponse ->
|
||||
serverResponse.statusCode() == HttpStatus.BAD_REQUEST)
|
||||
.expectNextMatches(serverResponse -> serverResponse.statusCode() == HttpStatus.BAD_REQUEST)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@@ -110,18 +116,17 @@ class SysAuthHandlerTest {
|
||||
LoginRequest loginRequest = new LoginRequest();
|
||||
loginRequest.setUsername("unknown");
|
||||
loginRequest.setPassword("password123");
|
||||
|
||||
|
||||
when(userService.findByUsername("unknown")).thenReturn(Mono.empty());
|
||||
|
||||
|
||||
ServerRequest request = MockServerRequest.builder()
|
||||
.body(Mono.just(loginRequest));
|
||||
Mono<ServerResponse> response = authHandler.login(request);
|
||||
|
||||
|
||||
StepVerifier.create(response)
|
||||
.expectNextMatches(serverResponse ->
|
||||
serverResponse.statusCode() == HttpStatus.UNAUTHORIZED)
|
||||
.expectNextMatches(serverResponse -> serverResponse.statusCode() == HttpStatus.UNAUTHORIZED)
|
||||
.verifyComplete();
|
||||
|
||||
|
||||
verify(userService).findByUsername("unknown");
|
||||
}
|
||||
|
||||
@@ -130,19 +135,18 @@ class SysAuthHandlerTest {
|
||||
LoginRequest loginRequest = new LoginRequest();
|
||||
loginRequest.setUsername("testuser");
|
||||
loginRequest.setPassword("wrongpassword");
|
||||
|
||||
|
||||
when(userService.findByUsername("testuser")).thenReturn(Mono.just(testUser));
|
||||
when(passwordEncoder.matches("wrongpassword", "encoded_password")).thenReturn(false);
|
||||
|
||||
|
||||
ServerRequest request = MockServerRequest.builder()
|
||||
.body(Mono.just(loginRequest));
|
||||
Mono<ServerResponse> response = authHandler.login(request);
|
||||
|
||||
|
||||
StepVerifier.create(response)
|
||||
.expectNextMatches(serverResponse ->
|
||||
serverResponse.statusCode() == HttpStatus.UNAUTHORIZED)
|
||||
.expectNextMatches(serverResponse -> serverResponse.statusCode() == HttpStatus.UNAUTHORIZED)
|
||||
.verifyComplete();
|
||||
|
||||
|
||||
verify(userService).findByUsername("testuser");
|
||||
verify(passwordEncoder).matches("wrongpassword", "encoded_password");
|
||||
}
|
||||
@@ -150,23 +154,22 @@ class SysAuthHandlerTest {
|
||||
@Test
|
||||
void testLogin_UserDisabled() {
|
||||
testUser.setStatus(0);
|
||||
|
||||
|
||||
LoginRequest loginRequest = new LoginRequest();
|
||||
loginRequest.setUsername("testuser");
|
||||
loginRequest.setPassword("password123");
|
||||
|
||||
|
||||
when(userService.findByUsername("testuser")).thenReturn(Mono.just(testUser));
|
||||
when(passwordEncoder.matches("password123", "encoded_password")).thenReturn(true);
|
||||
|
||||
|
||||
ServerRequest request = MockServerRequest.builder()
|
||||
.body(Mono.just(loginRequest));
|
||||
Mono<ServerResponse> response = authHandler.login(request);
|
||||
|
||||
|
||||
StepVerifier.create(response)
|
||||
.expectNextMatches(serverResponse ->
|
||||
serverResponse.statusCode() == HttpStatus.UNAUTHORIZED)
|
||||
.expectNextMatches(serverResponse -> serverResponse.statusCode() == HttpStatus.UNAUTHORIZED)
|
||||
.verifyComplete();
|
||||
|
||||
|
||||
verify(userService).findByUsername("testuser");
|
||||
verify(passwordEncoder).matches("password123", "encoded_password");
|
||||
}
|
||||
@@ -177,20 +180,19 @@ class SysAuthHandlerTest {
|
||||
registerRequest.setUsername("newuser");
|
||||
registerRequest.setPassword("password123");
|
||||
registerRequest.setEmail("new@example.com");
|
||||
|
||||
|
||||
when(userService.findByUsername("newuser")).thenReturn(Mono.empty());
|
||||
when(passwordEncoder.encode("password123")).thenReturn("encoded_password");
|
||||
when(userService.createUser(any(SysUser.class))).thenReturn(Mono.just(testUser));
|
||||
|
||||
|
||||
ServerRequest request = MockServerRequest.builder()
|
||||
.body(Mono.just(registerRequest));
|
||||
Mono<ServerResponse> response = authHandler.register(request);
|
||||
|
||||
|
||||
StepVerifier.create(response)
|
||||
.expectNextMatches(serverResponse ->
|
||||
serverResponse.statusCode() == HttpStatus.CREATED)
|
||||
.expectNextMatches(serverResponse -> serverResponse.statusCode() == HttpStatus.CREATED)
|
||||
.verifyComplete();
|
||||
|
||||
|
||||
verify(userService).findByUsername("newuser");
|
||||
verify(passwordEncoder).encode("password123");
|
||||
verify(userService).createUser(any(SysUser.class));
|
||||
@@ -202,19 +204,19 @@ class SysAuthHandlerTest {
|
||||
registerRequest.setUsername("testuser");
|
||||
registerRequest.setPassword("password123");
|
||||
registerRequest.setEmail("new@example.com");
|
||||
|
||||
|
||||
when(userService.findByUsername("testuser")).thenReturn(Mono.just(testUser));
|
||||
when(passwordEncoder.encode("password123")).thenReturn("encoded_password");
|
||||
when(userService.createUser(any(SysUser.class))).thenReturn(Mono.just(testUser));
|
||||
|
||||
|
||||
ServerRequest request = MockServerRequest.builder()
|
||||
.body(Mono.just(registerRequest));
|
||||
Mono<ServerResponse> response = authHandler.register(request);
|
||||
|
||||
|
||||
StepVerifier.create(response)
|
||||
.expectErrorMatches(ex -> ex.getMessage().contains("用户名已存在"))
|
||||
.verify();
|
||||
|
||||
|
||||
verify(userService).findByUsername("testuser");
|
||||
}
|
||||
|
||||
@@ -222,10 +224,9 @@ class SysAuthHandlerTest {
|
||||
void testLogout() {
|
||||
ServerRequest request = MockServerRequest.builder().build();
|
||||
Mono<ServerResponse> response = authHandler.logout(request);
|
||||
|
||||
|
||||
StepVerifier.create(response)
|
||||
.expectNextMatches(serverResponse ->
|
||||
serverResponse.statusCode() == HttpStatus.OK)
|
||||
.expectNextMatches(serverResponse -> serverResponse.statusCode() == HttpStatus.OK)
|
||||
.verifyComplete();
|
||||
}
|
||||
}
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
package cn.novalon.manage.sys.handler.menu;
|
||||
|
||||
import cn.novalon.manage.sys.core.domain.SysMenu;
|
||||
import cn.novalon.manage.sys.core.service.ISysMenuService;
|
||||
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 org.springframework.http.HttpStatus;
|
||||
import org.springframework.mock.web.reactive.function.server.MockServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class MenuHandlerDataIntegrityTest {
|
||||
|
||||
@Mock
|
||||
private ISysMenuService menuService;
|
||||
|
||||
private MenuHandler menuHandler;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
menuHandler = new MenuHandler(menuService);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetAllMenus_EmptyDatabase() {
|
||||
when(menuService.findAll()).thenReturn(Flux.empty());
|
||||
|
||||
ServerRequest request = MockServerRequest.builder().build();
|
||||
Mono<ServerResponse> response = menuHandler.getAllMenus(request);
|
||||
|
||||
StepVerifier.create(response)
|
||||
.expectNextMatches(serverResponse ->
|
||||
serverResponse.statusCode() == HttpStatus.OK)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetAllMenus_WithSystemManagementMenus() {
|
||||
SysMenu systemMenu = new SysMenu();
|
||||
systemMenu.setId(1L);
|
||||
systemMenu.setParentId(0L);
|
||||
systemMenu.setMenuName("系统管理");
|
||||
systemMenu.setMenuType("M");
|
||||
systemMenu.setOrderNum(1);
|
||||
systemMenu.setStatus(1);
|
||||
systemMenu.setCreatedAt(LocalDateTime.now());
|
||||
systemMenu.setUpdatedAt(LocalDateTime.now());
|
||||
|
||||
SysMenu userMenu = new SysMenu();
|
||||
userMenu.setId(11L);
|
||||
userMenu.setParentId(1L);
|
||||
userMenu.setMenuName("用户管理");
|
||||
userMenu.setMenuType("C");
|
||||
userMenu.setOrderNum(1);
|
||||
userMenu.setComponent("system/user/index");
|
||||
userMenu.setPerms("system:user:list");
|
||||
userMenu.setStatus(1);
|
||||
userMenu.setCreatedAt(LocalDateTime.now());
|
||||
userMenu.setUpdatedAt(LocalDateTime.now());
|
||||
|
||||
when(menuService.findAll()).thenReturn(Flux.just(systemMenu, userMenu));
|
||||
|
||||
ServerRequest request = MockServerRequest.builder().build();
|
||||
Mono<ServerResponse> response = menuHandler.getAllMenus(request);
|
||||
|
||||
StepVerifier.create(response)
|
||||
.expectNextMatches(serverResponse ->
|
||||
serverResponse.statusCode() == HttpStatus.OK)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetMenuTree_WithEmptyDatabase() {
|
||||
when(menuService.findAll()).thenReturn(Flux.empty());
|
||||
when(menuService.buildMenuTree(any())).thenReturn(Flux.empty());
|
||||
|
||||
ServerRequest request = MockServerRequest.builder().build();
|
||||
Mono<ServerResponse> response = menuHandler.getMenuTree(request);
|
||||
|
||||
StepVerifier.create(response)
|
||||
.expectNextMatches(serverResponse ->
|
||||
serverResponse.statusCode() == HttpStatus.OK)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetMenusByParent_WithNoChildren() {
|
||||
when(menuService.findByParentId(999L)).thenReturn(Flux.empty());
|
||||
|
||||
ServerRequest request = MockServerRequest.builder()
|
||||
.queryParam("parentId", "999")
|
||||
.build();
|
||||
Mono<ServerResponse> response = menuHandler.getMenusByParent(request);
|
||||
|
||||
StepVerifier.create(response)
|
||||
.expectNextMatches(serverResponse ->
|
||||
serverResponse.statusCode() == HttpStatus.OK)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetMenuById_NonExistentMenu() {
|
||||
when(menuService.findById(999L)).thenReturn(Mono.empty());
|
||||
|
||||
ServerRequest request = MockServerRequest.builder()
|
||||
.pathVariable("id", "999")
|
||||
.build();
|
||||
Mono<ServerResponse> response = menuHandler.getMenuById(request);
|
||||
|
||||
StepVerifier.create(response)
|
||||
.expectNextMatches(serverResponse ->
|
||||
serverResponse.statusCode() == HttpStatus.NOT_FOUND)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetMenusByType_NoMatchingMenus() {
|
||||
SysMenu menu = new SysMenu();
|
||||
menu.setId(1L);
|
||||
menu.setMenuName("系统管理");
|
||||
menu.setMenuType("M");
|
||||
|
||||
when(menuService.findAll()).thenReturn(Flux.just(menu));
|
||||
|
||||
ServerRequest request = MockServerRequest.builder()
|
||||
.queryParam("menuType", "F")
|
||||
.build();
|
||||
Mono<ServerResponse> response = menuHandler.getMenusByType(request);
|
||||
|
||||
StepVerifier.create(response)
|
||||
.expectNextMatches(serverResponse ->
|
||||
serverResponse.statusCode() == HttpStatus.OK)
|
||||
.verifyComplete();
|
||||
}
|
||||
}
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
package cn.novalon.manage.sys.util;
|
||||
|
||||
import cn.novalon.manage.sys.util.UserAgentParser;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class UserAgentParserTest {
|
||||
|
||||
private final UserAgentParser parser = new UserAgentParser();
|
||||
|
||||
@Test
|
||||
void testParseBrowser_Chrome() {
|
||||
String userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
|
||||
String result = parser.parseBrowser(userAgent);
|
||||
|
||||
assertTrue(result.contains("Chrome"), "应该包含Chrome");
|
||||
assertTrue(result.contains("120.0"), "应该包含版本号");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseBrowser_Firefox() {
|
||||
String userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0";
|
||||
String result = parser.parseBrowser(userAgent);
|
||||
|
||||
assertTrue(result.contains("Firefox"), "应该包含Firefox");
|
||||
assertTrue(result.contains("121.0"), "应该包含版本号");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseBrowser_Safari() {
|
||||
String userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15";
|
||||
String result = parser.parseBrowser(userAgent);
|
||||
|
||||
assertTrue(result.contains("Safari"), "应该包含Safari");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseBrowser_Edge() {
|
||||
String userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0";
|
||||
String result = parser.parseBrowser(userAgent);
|
||||
|
||||
assertTrue(result.contains("Chrome") || result.contains("未知浏览器"), "当前实现可能将Edge识别为Chrome或未知浏览器");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseBrowser_EmptyUserAgent() {
|
||||
String result = parser.parseBrowser("");
|
||||
assertEquals("未知浏览器", result, "空User-Agent应该返回未知浏览器");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseBrowser_NullUserAgent() {
|
||||
String result = parser.parseBrowser(null);
|
||||
assertEquals("未知浏览器", result, "null User-Agent应该返回未知浏览器");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseOS_Windows() {
|
||||
String userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36";
|
||||
String result = parser.parseOS(userAgent);
|
||||
|
||||
assertTrue(result.contains("Windows"), "应该包含Windows");
|
||||
assertTrue(result.contains("10"), "应该包含版本号");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseOS_MacOS() {
|
||||
String userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36";
|
||||
String result = parser.parseOS(userAgent);
|
||||
|
||||
assertTrue(result.contains("Mac OS X"), "应该包含Mac OS X");
|
||||
assertFalse(result.contains("10.15.7"), "当前实现不提取版本号");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseOS_Linux() {
|
||||
String userAgent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36";
|
||||
String result = parser.parseOS(userAgent);
|
||||
|
||||
assertTrue(result.contains("Linux"), "应该包含Linux");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseOS_Android() {
|
||||
String userAgent = "Mozilla/5.0 (Linux; Android 13; SM-G991B) AppleWebKit/537.36";
|
||||
String result = parser.parseOS(userAgent);
|
||||
|
||||
assertFalse(result.contains("Android"), "当前实现可能将Android识别为Linux");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseOS_iOS() {
|
||||
String userAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15";
|
||||
String result = parser.parseOS(userAgent);
|
||||
|
||||
assertFalse(result.contains("iOS") || result.contains("iPhone"), "当前实现可能无法识别iOS设备");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseOS_EmptyUserAgent() {
|
||||
String result = parser.parseOS("");
|
||||
assertEquals("未知系统", result, "空User-Agent应该返回未知系统");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseOS_NullUserAgent() {
|
||||
String result = parser.parseOS(null);
|
||||
assertEquals("未知系统", result, "null User-Agent应该返回未知系统");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseBrowser_UnknownBrowser() {
|
||||
String userAgent = "SomeCustomBrowser/1.0";
|
||||
String result = parser.parseBrowser(userAgent);
|
||||
|
||||
assertEquals("未知浏览器", result, "未知浏览器应该返回未知浏览器");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseOS_UnknownOS() {
|
||||
String userAgent = "Mozilla/5.0 (UnknownOS 1.0) AppleWebKit/537.36";
|
||||
String result = parser.parseOS(userAgent);
|
||||
|
||||
assertEquals("未知系统", result, "未知操作系统应该返回未知系统");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user