refactor(backend): 重命名后端项目为 gym-manage-api,修改包名为 cn.novalon.gym.manage
This commit is contained in:
+26
@@ -0,0 +1,26 @@
|
||||
package cn.novalon.gym.manage.app;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration;
|
||||
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories;
|
||||
|
||||
@SpringBootApplication(scanBasePackages = "cn.novalon.gym.manage", exclude = {ReactiveUserDetailsServiceAutoConfiguration.class})
|
||||
@EnableR2dbcRepositories(basePackages = {"cn.novalon.gym.manage.db.dao", "cn.novalon.gym.manage.sys.audit.repository"})
|
||||
public class ManageApplication {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ManageApplication.class);
|
||||
|
||||
public static void main(String[] args) {
|
||||
logger.info("应用程序启动中...");
|
||||
logger.info("包扫描路径: cn.novalon.gym.manage");
|
||||
|
||||
// 使用简单的启动方式,避免自动配置问题
|
||||
SpringApplication.run(ManageApplication.class, args);
|
||||
logger.info("应用程序启动完成");
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package cn.novalon.gym.manage.app;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* 最小化应用程序启动类
|
||||
* 避免复杂的自动配置问题,专注于核心功能
|
||||
*/
|
||||
@SpringBootApplication(
|
||||
scanBasePackages = {
|
||||
"cn.novalon.gym.manage.app.config",
|
||||
"cn.novalon.gym.manage.app.controller",
|
||||
"cn.novalon.gym.manage.app.service"
|
||||
}
|
||||
)
|
||||
public class MinimalApplication {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(MinimalApplication.class);
|
||||
|
||||
public static void main(String[] args) {
|
||||
logger.info("最小化应用程序启动中...");
|
||||
|
||||
// 设置系统属性,避免自动配置问题
|
||||
System.setProperty("spring.autoconfigure.exclude",
|
||||
"org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration");
|
||||
|
||||
// 禁用复杂的自动配置
|
||||
System.setProperty("spring.main.lazy-initialization", "true");
|
||||
System.setProperty("spring.main.banner-mode", "off");
|
||||
|
||||
try {
|
||||
SpringApplication.run(MinimalApplication.class, args);
|
||||
logger.info("最小化应用程序启动完成");
|
||||
} catch (Exception e) {
|
||||
logger.error("应用程序启动失败: {}", e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package cn.novalon.gym.manage.app;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration;
|
||||
|
||||
/**
|
||||
* 简化的应用程序启动类
|
||||
* 避免复杂的自动配置问题
|
||||
*/
|
||||
@SpringBootApplication(
|
||||
scanBasePackages = "cn.novalon.gym.manage.app",
|
||||
exclude = {ReactiveUserDetailsServiceAutoConfiguration.class}
|
||||
)
|
||||
public class SimpleManageApplication {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(SimpleManageApplication.class);
|
||||
|
||||
public static void main(String[] args) {
|
||||
logger.info("简化版应用程序启动中...");
|
||||
logger.info("包扫描路径: cn.novalon.gym.manage.app");
|
||||
|
||||
// 设置系统属性,避免自动配置问题
|
||||
System.setProperty("spring.autoconfigure.exclude",
|
||||
"org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration");
|
||||
|
||||
SpringApplication.run(SimpleManageApplication.class, args);
|
||||
logger.info("简化版应用程序启动完成");
|
||||
}
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
package cn.novalon.gym.manage.app.config;
|
||||
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.codec.json.Jackson2JsonDecoder;
|
||||
import org.springframework.http.codec.json.Jackson2JsonEncoder;
|
||||
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
/**
|
||||
* Jackson配置类
|
||||
*
|
||||
* 用于统一时间格式化配置
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-26
|
||||
*/
|
||||
@Configuration
|
||||
public class JacksonConfig {
|
||||
|
||||
private static final String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
|
||||
|
||||
@Bean
|
||||
public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
|
||||
ObjectMapper objectMapper = builder.createXmlMapper(false).build();
|
||||
|
||||
JavaTimeModule javaTimeModule = new JavaTimeModule();
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_TIME_FORMAT);
|
||||
|
||||
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter));
|
||||
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter));
|
||||
|
||||
objectMapper.registerModule(javaTimeModule);
|
||||
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
||||
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
|
||||
|
||||
return objectMapper;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Jackson2JsonEncoder jackson2JsonEncoder(ObjectMapper objectMapper) {
|
||||
return new Jackson2JsonEncoder(objectMapper);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Jackson2JsonDecoder jackson2JsonDecoder(ObjectMapper objectMapper) {
|
||||
return new Jackson2JsonDecoder(objectMapper);
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package cn.novalon.gym.manage.app.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.codec.multipart.DefaultPartHttpMessageReader;
|
||||
import org.springframework.http.codec.multipart.MultipartHttpMessageReader;
|
||||
|
||||
@Configuration
|
||||
public class MultipartConfig {
|
||||
|
||||
@Bean
|
||||
public MultipartHttpMessageReader multipartHttpMessageReader() {
|
||||
DefaultPartHttpMessageReader partReader = new DefaultPartHttpMessageReader();
|
||||
partReader.setMaxHeadersSize(8192);
|
||||
partReader.setMaxDiskUsagePerPart(10 * 1024 * 1024);
|
||||
partReader.setEnableLoggingRequestDetails(true);
|
||||
return new MultipartHttpMessageReader(partReader);
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package cn.novalon.gym.manage.app.config;
|
||||
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.info.Contact;
|
||||
import io.swagger.v3.oas.models.info.Info;
|
||||
import io.swagger.v3.oas.models.info.License;
|
||||
import io.swagger.v3.oas.models.servers.Server;
|
||||
import io.swagger.v3.oas.models.tags.Tag;
|
||||
import org.springdoc.core.models.GroupedOpenApi;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* OpenAPI配置类
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-14
|
||||
*/
|
||||
@Configuration
|
||||
public class OpenApiConfig {
|
||||
|
||||
@Bean
|
||||
public OpenAPI customOpenAPI() {
|
||||
return new OpenAPI()
|
||||
.info(new Info()
|
||||
.title("Novalon Manage System API")
|
||||
.version("1.0.0")
|
||||
.description("Novalon 管理系统 RESTful API 文档")
|
||||
.contact(new Contact()
|
||||
.name("Novalon Team")
|
||||
.email("support@novalon.cn"))
|
||||
.license(new License()
|
||||
.name("Apache 2.0")
|
||||
.url("https://www.apache.org/licenses/LICENSE-2.0")))
|
||||
.servers(List.of(
|
||||
new Server().url("http://localhost:8084").description("开发环境"),
|
||||
new Server().url("https://api.novalon.cn").description("生产环境")))
|
||||
.tags(Arrays.asList(
|
||||
new Tag().name("用户管理").description("用户相关操作"),
|
||||
new Tag().name("角色管理").description("角色相关操作"),
|
||||
new Tag().name("配置管理").description("系统配置相关操作"),
|
||||
new Tag().name("字典管理").description("字典数据相关操作"),
|
||||
new Tag().name("通知管理").description("系统通知相关操作"),
|
||||
new Tag().name("文件管理").description("文件上传下载相关操作"),
|
||||
new Tag().name("日志管理").description("操作日志相关操作"),
|
||||
new Tag().name("认证管理").description("登录认证相关操作"),
|
||||
new Tag().name("统计信息").description("系统统计相关操作")));
|
||||
}
|
||||
|
||||
@Bean
|
||||
public GroupedOpenApi allApi() {
|
||||
return GroupedOpenApi.builder()
|
||||
.group("all")
|
||||
.pathsToMatch("/api/**")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package cn.novalon.gym.manage.app.config;
|
||||
|
||||
import io.github.resilience4j.ratelimiter.RateLimiter;
|
||||
import io.github.resilience4j.ratelimiter.RateLimiterConfig;
|
||||
import io.github.resilience4j.ratelimiter.RateLimiterRegistry;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
@Configuration
|
||||
public class RateLimitConfig {
|
||||
|
||||
@Value("${rate.limit.limit-for-period:100}")
|
||||
private int limitForPeriod;
|
||||
|
||||
@Value("${rate.limit.limit-refresh-period:1s}")
|
||||
private Duration limitRefreshPeriod;
|
||||
|
||||
@Value("${rate.limit.timeout-duration:0}")
|
||||
private Duration timeoutDuration;
|
||||
|
||||
@Bean
|
||||
public RateLimiterRegistry rateLimiterRegistry() {
|
||||
RateLimiterConfig config = RateLimiterConfig.custom()
|
||||
.limitForPeriod(limitForPeriod)
|
||||
.limitRefreshPeriod(limitRefreshPeriod)
|
||||
.timeoutDuration(timeoutDuration)
|
||||
.build();
|
||||
|
||||
return RateLimiterRegistry.of(config);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Qualifier("apiRateLimiter")
|
||||
public RateLimiter apiRateLimiter(RateLimiterRegistry registry) {
|
||||
return registry.rateLimiter("apiRateLimiter");
|
||||
}
|
||||
}
|
||||
+198
@@ -0,0 +1,198 @@
|
||||
package cn.novalon.gym.manage.app.config;
|
||||
|
||||
import cn.novalon.gym.manage.sys.handler.auth.SysAuthHandler;
|
||||
import cn.novalon.gym.manage.sys.handler.auth.PasswordDiagnosticHandler;
|
||||
import cn.novalon.gym.manage.sys.handler.config.SysConfigHandler;
|
||||
import cn.novalon.gym.manage.sys.handler.dictionary.DictionaryHandler;
|
||||
import cn.novalon.gym.manage.sys.handler.dict.SysDictHandler;
|
||||
import cn.novalon.gym.manage.sys.handler.log.SysLogHandler;
|
||||
import cn.novalon.gym.manage.sys.handler.log.OperationLogHandler;
|
||||
import cn.novalon.gym.manage.sys.handler.menu.MenuHandler;
|
||||
import cn.novalon.gym.manage.sys.handler.role.SysRoleHandler;
|
||||
import cn.novalon.gym.manage.sys.handler.permission.SysPermissionHandler;
|
||||
import cn.novalon.gym.manage.sys.handler.stats.StatsHandler;
|
||||
import cn.novalon.gym.manage.sys.handler.user.SysUserHandler;
|
||||
import cn.novalon.gym.manage.notify.handler.SysNoticeHandler;
|
||||
import cn.novalon.gym.manage.notify.handler.SysUserMessageHandler;
|
||||
import cn.novalon.gym.manage.file.handler.SysFileHandler;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
|
||||
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
|
||||
|
||||
/**
|
||||
* 系统路由配置类
|
||||
*
|
||||
* 文件定义:配置WebFlux函数式路由,将HTTP请求映射到对应的Handler方法
|
||||
* 涉及业务:用户、角色、字典、菜单、公告、文件等所有RESTful API路由
|
||||
* 算法:使用RouterFunctions.route()构建函数式路由规则
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-13
|
||||
*/
|
||||
@Configuration
|
||||
public class SystemRouter {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> systemRoutes(
|
||||
DictionaryHandler dictionaryHandler,
|
||||
SysUserHandler userHandler,
|
||||
MenuHandler menuHandler,
|
||||
SysRoleHandler roleHandler,
|
||||
SysConfigHandler configHandler,
|
||||
SysLogHandler logHandler,
|
||||
OperationLogHandler operationLogHandler,
|
||||
SysAuthHandler authHandler,
|
||||
StatsHandler statsHandler,
|
||||
SysDictHandler dictHandler,
|
||||
SysNoticeHandler noticeHandler,
|
||||
SysUserMessageHandler messageHandler,
|
||||
SysFileHandler fileHandler,
|
||||
SysPermissionHandler permissionHandler,
|
||||
PasswordDiagnosticHandler passwordDiagnosticHandler) {
|
||||
|
||||
return route()
|
||||
// ========== 诊断路由 ==========
|
||||
.GET("/api/diagnostic/password", passwordDiagnosticHandler::diagnose)
|
||||
|
||||
// ========== 字典路由 ==========
|
||||
.GET("/api/dictionaries", dictionaryHandler::getAllDictionaries)
|
||||
.GET("/api/dictionaries/{id}", dictionaryHandler::getDictionaryById)
|
||||
.GET("/api/dictionaries/type/{type}", dictionaryHandler::getDictionariesByType)
|
||||
.GET("/api/dictionaries/check/exists", dictionaryHandler::checkTypeAndCodeExists)
|
||||
.POST("/api/dictionaries", dictionaryHandler::createDictionary)
|
||||
.PUT("/api/dictionaries/{id}", dictionaryHandler::updateDictionary)
|
||||
.DELETE("/api/dictionaries/{id}", dictionaryHandler::deleteDictionary)
|
||||
|
||||
// ========== 用户路由 ==========
|
||||
.GET("/api/users", userHandler::getAllUsers)
|
||||
.GET("/api/users/page", userHandler::getUsersByPage)
|
||||
.GET("/api/users/count", userHandler::getUserCount)
|
||||
.GET("/api/users/username/{username}", userHandler::getUserByUsername)
|
||||
.GET("/api/users/check/username", userHandler::checkUsernameExists)
|
||||
.GET("/api/users/check/email", userHandler::checkEmailExists)
|
||||
.POST("/api/users", userHandler::createUser)
|
||||
.GET("/api/users/{id}", userHandler::getUserById)
|
||||
.PUT("/api/users/{id}", userHandler::updateUser)
|
||||
.DELETE("/api/users/{id}", userHandler::deleteUser)
|
||||
.POST("/api/users/{id}/action/change-password", userHandler::changePassword)
|
||||
.POST("/api/users/{id}/action/logical-delete", userHandler::logicalDeleteUser)
|
||||
.POST("/api/users/logical-delete", userHandler::logicalDeleteUsers)
|
||||
.POST("/api/users/action/restore", userHandler::restoreUsers)
|
||||
.POST("/api/users/{id}/action/restore", userHandler::restoreUser)
|
||||
.GET("/api/users/{id}/roles", userHandler::getUserRoles)
|
||||
.POST("/api/users/{id}/roles", userHandler::assignRoles)
|
||||
|
||||
// ========== 菜单路由 ==========
|
||||
.GET("/api/menus", menuHandler::getAllMenus)
|
||||
.GET("/api/menus/tree", menuHandler::getMenuTree)
|
||||
.GET("/api/menus/{id}", menuHandler::getMenuById)
|
||||
.POST("/api/menus", menuHandler::createMenu)
|
||||
.PUT("/api/menus/{id}", menuHandler::updateMenu)
|
||||
.DELETE("/api/menus/{id}", menuHandler::deleteMenu)
|
||||
|
||||
// ========== 角色路由 ==========
|
||||
.GET("/api/roles", roleHandler::getAllRoles)
|
||||
.GET("/api/roles/page", roleHandler::getRolesByPage)
|
||||
.GET("/api/roles/count", roleHandler::getRoleCount)
|
||||
.GET("/api/roles/name/{roleName}", roleHandler::getRoleByName)
|
||||
.GET("/api/roles/check-name", roleHandler::checkNameExists)
|
||||
.GET("/api/roles/{id}", roleHandler::getRoleById)
|
||||
.POST("/api/roles", roleHandler::createRole)
|
||||
.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)
|
||||
|
||||
// ========== 配置路由 ==========
|
||||
.GET("/api/config", configHandler::getAllConfigs)
|
||||
.GET("/api/config/{id}", configHandler::getConfigById)
|
||||
.GET("/api/config/key/{configKey}", configHandler::getConfigByKey)
|
||||
.POST("/api/config", configHandler::createConfig)
|
||||
.PUT("/api/config/{id}", configHandler::updateConfig)
|
||||
.DELETE("/api/config/{id}", configHandler::deleteConfig)
|
||||
|
||||
// ========== 日志路由 ==========
|
||||
.GET("/api/logs/login", logHandler::getAllLoginLogs)
|
||||
.GET("/api/logs/login/page", logHandler::getLoginLogsByPage)
|
||||
.GET("/api/logs/login/count", logHandler::getLoginLogCount)
|
||||
.GET("/api/logs/login/today/count", logHandler::getTodayLoginCount)
|
||||
.GET("/api/logs/login/recent", logHandler::getRecentLoginLogs)
|
||||
.GET("/api/logs/login/{id}", logHandler::getLoginLogById)
|
||||
.POST("/api/logs/login", logHandler::createLoginLog)
|
||||
.GET("/api/logs/exception", logHandler::getAllExceptionLogs)
|
||||
.GET("/api/logs/exception/page", logHandler::getExceptionLogsByPage)
|
||||
.GET("/api/logs/exception/count", logHandler::getExceptionLogCount)
|
||||
.GET("/api/logs/exception/{id}", logHandler::getExceptionLogById)
|
||||
.POST("/api/logs/exception", logHandler::createExceptionLog)
|
||||
.GET("/api/logs/operation", operationLogHandler::getAllOperationLogs)
|
||||
.GET("/api/logs/operation/export", operationLogHandler::exportOperationLogs)
|
||||
.GET("/api/logs/operation/page", operationLogHandler::getOperationLogsByPage)
|
||||
.GET("/api/logs/operation/count", operationLogHandler::getOperationLogCount)
|
||||
.GET("/api/logs/operation/{id}", operationLogHandler::getOperationLogById)
|
||||
.POST("/api/logs/operation", operationLogHandler::createOperationLog)
|
||||
|
||||
// ========== 认证路由 ==========
|
||||
.POST("/api/auth/login", authHandler::login)
|
||||
.POST("/api/auth/register", authHandler::register)
|
||||
.POST("/api/auth/logout", authHandler::logout)
|
||||
|
||||
// ========== 统计路由 ==========
|
||||
.GET("/api/stats/overview", statsHandler::getOverview)
|
||||
|
||||
// ========== 数据字典路由 ==========
|
||||
.GET("/api/dict/types", dictHandler::getAllDictTypes)
|
||||
.GET("/api/dict/types/{id}", dictHandler::getDictTypeById)
|
||||
.GET("/api/dict/types/type/{dictType}", dictHandler::getDictTypeByType)
|
||||
.POST("/api/dict/types", dictHandler::createDictType)
|
||||
.PUT("/api/dict/types/{id}", dictHandler::updateDictType)
|
||||
.DELETE("/api/dict/types/{id}", dictHandler::deleteDictType)
|
||||
.GET("/api/dict/data", dictHandler::getAllDictData)
|
||||
.GET("/api/dict/data/type/{dictType}", dictHandler::getDictDataByType)
|
||||
.GET("/api/dict/data/{id}", dictHandler::getDictDataById)
|
||||
.POST("/api/dict/data", dictHandler::createDictData)
|
||||
.PUT("/api/dict/data/{id}", dictHandler::updateDictData)
|
||||
.DELETE("/api/dict/data/{id}", dictHandler::deleteDictData)
|
||||
|
||||
// ========== 公告路由 ==========
|
||||
.GET("/api/notices", noticeHandler::getAllNotices)
|
||||
.GET("/api/notices/{id}", noticeHandler::getNoticeById)
|
||||
.GET("/api/notices/status/{status}", noticeHandler::getNoticesByStatus)
|
||||
.POST("/api/notices", noticeHandler::createNotice)
|
||||
.PUT("/api/notices/{id}", noticeHandler::updateNotice)
|
||||
.DELETE("/api/notices/{id}", noticeHandler::deleteNotice)
|
||||
|
||||
// ========== 消息路由 ==========
|
||||
.GET("/api/messages/user/{userId}", messageHandler::getMessagesByUser)
|
||||
.GET("/api/messages/user/{userId}/unread", messageHandler::getUnreadCount)
|
||||
.GET("/api/messages/user/{userId}/unread/list", messageHandler::getUnreadList)
|
||||
.POST("/api/messages", messageHandler::createMessage)
|
||||
.PUT("/api/messages/{id}/read", messageHandler::markAsRead)
|
||||
.DELETE("/api/messages/{id}", messageHandler::deleteMessage)
|
||||
|
||||
// ========== 文件路由 ==========
|
||||
.GET("/api/files", fileHandler::getAllFiles)
|
||||
.GET("/api/files/{id}", fileHandler::getFileById)
|
||||
.POST("/api/files/upload", fileHandler::uploadFile)
|
||||
.GET("/api/files/{id}/download", fileHandler::downloadFile)
|
||||
.GET("/api/files/download/{fileName}", fileHandler::downloadFileByName)
|
||||
.GET("/api/files/{id}/preview", fileHandler::previewFile)
|
||||
.GET("/api/files/preview/{fileName}", fileHandler::previewFileByName)
|
||||
.DELETE("/api/files/{id}", fileHandler::deleteFile)
|
||||
|
||||
// ========== 权限路由 ==========
|
||||
.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();
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package cn.novalon.gym.manage.app.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.codec.ServerCodecConfigurer;
|
||||
import org.springframework.web.reactive.config.WebFluxConfigurer;
|
||||
|
||||
/**
|
||||
* WebFlux配置类
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-14
|
||||
*/
|
||||
@Configuration
|
||||
public class WebFluxConfig implements WebFluxConfigurer {
|
||||
|
||||
@Override
|
||||
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
|
||||
configurer.defaultCodecs().maxInMemorySize(16 * 1024 * 1024);
|
||||
}
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
cn.novalon.manage.app.config.OpenApiConfig
|
||||
cn.novalon.manage.app.config.WebFluxConfig
|
||||
cn.novalon.manage.app.config.SystemRouter
|
||||
cn.novalon.manage.app.config.MultipartConfig
|
||||
cn.novalon.manage.app.config.RateLimitConfig
|
||||
@@ -0,0 +1,22 @@
|
||||
spring:
|
||||
r2dbc:
|
||||
url: r2dbc:postgresql://localhost:55432/manage_system
|
||||
username: novalon
|
||||
password: novalon123
|
||||
flyway:
|
||||
enabled: true
|
||||
locations: classpath:db/migration
|
||||
baseline-on-migrate: true
|
||||
validate-on-migrate: true
|
||||
|
||||
rate:
|
||||
limit:
|
||||
limit-for-period: 10000
|
||||
limit-refresh-period: 1s
|
||||
timeout-duration: 0
|
||||
|
||||
logging:
|
||||
level:
|
||||
cn.novalon.manage: DEBUG
|
||||
org.springframework.r2dbc: DEBUG
|
||||
org.springframework.web: TRACE
|
||||
@@ -0,0 +1,36 @@
|
||||
# 本地开发环境配置
|
||||
spring:
|
||||
config:
|
||||
activate:
|
||||
on-profile: local
|
||||
r2dbc:
|
||||
url: r2dbc:postgresql://localhost:55432/manage_system
|
||||
username: novalon
|
||||
password: novalon123
|
||||
pool:
|
||||
initial-size: 5
|
||||
max-size: 20
|
||||
max-idle-time: 10m
|
||||
max-life-time: 30m
|
||||
acquire-timeout: 3s
|
||||
datasource:
|
||||
url: jdbc:postgresql://localhost:55432/manage_system
|
||||
username: novalon
|
||||
password: novalon123
|
||||
driver-class-name: org.postgresql.Driver
|
||||
flyway:
|
||||
enabled: true
|
||||
locations: classpath:db/migration
|
||||
baseline-on-migrate: true
|
||||
baseline-version: 0
|
||||
validate-on-migrate: true
|
||||
sql:
|
||||
init:
|
||||
mode: always
|
||||
|
||||
logging:
|
||||
level:
|
||||
cn.novalon.manage: DEBUG
|
||||
org.springframework.r2dbc: DEBUG
|
||||
cn.novalon.manage.db: DEBUG
|
||||
org.flywaydb: DEBUG
|
||||
@@ -0,0 +1,17 @@
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: health,info,metrics
|
||||
endpoint:
|
||||
health:
|
||||
show-details: always
|
||||
metrics:
|
||||
enabled: true
|
||||
info:
|
||||
env:
|
||||
enabled: true
|
||||
metrics:
|
||||
export:
|
||||
simple:
|
||||
enabled: true
|
||||
@@ -0,0 +1,12 @@
|
||||
spring:
|
||||
r2dbc:
|
||||
url: r2dbc:postgresql://postgres:5432/novalon_manage
|
||||
username: ${DB_USERNAME}
|
||||
password: ${DB_PASSWORD}
|
||||
flyway:
|
||||
enabled: true
|
||||
|
||||
logging:
|
||||
level:
|
||||
cn.novalon.manage: INFO
|
||||
org.springframework.r2dbc: INFO
|
||||
@@ -0,0 +1,62 @@
|
||||
server:
|
||||
port: 8084
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: manage-app
|
||||
r2dbc:
|
||||
url: r2dbc:postgresql://localhost:55432/manage_system
|
||||
username: novalon
|
||||
password: novalon123
|
||||
pool:
|
||||
initial-size: 5
|
||||
max-size: 20
|
||||
max-idle-time: 30m
|
||||
max-life-time: 1h
|
||||
acquire-timeout: 5s
|
||||
flyway:
|
||||
enabled: true
|
||||
locations: classpath:db/migration
|
||||
baseline-on-migrate: true
|
||||
validate-on-migrate: true
|
||||
sql:
|
||||
init:
|
||||
mode: never
|
||||
security:
|
||||
user:
|
||||
name: disabled
|
||||
password: disabled
|
||||
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: health,info,metrics,env,loggers
|
||||
base-path: /actuator
|
||||
endpoint:
|
||||
health:
|
||||
show-details: always
|
||||
metrics:
|
||||
tags:
|
||||
application: ${spring.application.name}
|
||||
environment: ${spring.profiles.active}
|
||||
|
||||
logging:
|
||||
level:
|
||||
cn.novalon.manage: DEBUG
|
||||
org.springframework.r2dbc: DEBUG
|
||||
cn.novalon.manage.db: DEBUG
|
||||
org.flywaydb: INFO
|
||||
|
||||
springdoc:
|
||||
api-docs:
|
||||
path: /api-docs
|
||||
enabled: true
|
||||
swagger-ui:
|
||||
path: /swagger-ui.html
|
||||
enabled: true
|
||||
tags-sorter: alpha
|
||||
operations-sorter: alpha
|
||||
show-actuator: false
|
||||
default-consumes-media-type: application/json
|
||||
default-produces-media-type: application/json
|
||||
@@ -0,0 +1,68 @@
|
||||
server:
|
||||
port: 8084
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: gym-manage-api
|
||||
r2dbc:
|
||||
url: r2dbc:postgresql://${DB_HOST:localhost}:${DB_PORT:55432}/${DB_NAME:manage_system}
|
||||
username: ${DB_USERNAME:postgres}
|
||||
password: ${DB_PASSWORD:postgres}
|
||||
pool:
|
||||
initial-size: 10
|
||||
max-size: 50
|
||||
max-idle-time: 30m
|
||||
max-life-time: 1h
|
||||
acquire-timeout: 5s
|
||||
datasource:
|
||||
url: jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:55432}/${DB_NAME:manage_system}
|
||||
username: ${DB_USERNAME:postgres}
|
||||
password: ${DB_PASSWORD:postgres}
|
||||
driver-class-name: org.postgresql.Driver
|
||||
flyway:
|
||||
enabled: true
|
||||
locations: classpath:db/migration
|
||||
baseline-on-migrate: true
|
||||
baseline-version: 0
|
||||
validate-on-migrate: true
|
||||
security:
|
||||
user:
|
||||
name: disabled
|
||||
password: disabled
|
||||
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: health,info,metrics,env,loggers
|
||||
base-path: /actuator
|
||||
endpoint:
|
||||
health:
|
||||
show-details: always
|
||||
metrics:
|
||||
tags:
|
||||
application: ${spring.application.name}
|
||||
environment: ${spring.profiles.active}
|
||||
|
||||
logging:
|
||||
level:
|
||||
cn.novalon.manage: DEBUG
|
||||
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
|
||||
enabled: true
|
||||
swagger-ui:
|
||||
path: /swagger-ui.html
|
||||
enabled: true
|
||||
tags-sorter: alpha
|
||||
operations-sorter: alpha
|
||||
show-actuator: false
|
||||
default-consumes-media-type: application/json
|
||||
default-produces-media-type: application/json
|
||||
@@ -0,0 +1,30 @@
|
||||
╔═══════════════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ██╗ ██████╗ ███╗ ██╗ ║
|
||||
║ ████╗ ██║██╔═══██╗██║ ██║██╔══██╗██║ ██╔═══██╗████╗ ██║ ║
|
||||
║ ██╔██╗ ██║██║ ██║██║ ██║███████║██║ ██║ ██║██╔██╗ ██║ ║
|
||||
║ ██║╚██╗██║██║ ██║╚██╗ ██╔╝██╔══██║██║ ██║ ██║██║╚██╗██║ ║
|
||||
║ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║███████╗╚██████╔╝██║ ╚████║ ║
|
||||
║ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═══╝ ║
|
||||
║ ║
|
||||
║ ███╗ ███╗ █████╗ ███╗ ██╗ █████╗ ██████╗ ███████╗ ║
|
||||
║ ████╗ ████║██╔══██╗████╗ ██║██╔══██╗██╔════╝ ██╔════╝ ║
|
||||
║ ██╔████╔██║███████║██╔██╗ ██║███████║██║ ███╗█████╗ ║
|
||||
║ ██║╚██╔╝██║██╔══██║██║╚██╗██║██╔══██║██║ ██║██╔══╝ ║
|
||||
║ ██║ ╚═╝ ██║██║ ██║██║ ╚████║██║ ██║╚██████╔╝███████╗ ║
|
||||
║ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝ ║
|
||||
║ ║
|
||||
║ ███████╗██╗ ██╗███████╗████████╗███████╗███╗ ███╗ ║
|
||||
║ ██╔════╝╚██╗ ██╔╝██╔════╝╚══██╔══╝██╔════╝████╗ ████║ ║
|
||||
║ ███████╗ ╚████╔╝ ███████╗ ██║ █████╗ ██╔████╔██║ ║
|
||||
║ ╚════██║ ╚██╔╝ ╚════██║ ██║ ██╔══╝ ██║╚██╔╝██║ ║
|
||||
║ ███████║ ██║ ███████║ ██║ ███████╗██║ ╚═╝ ██║ ║
|
||||
║ ╚══════╝ ╚═╝ ╚══════╝ ╚═╝ ╚══════╝╚═╝ ╚═╝ ║
|
||||
║ ║
|
||||
╚═══════════════════════════════════════════════════════════════════╝
|
||||
|
||||
:: Novalon Manage System ::
|
||||
Version: ${application.version:Unknown}
|
||||
Spring Boot: ${spring-boot.version}
|
||||
Java: ${java.version}
|
||||
PID: ${PID}
|
||||
@@ -0,0 +1,84 @@
|
||||
-- H2数据库测试数据
|
||||
-- 用于测试环境
|
||||
|
||||
-- 插入测试角色
|
||||
MERGE INTO sys_role (id, role_name, role_key, role_sort, status, create_by, update_by)
|
||||
KEY(id)
|
||||
VALUES
|
||||
(1, '超级管理员', 'admin', 1, 1, 'system', 'system'),
|
||||
(2, '测试管理员', 'test_admin', 2, 1, 'system', 'system'),
|
||||
(3, '普通用户', 'normal_user', 3, 1, 'system', 'system'),
|
||||
(4, '访客', 'guest', 4, 1, 'system', 'system');
|
||||
|
||||
-- 插入测试用户
|
||||
-- BCrypt哈希值对应明文密码: Test@123
|
||||
MERGE INTO sys_user (id, username, password, email, phone, nickname, status, create_by, update_by)
|
||||
KEY(id)
|
||||
VALUES
|
||||
(1, 'admin', '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C', 'admin@novalon.com', '13800138000', '超级管理员', 1, 'system', 'system'),
|
||||
(2, 'testadmin', '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C', 'testadmin@novalon.com', '13800138001', '测试管理员', 1, 'system', 'system'),
|
||||
(3, 'normaluser', '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C', 'normaluser@novalon.com', '13800138002', '普通用户', 1, 'system', 'system'),
|
||||
(4, 'guestuser', '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C', 'guestuser@novalon.com', '13800138003', '访客用户', 1, 'system', 'system'),
|
||||
(5, 'disableduser', '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C', 'disableduser@novalon.com', '13800138004', '禁用用户', 0, 'system', 'system'),
|
||||
(10, 'e2e_test_user', '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C', 'e2e@test.com', '13900139000', 'E2E测试用户', 1, 'system', 'system');
|
||||
|
||||
-- 为用户分配角色
|
||||
INSERT INTO user_role (user_id, role_id, created_by)
|
||||
VALUES
|
||||
(1, 1, 'system'),
|
||||
(2, 2, 'system'),
|
||||
(3, 3, 'system'),
|
||||
(4, 4, 'system'),
|
||||
(10, 1, 'system');
|
||||
|
||||
-- 插入测试菜单
|
||||
INSERT INTO sys_menu (id, menu_name, parent_id, order_num, path, component, menu_type, visible, status, perms, icon, create_by, update_by)
|
||||
VALUES
|
||||
(1, '系统管理', 0, 1, '/system', 'Layout', 'M', '1', '1', '', 'system', 'system', 'system'),
|
||||
(2, '用户管理', 1, 1, 'user', 'system/user/index', 'C', '1', '1', 'system:user:list', 'user', 'system', 'system'),
|
||||
(3, '角色管理', 1, 2, 'role', 'system/role/index', 'C', '1', '1', 'system:role:list', 'role', 'system', 'system'),
|
||||
(4, '菜单管理', 1, 3, 'menu', 'system/menu/index', 'C', '1', '1', 'system:menu:list', 'menu', 'system', 'system'),
|
||||
(5, '测试菜单', 0, 99, '/test', 'Layout', 'M', '1', '1', '', 'test', 'system', 'system'),
|
||||
(6, '用户测试', 5, 1, 'user-test', 'system/user-test/index', 'C', '1', '1', 'system:user:test', 'user', 'system', 'system');
|
||||
|
||||
-- 插入测试权限
|
||||
INSERT INTO sys_permission (id, permission_name, permission_code, resource, action, description, status, create_by, update_by)
|
||||
VALUES
|
||||
(1, '系统管理', 'system:manage', '/api/system', 'GET', '系统管理权限', 1, 'system', 'system'),
|
||||
(2, '用户管理', 'system:user:manage', '/api/users', 'GET', '用户管理权限', 1, 'system', 'system'),
|
||||
(3, '用户查询', 'system:user:list', '/api/users', 'GET', '用户查询权限', 1, 'system', 'system'),
|
||||
(4, '用户新增', 'system:user:add', '/api/users', 'POST', '用户新增权限', 1, 'system', 'system'),
|
||||
(5, '用户编辑', 'system:user:edit', '/api/users', 'PUT', '用户编辑权限', 1, 'system', 'system'),
|
||||
(6, '用户删除', 'system:user:delete', '/api/users', 'DELETE', '用户删除权限', 1, 'system', 'system'),
|
||||
(7, '测试权限', 'test:permission', '/api/test', 'GET', '测试权限', 1, 'system', 'system'),
|
||||
(8, '用户测试权限', 'system:user:test', '/api/users/test', 'GET', '用户测试权限', 1, 'system', 'system');
|
||||
|
||||
-- 为角色分配权限
|
||||
INSERT INTO sys_role_permission (role_id, permission_id, created_by, updated_by)
|
||||
SELECT 1, id, 'system', 'system' FROM sys_permission
|
||||
UNION ALL
|
||||
SELECT 2, id, 'system', 'system' FROM sys_permission WHERE id IN (7, 8);
|
||||
|
||||
-- 插入字典类型
|
||||
INSERT INTO sys_dict_type (id, dict_name, dict_type, status, remark, create_by, update_by)
|
||||
VALUES
|
||||
(1, '用户状态', 'user_status', '0', '用户状态列表', 'system', 'system'),
|
||||
(2, '菜单状态', 'menu_status', '0', '菜单状态列表', 'system', 'system'),
|
||||
(3, '角色状态', 'role_status', '0', '角色状态列表', 'system', 'system');
|
||||
|
||||
-- 插入字典数据
|
||||
INSERT INTO sys_dict_data (id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, update_by)
|
||||
VALUES
|
||||
(1, 1, '正常', '1', 'user_status', '', 'primary', 'Y', '0', 'system', 'system'),
|
||||
(2, 2, '停用', '0', 'user_status', '', 'danger', 'N', '0', 'system', 'system'),
|
||||
(3, 1, '正常', '0', 'menu_status', '', 'primary', 'Y', '0', 'system', 'system'),
|
||||
(4, 2, '停用', '1', 'menu_status', '', 'danger', 'N', '0', 'system', 'system'),
|
||||
(5, 1, '正常', '0', 'role_status', '', 'primary', 'Y', '0', 'system', 'system'),
|
||||
(6, 2, '停用', '1', 'role_status', '', 'danger', 'N', '0', 'system', 'system');
|
||||
|
||||
-- 插入系统配置
|
||||
INSERT INTO sys_config (id, config_name, config_key, config_value, config_type, create_by, update_by)
|
||||
VALUES
|
||||
(1, '用户管理-用户初始密码', 'sys.user.initPassword', '123456', 'Y', 'system', 'system'),
|
||||
(2, '主框架页-默认皮肤样式名称', 'sys.index.skinName', 'skin-blue', 'Y', 'system', 'system'),
|
||||
(3, '用户自助-验证码开关', 'sys.account.captchaEnabled', 'true', 'Y', 'system', 'system');
|
||||
@@ -0,0 +1,253 @@
|
||||
-- H2数据库Schema for Integration Testing
|
||||
-- Create用户表
|
||||
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),
|
||||
phone VARCHAR(20),
|
||||
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,
|
||||
deleted_at TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create角色表
|
||||
CREATE TABLE IF NOT EXISTS sys_role (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
role_name VARCHAR(100) NOT NULL,
|
||||
role_key VARCHAR(100) NOT NULL UNIQUE,
|
||||
role_sort INTEGER DEFAULT 0,
|
||||
status INTEGER DEFAULT 1,
|
||||
create_by VARCHAR(50),
|
||||
update_by VARCHAR(50),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create用户角色关联表
|
||||
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,
|
||||
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菜单表
|
||||
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(200),
|
||||
menu_type VARCHAR(1) DEFAULT 'C',
|
||||
visible VARCHAR(1) DEFAULT '1',
|
||||
status VARCHAR(1) DEFAULT '1',
|
||||
perms VARCHAR(100),
|
||||
icon VARCHAR(100),
|
||||
create_by VARCHAR(50),
|
||||
update_by VARCHAR(50),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create权限表
|
||||
CREATE TABLE IF NOT EXISTS sys_permission (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
permission_name VARCHAR(100) NOT NULL,
|
||||
permission_code VARCHAR(100) NOT NULL UNIQUE,
|
||||
resource VARCHAR(200),
|
||||
action VARCHAR(20),
|
||||
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角色权限关联表
|
||||
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,
|
||||
created_by VARCHAR(50),
|
||||
updated_by VARCHAR(50),
|
||||
CONSTRAINT fk_role_permission_role FOREIGN KEY (role_id) REFERENCES sys_role(id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_role_permission_permission FOREIGN KEY (permission_id) REFERENCES sys_permission(id) ON DELETE CASCADE,
|
||||
CONSTRAINT uk_role_permission UNIQUE (role_id, permission_id)
|
||||
);
|
||||
|
||||
-- Create字典类型表
|
||||
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 VARCHAR(1) DEFAULT '0',
|
||||
remark VARCHAR(500),
|
||||
create_by VARCHAR(50),
|
||||
update_by VARCHAR(50),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create字典数据表
|
||||
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 VARCHAR(1) DEFAULT 'N',
|
||||
status VARCHAR(1) DEFAULT '0',
|
||||
create_by VARCHAR(50),
|
||||
update_by VARCHAR(50),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create字典表(通用字典)
|
||||
CREATE TABLE IF NOT EXISTS sys_dictionary (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
type VARCHAR(100) NOT NULL,
|
||||
code VARCHAR(100) NOT NULL,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
dict_value VARCHAR(500),
|
||||
remark VARCHAR(500),
|
||||
sort INTEGER DEFAULT 0,
|
||||
create_by VARCHAR(50),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create系统配置表
|
||||
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 VARCHAR(1) DEFAULT 'N',
|
||||
create_by VARCHAR(50),
|
||||
update_by VARCHAR(50),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create登录日志表
|
||||
CREATE TABLE IF NOT EXISTS sys_login_log (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
username VARCHAR(50),
|
||||
ip VARCHAR(50),
|
||||
location VARCHAR(255),
|
||||
browser VARCHAR(50),
|
||||
os VARCHAR(50),
|
||||
status VARCHAR(1),
|
||||
message VARCHAR(255),
|
||||
login_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create异常日志表
|
||||
CREATE TABLE IF NOT EXISTS sys_exception_log (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
username VARCHAR(50),
|
||||
title VARCHAR(100),
|
||||
exception_name VARCHAR(100),
|
||||
method_name VARCHAR(255),
|
||||
method_params TEXT,
|
||||
exception_msg TEXT,
|
||||
exception_stack TEXT,
|
||||
ip VARCHAR(50),
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create操作日志表
|
||||
CREATE TABLE IF NOT EXISTS operation_log (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
username VARCHAR(50),
|
||||
operation VARCHAR(100),
|
||||
method VARCHAR(200),
|
||||
params TEXT,
|
||||
result TEXT,
|
||||
ip VARCHAR(50),
|
||||
duration BIGINT,
|
||||
status VARCHAR(1) DEFAULT '0',
|
||||
error_msg TEXT,
|
||||
create_by VARCHAR(50),
|
||||
update_by VARCHAR(50),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create系统公告表
|
||||
CREATE TABLE IF NOT EXISTS sys_notice (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
notice_title VARCHAR(50) NOT NULL,
|
||||
notice_type VARCHAR(1) NOT NULL,
|
||||
notice_content TEXT,
|
||||
status VARCHAR(1) DEFAULT '0',
|
||||
create_by VARCHAR(50),
|
||||
update_by VARCHAR(50),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create用户消息表
|
||||
CREATE TABLE IF NOT EXISTS sys_user_message (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL,
|
||||
notice_id BIGINT,
|
||||
message_title VARCHAR(255),
|
||||
message_content TEXT,
|
||||
is_read VARCHAR(1) DEFAULT '0',
|
||||
read_time TIMESTAMP,
|
||||
create_by VARCHAR(50),
|
||||
update_by VARCHAR(50),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create文件管理表
|
||||
CREATE TABLE IF NOT EXISTS sys_file (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
file_name VARCHAR(255) NOT NULL,
|
||||
file_path VARCHAR(500) NOT NULL,
|
||||
file_size BIGINT,
|
||||
file_type VARCHAR(100),
|
||||
file_extension VARCHAR(10),
|
||||
storage_type VARCHAR(50),
|
||||
create_by VARCHAR(50),
|
||||
update_by VARCHAR(50),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create索引
|
||||
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);
|
||||
CREATE INDEX IF NOT EXISTS idx_sys_menu_parent_id ON sys_menu(parent_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_sys_dict_type ON sys_dict_data(dict_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_sys_login_log_username ON sys_login_log(username);
|
||||
CREATE INDEX IF NOT EXISTS idx_operation_log_username ON operation_log(username);
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package cn.novalon.gym.manage.app.config;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.http.codec.multipart.MultipartHttpMessageReader;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class MultipartConfigTest {
|
||||
|
||||
private MultipartConfig multipartConfig;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
multipartConfig = new MultipartConfig();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMultipartConfig() {
|
||||
assertThat(multipartConfig).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMultipartHttpMessageReader() {
|
||||
MultipartHttpMessageReader reader = multipartConfig.multipartHttpMessageReader();
|
||||
|
||||
assertThat(reader).isNotNull();
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package cn.novalon.gym.manage.app.config;
|
||||
|
||||
import io.github.resilience4j.ratelimiter.RateLimiter;
|
||||
import io.github.resilience4j.ratelimiter.RateLimiterRegistry;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.time.Duration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class RateLimitConfigTest {
|
||||
|
||||
@Test
|
||||
void testRateLimiterRegistry() throws Exception {
|
||||
RateLimitConfig rateLimitConfig = new RateLimitConfig();
|
||||
|
||||
setField(rateLimitConfig, "limitForPeriod", 100);
|
||||
setField(rateLimitConfig, "limitRefreshPeriod", Duration.ofSeconds(1));
|
||||
setField(rateLimitConfig, "timeoutDuration", Duration.ZERO);
|
||||
|
||||
RateLimiterRegistry registry = rateLimitConfig.rateLimiterRegistry();
|
||||
|
||||
assertThat(registry).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testApiRateLimiter() throws Exception {
|
||||
RateLimitConfig rateLimitConfig = new RateLimitConfig();
|
||||
|
||||
setField(rateLimitConfig, "limitForPeriod", 100);
|
||||
setField(rateLimitConfig, "limitRefreshPeriod", Duration.ofSeconds(1));
|
||||
setField(rateLimitConfig, "timeoutDuration", Duration.ZERO);
|
||||
|
||||
RateLimiterRegistry registry = rateLimitConfig.rateLimiterRegistry();
|
||||
RateLimiter rateLimiter = rateLimitConfig.apiRateLimiter(registry);
|
||||
|
||||
assertThat(rateLimiter).isNotNull();
|
||||
assertThat(rateLimiter.getName()).isEqualTo("apiRateLimiter");
|
||||
}
|
||||
|
||||
private void setField(Object target, String fieldName, Object value) throws Exception {
|
||||
Field field = target.getClass().getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
field.set(target, value);
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
package cn.novalon.gym.manage.app.integration;
|
||||
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* 数据库初始化验证测试
|
||||
*
|
||||
* 注意:此测试需要完整的数据库初始化,暂时禁用。
|
||||
* TODO: 修复数据库初始化问题
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-04-03
|
||||
*/
|
||||
@Disabled("暂时禁用:数据库初始化问题需要修复")
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
@ActiveProfiles("test")
|
||||
class DatabaseInitTest {
|
||||
|
||||
@Autowired
|
||||
private R2dbcEntityTemplate r2dbcEntityTemplate;
|
||||
|
||||
@Test
|
||||
void testSysUserTableExists() {
|
||||
r2dbcEntityTemplate.getDatabaseClient()
|
||||
.sql("SELECT COUNT(*) FROM sys_user")
|
||||
.fetch()
|
||||
.one()
|
||||
.as(StepVerifier::create)
|
||||
.expectNextCount(1)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOperationLogTableExists() {
|
||||
r2dbcEntityTemplate.getDatabaseClient()
|
||||
.sql("SELECT COUNT(*) FROM operation_log")
|
||||
.fetch()
|
||||
.one()
|
||||
.as(StepVerifier::create)
|
||||
.expectNextCount(1)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAllTablesCreated() {
|
||||
r2dbcEntityTemplate.getDatabaseClient()
|
||||
.sql("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'PUBLIC'")
|
||||
.fetch()
|
||||
.all()
|
||||
.map(row -> row.get("TABLE_NAME"))
|
||||
.collectList()
|
||||
.as(StepVerifier::create)
|
||||
.assertNext(tables -> {
|
||||
System.out.println("Created tables: " + tables);
|
||||
assert tables.contains("SYS_USER") : "SYS_USER table not found";
|
||||
assert tables.contains("OPERATION_LOG") : "OPERATION_LOG table not found";
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
package cn.novalon.gym.manage.app.integration;
|
||||
|
||||
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.data.r2dbc.core.R2dbcEntityTemplate;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
/**
|
||||
* 手动创建表测试
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-04-03
|
||||
*/
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
@ActiveProfiles("test")
|
||||
class ManualTableCreationTest {
|
||||
|
||||
@Autowired
|
||||
private R2dbcEntityTemplate r2dbcEntityTemplate;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
r2dbcEntityTemplate.getDatabaseClient()
|
||||
.sql("CREATE TABLE IF NOT EXISTS operation_log (" +
|
||||
"id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
|
||||
"username VARCHAR(50), " +
|
||||
"operation VARCHAR(100), " +
|
||||
"method VARCHAR(200), " +
|
||||
"params TEXT, " +
|
||||
"result TEXT, " +
|
||||
"ip VARCHAR(50), " +
|
||||
"duration BIGINT, " +
|
||||
"status VARCHAR(1) DEFAULT '0', " +
|
||||
"error_msg TEXT, " +
|
||||
"create_by VARCHAR(50), " +
|
||||
"update_by VARCHAR(50), " +
|
||||
"created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, " +
|
||||
"updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, " +
|
||||
"deleted_at TIMESTAMP)")
|
||||
.then()
|
||||
.as(StepVerifier::create)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOperationLogTableExists() {
|
||||
r2dbcEntityTemplate.getDatabaseClient()
|
||||
.sql("SELECT COUNT(*) FROM operation_log")
|
||||
.fetch()
|
||||
.one()
|
||||
.as(StepVerifier::create)
|
||||
.expectNextCount(1)
|
||||
.verifyComplete();
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
package cn.novalon.gym.manage.app.integration;
|
||||
|
||||
import cn.novalon.gym.manage.app.ManageApplication;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.test.context.support.WithMockUser;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
/**
|
||||
* 操作日志导出功能集成测试
|
||||
*
|
||||
* 注意:此测试存在超时问题,暂时禁用。
|
||||
* TODO: 修复Excel导出的超时问题
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-04-03
|
||||
*/
|
||||
@Disabled("暂时禁用:Excel导出功能存在超时问题,需要优化")
|
||||
@SpringBootTest(
|
||||
classes = ManageApplication.class,
|
||||
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT
|
||||
)
|
||||
@ActiveProfiles("test")
|
||||
class OperationLogExportIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
private WebTestClient webTestClient;
|
||||
|
||||
@Test
|
||||
@WithMockUser(username = "admin", roles = {"ADMIN"})
|
||||
void testExportOperationLogs_ShouldReturnExcelFile() {
|
||||
webTestClient.get()
|
||||
.uri("/api/logs/operation/export")
|
||||
.accept(MediaType.APPLICATION_OCTET_STREAM)
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectHeader().contentType(MediaType.APPLICATION_OCTET_STREAM)
|
||||
.expectHeader().valueMatches("Content-Disposition", "attachment; filename=\"operation_logs_.*\\.xlsx\"")
|
||||
.expectBody(byte[].class)
|
||||
.value(bytes -> {
|
||||
assert bytes != null;
|
||||
assert bytes.length > 0;
|
||||
assert bytes[0] == 0x50;
|
||||
assert bytes[1] == 0x4B;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(username = "admin", roles = {"ADMIN"})
|
||||
void testExportOperationLogsWithKeyword_ShouldReturnFilteredExcel() {
|
||||
webTestClient.get()
|
||||
.uri(uriBuilder -> uriBuilder
|
||||
.path("/api/logs/operation/export")
|
||||
.queryParam("keyword", "test")
|
||||
.build())
|
||||
.accept(MediaType.APPLICATION_OCTET_STREAM)
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectHeader().contentType(MediaType.APPLICATION_OCTET_STREAM)
|
||||
.expectBody(byte[].class)
|
||||
.value(bytes -> {
|
||||
assert bytes != null;
|
||||
assert bytes.length > 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
package cn.novalon.gym.manage.app.integration;
|
||||
|
||||
import cn.novalon.gym.manage.sys.core.domain.OperationLog;
|
||||
import cn.novalon.gym.manage.sys.core.service.IOperationLogService;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.test.context.support.WithMockUser;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* 操作日志集成测试
|
||||
*
|
||||
* 注意:此测试需要完整的Spring上下文,暂时禁用。
|
||||
* TODO: 优化集成测试配置
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-04-03
|
||||
*/
|
||||
@Disabled("暂时禁用:集成测试配置需要优化")
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
@ActiveProfiles("test")
|
||||
class OperationLogIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
private WebTestClient webTestClient;
|
||||
|
||||
@Autowired
|
||||
private IOperationLogService logService;
|
||||
|
||||
@Autowired
|
||||
private R2dbcEntityTemplate r2dbcEntityTemplate;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
webTestClient = webTestClient.mutate()
|
||||
.responseTimeout(Duration.ofSeconds(10))
|
||||
.build();
|
||||
|
||||
r2dbcEntityTemplate.getDatabaseClient()
|
||||
.sql("CREATE TABLE IF NOT EXISTS operation_log (" +
|
||||
"id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
|
||||
"username VARCHAR(50), " +
|
||||
"operation VARCHAR(100), " +
|
||||
"method VARCHAR(200), " +
|
||||
"params TEXT, " +
|
||||
"result TEXT, " +
|
||||
"ip VARCHAR(50), " +
|
||||
"duration BIGINT, " +
|
||||
"status VARCHAR(1) DEFAULT '0', " +
|
||||
"error_msg TEXT, " +
|
||||
"create_by VARCHAR(50), " +
|
||||
"update_by VARCHAR(50), " +
|
||||
"created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, " +
|
||||
"updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, " +
|
||||
"deleted_at TIMESTAMP)")
|
||||
.then()
|
||||
.as(StepVerifier::create)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(username = "test_user", roles = {"admin"})
|
||||
void testCreateUserOperation_ShouldLogOperation() {
|
||||
String userJson = """
|
||||
{
|
||||
"username": "test_integration_user",
|
||||
"password": "Test123!@#",
|
||||
"email": "test@example.com",
|
||||
"phone": "13900139000",
|
||||
"nickname": "集成测试用户"
|
||||
}
|
||||
""";
|
||||
|
||||
webTestClient.post()
|
||||
.uri("/api/users")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.bodyValue(userJson)
|
||||
.exchange()
|
||||
.expectStatus().isCreated()
|
||||
.expectBody()
|
||||
.jsonPath("$.id").exists()
|
||||
.jsonPath("$.username").isEqualTo("test_integration_user");
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(username = "test_user", roles = {"admin"})
|
||||
void testDeleteUserOperation_ShouldLogOperation() {
|
||||
String userJson = """
|
||||
{
|
||||
"username": "test_delete_user",
|
||||
"password": "Test123!@#",
|
||||
"email": "delete@example.com",
|
||||
"phone": "13900139001",
|
||||
"nickname": "待删除用户"
|
||||
}
|
||||
""";
|
||||
|
||||
webTestClient.post()
|
||||
.uri("/api/users")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.bodyValue(userJson)
|
||||
.exchange()
|
||||
.expectStatus().isCreated()
|
||||
.expectBody()
|
||||
.jsonPath("$.id").value(id -> {
|
||||
Long userId = Long.valueOf(id.toString());
|
||||
|
||||
webTestClient.delete()
|
||||
.uri("/api/users/{id}", userId)
|
||||
.exchange()
|
||||
.expectStatus().isNoContent();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(username = "test_user", roles = {"admin"})
|
||||
void testFailedOperation_ShouldLogError() {
|
||||
String userJson = """
|
||||
{
|
||||
"username": "admin",
|
||||
"password": "Test123!@#",
|
||||
"email": "duplicate@example.com",
|
||||
"phone": "13900139002",
|
||||
"nickname": "重复用户"
|
||||
}
|
||||
""";
|
||||
|
||||
webTestClient.post()
|
||||
.uri("/api/users")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.bodyValue(userJson)
|
||||
.exchange()
|
||||
.expectStatus().isCreated();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFindAllOperationLogs_ShouldReturnLogs() {
|
||||
StepVerifier.create(logService.findAll().take(5))
|
||||
.expectNextCount(0)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCountOperationLogs_ShouldReturnCount() {
|
||||
StepVerifier.create(logService.count())
|
||||
.expectNextCount(1)
|
||||
.verifyComplete();
|
||||
}
|
||||
}
|
||||
+224
@@ -0,0 +1,224 @@
|
||||
package cn.novalon.gym.manage.app.integration;
|
||||
|
||||
import cn.novalon.gym.manage.common.util.StatusConstants;
|
||||
import cn.novalon.gym.manage.sys.core.domain.SysUser;
|
||||
import cn.novalon.gym.manage.sys.core.domain.SysRole;
|
||||
import cn.novalon.gym.manage.sys.core.domain.UserRole;
|
||||
import cn.novalon.gym.manage.sys.core.repository.ISysUserRepository;
|
||||
import cn.novalon.gym.manage.sys.core.repository.ISysRoleRepository;
|
||||
import cn.novalon.gym.manage.sys.core.repository.IUserRoleRepository;
|
||||
import cn.novalon.gym.manage.sys.core.service.impl.SysUserService;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
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.*;
|
||||
|
||||
/**
|
||||
* 用户服务集成测试
|
||||
*
|
||||
* 使用PostgreSQL数据库进行集成测试
|
||||
*
|
||||
* 注意:此测试需要完整的Spring上下文,暂时禁用。
|
||||
* TODO: 优化集成测试配置
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-04-02
|
||||
*/
|
||||
@Disabled("暂时禁用:集成测试配置需要优化")
|
||||
@SpringBootTest
|
||||
@ActiveProfiles("test")
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
spring:
|
||||
r2dbc:
|
||||
url: r2dbc:postgresql://localhost:55432/manage_system
|
||||
username: novalon
|
||||
password: novalon123
|
||||
pool:
|
||||
enabled: true
|
||||
initial-size: 2
|
||||
max-size: 10
|
||||
|
||||
flyway:
|
||||
enabled: true
|
||||
locations: classpath:db/migration
|
||||
baseline-on-migrate: true
|
||||
validate-on-migrate: true
|
||||
|
||||
sql:
|
||||
init:
|
||||
mode: never
|
||||
|
||||
security:
|
||||
enabled: false
|
||||
|
||||
jwt:
|
||||
secret: test-secret-key-for-integration-testing
|
||||
expiration: 86400000
|
||||
|
||||
logging:
|
||||
level:
|
||||
cn.novalon.manage: DEBUG
|
||||
org.springframework.r2dbc: DEBUG
|
||||
@@ -0,0 +1,80 @@
|
||||
-- H2数据库测试数据
|
||||
-- 用于测试环境
|
||||
|
||||
-- 插入测试角色
|
||||
INSERT INTO sys_role (id, role_name, role_key, role_sort, status, create_by, update_by)
|
||||
VALUES
|
||||
(1, '超级管理员', 'admin', 1, 1, 'system', 'system'),
|
||||
(2, '测试管理员', 'test_admin', 2, 1, 'system', 'system'),
|
||||
(3, '普通用户', 'normal_user', 3, 1, 'system', 'system'),
|
||||
(4, '访客', 'guest', 4, 1, 'system', 'system');
|
||||
|
||||
-- 插入测试用户
|
||||
-- BCrypt哈希值对应明文密码: Test@123
|
||||
INSERT INTO sys_user (id, username, password, email, phone, nickname, status, create_by, update_by)
|
||||
VALUES
|
||||
(1, 'admin', '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C', 'admin@novalon.com', '13800138000', '超级管理员', 1, 'system', 'system'),
|
||||
(2, 'testadmin', '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C', 'testadmin@novalon.com', '13800138001', '测试管理员', 1, 'system', 'system'),
|
||||
(3, 'normaluser', '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C', 'normaluser@novalon.com', '13800138002', '普通用户', 1, 'system', 'system'),
|
||||
(4, 'guestuser', '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C', 'guestuser@novalon.com', '13800138003', '访客用户', 1, 'system', 'system'),
|
||||
(5, 'disableduser', '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C', 'disableduser@novalon.com', '13800138004', '禁用用户', 0, 'system', 'system');
|
||||
|
||||
-- 为用户分配角色
|
||||
INSERT INTO user_role (user_id, role_id, created_by)
|
||||
VALUES
|
||||
(1, 1, 'system'),
|
||||
(2, 2, 'system'),
|
||||
(3, 3, 'system'),
|
||||
(4, 4, 'system');
|
||||
|
||||
-- 插入测试菜单
|
||||
INSERT INTO sys_menu (id, menu_name, parent_id, order_num, path, component, menu_type, visible, status, perms, icon, created_by, updated_by)
|
||||
VALUES
|
||||
(1, '系统管理', 0, 1, '/system', 'Layout', 'M', '1', '1', '', 'system', 'system', 'system'),
|
||||
(2, '用户管理', 1, 1, 'user', 'system/user/index', 'C', '1', '1', 'system:user:list', 'user', 'system', 'system'),
|
||||
(3, '角色管理', 1, 2, 'role', 'system/role/index', 'C', '1', '1', 'system:role:list', 'role', 'system', 'system'),
|
||||
(4, '菜单管理', 1, 3, 'menu', 'system/menu/index', 'C', '1', '1', 'system:menu:list', 'menu', 'system', 'system'),
|
||||
(5, '测试菜单', 0, 99, '/test', 'Layout', 'M', '1', '1', '', 'test', 'system', 'system'),
|
||||
(6, '用户测试', 5, 1, 'user-test', 'system/user-test/index', 'C', '1', '1', 'system:user:test', 'user', 'system', 'system');
|
||||
|
||||
-- 插入测试权限
|
||||
INSERT INTO sys_permission (id, permission_name, permission_key, permission_type, parent_id, status, created_by, updated_by)
|
||||
VALUES
|
||||
(1, '系统管理', 'system:manage', 'menu', 0, 1, 'system', 'system'),
|
||||
(2, '用户管理', 'system:user:manage', 'menu', 1, 1, 'system', 'system'),
|
||||
(3, '用户查询', 'system:user:list', 'button', 2, 1, 'system', 'system'),
|
||||
(4, '用户新增', 'system:user:add', 'button', 2, 1, 'system', 'system'),
|
||||
(5, '用户编辑', 'system:user:edit', 'button', 2, 1, 'system', 'system'),
|
||||
(6, '用户删除', 'system:user:delete', 'button', 2, 1, 'system', 'system'),
|
||||
(7, '测试权限', 'test:permission', 'menu', 0, 1, 'system', 'system'),
|
||||
(8, '用户测试权限', 'system:user:test', 'button', 7, 1, 'system', 'system');
|
||||
|
||||
-- 为角色分配权限
|
||||
INSERT INTO sys_role_permission (role_id, permission_id, created_by, updated_by)
|
||||
SELECT 1, id, 'system', 'system' FROM sys_permission
|
||||
UNION ALL
|
||||
SELECT 2, id, 'system', 'system' FROM sys_permission WHERE id IN (7, 8);
|
||||
|
||||
-- 插入字典类型
|
||||
INSERT INTO sys_dict_type (id, dict_name, dict_type, status, remark, created_by, updated_by)
|
||||
VALUES
|
||||
(1, '用户状态', 'user_status', '0', '用户状态列表', 'system', 'system'),
|
||||
(2, '菜单状态', 'menu_status', '0', '菜单状态列表', 'system', 'system'),
|
||||
(3, '角色状态', 'role_status', '0', '角色状态列表', 'system', 'system');
|
||||
|
||||
-- 插入字典数据
|
||||
INSERT INTO sys_dict_data (id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, created_by, updated_by)
|
||||
VALUES
|
||||
(1, 1, '正常', '1', 'user_status', '', 'primary', 'Y', '0', 'system', 'system'),
|
||||
(2, 2, '停用', '0', 'user_status', '', 'danger', 'N', '0', 'system', 'system'),
|
||||
(3, 1, '正常', '0', 'menu_status', '', 'primary', 'Y', '0', 'system', 'system'),
|
||||
(4, 2, '停用', '1', 'menu_status', '', 'danger', 'N', '0', 'system', 'system'),
|
||||
(5, 1, '正常', '0', 'role_status', '', 'primary', 'Y', '0', 'system', 'system'),
|
||||
(6, 2, '停用', '1', 'role_status', '', 'danger', 'N', '0', 'system', 'system');
|
||||
|
||||
-- 插入系统配置
|
||||
INSERT INTO sys_config (id, config_name, config_key, config_value, config_type, remark, created_by, updated_by)
|
||||
VALUES
|
||||
(1, '用户管理-用户初始密码', 'sys.user.initPassword', '123456', 'Y', '初始化用户密码', 'system', 'system'),
|
||||
(2, '主框架页-默认皮肤样式名称', 'sys.index.skinName', 'skin-blue', 'Y', '默认皮肤', 'system', 'system'),
|
||||
(3, '用户自助-验证码开关', 'sys.account.captchaEnabled', 'true', 'Y', '是否开启验证码功能', 'system', 'system');
|
||||
@@ -0,0 +1,76 @@
|
||||
-- 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),
|
||||
phone VARCHAR(20),
|
||||
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,
|
||||
deleted_at TIMESTAMP
|
||||
);
|
||||
|
||||
-- 创建角色表
|
||||
CREATE TABLE IF NOT EXISTS sys_role (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
role_name VARCHAR(100) NOT NULL,
|
||||
role_key VARCHAR(100) NOT NULL UNIQUE,
|
||||
role_sort INTEGER DEFAULT 0,
|
||||
status INTEGER DEFAULT 1,
|
||||
create_by VARCHAR(50),
|
||||
update_by VARCHAR(50),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP
|
||||
);
|
||||
|
||||
-- 创建用户角色关联表
|
||||
CREATE TABLE IF NOT EXISTS user_role (
|
||||
id BIGINT AUTO_INCREMENT 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 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 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);
|
||||
|
||||
-- 创建审计日志表
|
||||
CREATE TABLE IF NOT EXISTS audit_log (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
entity_type VARCHAR(100) NOT NULL,
|
||||
entity_id BIGINT,
|
||||
operation_type VARCHAR(20) NOT NULL,
|
||||
operator VARCHAR(100),
|
||||
operation_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
before_data CLOB,
|
||||
after_data CLOB,
|
||||
changed_fields CLOB,
|
||||
ip_address VARCHAR(50),
|
||||
user_agent CLOB,
|
||||
description CLOB,
|
||||
create_by VARCHAR(50),
|
||||
update_by VARCHAR(50),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP
|
||||
);
|
||||
|
||||
-- 创建审计日志索引
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_log_entity_type ON audit_log(entity_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_log_entity_id ON audit_log(entity_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_log_operation_type ON audit_log(operation_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_log_operator ON audit_log(operator);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_log_operation_time ON audit_log(operation_time);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_log_entity ON audit_log(entity_type, entity_id);
|
||||
Reference in New Issue
Block a user