feat: 统一JWT密钥配置并修复签名验证问题

修复前端签名生成中bodyString硬编码问题
添加start-frontend.sh脚本启动前端服务
统一manage-app和gateway的JWT密钥配置
修复Repository扫描路径问题
更新测试配置和依赖
重构表名映射为sys_user和sys_role
完善用户实体类字段映射
添加集成测试配置和测试用例
This commit is contained in:
张翔
2026-04-02 12:28:49 +08:00
parent 6392c08560
commit b0f91d74f5
63 changed files with 3287 additions and 889 deletions
@@ -12,7 +12,7 @@ import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories;
@SpringBootApplication(exclude = {ReactiveUserDetailsServiceAutoConfiguration.class})
@ConfigurationPropertiesScan(basePackages = "cn.novalon.manage")
@ComponentScan(basePackages = "cn.novalon.manage")
@EnableR2dbcRepositories(basePackages = {"cn.novalon.manage.db.dao"})
@EnableR2dbcRepositories(basePackages = {"cn.novalon.manage.db.dao", "cn.novalon.manage.sys.audit.repository"})
public class ManageApplication {
private static final Logger logger = LoggerFactory.getLogger(ManageApplication.class);
@@ -46,6 +46,10 @@ logging:
org.springframework.r2dbc: DEBUG
cn.novalon.manage.db: DEBUG
jwt:
secret: ${JWT_SECRET:U2FsdGVkX1+vZ5Y9QmKxL8nN3rP7tW2jH4fG6dA8sB1cE5yN0zX3qV7wM4}
expiration: ${JWT_EXPIRATION:86400000}
springdoc:
api-docs:
path: /api-docs
@@ -0,0 +1,29 @@
package cn.novalon.manage.app.config;
import io.r2dbc.spi.ConnectionFactory;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.ClassPathResource;
import org.springframework.r2dbc.connection.init.ConnectionFactoryInitializer;
import org.springframework.r2dbc.connection.init.ResourceDatabasePopulator;
/**
* 测试数据库配置类
*
* 初始化H2内存数据库schema
*
* @author 张翔
* @date 2026-04-02
*/
@TestConfiguration
public class TestDatabaseConfig {
@Bean
public ConnectionFactoryInitializer initializer(ConnectionFactory connectionFactory) {
ConnectionFactoryInitializer initializer = new ConnectionFactoryInitializer();
initializer.setConnectionFactory(connectionFactory);
initializer.setDatabasePopulator(new ResourceDatabasePopulator(
new ClassPathResource("schema-h2.sql")));
return initializer;
}
}
@@ -0,0 +1,222 @@
package cn.novalon.manage.app.integration;
import cn.novalon.manage.app.config.TestDatabaseConfig;
import cn.novalon.manage.common.util.StatusConstants;
import cn.novalon.manage.sys.core.domain.SysUser;
import cn.novalon.manage.sys.core.domain.SysRole;
import cn.novalon.manage.sys.core.domain.UserRole;
import cn.novalon.manage.sys.core.repository.ISysUserRepository;
import cn.novalon.manage.sys.core.repository.ISysRoleRepository;
import cn.novalon.manage.sys.core.repository.IUserRoleRepository;
import cn.novalon.manage.sys.core.service.impl.SysUserService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.test.context.ActiveProfiles;
import reactor.test.StepVerifier;
import java.util.Arrays;
import static org.junit.jupiter.api.Assertions.*;
/**
* 用户服务集成测试
*
* 使用H2内存数据库进行集成测试
*
* @author 张翔
* @date 2026-04-02
*/
@SpringBootTest
@ActiveProfiles("test")
@Import(TestDatabaseConfig.class)
class SysUserServiceIntegrationTest {
@Autowired
private ISysUserRepository userRepository;
@Autowired
private ISysRoleRepository roleRepository;
@Autowired
private IUserRoleRepository userRoleRepository;
@Autowired
private R2dbcEntityTemplate r2dbcEntityTemplate;
@Autowired
private SysUserService userService;
@Autowired
private PasswordEncoder passwordEncoder;
@BeforeEach
void setUp() {
r2dbcEntityTemplate.delete(SysUser.class).all().block();
r2dbcEntityTemplate.delete(SysRole.class).all().block();
r2dbcEntityTemplate.delete(UserRole.class).all().block();
}
@Test
void testCreateAndFindUser() {
SysUser user = new SysUser();
user.setUsername("testuser");
user.setPassword("password123");
user.setEmail("test@example.com");
user.setNickname("Test User");
user.setPhone("13800138000");
StepVerifier.create(userService.createUser(user))
.expectNextMatches(createdUser -> {
assertNotNull(createdUser.getId());
assertEquals("testuser", createdUser.getUsername());
assertEquals("test@example.com", createdUser.getEmail());
assertTrue(createdUser.getPassword().startsWith("$2"));
assertEquals(StatusConstants.ENABLED, createdUser.getStatus());
return true;
})
.verifyComplete();
StepVerifier.create(userService.findByUsername("testuser"))
.expectNextMatches(foundUser -> {
assertEquals("testuser", foundUser.getUsername());
assertEquals("test@example.com", foundUser.getEmail());
return true;
})
.verifyComplete();
}
@Test
void testUpdateUser() {
SysUser user = new SysUser();
user.setUsername("updateuser");
user.setPassword("password123");
user.setEmail("update@example.com");
SysUser createdUser = userService.createUser(user).block();
assertNotNull(createdUser);
createdUser.setEmail("updated@example.com");
createdUser.setNickname("Updated User");
StepVerifier.create(userService.updateUser(createdUser))
.expectNextMatches(updatedUser -> {
assertEquals("updated@example.com", updatedUser.getEmail());
assertEquals("Updated User", updatedUser.getNickname());
return true;
})
.verifyComplete();
}
@Test
void testDeleteUser() {
SysUser user = new SysUser();
user.setUsername("deleteuser");
user.setPassword("password123");
user.setEmail("delete@example.com");
SysUser createdUser = userService.createUser(user).block();
assertNotNull(createdUser);
StepVerifier.create(userService.deleteUser(createdUser.getId()))
.verifyComplete();
StepVerifier.create(userService.findById(createdUser.getId()))
.verifyComplete();
}
@Test
void testChangePassword() {
SysUser user = new SysUser();
user.setUsername("pwduser");
user.setPassword("oldPassword");
user.setEmail("pwd@example.com");
SysUser createdUser = userService.createUser(user).block();
assertNotNull(createdUser);
StepVerifier.create(userService.changePassword(createdUser.getId(), "oldPassword", "newPassword"))
.expectNextMatches(updatedUser -> {
assertNotEquals(createdUser.getPassword(), updatedUser.getPassword());
assertTrue(passwordEncoder.matches("newPassword", updatedUser.getPassword()));
return true;
})
.verifyComplete();
}
@Test
void testAssignRolesToUser() {
SysRole role1 = new SysRole();
role1.setRoleName("Test Role 1");
role1.setRoleKey("test_role_1");
role1.setStatus(1);
SysRole role2 = new SysRole();
role2.setRoleName("Test Role 2");
role2.setRoleKey("test_role_2");
role2.setStatus(1);
SysRole createdRole1 = roleRepository.save(role1).block();
SysRole createdRole2 = roleRepository.save(role2).block();
assertNotNull(createdRole1);
assertNotNull(createdRole2);
SysUser user = new SysUser();
user.setUsername("roleuser");
user.setPassword("password123");
user.setEmail("role@example.com");
SysUser createdUser = userService.createUser(user).block();
assertNotNull(createdUser);
StepVerifier.create(userService.assignRolesToUser(createdUser.getId(),
Arrays.asList(createdRole1.getId(), createdRole2.getId())))
.verifyComplete();
StepVerifier.create(userRoleRepository.findByUserId(createdUser.getId()).collectList())
.expectNextMatches(userRoles -> {
assertEquals(2, userRoles.size());
return true;
})
.verifyComplete();
}
@Test
void testFindAllUsers() {
for (int i = 1; i <= 3; i++) {
SysUser user = new SysUser();
user.setUsername("user" + i);
user.setPassword("password" + i);
user.setEmail("user" + i + "@example.com");
userService.createUser(user).block();
}
StepVerifier.create(userService.findAll(false).collectList())
.expectNextMatches(users -> {
assertEquals(3, users.size());
return true;
})
.verifyComplete();
}
@Test
void testExistsByUsername() {
SysUser user = new SysUser();
user.setUsername("existinguser");
user.setPassword("password123");
user.setEmail("existing@example.com");
userService.createUser(user).block();
StepVerifier.create(userService.existsByUsername("existinguser"))
.expectNext(true)
.verifyComplete();
StepVerifier.create(userService.existsByUsername("nonexistinguser"))
.expectNext(false)
.verifyComplete();
}
}
@@ -1,22 +1,24 @@
spring.r2dbc.url=r2dbc:h2:mem:///testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
spring.r2dbc.username=sa
spring.r2dbc.password=
spring.r2dbc.pool.enabled=true
spring.r2dbc.pool.initial-size=5
spring.r2dbc.pool.max-size=20
spring.sql.init.mode=always
spring.sql.init.schema-locations=classpath:schema-h2.sql
spring.sql.init.data-locations=classpath:data-h2.sql
logging.level.org.springframework.r2dbc=DEBUG
logging.level.cn.novalon.manage=DEBUG
spring.flyway.enabled=false
server.port=8085
jwt.secret=test-secret-key-for-testing-purposes-only-minimum-256-bits
jwt.expiration=3600000
signature.enabled=false
spring:
r2dbc:
url: r2dbc:h2:mem:///testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
username: sa
password:
pool:
enabled: true
initial-size: 2
max-size: 10
flyway:
enabled: false
security:
enabled: false
jwt:
secret: test-secret-key-for-integration-testing
expiration: 86400000
logging:
level:
cn.novalon.manage: DEBUG
org.springframework.r2dbc: DEBUG
@@ -1,189 +1,47 @@
-- H2数据库Schema
-- 用于测试环境
-- 用户表
CREATE TABLE IF NOT EXISTS users (
-- H2数据库Schema for Integration Testing
-- 创建用户表
CREATE TABLE IF NOT EXISTS sys_user (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
email VARCHAR(100) NOT NULL UNIQUE,
email VARCHAR(100),
phone VARCHAR(20),
nickname VARCHAR(50),
avatar VARCHAR(255),
nickname VARCHAR(100),
role_id BIGINT,
status INTEGER DEFAULT 1,
create_by VARCHAR(50),
update_by VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by VARCHAR(50) DEFAULT 'system',
updated_by VARCHAR(50) DEFAULT 'system',
deleted_at TIMESTAMP
);
-- 角色表
CREATE TABLE IF NOT EXISTS roles (
-- 创建角色表
CREATE TABLE IF NOT EXISTS sys_role (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
role_name VARCHAR(50) NOT NULL,
role_key VARCHAR(50) NOT NULL UNIQUE,
role_name VARCHAR(100) NOT NULL,
role_key VARCHAR(100) NOT NULL UNIQUE,
role_sort INTEGER DEFAULT 0,
status INTEGER DEFAULT 1,
create_by VARCHAR(50),
update_by VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by VARCHAR(50) DEFAULT 'system',
updated_by VARCHAR(50) DEFAULT 'system',
deleted_at TIMESTAMP
);
-- 用户角色关联表
CREATE TABLE IF NOT EXISTS user_roles (
-- 创建用户角色关联表
CREATE TABLE IF NOT EXISTS user_role (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT NOT NULL,
role_id BIGINT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by VARCHAR(50) DEFAULT 'system',
updated_by VARCHAR(50) DEFAULT 'system',
UNIQUE(user_id, role_id)
);
-- 菜单表
CREATE TABLE IF NOT EXISTS sys_menu (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
menu_name VARCHAR(50) NOT NULL,
parent_id BIGINT DEFAULT 0,
order_num INTEGER DEFAULT 0,
path VARCHAR(200),
component VARCHAR(255),
menu_type CHAR(1) DEFAULT 'C',
visible CHAR(1) DEFAULT '1',
status CHAR(1) DEFAULT '1',
perms VARCHAR(100),
icon VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by VARCHAR(50) DEFAULT 'system',
updated_by VARCHAR(50) DEFAULT 'system',
deleted_at TIMESTAMP
);
-- 权限表
CREATE TABLE IF NOT EXISTS sys_permission (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
permission_name VARCHAR(50) NOT NULL,
permission_key VARCHAR(100) NOT NULL UNIQUE,
permission_type VARCHAR(20) DEFAULT 'menu',
parent_id BIGINT DEFAULT 0,
status INTEGER DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by VARCHAR(50) DEFAULT 'system',
updated_by VARCHAR(50) DEFAULT 'system',
deleted_at TIMESTAMP
);
-- 角色权限关联表
CREATE TABLE IF NOT EXISTS sys_role_permission (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
role_id BIGINT NOT NULL,
permission_id BIGINT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by VARCHAR(50) DEFAULT 'system',
updated_by VARCHAR(50) DEFAULT 'system',
UNIQUE(role_id, permission_id)
);
-- 字典类型表
CREATE TABLE IF NOT EXISTS sys_dict_type (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
dict_name VARCHAR(100) NOT NULL,
dict_type VARCHAR(100) NOT NULL UNIQUE,
status CHAR(1) DEFAULT '0',
remark VARCHAR(500),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by VARCHAR(50) DEFAULT 'system',
updated_by VARCHAR(50) DEFAULT 'system',
deleted_at TIMESTAMP
);
-- 字典数据表
CREATE TABLE IF NOT EXISTS sys_dict_data (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
dict_sort INTEGER DEFAULT 0,
dict_label VARCHAR(100) NOT NULL,
dict_value VARCHAR(100) NOT NULL,
dict_type VARCHAR(100) NOT NULL,
css_class VARCHAR(100),
list_class VARCHAR(100),
is_default CHAR(1) DEFAULT 'N',
status CHAR(1) DEFAULT '0',
remark VARCHAR(500),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by VARCHAR(50) DEFAULT 'system',
updated_by VARCHAR(50) DEFAULT 'system',
deleted_at TIMESTAMP
);
-- 系统配置表
CREATE TABLE IF NOT EXISTS sys_config (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
config_name VARCHAR(100) NOT NULL,
config_key VARCHAR(100) NOT NULL UNIQUE,
config_value VARCHAR(500) NOT NULL,
config_type CHAR(1) DEFAULT 'N',
remark VARCHAR(500),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by VARCHAR(50) DEFAULT 'system',
updated_by VARCHAR(50) DEFAULT 'system',
deleted_at TIMESTAMP
);
-- 操作日志表
CREATE TABLE IF NOT EXISTS operation_log (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT,
username VARCHAR(50),
operation VARCHAR(100),
method VARCHAR(200),
params TEXT,
time BIGINT,
ip VARCHAR(64),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 登录日志表
CREATE TABLE IF NOT EXISTS sys_login_log (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT,
username VARCHAR(50),
ip VARCHAR(64),
location VARCHAR(255),
browser VARCHAR(100),
os VARCHAR(100),
status INTEGER DEFAULT 1,
msg VARCHAR(255),
login_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 异常日志表
CREATE TABLE IF NOT EXISTS sys_exception_log (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT,
username VARCHAR(50),
method VARCHAR(200),
params TEXT,
exception TEXT,
ip VARCHAR(64),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
created_by VARCHAR(50),
CONSTRAINT fk_user_role_user FOREIGN KEY (user_id) REFERENCES sys_user(id) ON DELETE CASCADE,
CONSTRAINT fk_user_role_role FOREIGN KEY (role_id) REFERENCES sys_role(id) ON DELETE CASCADE,
CONSTRAINT uk_user_role UNIQUE (user_id, role_id)
);
-- 创建索引
CREATE INDEX idx_users_username ON users(username);
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_status ON users(status);
CREATE INDEX idx_user_roles_user_id ON user_roles(user_id);
CREATE INDEX idx_user_roles_role_id ON user_roles(role_id);
CREATE INDEX idx_sys_menu_parent_id ON sys_menu(parent_id);
CREATE INDEX idx_sys_permission_key ON sys_permission(permission_key);
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);