diff --git a/novalon-manage-api/manage-app/pom.xml b/novalon-manage-api/manage-app/pom.xml
index 0be245b..d1cc872 100644
--- a/novalon-manage-api/manage-app/pom.xml
+++ b/novalon-manage-api/manage-app/pom.xml
@@ -22,6 +22,16 @@
manage-sys
${project.version}
+
+ cn.novalon.manage
+ manage-notify
+ ${project.version}
+
+
+ cn.novalon.manage
+ manage-file
+ ${project.version}
+
cn.novalon.manage
manage-db
@@ -35,6 +45,16 @@
org.springframework.boot
spring-boot-starter-actuator
+
+ io.github.resilience4j
+ resilience4j-spring-boot3
+ 2.2.0
+
+
+ io.github.resilience4j
+ resilience4j-reactor
+ 2.2.0
+
io.micrometer
micrometer-registry-prometheus
diff --git a/novalon-manage-api/manage-app/src/main/java/cn/novalon/manage/app/ManageApplication.java b/novalon-manage-api/manage-app/src/main/java/cn/novalon/manage/app/ManageApplication.java
index e601cd7..ba2dea4 100644
--- a/novalon-manage-api/manage-app/src/main/java/cn/novalon/manage/app/ManageApplication.java
+++ b/novalon-manage-api/manage-app/src/main/java/cn/novalon/manage/app/ManageApplication.java
@@ -3,13 +3,11 @@ package cn.novalon.manage.app;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
-import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories;
@SpringBootApplication
@ConfigurationPropertiesScan(basePackages = "cn.novalon.manage")
-@ComponentScan(basePackages = "cn.novalon.manage")
-@EnableR2dbcRepositories(basePackages = "cn.novalon.manage.db.dao")
+@EnableR2dbcRepositories(basePackages = {"cn.novalon.manage.db.dao"})
public class ManageApplication {
public static void main(String[] args) {
diff --git a/novalon-manage-api/manage-app/src/main/java/cn/novalon/manage/app/config/MultipartConfig.java b/novalon-manage-api/manage-app/src/main/java/cn/novalon/manage/app/config/MultipartConfig.java
new file mode 100644
index 0000000..8b7110f
--- /dev/null
+++ b/novalon-manage-api/manage-app/src/main/java/cn/novalon/manage/app/config/MultipartConfig.java
@@ -0,0 +1,19 @@
+package cn.novalon.manage.sys.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);
+ }
+}
\ No newline at end of file
diff --git a/novalon-manage-api/manage-app/src/main/java/cn/novalon/manage/app/config/OpenApiConfig.java b/novalon-manage-api/manage-app/src/main/java/cn/novalon/manage/app/config/OpenApiConfig.java
new file mode 100644
index 0000000..e50384b
--- /dev/null
+++ b/novalon-manage-api/manage-app/src/main/java/cn/novalon/manage/app/config/OpenApiConfig.java
@@ -0,0 +1,45 @@
+package cn.novalon.manage.sys.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.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.Arrays;
+import java.util.List;
+
+@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:8080").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("系统统计相关操作")));
+ }
+}
diff --git a/novalon-manage-api/manage-app/src/main/java/cn/novalon/manage/app/config/RateLimitConfig.java b/novalon-manage-api/manage-app/src/main/java/cn/novalon/manage/app/config/RateLimitConfig.java
new file mode 100644
index 0000000..b28d5f7
--- /dev/null
+++ b/novalon-manage-api/manage-app/src/main/java/cn/novalon/manage/app/config/RateLimitConfig.java
@@ -0,0 +1,41 @@
+package cn.novalon.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");
+ }
+}
\ No newline at end of file
diff --git a/novalon-manage-api/manage-app/src/main/java/cn/novalon/manage/app/config/SystemRouter.java b/novalon-manage-api/manage-app/src/main/java/cn/novalon/manage/app/config/SystemRouter.java
new file mode 100644
index 0000000..2b8ebfc
--- /dev/null
+++ b/novalon-manage-api/manage-app/src/main/java/cn/novalon/manage/app/config/SystemRouter.java
@@ -0,0 +1,155 @@
+package cn.novalon.manage.sys.config;
+
+import cn.novalon.manage.sys.handler.auth.SysAuthHandler;
+import cn.novalon.manage.sys.handler.config.SysConfigHandler;
+import cn.novalon.manage.sys.handler.dictionary.DictionaryHandler;
+import cn.novalon.manage.sys.handler.dict.SysDictHandler;
+import cn.novalon.manage.sys.handler.log.SysLogHandler;
+import cn.novalon.manage.sys.handler.menu.MenuHandler;
+import cn.novalon.manage.sys.handler.role.SysRoleHandler;
+import cn.novalon.manage.sys.handler.stats.StatsHandler;
+import cn.novalon.manage.sys.handler.user.SysUserHandler;
+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 dictionaryRoutes(DictionaryHandler dictionaryHandler) {
+ return route()
+ .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)
+ .build();
+ }
+
+ @Bean
+ public RouterFunction userRoutes(SysUserHandler userHandler) {
+ return route()
+ .GET("/api/users", userHandler::getAllUsers)
+ .GET("/api/users/page", userHandler::getUsersByPage)
+ .GET("/api/users/count", userHandler::getUserCount)
+ .GET("/api/users/{id}", userHandler::getUserById)
+ .GET("/api/users/username/{username}", userHandler::getUserByUsername)
+ .POST("/api/users", userHandler::createUser)
+ .PUT("/api/users/{id}", userHandler::updateUser)
+ .DELETE("/api/users/{id}", userHandler::deleteUser)
+ .POST("/api/users/{id}/password", userHandler::changePassword)
+ .DELETE("/api/users/{id}/logical", userHandler::logicalDeleteUser)
+ .POST("/api/users/logical-delete", userHandler::logicalDeleteUsers)
+ .POST("/api/users/{id}/restore", userHandler::restoreUser)
+ .POST("/api/users/restore", userHandler::restoreUsers)
+ .GET("/api/users/check/username", userHandler::checkUsernameExists)
+ .GET("/api/users/check/email", userHandler::checkEmailExists)
+ .build();
+ }
+
+ @Bean
+ public RouterFunction menuRoutes(MenuHandler menuHandler) {
+ return route()
+ .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)
+ .build();
+ }
+
+ @Bean
+ public RouterFunction roleRoutes(SysRoleHandler roleHandler) {
+ return route()
+ .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)
+ .build();
+ }
+
+ @Bean
+ public RouterFunction configRoutes(SysConfigHandler configHandler) {
+ return route()
+ .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)
+ .build();
+ }
+
+ @Bean
+ public RouterFunction logRoutes(SysLogHandler logHandler) {
+ return route()
+ .GET("/api/logs/login", logHandler::getAllLoginLogs)
+ .GET("/api/logs/login/page", logHandler::getLoginLogsByPage)
+ .GET("/api/logs/login/count", logHandler::getLoginLogCount)
+ .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)
+ .build();
+ }
+
+ @Bean
+ public RouterFunction authRoutes(SysAuthHandler authHandler) {
+ return route()
+ .POST("/api/auth/login", authHandler::login)
+ .POST("/api/auth/register", authHandler::register)
+ .POST("/api/auth/logout", authHandler::logout)
+ .build();
+ }
+
+ @Bean
+ public RouterFunction statsRoutes(StatsHandler statsHandler) {
+ return route()
+ .GET("/api/stats/overview", statsHandler::getOverview)
+ .build();
+ }
+
+ @Bean
+ public RouterFunction dictRoutes(SysDictHandler dictHandler) {
+ return route()
+ .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)
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/novalon-manage-api/manage-app/src/main/java/cn/novalon/manage/app/config/WebFluxConfig.java b/novalon-manage-api/manage-app/src/main/java/cn/novalon/manage/app/config/WebFluxConfig.java
new file mode 100644
index 0000000..cafa2a4
--- /dev/null
+++ b/novalon-manage-api/manage-app/src/main/java/cn/novalon/manage/app/config/WebFluxConfig.java
@@ -0,0 +1,14 @@
+package cn.novalon.manage.sys.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.codec.ServerCodecConfigurer;
+import org.springframework.web.reactive.config.WebFluxConfigurer;
+
+@Configuration
+public class WebFluxConfig implements WebFluxConfigurer {
+
+ @Override
+ public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
+ configurer.defaultCodecs().maxInMemorySize(16 * 1024 * 1024);
+ }
+}
\ No newline at end of file
diff --git a/novalon-manage-api/manage-app/src/main/resources/application.yml b/novalon-manage-api/manage-app/src/main/resources/application.yml
index c9b70a7..bd5c468 100644
--- a/novalon-manage-api/manage-app/src/main/resources/application.yml
+++ b/novalon-manage-api/manage-app/src/main/resources/application.yml
@@ -5,7 +5,7 @@ spring:
application:
name: manage-app
r2dbc:
- url: r2dbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_NAME:novalon_manage}
+ url: r2dbc:postgresql://${DB_HOST:localhost}:${DB_PORT:55432}/${DB_NAME:manage_system}
username: ${DB_USERNAME:postgres}
password: ${DB_PASSWORD:postgres}
pool: