refactor(security): 重构安全配置并优化测试环境
- 移除旧的测试套件和UAT测试文件 - 更新密码编码器配置使用BCrypt strength=12 - 添加用户角色关联表和相关服务 - 优化前端日期显示格式 - 清理无用资源和配置文件 - 增强测试数据管理和清理功能
This commit is contained in:
@@ -41,6 +41,10 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-r2dbc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-jdbc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-r2dbc</artifactId>
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package cn.novalon.manage.db.config;
|
||||
|
||||
import org.flywaydb.core.Flyway;
|
||||
import org.springframework.boot.autoconfigure.flyway.FlywayProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
@Configuration
|
||||
public class FlywayConfig {
|
||||
|
||||
@Bean
|
||||
@Profile("!test")
|
||||
public Flyway flyway(DataSource dataSource, FlywayProperties flywayProperties) {
|
||||
Flyway flyway = Flyway.configure()
|
||||
.dataSource(dataSource)
|
||||
.locations(flywayProperties.getLocations().toArray(new String[0]))
|
||||
.baselineOnMigrate(true)
|
||||
.baselineVersion("0")
|
||||
.table("flyway_schema_history")
|
||||
.validateOnMigrate(true)
|
||||
.outOfOrder(false)
|
||||
.load();
|
||||
|
||||
flyway.migrate();
|
||||
return flyway;
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package cn.novalon.manage.db.converter;
|
||||
|
||||
import cn.novalon.manage.db.entity.UserRoleEntity;
|
||||
import cn.novalon.manage.sys.core.domain.UserRole;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class UserRoleConverter {
|
||||
|
||||
public UserRole toDomain(UserRoleEntity entity) {
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
UserRole domain = new UserRole();
|
||||
domain.setId(entity.getId());
|
||||
domain.setUserId(entity.getUserId());
|
||||
domain.setRoleId(entity.getRoleId());
|
||||
domain.setCreatedAt(entity.getCreatedAt());
|
||||
domain.setCreatedBy(entity.getCreatedBy());
|
||||
return domain;
|
||||
}
|
||||
|
||||
public UserRoleEntity toEntity(UserRole domain) {
|
||||
if (domain == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
UserRoleEntity entity = new UserRoleEntity();
|
||||
entity.setId(domain.getId());
|
||||
entity.setUserId(domain.getUserId());
|
||||
entity.setRoleId(domain.getRoleId());
|
||||
entity.setCreatedAt(domain.getCreatedAt());
|
||||
entity.setCreatedBy(domain.getCreatedBy());
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package cn.novalon.manage.db.dao;
|
||||
|
||||
import cn.novalon.manage.db.entity.UserRoleEntity;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.r2dbc.repository.R2dbcRepository;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
public interface UserRoleDao extends R2dbcRepository<UserRoleEntity, Long> {
|
||||
|
||||
Flux<UserRoleEntity> findByUserId(Long userId);
|
||||
|
||||
Flux<UserRoleEntity> findByUserId(Long userId, Sort sort);
|
||||
|
||||
Flux<UserRoleEntity> findByRoleId(Long roleId);
|
||||
|
||||
Flux<UserRoleEntity> findByRoleId(Long roleId, Sort sort);
|
||||
|
||||
Mono<Long> countByUserId(Long userId);
|
||||
|
||||
Mono<Long> countByRoleId(Long roleId);
|
||||
|
||||
Mono<Void> deleteByUserId(Long userId);
|
||||
|
||||
Mono<Void> deleteByRoleId(Long roleId);
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
package cn.novalon.manage.db.entity;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.relational.core.mapping.Column;
|
||||
import org.springframework.data.relational.core.mapping.Table;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Table("user_role")
|
||||
public class UserRoleEntity {
|
||||
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
@Column("user_id")
|
||||
private Long userId;
|
||||
|
||||
@Column("role_id")
|
||||
private Long roleId;
|
||||
|
||||
@Column("created_at")
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@Column("created_by")
|
||||
private String createdBy;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Long getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public void setUserId(Long userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
public Long getRoleId() {
|
||||
return roleId;
|
||||
}
|
||||
|
||||
public void setRoleId(Long roleId) {
|
||||
this.roleId = roleId;
|
||||
}
|
||||
|
||||
public LocalDateTime getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(LocalDateTime createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public String getCreatedBy() {
|
||||
return createdBy;
|
||||
}
|
||||
|
||||
public void setCreatedBy(String createdBy) {
|
||||
this.createdBy = createdBy;
|
||||
}
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
package cn.novalon.manage.db.repository;
|
||||
|
||||
import cn.novalon.manage.db.converter.UserRoleConverter;
|
||||
import cn.novalon.manage.db.dao.UserRoleDao;
|
||||
import cn.novalon.manage.db.entity.UserRoleEntity;
|
||||
import cn.novalon.manage.sys.core.domain.UserRole;
|
||||
import cn.novalon.manage.sys.core.repository.IUserRoleRepository;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@Repository
|
||||
public class UserRoleRepository implements IUserRoleRepository {
|
||||
|
||||
private final UserRoleDao userRoleDao;
|
||||
private final UserRoleConverter userRoleConverter;
|
||||
|
||||
public UserRoleRepository(UserRoleDao userRoleDao, UserRoleConverter userRoleConverter) {
|
||||
this.userRoleDao = userRoleDao;
|
||||
this.userRoleConverter = userRoleConverter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<UserRole> save(UserRole userRole) {
|
||||
UserRoleEntity entity = userRoleConverter.toEntity(userRole);
|
||||
return userRoleDao.save(entity)
|
||||
.map(userRoleConverter::toDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> deleteById(Long id) {
|
||||
return userRoleDao.deleteById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> deleteByUserId(Long userId) {
|
||||
return userRoleDao.deleteByUserId(userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> deleteByRoleId(Long roleId) {
|
||||
return userRoleDao.deleteByRoleId(roleId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<UserRole> findByUserId(Long userId) {
|
||||
return userRoleDao.findByUserId(userId, Sort.by(Sort.Direction.DESC, "created_at"))
|
||||
.map(userRoleConverter::toDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<UserRole> findByRoleId(Long roleId) {
|
||||
return userRoleDao.findByRoleId(roleId, Sort.by(Sort.Direction.DESC, "created_at"))
|
||||
.map(userRoleConverter::toDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Long> countByUserId(Long userId) {
|
||||
return userRoleDao.countByUserId(userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Long> countByRoleId(Long roleId) {
|
||||
return userRoleDao.countByRoleId(roleId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<UserRole> findAll() {
|
||||
return userRoleDao.findAll()
|
||||
.map(userRoleConverter::toDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<UserRole> findById(Long id) {
|
||||
return userRoleDao.findById(id)
|
||||
.map(userRoleConverter::toDomain);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
spring:
|
||||
flyway:
|
||||
enabled: true
|
||||
locations: classpath:db/migration
|
||||
baseline-on-migrate: true
|
||||
baseline-version: 0
|
||||
table: flyway_schema_history
|
||||
validate-on-migrate: true
|
||||
out-of-order: false
|
||||
+6
-4
@@ -9,7 +9,6 @@ CREATE TABLE IF NOT EXISTS users (
|
||||
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),
|
||||
@@ -32,8 +31,8 @@ CREATE TABLE IF NOT EXISTS roles (
|
||||
deleted_at TIMESTAMP
|
||||
);
|
||||
|
||||
-- 菜单表
|
||||
CREATE TABLE IF NOT EXISTS menus (
|
||||
-- 菜单表(统一使用sys_menu表名)
|
||||
CREATE TABLE IF NOT EXISTS sys_menu (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
menu_name VARCHAR(50) NOT NULL,
|
||||
parent_id BIGINT DEFAULT 0,
|
||||
@@ -123,7 +122,7 @@ CREATE TABLE IF NOT EXISTS sys_login_log (
|
||||
login_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 异常日志表(修复后的结构)
|
||||
-- 异常日志表
|
||||
CREATE TABLE IF NOT EXISTS sys_exception_log (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
username VARCHAR(50),
|
||||
@@ -234,3 +233,6 @@ COMMENT ON COLUMN sys_exception_log.exception_msg IS '异常消息';
|
||||
COMMENT ON COLUMN sys_exception_log.exception_stack IS '异常堆栈';
|
||||
COMMENT ON COLUMN sys_exception_log.ip IS 'IP地址';
|
||||
COMMENT ON COLUMN sys_exception_log.create_time IS '创建时间';
|
||||
|
||||
COMMENT ON TABLE sys_menu IS '系统菜单表';
|
||||
COMMENT ON TABLE sys_login_log IS '登录日志表';
|
||||
+3
-3
@@ -9,8 +9,8 @@ ON CONFLICT (role_key) DO NOTHING;
|
||||
|
||||
-- 插入初始管理员用户
|
||||
-- BCrypt哈希值对应明文密码: admin123
|
||||
INSERT INTO users (id, username, password, email, phone, role_id, status, create_by, update_by)
|
||||
VALUES (1, 'admin', '$2b$12$SFefXlGRFMA0fvxIufpWPuIAl0OPLgRDoCZPThCvjpiJGPYS8yNYy', 'admin@novalon.com', '13800138000', 1, 1, 'system', 'system')
|
||||
INSERT INTO users (id, username, password, email, phone, status, create_by, update_by)
|
||||
VALUES (1, 'admin', '$2b$12$SFefXlGRFMA0fvxIufpWPuIAl0OPLgRDoCZPThCvjpiJGPYS8yNYy', 'admin@novalon.com', '13800138000', 1, 'system', 'system')
|
||||
ON CONFLICT (username) DO UPDATE SET
|
||||
password = EXCLUDED.password,
|
||||
status = EXCLUDED.status;
|
||||
@@ -56,4 +56,4 @@ SELECT setval('users_id_seq', (SELECT COALESCE(MAX(id), 1) FROM users));
|
||||
SELECT setval('roles_id_seq', (SELECT COALESCE(MAX(id), 1) FROM roles));
|
||||
SELECT setval('sys_dict_type_id_seq', (SELECT COALESCE(MAX(id), 1) FROM sys_dict_type));
|
||||
SELECT setval('sys_dict_data_id_seq', (SELECT COALESCE(MAX(id), 1) FROM sys_dict_data));
|
||||
SELECT setval('sys_config_id_seq', (SELECT COALESCE(MAX(id), 1) FROM sys_config));
|
||||
SELECT setval('sys_config_id_seq', (SELECT COALESCE(MAX(id), 1) FROM sys_config));
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
-- 创建用户角色关联表(支持多对多关系)
|
||||
CREATE TABLE IF NOT EXISTS user_role (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL,
|
||||
role_id BIGINT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
created_by VARCHAR(50),
|
||||
CONSTRAINT fk_user_role_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_user_role_role FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE,
|
||||
CONSTRAINT uk_user_role UNIQUE (user_id, role_id)
|
||||
);
|
||||
|
||||
-- 创建索引
|
||||
CREATE INDEX IF NOT EXISTS idx_user_role_user_id ON user_role(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_user_role_role_id ON user_role(role_id);
|
||||
|
||||
-- 表注释
|
||||
COMMENT ON TABLE user_role IS '用户角色关联表';
|
||||
COMMENT ON COLUMN user_role.id IS '主键ID';
|
||||
COMMENT ON COLUMN user_role.user_id IS '用户ID';
|
||||
COMMENT ON COLUMN user_role.role_id IS '角色ID';
|
||||
COMMENT ON COLUMN user_role.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN user_role.created_by IS '创建人';
|
||||
+5
-6
@@ -1,11 +1,10 @@
|
||||
-- Novalon管理系统索引优化脚本
|
||||
-- 版本: V3
|
||||
-- 版本: V5
|
||||
-- 描述: 为表创建必要的索引以提升查询性能
|
||||
|
||||
-- 用户表索引
|
||||
CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);
|
||||
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
|
||||
CREATE INDEX IF NOT EXISTS idx_users_role_id ON users(role_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_users_status ON users(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_users_deleted_at ON users(deleted_at);
|
||||
|
||||
@@ -15,9 +14,9 @@ CREATE INDEX IF NOT EXISTS idx_roles_status ON roles(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_roles_deleted_at ON roles(deleted_at);
|
||||
|
||||
-- 菜单表索引
|
||||
CREATE INDEX IF NOT EXISTS idx_menus_parent_id ON menus(parent_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_menus_status ON menus(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_menus_deleted_at ON menus(deleted_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_sys_menu_parent_id ON sys_menu(parent_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_sys_menu_status ON sys_menu(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_sys_menu_deleted_at ON sys_menu(deleted_at);
|
||||
|
||||
-- 字典类型表索引
|
||||
CREATE INDEX IF NOT EXISTS idx_sys_dict_type_dict_type ON sys_dict_type(dict_type);
|
||||
@@ -76,4 +75,4 @@ CREATE INDEX IF NOT EXISTS idx_sys_file_deleted_at ON sys_file(deleted_at);
|
||||
-- OAuth2客户端表索引
|
||||
CREATE INDEX IF NOT EXISTS idx_oauth2_client_client_id ON oauth2_client(client_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_oauth2_client_enabled ON oauth2_client(enabled);
|
||||
CREATE INDEX IF NOT EXISTS idx_oauth2_client_deleted_at ON oauth2_client(deleted_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_oauth2_client_deleted_at ON oauth2_client(deleted_at);
|
||||
@@ -0,0 +1,90 @@
|
||||
-- 系统菜单初始化数据
|
||||
-- 版本: V6
|
||||
-- 描述: 初始化系统菜单数据
|
||||
|
||||
-- 一级菜单
|
||||
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());
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
package cn.novalon.manage.db.config;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class FlywayMigrationScriptTest {
|
||||
|
||||
@Test
|
||||
void testMigrationScriptsExist() throws IOException {
|
||||
Path migrationDir = Paths.get("src/main/resources/db/migration");
|
||||
|
||||
assertTrue(Files.exists(migrationDir), "Migration directory should exist");
|
||||
|
||||
List<Path> sqlFiles = Files.list(migrationDir)
|
||||
.filter(p -> p.toString().endsWith(".sql"))
|
||||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
assertFalse(sqlFiles.isEmpty(), "Should have migration scripts");
|
||||
|
||||
System.out.println("Found migration scripts:");
|
||||
sqlFiles.forEach(p -> System.out.println(" - " + p.getFileName()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMigrationScriptNaming() throws IOException {
|
||||
Path migrationDir = Paths.get("src/main/resources/db/migration");
|
||||
|
||||
List<Path> sqlFiles = Files.list(migrationDir)
|
||||
.filter(p -> p.toString().endsWith(".sql"))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
for (Path file : sqlFiles) {
|
||||
String filename = file.getFileName().toString();
|
||||
assertTrue(filename.matches("V\\d+__.*\\.sql"),
|
||||
"Migration script should follow Flyway naming convention: " + filename);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMigrationScriptContent() throws IOException {
|
||||
Path migrationDir = Paths.get("src/main/resources/db/migration");
|
||||
|
||||
List<Path> sqlFiles = Files.list(migrationDir)
|
||||
.filter(p -> p.toString().endsWith(".sql"))
|
||||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
for (Path file : sqlFiles) {
|
||||
String content = Files.readString(file);
|
||||
assertNotNull(content, "Migration script should have content: " + file.getFileName());
|
||||
assertFalse(content.trim().isEmpty(), "Migration script should not be empty: " + file.getFileName());
|
||||
|
||||
if (content.contains("CREATE TABLE")) {
|
||||
assertTrue(content.contains("IF NOT EXISTS"),
|
||||
"CREATE TABLE statements should use IF NOT EXISTS: " + file.getFileName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMigrationScriptVersionOrder() throws IOException {
|
||||
Path migrationDir = Paths.get("src/main/resources/db/migration");
|
||||
|
||||
List<Path> sqlFiles = Files.list(migrationDir)
|
||||
.filter(p -> p.toString().endsWith(".sql"))
|
||||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<Integer> versions = sqlFiles.stream()
|
||||
.map(p -> {
|
||||
String filename = p.getFileName().toString();
|
||||
String versionStr = filename.substring(1, filename.indexOf("__"));
|
||||
return Integer.parseInt(versionStr);
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
for (int i = 1; i < versions.size(); i++) {
|
||||
assertTrue(versions.get(i) > versions.get(i - 1),
|
||||
"Migration versions should be in ascending order");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
spring:
|
||||
r2dbc:
|
||||
url: r2dbc:h2:mem:testdb;MODE=PostgreSQL
|
||||
username: sa
|
||||
password:
|
||||
flyway:
|
||||
enabled: true
|
||||
locations: classpath:db/migration
|
||||
baseline-on-migrate: true
|
||||
baseline-version: 0
|
||||
table: flyway_schema_history
|
||||
validate-on-migrate: true
|
||||
out-of-order: false
|
||||
Reference in New Issue
Block a user