refactor: migrate SysNotice to manage-notify module

This commit is contained in:
张翔
2026-03-14 10:24:06 +08:00
parent 4f4331f2d9
commit 4f1caaf758
155 changed files with 3272 additions and 865 deletions
@@ -1,18 +0,0 @@
package cn.novalon.manage.sys;
import cn.novalon.manage.common.config.JwtProperties;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories;
@SpringBootApplication
@EnableConfigurationProperties(JwtProperties.class)
@ComponentScan(basePackages = {"cn.novalon.manage.sys", "cn.novalon.manage.db"})
@EnableR2dbcRepositories(basePackages = "cn.novalon.manage.db.dao")
public class ManageSysApplication {
public static void main(String[] args) {
SpringApplication.run(ManageSysApplication.class, args);
}
}
@@ -0,0 +1,21 @@
package cn.novalon.manage.sys.config;
import cn.novalon.manage.common.handler.ExceptionLogService;
import cn.novalon.manage.sys.handler.ExceptionLogServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 异常日志配置类
*
* @author 张翔
* @date 2026-03-13
*/
@Configuration
public class ExceptionLogConfig {
@Bean
public ExceptionLogService exceptionLogService(ExceptionLogServiceImpl exceptionLogServiceImpl) {
return exceptionLogServiceImpl;
}
}
@@ -1,19 +0,0 @@
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);
}
}
@@ -1,45 +0,0 @@
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("系统统计相关操作")));
}
}
@@ -11,6 +11,12 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.server.SecurityWebFilterChain;
/**
* 安全配置类
*
* @author 张翔
* @date 2026-03-13
*/
@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {
@@ -1,173 +0,0 @@
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.file.SysFileHandler;
import cn.novalon.manage.sys.handler.log.SysLogHandler;
import cn.novalon.manage.sys.handler.message.SysUserMessageHandler;
import cn.novalon.manage.sys.handler.notice.SysNoticeHandler;
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;
@Configuration
public class SystemRouter {
@Bean
public RouterFunction<ServerResponse> 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<ServerResponse> 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<ServerResponse> 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<ServerResponse> 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<ServerResponse> noticeRoutes(SysNoticeHandler noticeHandler) {
return route()
.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)
.build();
}
@Bean
public RouterFunction<ServerResponse> fileRoutes(SysFileHandler fileHandler) {
return route()
.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)
.build();
}
@Bean
public RouterFunction<ServerResponse> 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<ServerResponse> 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<ServerResponse> messageRoutes(SysUserMessageHandler messageHandler) {
return route()
.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)
.build();
}
@Bean
public RouterFunction<ServerResponse> statsRoutes(StatsHandler statsHandler) {
return route()
.GET("/api/stats/overview", statsHandler::getOverview)
.build();
}
@Bean
public RouterFunction<ServerResponse> 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();
}
}
@@ -1,14 +0,0 @@
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);
}
}
@@ -3,6 +3,7 @@ package cn.novalon.manage.sys.config;
import cn.novalon.manage.sys.websocket.SysWebSocketHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.web.reactive.HandlerMapping;
import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping;
import org.springframework.web.reactive.socket.WebSocketHandler;
@@ -11,6 +12,12 @@ import org.springframework.web.reactive.socket.server.support.WebSocketHandlerAd
import java.util.HashMap;
import java.util.Map;
/**
* WebSocket配置类
*
* @author 张翔
* @date 2026-03-13
*/
@Configuration
public class WebSocketConfig {
@@ -20,7 +27,7 @@ public class WebSocketConfig {
map.put("/ws", webSocketHandler);
SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping();
handlerMapping.setOrder(1);
handlerMapping.setOrder(Ordered.HIGHEST_PRECEDENCE);
handlerMapping.setUrlMap(map);
return handlerMapping;
}
@@ -0,0 +1,21 @@
package cn.novalon.manage.sys.core.command;
/**
* 创建菜单命令对象
*
* @author 张翔
* @date 2026-03-13
*/
public record CreateMenuCommand(
Long parentId,
String menuName,
String menuType,
Integer orderNum,
String component,
String perms,
Integer status) {
public static CreateMenuCommand of(Long parentId, String menuName, String menuType, Integer orderNum,
String component, String perms, Integer status) {
return new CreateMenuCommand(parentId, menuName, menuType, orderNum, component, perms, status);
}
}
@@ -0,0 +1,39 @@
package cn.novalon.manage.sys.core.command;
/**
* 创建公告命令对象
*
* @author 张翔
* @date 2026-03-13
*/
public record CreateNoticeCommand(
String noticeTitle,
String noticeContent,
String noticeType,
String status) {
public static CreateNoticeCommand of(String noticeTitle, String noticeContent, String noticeType, String status) {
validateNoticeTitle(noticeTitle);
validateNoticeContent(noticeContent);
validateNoticeType(noticeType);
return new CreateNoticeCommand(noticeTitle, noticeContent, noticeType, status);
}
private static void validateNoticeTitle(String noticeTitle) {
if (noticeTitle == null || noticeTitle.trim().isEmpty()) {
throw new IllegalArgumentException("Notice title is required");
}
}
private static void validateNoticeContent(String noticeContent) {
if (noticeContent == null || noticeContent.trim().isEmpty()) {
throw new IllegalArgumentException("Notice content is required");
}
}
private static void validateNoticeType(String noticeType) {
if (noticeType != null && !noticeType.equals("1") && !noticeType.equals("2")) {
throw new IllegalArgumentException(
"Invalid notice type. Notice type must be 1 (notification) or 2 (announcement)");
}
}
}
@@ -0,0 +1,27 @@
package cn.novalon.manage.sys.core.command;
import cn.novalon.manage.common.util.StatusConstants;
/**
* 创建角色命令对象
*
* @author 张翔
* @date 2026-03-13
*/
public record CreateRoleCommand(
String roleName,
String roleKey,
Integer roleSort,
Integer status
) {
public static CreateRoleCommand of(String roleName, String roleKey, Integer roleSort, Integer status) {
validateStatus(status);
return new CreateRoleCommand(roleName, roleKey, roleSort, status);
}
private static void validateStatus(Integer status) {
if (status != null && status != StatusConstants.ENABLED && status != StatusConstants.DISABLED) {
throw new IllegalArgumentException("Invalid status value. Status must be 0 (disabled) or 1 (enabled)");
}
}
}
@@ -0,0 +1,29 @@
package cn.novalon.manage.sys.core.command;
import cn.novalon.manage.sys.primitive.Email;
import cn.novalon.manage.sys.primitive.Password;
import cn.novalon.manage.sys.primitive.Username;
/**
* 创建用户命令对象
*
* @author 张翔
* @date 2026-03-13
*/
public record CreateUserCommand(
Username username,
Password password,
Email email,
Long roleId,
Integer status
) {
public static CreateUserCommand of(String username, String password, String email, Long roleId, Integer status) {
return new CreateUserCommand(
Username.of(username),
Password.of(password),
Email.of(email),
roleId,
status
);
}
}
@@ -0,0 +1,23 @@
package cn.novalon.manage.sys.core.command;
/**
* 更新菜单命令对象
*
* @author 张翔
* @date 2026-03-13
*/
public record UpdateMenuCommand(
Long id,
Long parentId,
String menuName,
String menuType,
Integer orderNum,
String component,
String perms,
Integer status
) {
public static UpdateMenuCommand of(Long id, Long parentId, String menuName, String menuType, Integer orderNum,
String component, String perms, Integer status) {
return new UpdateMenuCommand(id, parentId, menuName, menuType, orderNum, component, perms, status);
}
}
@@ -0,0 +1,19 @@
package cn.novalon.manage.sys.core.command;
/**
* 更新角色命令对象
*
* @author 张翔
* @date 2026-03-13
*/
public record UpdateRoleCommand(
Long id,
String roleName,
String roleKey,
Integer roleSort,
Integer status
) {
public static UpdateRoleCommand of(Long id, String roleName, String roleKey, Integer roleSort, Integer status) {
return new UpdateRoleCommand(id, roleName, roleKey, roleSort, status);
}
}
@@ -0,0 +1,20 @@
package cn.novalon.manage.sys.core.command;
/**
* 更新用户命令对象
*
* @author 张翔
* @date 2026-03-13
*/
public record UpdateUserCommand(
Long id,
String username,
String password,
String email,
Long roleId,
Integer status
) {
public static UpdateUserCommand of(Long id, String username, String password, String email, Long roleId, Integer status) {
return new UpdateUserCommand(id, username, password, email, roleId, status);
}
}
@@ -0,0 +1,67 @@
package cn.novalon.manage.sys.core.domain;
import java.time.LocalDateTime;
/**
* 基础领域对象
*
* @author 张翔
* @date 2026-03-13
*/
public abstract class BaseDomain {
protected Long id;
protected String createBy;
protected String updateBy;
protected LocalDateTime createdAt;
protected LocalDateTime updatedAt;
protected LocalDateTime deletedAt;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getCreateBy() {
return createBy;
}
public void setCreateBy(String createBy) {
this.createBy = createBy;
}
public String getUpdateBy() {
return updateBy;
}
public void setUpdateBy(String updateBy) {
this.updateBy = updateBy;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
public LocalDateTime getDeletedAt() {
return deletedAt;
}
public void setDeletedAt(LocalDateTime deletedAt) {
this.deletedAt = deletedAt;
}
}
@@ -0,0 +1,123 @@
package cn.novalon.manage.sys.core.domain;
import java.time.LocalDateTime;
/**
* 字典领域对象
*
* @author 张翔
* @date 2026-03-13
*/
public class Dictionary {
private Long id;
private String type;
private String code;
private String name;
private String value;
private String remark;
private Integer sort;
private String createBy;
private String updateBy;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private LocalDateTime deletedAt;
public Dictionary() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
public Integer getSort() {
return sort;
}
public void setSort(Integer sort) {
this.sort = sort;
}
public String getCreateBy() {
return createBy;
}
public void setCreateBy(String createBy) {
this.createBy = createBy;
}
public String getUpdateBy() {
return updateBy;
}
public void setUpdateBy(String updateBy) {
this.updateBy = updateBy;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
public LocalDateTime getDeletedAt() {
return deletedAt;
}
public void setDeletedAt(LocalDateTime deletedAt) {
this.deletedAt = deletedAt;
}
}
@@ -0,0 +1,92 @@
package cn.novalon.manage.sys.core.domain;
/**
* 操作日志领域对象
*
* @author 张翔
* @date 2026-03-13
*/
public class OperationLog extends BaseDomain {
private String username;
private String operation;
private String method;
private String params;
private String result;
private String ip;
private Long duration;
private String status;
private String errorMsg;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getOperation() {
return operation;
}
public void setOperation(String operation) {
this.operation = operation;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public String getParams() {
return params;
}
public void setParams(String params) {
this.params = params;
}
public String getResult() {
return result;
}
public void setResult(String result) {
this.result = result;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public Long getDuration() {
return duration;
}
public void setDuration(Long duration) {
this.duration = duration;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getErrorMsg() {
return errorMsg;
}
public void setErrorMsg(String errorMsg) {
this.errorMsg = errorMsg;
}
}
@@ -0,0 +1,44 @@
package cn.novalon.manage.sys.core.domain;
import java.time.LocalDateTime;
/**
* 系统配置领域对象
*
* @author 张翔
* @date 2026-03-13
*/
public class SysConfig {
private Long id;
private String configName;
private String configKey;
private String configValue;
private String configType;
private String createBy;
private String updateBy;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private LocalDateTime deletedAt;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getConfigName() { return configName; }
public void setConfigName(String configName) { this.configName = configName; }
public String getConfigKey() { return configKey; }
public void setConfigKey(String configKey) { this.configKey = configKey; }
public String getConfigValue() { return configValue; }
public void setConfigValue(String configValue) { this.configValue = configValue; }
public String getConfigType() { return configType; }
public void setConfigType(String configType) { this.configType = configType; }
public String getCreateBy() { return createBy; }
public void setCreateBy(String createBy) { this.createBy = createBy; }
public String getUpdateBy() { return updateBy; }
public void setUpdateBy(String updateBy) { this.updateBy = updateBy; }
public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
public LocalDateTime getUpdatedAt() { return updatedAt; }
public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
public LocalDateTime getDeletedAt() { return deletedAt; }
public void setDeletedAt(LocalDateTime deletedAt) { this.deletedAt = deletedAt; }
}
@@ -0,0 +1,59 @@
package cn.novalon.manage.sys.core.domain;
import java.time.LocalDateTime;
/**
* 字典数据领域对象
*
* @author 张翔
* @date 2026-03-13
*/
public class SysDictData {
private Long id;
private Long dictTypeId;
private String dictLabel;
private String dictValue;
private Integer dictSort;
private String dictType;
private String cssClass;
private String listClass;
private String isDefault;
private String status;
private String createBy;
private String updateBy;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private LocalDateTime deletedAt;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public Long getDictTypeId() { return dictTypeId; }
public void setDictTypeId(Long dictTypeId) { this.dictTypeId = dictTypeId; }
public String getDictLabel() { return dictLabel; }
public void setDictLabel(String dictLabel) { this.dictLabel = dictLabel; }
public String getDictValue() { return dictValue; }
public void setDictValue(String dictValue) { this.dictValue = dictValue; }
public Integer getDictSort() { return dictSort; }
public void setDictSort(Integer dictSort) { this.dictSort = dictSort; }
public String getDictType() { return dictType; }
public void setDictType(String dictType) { this.dictType = dictType; }
public String getCssClass() { return cssClass; }
public void setCssClass(String cssClass) { this.cssClass = cssClass; }
public String getListClass() { return listClass; }
public void setListClass(String listClass) { this.listClass = listClass; }
public String getIsDefault() { return isDefault; }
public void setIsDefault(String isDefault) { this.isDefault = isDefault; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
public String getCreateBy() { return createBy; }
public void setCreateBy(String createBy) { this.createBy = createBy; }
public String getUpdateBy() { return updateBy; }
public void setUpdateBy(String updateBy) { this.updateBy = updateBy; }
public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
public LocalDateTime getUpdatedAt() { return updatedAt; }
public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
public LocalDateTime getDeletedAt() { return deletedAt; }
public void setDeletedAt(LocalDateTime deletedAt) { this.deletedAt = deletedAt; }
}
@@ -0,0 +1,44 @@
package cn.novalon.manage.sys.core.domain;
import java.time.LocalDateTime;
/**
* 字典类型领域对象
*
* @author 张翔
* @date 2026-03-13
*/
public class SysDictType {
private Long id;
private String dictName;
private String dictType;
private String status;
private String remark;
private String createBy;
private String updateBy;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private LocalDateTime deletedAt;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getDictName() { return dictName; }
public void setDictName(String dictName) { this.dictName = dictName; }
public String getDictType() { return dictType; }
public void setDictType(String dictType) { this.dictType = dictType; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
public String getRemark() { return remark; }
public void setRemark(String remark) { this.remark = remark; }
public String getCreateBy() { return createBy; }
public void setCreateBy(String createBy) { this.createBy = createBy; }
public String getUpdateBy() { return updateBy; }
public void setUpdateBy(String updateBy) { this.updateBy = updateBy; }
public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
public LocalDateTime getUpdatedAt() { return updatedAt; }
public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
public LocalDateTime getDeletedAt() { return deletedAt; }
public void setDeletedAt(LocalDateTime deletedAt) { this.deletedAt = deletedAt; }
}
@@ -0,0 +1,44 @@
package cn.novalon.manage.sys.core.domain;
import java.time.LocalDateTime;
/**
* 异常日志领域对象
*
* @author 张翔
* @date 2026-03-13
*/
public class SysExceptionLog {
private Long id;
private String username;
private String title;
private String exceptionName;
private String methodName;
private String methodParams;
private String exceptionMsg;
private String exceptionStack;
private String ip;
private LocalDateTime createTime;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getExceptionName() { return exceptionName; }
public void setExceptionName(String exceptionName) { this.exceptionName = exceptionName; }
public String getMethodName() { return methodName; }
public void setMethodName(String methodName) { this.methodName = methodName; }
public String getMethodParams() { return methodParams; }
public void setMethodParams(String methodParams) { this.methodParams = methodParams; }
public String getExceptionMsg() { return exceptionMsg; }
public void setExceptionMsg(String exceptionMsg) { this.exceptionMsg = exceptionMsg; }
public String getExceptionStack() { return exceptionStack; }
public void setExceptionStack(String exceptionStack) { this.exceptionStack = exceptionStack; }
public String getIp() { return ip; }
public void setIp(String ip) { this.ip = ip; }
public LocalDateTime getCreateTime() { return createTime; }
public void setCreateTime(LocalDateTime createTime) { this.createTime = createTime; }
}
@@ -0,0 +1,44 @@
package cn.novalon.manage.sys.core.domain;
import java.time.LocalDateTime;
/**
* 文件管理领域对象
*
* @author 张翔
* @date 2026-03-13
*/
public class SysFile {
private Long id;
private String fileName;
private String filePath;
private String fileSize;
private String fileType;
private String storageType;
private String createBy;
private String updateBy;
private LocalDateTime createdAt;
private LocalDateTime deletedAt;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getFileName() { return fileName; }
public void setFileName(String fileName) { this.fileName = fileName; }
public String getFilePath() { return filePath; }
public void setFilePath(String filePath) { this.filePath = filePath; }
public String getFileSize() { return fileSize; }
public void setFileSize(String fileSize) { this.fileSize = fileSize; }
public String getFileType() { return fileType; }
public void setFileType(String fileType) { this.fileType = fileType; }
public String getStorageType() { return storageType; }
public void setStorageType(String storageType) { this.storageType = storageType; }
public String getCreateBy() { return createBy; }
public void setCreateBy(String createBy) { this.createBy = createBy; }
public String getUpdateBy() { return updateBy; }
public void setUpdateBy(String updateBy) { this.updateBy = updateBy; }
public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
public LocalDateTime getDeletedAt() { return deletedAt; }
public void setDeletedAt(LocalDateTime deletedAt) { this.deletedAt = deletedAt; }
}
@@ -0,0 +1,41 @@
package cn.novalon.manage.sys.core.domain;
import java.time.LocalDateTime;
/**
* 登录日志领域对象
*
* @author 张翔
* @date 2026-03-13
*/
public class SysLoginLog {
private Long id;
private String username;
private String ip;
private String location;
private String browser;
private String os;
private String status;
private String message;
private LocalDateTime loginTime;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getIp() { return ip; }
public void setIp(String ip) { this.ip = ip; }
public String getLocation() { return location; }
public void setLocation(String location) { this.location = location; }
public String getBrowser() { return browser; }
public void setBrowser(String browser) { this.browser = browser; }
public String getOs() { return os; }
public void setOs(String os) { this.os = os; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
public LocalDateTime getLoginTime() { return loginTime; }
public void setLoginTime(LocalDateTime loginTime) { this.loginTime = loginTime; }
}
@@ -0,0 +1,103 @@
package cn.novalon.manage.sys.core.domain;
import java.util.List;
/**
* 菜单领域对象
*
* @author 张翔
* @date 2026-03-13
*/
public class SysMenu extends BaseDomain {
private String menuName;
private Long parentId;
private Integer orderNum;
private String menuType;
private String perms;
private String component;
private Integer status;
private String createBy;
private String updateBy;
private List<SysMenu> children;
public String getMenuName() {
return menuName;
}
public void setMenuName(String menuName) {
this.menuName = menuName;
}
public Long getParentId() {
return parentId;
}
public void setParentId(Long parentId) {
this.parentId = parentId;
}
public Integer getOrderNum() {
return orderNum;
}
public void setOrderNum(Integer orderNum) {
this.orderNum = orderNum;
}
public String getMenuType() {
return menuType;
}
public void setMenuType(String menuType) {
this.menuType = menuType;
}
public String getPerms() {
return perms;
}
public void setPerms(String perms) {
this.perms = perms;
}
public String getComponent() {
return component;
}
public void setComponent(String component) {
this.component = component;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getCreateBy() {
return createBy;
}
public void setCreateBy(String createBy) {
this.createBy = createBy;
}
public String getUpdateBy() {
return updateBy;
}
public void setUpdateBy(String updateBy) {
this.updateBy = updateBy;
}
public List<SysMenu> getChildren() {
return children;
}
public void setChildren(List<SysMenu> children) {
this.children = children;
}
}
@@ -0,0 +1,75 @@
package cn.novalon.manage.sys.core.domain;
import cn.novalon.manage.common.util.SnowflakeId;
import java.time.LocalDateTime;
/**
* 角色领域对象
*
* @author 张翔
* @date 2026-03-13
*/
public class SysRole extends BaseDomain {
private String roleName;
private String roleKey;
private Integer roleSort;
private Integer status;
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
public String getRoleKey() {
return roleKey;
}
public void setRoleKey(String roleKey) {
this.roleKey = roleKey;
}
public Integer getRoleSort() {
return roleSort;
}
public void setRoleSort(Integer roleSort) {
this.roleSort = roleSort;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
/**
* 生成主键ID
*
* @return 主键ID
*/
public Long generateId() {
this.id = SnowflakeId.nextId();
return this.id;
}
/**
* 删除角色
*/
public void delete() {
this.deletedAt = LocalDateTime.now();
}
/**
* 恢复角色
*/
public void restore() {
this.deletedAt = null;
}
}
@@ -0,0 +1,77 @@
package cn.novalon.manage.sys.core.domain;
import cn.novalon.manage.common.util.SnowflakeId;
import java.time.LocalDateTime;
/**
* 用户领域对象
*
* @author 张翔
* @date 2026-03-13
*/
public class SysUser extends BaseDomain {
private String username;
private String password;
private String email;
private Long roleId;
private Integer status;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Long getRoleId() {
return roleId;
}
public void setRoleId(Long roleId) {
this.roleId = roleId;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
/**
* 生成主键ID
*
* @return 主键ID
*/
public Long generateId() {
this.id = SnowflakeId.nextId();
return this.id;
}
/**
* 删除用户
*/
public void delete() {
this.deletedAt = LocalDateTime.now();
}
}
@@ -0,0 +1,35 @@
package cn.novalon.manage.sys.core.domain;
import java.time.LocalDateTime;
/**
* 用户消息领域对象
*
* @author 张翔
* @date 2026-03-13
*/
public class SysUserMessage {
private Long id;
private Long userId;
private String title;
private String content;
private String messageType;
private String isRead;
private LocalDateTime createTime;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public Long getUserId() { return userId; }
public void setUserId(Long userId) { this.userId = userId; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getContent() { return content; }
public void setContent(String content) { this.content = content; }
public String getMessageType() { return messageType; }
public void setMessageType(String messageType) { this.messageType = messageType; }
public String getIsRead() { return isRead; }
public void setIsRead(String isRead) { this.isRead = isRead; }
public LocalDateTime getCreateTime() { return createTime; }
public void setCreateTime(LocalDateTime createTime) { this.createTime = createTime; }
}
@@ -1,5 +1,11 @@
package cn.novalon.manage.sys.core.exception;
/**
* 字典已存在异常
*
* @author 张翔
* @date 2026-03-13
*/
public class DictionaryAlreadyExistsException extends RuntimeException {
private final String type;
@@ -0,0 +1,56 @@
package cn.novalon.manage.sys.core.query;
/**
* 菜单查询对象
*
* @author 张翔
* @date 2026-03-13
*/
public class SysMenuQuery {
private String menuName;
private String menuType;
private Integer status;
private Long parentId;
private String keyword;
public String getMenuName() {
return menuName;
}
public void setMenuName(String menuName) {
this.menuName = menuName;
}
public String getMenuType() {
return menuType;
}
public void setMenuType(String menuType) {
this.menuType = menuType;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public Long getParentId() {
return parentId;
}
public void setParentId(Long parentId) {
this.parentId = parentId;
}
public String getKeyword() {
return keyword;
}
public void setKeyword(String keyword) {
this.keyword = keyword;
}
}
@@ -0,0 +1,50 @@
package cn.novalon.manage.sys.core.query;
/**
* 角色查询对象
*
* @author 张翔
* @date 2026-03-13
*/
public class SysRoleQuery {
private String roleName;
private String roleKey;
private Integer status;
private String keyword;
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
public String getRoleKey() {
return roleKey;
}
public void setRoleKey(String roleKey) {
this.roleKey = roleKey;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getKeyword() {
return keyword;
}
public void setKeyword(String keyword) {
this.keyword = keyword;
}
}
@@ -0,0 +1,60 @@
package cn.novalon.manage.sys.core.query;
/**
* 用户查询对象
*
* @author 张翔
* @date 2026-03-13
*/
public class SysUserQuery {
private String username;
private String email;
private Long roleId;
private Integer status;
private String keyword;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Long getRoleId() {
return roleId;
}
public void setRoleId(Long roleId) {
this.roleId = roleId;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getKeyword() {
return keyword;
}
public void setKeyword(String keyword) {
this.keyword = keyword;
}
}
@@ -0,0 +1,32 @@
package cn.novalon.manage.sys.core.repository;
import cn.novalon.manage.sys.core.domain.Dictionary;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* 字典仓储接口
*
* @author 张翔
* @date 2026-03-13
*/
public interface IDictionaryRepository {
Flux<Dictionary> findAll();
Flux<Dictionary> findByDeletedAtIsNullOrderBySortAsc();
Mono<Dictionary> findById(Long id);
Flux<Dictionary> findByType(String type);
Mono<Dictionary> findByTypeAndCode(String type, String code);
Mono<Boolean> existsByTypeAndCode(String type, String code);
Mono<Dictionary> save(Dictionary dictionary);
Mono<Void> deleteById(Long id);
Mono<Void> deleteByIdAndDeletedAtIsNull(Long id);
}
@@ -0,0 +1,30 @@
package cn.novalon.manage.sys.core.repository;
import cn.novalon.manage.sys.core.domain.OperationLog;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
/**
* 操作日志仓储接口
*
* @author 张翔
* @date 2026-03-13
*/
public interface IOperationLogRepository {
Mono<OperationLog> findById(Long id);
Mono<OperationLog> save(OperationLog operationLog);
Mono<Void> deleteById(Long id);
Flux<OperationLog> findAll();
Flux<OperationLog> findByUsername(String username);
Mono<Long> count();
Mono<Long> countByCreatedAtAfter(LocalDateTime dateTime);
}
@@ -0,0 +1,33 @@
package cn.novalon.manage.sys.core.repository;
import cn.novalon.manage.sys.core.domain.SysConfig;
import org.springframework.data.domain.Sort;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* 系统配置仓储接口
*
* @author 张翔
* @date 2026-03-13
*/
public interface ISysConfigRepository {
Mono<SysConfig> findById(Long id);
Mono<SysConfig> findByConfigKeyAndDeletedAtIsNull(String configKey);
Flux<SysConfig> findByDeletedAtIsNull();
Flux<SysConfig> findAll();
Flux<SysConfig> findAll(Sort sort);
Mono<SysConfig> save(SysConfig config);
Mono<Void> deleteByIdAndDeletedAtIsNull(Long id);
Mono<Long> count();
Mono<Boolean> existsByConfigKey(String configKey);
}
@@ -0,0 +1,26 @@
package cn.novalon.manage.sys.core.repository;
import cn.novalon.manage.sys.core.domain.SysDictData;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* 字典数据仓储接口
*
* @author 张翔
* @date 2026-03-13
*/
public interface ISysDictDataRepository {
Flux<SysDictData> findByDeletedAtIsNull();
Flux<SysDictData> findByDictTypeAndDeletedAtIsNull(String dictType);
Flux<SysDictData> findByDictTypeAndStatusAndDeletedAtIsNull(String dictType, String status);
Mono<SysDictData> findById(Long id);
Mono<SysDictData> save(SysDictData dictData);
Mono<Void> deleteByIdAndDeletedAtIsNull(Long id);
}
@@ -0,0 +1,24 @@
package cn.novalon.manage.sys.core.repository;
import cn.novalon.manage.sys.core.domain.SysDictType;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* 字典类型仓储接口
*
* @author 张翔
* @date 2026-03-13
*/
public interface ISysDictTypeRepository {
Flux<SysDictType> findByDeletedAtIsNull();
Mono<SysDictType> findById(Long id);
Mono<SysDictType> findByDictTypeAndDeletedAtIsNull(String dictType);
Mono<SysDictType> save(SysDictType dictType);
Mono<Void> deleteByIdAndDeletedAtIsNull(Long id);
}
@@ -0,0 +1,28 @@
package cn.novalon.manage.sys.core.repository;
import cn.novalon.manage.sys.core.domain.SysExceptionLog;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
/**
* 异常日志仓储接口
*
* @author 张翔
* @date 2026-03-13
*/
public interface ISysExceptionLogRepository {
Flux<SysExceptionLog> findAllByOrderByCreateTimeDesc();
Flux<SysExceptionLog> findByUsernameOrderByCreateTimeDesc(String username);
Flux<SysExceptionLog> findByCreateTimeBetweenOrderByCreateTimeDesc(LocalDateTime startTime, LocalDateTime endTime);
Mono<SysExceptionLog> save(SysExceptionLog exceptionLog);
Mono<SysExceptionLog> findById(Long id);
Mono<Long> count();
}
@@ -0,0 +1,26 @@
package cn.novalon.manage.sys.core.repository;
import cn.novalon.manage.sys.core.domain.SysFile;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* 文件仓储接口
*
* @author 张翔
* @date 2026-03-13
*/
public interface ISysFileRepository {
Flux<SysFile> findByDeletedAtIsNullOrderByCreatedAtDesc();
Flux<SysFile> findByCreateByOrderByCreatedAtDesc(String createBy);
Mono<SysFile> findById(Long id);
Flux<SysFile> findByFilePathContaining(String fileName);
Mono<SysFile> save(SysFile sysFile);
Mono<Void> deleteByIdAndDeletedAtIsNull(Long id);
}
@@ -0,0 +1,28 @@
package cn.novalon.manage.sys.core.repository;
import cn.novalon.manage.sys.core.domain.SysLoginLog;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
/**
* 登录日志仓储接口
*
* @author 张翔
* @date 2026-03-13
*/
public interface ISysLoginLogRepository {
Flux<SysLoginLog> findAllByOrderByLoginTimeDesc();
Flux<SysLoginLog> findByUsernameOrderByLoginTimeDesc(String username);
Flux<SysLoginLog> findByLoginTimeBetweenOrderByLoginTimeDesc(LocalDateTime startTime, LocalDateTime endTime);
Mono<SysLoginLog> save(SysLoginLog loginLog);
Mono<SysLoginLog> findById(Long id);
Mono<Long> count();
}
@@ -0,0 +1,38 @@
package cn.novalon.manage.sys.core.repository;
import cn.novalon.manage.sys.core.domain.SysMenu;
import cn.novalon.manage.sys.core.query.SysMenuQuery;
import cn.novalon.manage.common.dto.PageRequest;
import cn.novalon.manage.common.dto.PageResponse;
import org.springframework.data.domain.Sort;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* 菜单仓储接口
*
* @author 张翔
* @date 2026-03-13
*/
public interface ISysMenuRepository {
Flux<SysMenu> findByParentId(Long parentId);
Flux<SysMenu> findByParentIdOrderBySort(Long parentId, Sort sort);
Mono<SysMenu> findById(Long id);
Mono<SysMenu> save(SysMenu sysMenu);
Mono<Void> deleteById(Long id);
Flux<SysMenu> findAll();
Flux<SysMenu> findAll(Sort sort);
Mono<Long> count();
Mono<PageResponse<SysMenu>> findByQueryWithPagination(SysMenuQuery query, PageRequest pageRequest);
Flux<SysMenu> findByStatus(String status);
}
@@ -0,0 +1,44 @@
package cn.novalon.manage.sys.core.repository;
import cn.novalon.manage.sys.core.domain.SysRole;
import cn.novalon.manage.sys.core.query.SysRoleQuery;
import cn.novalon.manage.common.dto.PageRequest;
import cn.novalon.manage.common.dto.PageResponse;
import org.springframework.data.domain.Sort;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* 角色仓储接口
*
* @author 张翔
* @date 2026-03-13
*/
public interface ISysRoleRepository {
Mono<SysRole> findById(Long id);
Mono<SysRole> findByIdIncludingDeleted(Long id);
Mono<SysRole> save(SysRole sysRole);
Mono<Void> deleteById(Long id);
Flux<SysRole> findAll();
Flux<SysRole> findAll(Sort sort);
Flux<SysRole> findByRoleNameLikeOrRoleKeyLike(String roleName, String roleKey, Sort sort);
Mono<Long> count();
Mono<Long> countByRoleNameLikeOrRoleKeyLike(String roleName, String roleKey);
Mono<PageResponse<SysRole>> findByQueryWithPagination(SysRoleQuery query, PageRequest pageRequest);
Mono<SysRole> findByRoleName(String roleName);
Mono<Boolean> existsByRoleName(String roleName);
Mono<SysRole> updateRole(SysRole role);
}
@@ -0,0 +1,26 @@
package cn.novalon.manage.sys.core.repository;
import cn.novalon.manage.sys.core.domain.SysUserMessage;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* 用户消息仓储接口
*
* @author 张翔
* @date 2026-03-13
*/
public interface ISysUserMessageRepository {
Flux<SysUserMessage> findByUserIdOrderByCreateTimeDesc(Long userId);
Flux<SysUserMessage> findByUserIdAndIsReadOrderByCreateTimeDesc(Long userId, String isRead);
Mono<Long> countByUserIdAndIsRead(Long userId, String isRead);
Mono<SysUserMessage> save(SysUserMessage message);
Mono<SysUserMessage> findById(Long id);
Mono<Void> deleteById(Long id);
}
@@ -0,0 +1,58 @@
package cn.novalon.manage.sys.core.repository;
import cn.novalon.manage.sys.core.domain.SysUser;
import cn.novalon.manage.sys.core.query.SysUserQuery;
import cn.novalon.manage.common.dto.PageRequest;
import cn.novalon.manage.common.dto.PageResponse;
import org.springframework.data.domain.Sort;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.List;
/**
* 用户仓储接口
*
* @author 张翔
* @date 2026-03-13
*/
public interface ISysUserRepository {
Mono<SysUser> findByUsername(String username);
Mono<SysUser> findByEmail(String email);
Mono<SysUser> findById(Long id);
Mono<SysUser> findByIdIncludingDeleted(Long id);
Mono<SysUser> save(SysUser sysUser);
Mono<Void> deleteById(Long id);
Flux<SysUser> findAll();
Flux<SysUser> findAll(Sort sort);
Flux<SysUser> findByDeletedAtIsNull();
Flux<SysUser> findByDeletedAtIsNull(Sort sort);
Mono<Long> count();
Mono<PageResponse<SysUser>> findByQueryWithPagination(SysUserQuery query, PageRequest pageRequest);
Mono<Boolean> existsByUsername(String username);
Mono<Boolean> existsByEmail(String email);
Mono<Void> logicalDeleteById(Long id);
Mono<Void> logicalDeleteByIds(List<Long> ids);
Mono<Void> restoreById(Long id);
Mono<Void> restoreByIds(List<Long> ids);
Mono<Void> updateRoleIdToNullByRoleId(Long roleId);
}
@@ -1,6 +1,6 @@
package cn.novalon.manage.sys.core.service;
import cn.novalon.manage.common.domain.Dictionary;
import cn.novalon.manage.sys.core.domain.Dictionary;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -1,9 +1,15 @@
package cn.novalon.manage.sys.core.service;
import cn.novalon.manage.common.domain.OperationLog;
import cn.novalon.manage.sys.core.domain.OperationLog;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* 操作日志服务接口
*
* @author 张翔
* @date 2026-03-13
*/
public interface IOperationLogService {
Mono<OperationLog> save(OperationLog log);
Flux<OperationLog> findAll();
@@ -1,9 +1,15 @@
package cn.novalon.manage.sys.core.service;
import cn.novalon.manage.common.domain.SysConfig;
import cn.novalon.manage.sys.core.domain.SysConfig;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* 系统配置服务接口
*
* @author 张翔
* @date 2026-03-13
*/
public interface ISysConfigService {
Flux<SysConfig> findAll();
Mono<SysConfig> findById(Long id);
@@ -1,9 +1,15 @@
package cn.novalon.manage.sys.core.service;
import cn.novalon.manage.common.domain.SysDictData;
import cn.novalon.manage.sys.core.domain.SysDictData;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* 字典数据服务接口
*
* @author 张翔
* @date 2026-03-13
*/
public interface ISysDictDataService {
Flux<SysDictData> findAll();
Flux<SysDictData> findByDictType(String dictType);
@@ -1,9 +1,15 @@
package cn.novalon.manage.sys.core.service;
import cn.novalon.manage.common.domain.SysDictType;
import cn.novalon.manage.sys.core.domain.SysDictType;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* 字典类型服务接口
*
* @author 张翔
* @date 2026-03-13
*/
public interface ISysDictTypeService {
Flux<SysDictType> findAll();
Mono<SysDictType> findById(Long id);
@@ -1,6 +1,6 @@
package cn.novalon.manage.sys.core.service;
import cn.novalon.manage.common.domain.SysExceptionLog;
import cn.novalon.manage.sys.core.domain.SysExceptionLog;
import cn.novalon.manage.common.dto.PageRequest;
import cn.novalon.manage.common.dto.PageResponse;
import reactor.core.publisher.Flux;
@@ -1,11 +1,17 @@
package cn.novalon.manage.sys.core.service;
import cn.novalon.manage.common.domain.SysFile;
import cn.novalon.manage.sys.core.domain.SysFile;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.http.codec.multipart.FilePart;
import org.springframework.web.multipart.MultipartFile;
/**
* 文件服务接口
*
* @author 张翔
* @date 2026-03-13
*/
public interface ISysFileService {
Flux<SysFile> findAll();
Flux<SysFile> findByCreateBy(String createBy);
@@ -1,6 +1,6 @@
package cn.novalon.manage.sys.core.service;
import cn.novalon.manage.common.domain.SysLoginLog;
import cn.novalon.manage.sys.core.domain.SysLoginLog;
import cn.novalon.manage.common.dto.PageRequest;
import cn.novalon.manage.common.dto.PageResponse;
import reactor.core.publisher.Flux;
@@ -8,6 +8,12 @@ import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
/**
* 登录日志服务接口
*
* @author 张翔
* @date 2026-03-13
*/
public interface ISysLoginLogService {
Mono<SysLoginLog> findById(Long id);
Flux<SysLoginLog> findAll();
@@ -1,15 +1,25 @@
package cn.novalon.manage.sys.core.service;
import cn.novalon.manage.common.domain.SysMenu;
import cn.novalon.manage.sys.core.domain.SysMenu;
import cn.novalon.manage.sys.core.command.CreateMenuCommand;
import cn.novalon.manage.sys.core.command.UpdateMenuCommand;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* 菜单服务接口
*
* @author 张翔
* @date 2026-03-13
*/
public interface ISysMenuService {
Mono<SysMenu> findById(Long id);
Flux<SysMenu> findAll();
Flux<SysMenu> findByParentId(Long parentId);
Mono<SysMenu> createMenu(SysMenu menu);
Mono<SysMenu> createMenu(CreateMenuCommand command);
Mono<SysMenu> updateMenu(SysMenu menu);
Mono<SysMenu> updateMenu(UpdateMenuCommand command);
Mono<Void> deleteMenu(Long id);
Flux<SysMenu> buildMenuTree(Flux<SysMenu> menus);
}
@@ -1,13 +0,0 @@
package cn.novalon.manage.sys.core.service;
import cn.novalon.manage.common.domain.SysNotice;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public interface ISysNoticeService {
Flux<SysNotice> findAll();
Flux<SysNotice> findByStatus(String status);
Mono<SysNotice> findById(Long id);
Mono<SysNotice> save(SysNotice notice);
Mono<Void> deleteById(Long id);
}
@@ -1,18 +1,28 @@
package cn.novalon.manage.sys.core.service;
import cn.novalon.manage.common.domain.SysRole;
import cn.novalon.manage.sys.core.domain.SysRole;
import cn.novalon.manage.common.dto.PageRequest;
import cn.novalon.manage.common.dto.PageResponse;
import cn.novalon.manage.sys.core.command.CreateRoleCommand;
import cn.novalon.manage.sys.core.command.UpdateRoleCommand;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* 角色服务接口
*
* @author 张翔
* @date 2026-03-13
*/
public interface ISysRoleService {
Mono<SysRole> findById(Long id);
Flux<SysRole> findAll();
Mono<PageResponse<SysRole>> findRolesByPage(PageRequest pageRequest);
Mono<Long> count();
Mono<SysRole> createRole(SysRole role);
Mono<SysRole> createRole(CreateRoleCommand command);
Mono<SysRole> updateRole(SysRole role);
Mono<SysRole> updateRole(UpdateRoleCommand command);
Mono<Void> deleteRole(Long id);
Mono<SysRole> findByRoleName(String roleName);
Mono<Boolean> existsByRoleName(String roleName);
@@ -1,6 +1,6 @@
package cn.novalon.manage.sys.core.service;
import cn.novalon.manage.common.domain.SysUserMessage;
import cn.novalon.manage.sys.core.domain.SysUserMessage;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -1,28 +1,57 @@
package cn.novalon.manage.sys.core.service;
import cn.novalon.manage.common.domain.SysUser;
import cn.novalon.manage.sys.core.domain.SysUser;
import cn.novalon.manage.common.dto.PageRequest;
import cn.novalon.manage.common.dto.PageResponse;
import cn.novalon.manage.sys.core.command.CreateUserCommand;
import cn.novalon.manage.sys.core.command.UpdateUserCommand;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.List;
/**
* 用户服务接口
*
* @author 张翔
* @date 2026-03-13
*/
public interface ISysUserService {
Mono<SysUser> findById(Long id);
Flux<SysUser> findAll();
Flux<SysUser> findAll(boolean includeDeleted);
Mono<PageResponse<SysUser>> findUsersByPage(PageRequest pageRequest);
Mono<Long> count();
Mono<SysUser> findByUsername(String username);
Mono<Boolean> existsByUsername(String username);
Mono<Boolean> existsByEmail(String email);
Mono<SysUser> createUser(SysUser user);
Mono<SysUser> createUser(CreateUserCommand command);
Mono<SysUser> updateUser(SysUser user);
Mono<SysUser> updateUser(UpdateUserCommand command);
Mono<Void> deleteUser(Long id);
Mono<Void> logicalDeleteUser(Long id);
Mono<Void> logicalDeleteUsers(List<Long> ids);
Mono<Void> restoreUser(Long id);
Mono<Void> restoreUsers(List<Long> ids);
Mono<SysUser> changePassword(Long userId, String oldPassword, String newPassword);
Mono<Void> updateRoleIdToNullByRoleId(Long roleId);
}
@@ -1,8 +1,8 @@
package cn.novalon.manage.sys.core.service.impl;
import cn.novalon.manage.common.domain.Dictionary;
import cn.novalon.manage.sys.core.domain.Dictionary;
import cn.novalon.manage.sys.core.exception.DictionaryAlreadyExistsException;
import cn.novalon.manage.db.repository.DictionaryRepository;
import cn.novalon.manage.sys.core.repository.IDictionaryRepository;
import cn.novalon.manage.sys.core.service.IDictionaryService;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
@@ -10,12 +10,18 @@ import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
/**
* 字典服务实现类
*
* @author 张翔
* @date 2026-03-14
*/
@Service
public class DictionaryService implements IDictionaryService {
private final DictionaryRepository repository;
private final IDictionaryRepository repository;
public DictionaryService(DictionaryRepository repository) {
public DictionaryService(IDictionaryRepository repository) {
this.repository = repository;
}
@@ -1,7 +1,7 @@
package cn.novalon.manage.sys.core.service.impl;
import cn.novalon.manage.common.domain.OperationLog;
import cn.novalon.manage.db.repository.OperationLogRepository;
import cn.novalon.manage.sys.core.domain.OperationLog;
import cn.novalon.manage.sys.core.repository.IOperationLogRepository;
import cn.novalon.manage.sys.core.service.IOperationLogService;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
@@ -12,9 +12,9 @@ import java.time.LocalDateTime;
@Service
public class OperationLogService implements IOperationLogService {
private final OperationLogRepository logRepository;
private final IOperationLogRepository logRepository;
public OperationLogService(OperationLogRepository logRepository) {
public OperationLogService(IOperationLogRepository logRepository) {
this.logRepository = logRepository;
}
@@ -1,12 +1,18 @@
package cn.novalon.manage.sys.core.service.impl;
import cn.novalon.manage.common.domain.SysConfig;
import cn.novalon.manage.db.repository.ISysConfigRepository;
import cn.novalon.manage.sys.core.domain.SysConfig;
import cn.novalon.manage.sys.core.repository.ISysConfigRepository;
import cn.novalon.manage.sys.core.service.ISysConfigService;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* 系统配置服务实现类
*
* @author 张翔
* @date 2026-03-14
*/
@Service
public class SysConfigService implements ISysConfigService {
@@ -1,7 +1,7 @@
package cn.novalon.manage.sys.core.service.impl;
import cn.novalon.manage.common.domain.SysDictData;
import cn.novalon.manage.db.repository.ISysDictDataRepository;
import cn.novalon.manage.sys.core.domain.SysDictData;
import cn.novalon.manage.sys.core.repository.ISysDictDataRepository;
import cn.novalon.manage.sys.core.service.ISysDictDataService;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
@@ -1,12 +1,18 @@
package cn.novalon.manage.sys.core.service.impl;
import cn.novalon.manage.common.domain.SysDictType;
import cn.novalon.manage.db.repository.ISysDictTypeRepository;
import cn.novalon.manage.sys.core.domain.SysDictType;
import cn.novalon.manage.sys.core.repository.ISysDictTypeRepository;
import cn.novalon.manage.sys.core.service.ISysDictTypeService;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* 字典类型服务实现类
*
* @author 张翔
* @date 2026-03-14
*/
@Service
public class SysDictTypeService implements ISysDictTypeService {
@@ -1,7 +1,7 @@
package cn.novalon.manage.sys.core.service.impl;
import cn.novalon.manage.common.domain.SysExceptionLog;
import cn.novalon.manage.db.repository.ISysExceptionLogRepository;
import cn.novalon.manage.sys.core.domain.SysExceptionLog;
import cn.novalon.manage.sys.core.repository.ISysExceptionLogRepository;
import cn.novalon.manage.sys.core.service.ISysExceptionLogService;
import cn.novalon.manage.common.dto.PageRequest;
import cn.novalon.manage.common.dto.PageResponse;
@@ -12,6 +12,12 @@ import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
import java.util.List;
/**
* 异常日志服务实现类
*
* @author 张翔
* @date 2026-03-14
*/
@Service
public class SysExceptionLogService implements ISysExceptionLogService {
@@ -1,7 +1,7 @@
package cn.novalon.manage.sys.core.service.impl;
import cn.novalon.manage.common.domain.SysFile;
import cn.novalon.manage.db.repository.ISysFileRepository;
import cn.novalon.manage.sys.core.domain.SysFile;
import cn.novalon.manage.sys.core.repository.ISysFileRepository;
import cn.novalon.manage.sys.core.service.ISysFileService;
import org.springframework.http.codec.multipart.FilePart;
import org.springframework.stereotype.Service;
@@ -1,7 +1,7 @@
package cn.novalon.manage.sys.core.service.impl;
import cn.novalon.manage.common.domain.SysLoginLog;
import cn.novalon.manage.db.repository.SysLoginLogRepository;
import cn.novalon.manage.sys.core.domain.SysLoginLog;
import cn.novalon.manage.sys.core.repository.ISysLoginLogRepository;
import cn.novalon.manage.sys.core.service.ISysLoginLogService;
import cn.novalon.manage.common.dto.PageRequest;
import cn.novalon.manage.common.dto.PageResponse;
@@ -13,12 +13,18 @@ import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
/**
* 登录日志服务实现类
*
* @author 张翔
* @date 2026-03-14
*/
@Service
public class SysLoginLogService implements ISysLoginLogService {
private final SysLoginLogRepository repository;
private final ISysLoginLogRepository repository;
public SysLoginLogService(SysLoginLogRepository repository) {
public SysLoginLogService(ISysLoginLogRepository repository) {
this.repository = repository;
}
@@ -1,8 +1,11 @@
package cn.novalon.manage.sys.core.service.impl;
import cn.novalon.manage.common.domain.SysMenu;
import cn.novalon.manage.db.repository.ISysMenuRepository;
import cn.novalon.manage.sys.core.domain.SysMenu;
import cn.novalon.manage.sys.core.repository.ISysMenuRepository;
import cn.novalon.manage.sys.core.service.ISysMenuService;
import cn.novalon.manage.sys.core.command.CreateMenuCommand;
import cn.novalon.manage.sys.core.command.UpdateMenuCommand;
import cn.novalon.manage.common.util.StatusConstants;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -11,6 +14,12 @@ import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;
/**
* 菜单服务实现类
*
* @author 张翔
* @date 2026-03-14
*/
@Service
public class SysMenuService implements ISysMenuService {
@@ -38,7 +47,20 @@ public class SysMenuService implements ISysMenuService {
@Override
public Mono<SysMenu> createMenu(SysMenu menu) {
menu.setCreatedAt(LocalDateTime.now());
menu.setStatus("0");
return menuRepository.save(menu);
}
@Override
public Mono<SysMenu> createMenu(CreateMenuCommand command) {
SysMenu menu = new SysMenu();
menu.setParentId(command.parentId());
menu.setMenuName(command.menuName());
menu.setMenuType(command.menuType());
menu.setOrderNum(command.orderNum());
menu.setComponent(command.component());
menu.setPerms(command.perms());
menu.setStatus(command.status() != null ? command.status() : StatusConstants.ENABLED);
menu.setCreatedAt(LocalDateTime.now());
return menuRepository.save(menu);
}
@@ -48,6 +70,37 @@ public class SysMenuService implements ISysMenuService {
return menuRepository.save(menu);
}
@Override
public Mono<SysMenu> updateMenu(UpdateMenuCommand command) {
return menuRepository.findById(command.id())
.switchIfEmpty(Mono.error(new RuntimeException("Menu not found")))
.flatMap(menu -> {
if (command.parentId() != null) {
menu.setParentId(command.parentId());
}
if (command.menuName() != null) {
menu.setMenuName(command.menuName());
}
if (command.menuType() != null) {
menu.setMenuType(command.menuType());
}
if (command.orderNum() != null) {
menu.setOrderNum(command.orderNum());
}
if (command.component() != null) {
menu.setComponent(command.component());
}
if (command.perms() != null) {
menu.setPerms(command.perms());
}
if (command.status() != null) {
menu.setStatus(command.status());
}
menu.setUpdatedAt(LocalDateTime.now());
return menuRepository.save(menu);
});
}
@Override
public Mono<Void> deleteMenu(Long id) {
return menuRepository.deleteById(id);
@@ -1,51 +0,0 @@
package cn.novalon.manage.sys.core.service.impl;
import cn.novalon.manage.common.domain.SysNotice;
import cn.novalon.manage.db.repository.ISysNoticeRepository;
import cn.novalon.manage.sys.core.service.ISysNoticeService;
import cn.novalon.manage.sys.core.service.IWebSocketService;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@Service
public class SysNoticeService implements ISysNoticeService {
private final ISysNoticeRepository repository;
private final IWebSocketService webSocketService;
public SysNoticeService(ISysNoticeRepository repository, IWebSocketService webSocketService) {
this.repository = repository;
this.webSocketService = webSocketService;
}
@Override
public Flux<SysNotice> findAll() {
return repository.findByDeletedAtIsNull();
}
@Override
public Flux<SysNotice> findByStatus(String status) {
return repository.findByStatusAndDeletedAtIsNull(status);
}
@Override
public Mono<SysNotice> findById(Long id) {
return repository.findById(id);
}
@Override
public Mono<SysNotice> save(SysNotice notice) {
return repository.save(notice)
.flatMap(savedNotice -> {
return webSocketService.notifyNewNotice(
savedNotice.getNoticeTitle(),
savedNotice.getNoticeContent()).thenReturn(savedNotice);
});
}
@Override
public Mono<Void> deleteById(Long id) {
return repository.deleteByIdAndDeletedAtIsNull(id);
}
}
@@ -1,15 +1,15 @@
package cn.novalon.manage.sys.core.service.impl;
import cn.novalon.manage.common.util.StatusConstants;
import cn.novalon.manage.common.domain.SysRole;
import cn.novalon.manage.common.domain.query.SysRoleQuery;
import cn.novalon.manage.db.repository.ISysRoleRepository;
import cn.novalon.manage.sys.core.domain.SysRole;
import cn.novalon.manage.sys.core.query.SysRoleQuery;
import cn.novalon.manage.sys.core.repository.ISysRoleRepository;
import cn.novalon.manage.sys.core.service.ISysRoleService;
import cn.novalon.manage.sys.core.service.ISysUserService;
import cn.novalon.manage.sys.core.command.CreateRoleCommand;
import cn.novalon.manage.sys.core.command.UpdateRoleCommand;
import cn.novalon.manage.common.dto.PageRequest;
import cn.novalon.manage.common.dto.PageResponse;
import cn.novalon.manage.db.dao.QueryUtil;
import cn.novalon.manage.db.entity.SysRoleQueryCriteria;
import org.springframework.data.relational.core.query.Query;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -20,9 +20,11 @@ import java.time.LocalDateTime;
public class SysRoleService implements ISysRoleService {
private final ISysRoleRepository roleRepository;
private final ISysUserService userService;
public SysRoleService(ISysRoleRepository roleRepository) {
public SysRoleService(ISysRoleRepository roleRepository, ISysUserService userService) {
this.roleRepository = roleRepository;
this.userService = userService;
}
@Override
@@ -44,12 +46,7 @@ public class SysRoleService implements ISysRoleService {
query.setRoleKey(pageRequest.getKeyword());
}
SysRoleQueryCriteria criteria = new SysRoleQueryCriteria();
criteria.convert(query);
Query queryObj = QueryUtil.getQuery(criteria);
return roleRepository.findByQueryWithPagination(queryObj, pageRequest);
return roleRepository.findByQueryWithPagination(query, pageRequest);
}
@Override
@@ -60,7 +57,20 @@ public class SysRoleService implements ISysRoleService {
@Override
public Mono<SysRole> createRole(SysRole role) {
role.setCreatedAt(LocalDateTime.now());
role.setStatus(StatusConstants.ENABLED);
if (role.getStatus() == null) {
role.setStatus(StatusConstants.ENABLED);
}
return roleRepository.save(role);
}
@Override
public Mono<SysRole> createRole(CreateRoleCommand command) {
SysRole role = new SysRole();
role.setRoleName(command.roleName());
role.setRoleKey(command.roleKey());
role.setRoleSort(command.roleSort());
role.setStatus(command.status() != null ? command.status() : StatusConstants.ENABLED);
role.setCreatedAt(LocalDateTime.now());
return roleRepository.save(role);
}
@@ -70,9 +80,35 @@ public class SysRoleService implements ISysRoleService {
return roleRepository.save(role);
}
@Override
public Mono<SysRole> updateRole(UpdateRoleCommand command) {
return roleRepository.findById(command.id())
.switchIfEmpty(Mono.error(new RuntimeException("Role not found")))
.flatMap(role -> {
if (command.roleName() != null) {
role.setRoleName(command.roleName());
}
if (command.roleKey() != null) {
role.setRoleKey(command.roleKey());
}
if (command.roleSort() != null) {
role.setRoleSort(command.roleSort());
}
if (command.status() != null) {
role.setStatus(command.status());
}
role.setUpdatedAt(LocalDateTime.now());
return roleRepository.save(role);
});
}
@Override
public Mono<Void> deleteRole(Long id) {
return roleRepository.deleteById(id);
return roleRepository.findById(id)
.flatMap(role -> {
return userService.updateRoleIdToNullByRoleId(id)
.then(roleRepository.deleteById(id));
});
}
@Override
@@ -1,13 +1,19 @@
package cn.novalon.manage.sys.core.service.impl;
import cn.novalon.manage.common.domain.SysUserMessage;
import cn.novalon.manage.db.repository.ISysUserMessageRepository;
import cn.novalon.manage.sys.core.domain.SysUserMessage;
import cn.novalon.manage.sys.core.repository.ISysUserMessageRepository;
import cn.novalon.manage.sys.core.service.ISysUserMessageService;
import cn.novalon.manage.sys.core.service.IWebSocketService;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* 用户消息服务实现类
*
* @author 张翔
* @date 2026-03-14
*/
@Service
public class SysUserMessageService implements ISysUserMessageService {
@@ -1,15 +1,14 @@
package cn.novalon.manage.sys.core.service.impl;
import cn.novalon.manage.common.util.StatusConstants;
import cn.novalon.manage.common.domain.SysUser;
import cn.novalon.manage.common.domain.query.SysUserQuery;
import cn.novalon.manage.sys.core.domain.SysUser;
import cn.novalon.manage.sys.core.query.SysUserQuery;
import cn.novalon.manage.common.dto.PageRequest;
import cn.novalon.manage.common.dto.PageResponse;
import cn.novalon.manage.db.repository.ISysUserRepository;
import cn.novalon.manage.db.dao.QueryUtil;
import cn.novalon.manage.db.entity.SysUserQueryCriteria;
import cn.novalon.manage.sys.core.repository.ISysUserRepository;
import cn.novalon.manage.sys.core.service.ISysUserService;
import org.springframework.data.relational.core.query.Query;
import cn.novalon.manage.sys.core.command.CreateUserCommand;
import cn.novalon.manage.sys.core.command.UpdateUserCommand;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
@@ -18,6 +17,16 @@ import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
import java.util.List;
/**
* 用户服务实现类
*
* 文件定义:实现用户管理的核心业务逻辑
* 涉及业务:用户注册、登录、信息修改、删除、密码修改、逻辑删除等用户生命周期管理
* 算法:使用R2DBC进行响应式数据库操作,支持分页查询、条件查询、批量操作
*
* @author 张翔
* @date 2026-03-13
*/
@Service
public class SysUserService implements ISysUserService {
@@ -57,12 +66,7 @@ public class SysUserService implements ISysUserService {
query.setEmail(pageRequest.getKeyword());
}
SysUserQueryCriteria criteria = new SysUserQueryCriteria();
criteria.convert(query);
Query queryObj = QueryUtil.getQuery(criteria);
return userRepository.findByQueryWithPagination(queryObj, pageRequest);
return userRepository.findByQueryWithPagination(query, pageRequest);
}
@Override
@@ -79,7 +83,21 @@ public class SysUserService implements ISysUserService {
public Mono<SysUser> createUser(SysUser user) {
user.setPassword(passwordEncoder.encode(user.getPassword()));
user.setCreatedAt(LocalDateTime.now());
user.setStatus(StatusConstants.ENABLED);
if (user.getStatus() == null) {
user.setStatus(StatusConstants.ENABLED);
}
return userRepository.save(user);
}
@Override
public Mono<SysUser> createUser(CreateUserCommand command) {
SysUser user = new SysUser();
user.setUsername(command.username().getValue());
user.setPassword(passwordEncoder.encode(command.password().getValue()));
user.setEmail(command.email().getValue());
user.setRoleId(command.roleId());
user.setStatus(command.status() != null ? command.status() : StatusConstants.ENABLED);
user.setCreatedAt(LocalDateTime.now());
return userRepository.save(user);
}
@@ -89,9 +107,41 @@ public class SysUserService implements ISysUserService {
return userRepository.save(user);
}
@Override
public Mono<SysUser> updateUser(UpdateUserCommand command) {
return userRepository.findById(command.id())
.switchIfEmpty(Mono.error(new RuntimeException("User not found")))
.flatMap(user -> {
if (command.username() != null) {
user.setUsername(command.username());
}
if (command.password() != null) {
user.setPassword(passwordEncoder.encode(command.password()));
}
if (command.email() != null) {
user.setEmail(command.email());
}
if (command.roleId() != null) {
user.setRoleId(command.roleId());
}
if (command.status() != null) {
user.setStatus(command.status());
}
user.setUpdatedAt(LocalDateTime.now());
return userRepository.save(user);
});
}
@Override
public Mono<Void> deleteUser(Long id) {
return userRepository.deleteById(id);
return userRepository.findById(id)
.switchIfEmpty(Mono.error(new RuntimeException("User not found")))
.flatMap(user -> userRepository.deleteById(id));
}
@Override
public Mono<Void> updateRoleIdToNullByRoleId(Long roleId) {
return userRepository.updateRoleIdToNullByRoleId(roleId);
}
@Override
@@ -8,6 +8,12 @@ import reactor.core.publisher.Mono;
import java.util.HashMap;
import java.util.Map;
/**
* WebSocket服务实现类
*
* @author 张翔
* @date 2026-03-14
*/
@Service
public class WebSocketServiceImpl implements IWebSocketService {
@@ -2,6 +2,16 @@ package cn.novalon.manage.sys.dto.request;
import jakarta.validation.constraints.NotBlank;
/**
* 登录请求DTO
*
* 文件定义:封装用户登录请求的参数
* 涉及业务:用户登录认证
* 算法:使用Jakarta Validation进行参数验证
*
* @author 张翔
* @date 2026-03-13
*/
public class LoginRequest {
@NotBlank(message = "用户名不能为空")
@@ -0,0 +1,89 @@
package cn.novalon.manage.sys.dto.request;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
/**
* 菜单创建请求DTO
*
* 文件定义:用于创建菜单的请求DTO对象,封装HTTP请求参数
* 涉及业务:菜单管理、权限分配等场景
* 算法:通过验证注解确保请求参数的有效性
*
* @author 张翔
* @date 2026-03-13
*/
public class MenuCreateRequest {
private Long parentId;
@NotBlank(message = "菜单名称不能为空")
private String menuName;
@NotBlank(message = "菜单类型不能为空")
private String menuType;
private Integer orderNum;
private String component;
private String perms;
private Integer status;
public Long getParentId() {
return parentId;
}
public void setParentId(Long parentId) {
this.parentId = parentId;
}
public String getMenuName() {
return menuName;
}
public void setMenuName(String menuName) {
this.menuName = menuName;
}
public String getMenuType() {
return menuType;
}
public void setMenuType(String menuType) {
this.menuType = menuType;
}
public Integer getOrderNum() {
return orderNum;
}
public void setOrderNum(Integer orderNum) {
this.orderNum = orderNum;
}
public String getComponent() {
return component;
}
public void setComponent(String component) {
this.component = component;
}
public String getPerms() {
return perms;
}
public void setPerms(String perms) {
this.perms = perms;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
}
@@ -0,0 +1,84 @@
package cn.novalon.manage.sys.dto.request;
/**
* 菜单更新请求DTO
*
* 文件定义:用于更新菜单的请求DTO对象,封装HTTP请求参数
* 涉及业务:菜单管理、权限分配等场景
* 算法:支持部分字段更新,通过验证注解确保请求参数的有效性
*
* @author 张翔
* @date 2026-03-13
*/
public class MenuUpdateRequest {
private Long parentId;
private String menuName;
private String menuType;
private Integer orderNum;
private String component;
private String perms;
private Integer status;
public Long getParentId() {
return parentId;
}
public void setParentId(Long parentId) {
this.parentId = parentId;
}
public String getMenuName() {
return menuName;
}
public void setMenuName(String menuName) {
this.menuName = menuName;
}
public String getMenuType() {
return menuType;
}
public void setMenuType(String menuType) {
this.menuType = menuType;
}
public Integer getOrderNum() {
return orderNum;
}
public void setOrderNum(Integer orderNum) {
this.orderNum = orderNum;
}
public String getComponent() {
return component;
}
public void setComponent(String component) {
this.component = component;
}
public String getPerms() {
return perms;
}
public void setPerms(String perms) {
this.perms = perms;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
}
@@ -0,0 +1,60 @@
package cn.novalon.manage.sys.dto.request;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
/**
* 角色创建请求DTO
*
* 文件定义:用于创建角色的请求DTO对象,封装HTTP请求参数
* 涉及业务:角色管理、权限分配等场景
* 算法:通过验证注解确保请求参数的有效性
*
* @author 张翔
* @date 2026-03-13
*/
public class RoleCreateRequest {
@NotBlank(message = "角色名称不能为空")
private String roleName;
@NotBlank(message = "角色权限字符串不能为空")
private String roleKey;
@NotNull(message = "显示顺序不能为空")
private Integer roleSort;
private Integer status;
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
public String getRoleKey() {
return roleKey;
}
public void setRoleKey(String roleKey) {
this.roleKey = roleKey;
}
public Integer getRoleSort() {
return roleSort;
}
public void setRoleSort(Integer roleSort) {
this.roleSort = roleSort;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
}
@@ -0,0 +1,56 @@
package cn.novalon.manage.sys.dto.request;
import jakarta.validation.constraints.NotBlank;
/**
* 角色更新请求DTO
*
* 文件定义:用于更新角色的请求DTO对象,封装HTTP请求参数
* 涉及业务:角色管理、权限分配等场景
* 算法:支持部分字段更新,通过验证注解确保请求参数的有效性
*
* @author 张翔
* @date 2026-03-13
*/
public class RoleUpdateRequest {
private String roleName;
private String roleKey;
private Integer roleSort;
private Integer status;
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
public String getRoleKey() {
return roleKey;
}
public void setRoleKey(String roleKey) {
this.roleKey = roleKey;
}
public Integer getRoleSort() {
return roleSort;
}
public void setRoleSort(Integer roleSort) {
this.roleSort = roleSort;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
}
@@ -68,7 +68,7 @@ public class UserResponse {
this.updatedAt = updatedAt;
}
public static UserResponse fromDomain(cn.novalon.manage.common.domain.SysUser user) {
public static UserResponse fromDomain(cn.novalon.manage.sys.core.domain.SysUser user) {
UserResponse response = new UserResponse();
response.setId(user.getId());
response.setUsername(user.getUsername());
@@ -0,0 +1,71 @@
package cn.novalon.manage.sys.filter;
import io.github.resilience4j.ratelimiter.RateLimiter;
import io.github.resilience4j.ratelimiter.RequestNotPermitted;
import io.github.resilience4j.ratelimiter.RateLimiterRegistry;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
import java.util.concurrent.ConcurrentHashMap;
/**
* API限流过滤器
*
* @author 张翔
* @date 2026-03-13
*/
@Component
public class RateLimitFilter implements WebFilter {
private final RateLimiter rateLimiter;
private final ConcurrentHashMap<String, RateLimiter> ipRateLimiterMap = new ConcurrentHashMap<>();
public RateLimitFilter(RateLimiterRegistry rateLimiterRegistry) {
this.rateLimiter = rateLimiterRegistry.rateLimiter("apiRateLimiter");
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
String clientIp = getClientIp(exchange);
RateLimiter ipRateLimiter = ipRateLimiterMap.computeIfAbsent(clientIp, k -> rateLimiter);
return Mono.fromCallable(() -> ipRateLimiter.acquirePermission())
.flatMap(permitted -> {
if (permitted) {
return chain.filter(exchange);
} else {
exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
exchange.getResponse().getHeaders().add("X-RateLimit-Limit",
String.valueOf(ipRateLimiter.getRateLimiterConfig().getLimitForPeriod()));
exchange.getResponse().getHeaders().add("X-RateLimit-Remaining", "0");
exchange.getResponse().getHeaders().add("Retry-After", "1");
return exchange.getResponse().setComplete();
}
})
.onErrorResume(RequestNotPermitted.class, e -> {
exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
exchange.getResponse().getHeaders().add("X-RateLimit-Limit",
String.valueOf(ipRateLimiter.getRateLimiterConfig().getLimitForPeriod()));
exchange.getResponse().getHeaders().add("X-RateLimit-Remaining", "0");
exchange.getResponse().getHeaders().add("Retry-After", "1");
return exchange.getResponse().setComplete();
});
}
private String getClientIp(ServerWebExchange exchange) {
String ip = exchange.getRequest().getHeaders().getFirst("X-Forwarded-For");
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = exchange.getRequest().getHeaders().getFirst("X-Real-IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = exchange.getRequest().getRemoteAddress() != null
? exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()
: "unknown";
}
return ip;
}
}
@@ -0,0 +1,42 @@
package cn.novalon.manage.sys.handler;
import cn.novalon.manage.common.handler.ExceptionLogService;
import cn.novalon.manage.sys.core.domain.SysExceptionLog;
import cn.novalon.manage.sys.core.service.ISysExceptionLogService;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
/**
* 异常日志服务实现
*
* 文件定义:实现异常日志记录接口,使用sys模块的异常日志服务
* 涉及业务:异常日志记录、错误追踪
* 算法:使用响应式编程实现异步日志记录
*
* @author 张翔
* @date 2026-03-13
*/
@Service
public class ExceptionLogServiceImpl implements ExceptionLogService {
private final ISysExceptionLogService exceptionLogService;
public ExceptionLogServiceImpl(ISysExceptionLogService exceptionLogService) {
this.exceptionLogService = exceptionLogService;
}
@Override
public Mono<Void> logException(String title, String exceptionName, String exceptionMsg,
String methodName, String ip, String stackTrace) {
SysExceptionLog exceptionLog = new SysExceptionLog();
exceptionLog.setTitle(title);
exceptionLog.setExceptionName(exceptionName);
exceptionLog.setExceptionMsg(exceptionMsg);
exceptionLog.setMethodName(methodName);
exceptionLog.setIp(ip);
exceptionLog.setCreateTime(java.time.LocalDateTime.now());
exceptionLog.setExceptionStack(stackTrace);
return exceptionLogService.save(exceptionLog).then();
}
}
@@ -1,162 +0,0 @@
package cn.novalon.manage.sys.handler;
import cn.novalon.manage.common.domain.SysExceptionLog;
import cn.novalon.manage.sys.core.exception.DictionaryAlreadyExistsException;
import cn.novalon.manage.sys.core.service.ISysExceptionLogService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.ServerWebInputException;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
private final ISysExceptionLogService exceptionLogService;
public GlobalExceptionHandler(ISysExceptionLogService exceptionLogService) {
this.exceptionLogService = exceptionLogService;
}
@ExceptionHandler(Exception.class)
public ResponseEntity<Map<String, Object>> handleException(Exception ex, ServerWebExchange exchange) {
logger.error("Exception occurred: ", ex);
SysExceptionLog exceptionLog = new SysExceptionLog();
exceptionLog.setTitle("System Exception");
exceptionLog.setExceptionName(ex.getClass().getSimpleName());
exceptionLog.setExceptionMsg(ex.getMessage());
exceptionLog.setMethodName(exchange.getRequest().getPath().value());
exceptionLog.setIp(getClientIp(exchange));
exceptionLog.setCreateTime(LocalDateTime.now());
StringBuilder stackTrace = new StringBuilder();
for (StackTraceElement element : ex.getStackTrace()) {
stackTrace.append(element.toString()).append("\n");
}
exceptionLog.setExceptionStack(stackTrace.toString());
exceptionLogService.save(exceptionLog).subscribe();
Map<String, Object> response = new HashMap<>();
response.put("code", HttpStatus.INTERNAL_SERVER_ERROR.value());
response.put("message", ex.getMessage());
response.put("timestamp", LocalDateTime.now());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<Map<String, Object>> handleIllegalArgumentException(IllegalArgumentException ex, ServerWebExchange exchange) {
logger.warn("Illegal argument: ", ex);
Map<String, Object> response = new HashMap<>();
response.put("code", HttpStatus.BAD_REQUEST.value());
response.put("message", ex.getMessage());
response.put("timestamp", LocalDateTime.now());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, Object>> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex, ServerWebExchange exchange) {
logger.warn("Validation failed: ", ex);
Map<String, Object> response = new HashMap<>();
response.put("code", HttpStatus.BAD_REQUEST.value());
String errorMessage = ex.getBindingResult().getFieldErrors().stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining(", "));
response.put("message", errorMessage);
response.put("timestamp", LocalDateTime.now());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
}
@ExceptionHandler(ServerWebInputException.class)
public ResponseEntity<Map<String, Object>> handleServerWebInputException(ServerWebInputException ex, ServerWebExchange exchange) {
logger.warn("Server web input exception: ", ex);
Map<String, Object> response = new HashMap<>();
response.put("code", HttpStatus.BAD_REQUEST.value());
response.put("message", ex.getReason());
response.put("timestamp", LocalDateTime.now());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
}
@ExceptionHandler(ResponseStatusException.class)
public ResponseEntity<Map<String, Object>> handleResponseStatusException(ResponseStatusException ex, ServerWebExchange exchange) {
logger.warn("Response status exception: ", ex);
Map<String, Object> response = new HashMap<>();
response.put("code", ex.getStatusCode().value());
response.put("message", ex.getReason());
response.put("timestamp", LocalDateTime.now());
return ResponseEntity.status(ex.getStatusCode()).body(response);
}
@ExceptionHandler(DuplicateKeyException.class)
public ResponseEntity<Map<String, Object>> handleDuplicateKeyException(DuplicateKeyException ex, ServerWebExchange exchange) {
logger.warn("Duplicate key: ", ex);
Map<String, Object> response = new HashMap<>();
response.put("code", HttpStatus.CONFLICT.value());
response.put("message", "Duplicate key violation");
response.put("timestamp", LocalDateTime.now());
return ResponseEntity.status(HttpStatus.CONFLICT).body(response);
}
@ExceptionHandler(DataIntegrityViolationException.class)
public ResponseEntity<Map<String, Object>> handleDataIntegrityViolationException(DataIntegrityViolationException ex, ServerWebExchange exchange) {
logger.warn("Data integrity violation: ", ex);
Map<String, Object> response = new HashMap<>();
response.put("code", HttpStatus.CONFLICT.value());
response.put("message", "Data integrity violation");
response.put("timestamp", LocalDateTime.now());
return ResponseEntity.status(HttpStatus.CONFLICT).body(response);
}
@ExceptionHandler(DictionaryAlreadyExistsException.class)
public ResponseEntity<Map<String, Object>> handleDictionaryAlreadyExistsException(DictionaryAlreadyExistsException ex, ServerWebExchange exchange) {
logger.warn("Dictionary already exists: type={}, code={}", ex.getType(), ex.getCode());
Map<String, Object> response = new HashMap<>();
response.put("code", HttpStatus.CONFLICT.value());
response.put("message", ex.getMessage());
response.put("timestamp", LocalDateTime.now());
return ResponseEntity.status(HttpStatus.CONFLICT).body(response);
}
private String getClientIp(ServerWebExchange exchange) {
String ip = exchange.getRequest().getHeaders().getFirst("X-Forwarded-For");
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = exchange.getRequest().getHeaders().getFirst("X-Real-IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = "127.0.0.1";
}
return ip;
}
}
@@ -4,7 +4,7 @@ import cn.novalon.manage.sys.dto.request.LoginRequest;
import cn.novalon.manage.sys.dto.request.UserRegisterRequest;
import cn.novalon.manage.sys.dto.response.AuthResponse;
import cn.novalon.manage.sys.security.JwtTokenProvider;
import cn.novalon.manage.common.domain.SysUser;
import cn.novalon.manage.sys.core.domain.SysUser;
import cn.novalon.manage.sys.core.service.ISysUserService;
import org.springframework.http.HttpStatus;
import org.springframework.security.crypto.password.PasswordEncoder;
@@ -19,6 +19,16 @@ import java.time.LocalDateTime;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 认证处理器
*
* 文件定义:处理用户登录、注册等认证相关的HTTP请求
* 涉及业务:用户登录、用户注册、Token生成、密码验证
* 算法:使用BCrypt验证密码,使用JWT生成Token
*
* @author 张翔
* @date 2026-03-13
*/
@Component
public class SysAuthHandler {
@@ -1,6 +1,6 @@
package cn.novalon.manage.sys.handler.config;
import cn.novalon.manage.common.domain.SysConfig;
import cn.novalon.manage.sys.core.domain.SysConfig;
import cn.novalon.manage.sys.core.service.ISysConfigService;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
@@ -1,7 +1,7 @@
package cn.novalon.manage.sys.handler.dict;
import cn.novalon.manage.common.domain.SysDictType;
import cn.novalon.manage.common.domain.SysDictData;
import cn.novalon.manage.sys.core.domain.SysDictType;
import cn.novalon.manage.sys.core.domain.SysDictData;
import cn.novalon.manage.sys.core.service.ISysDictTypeService;
import cn.novalon.manage.sys.core.service.ISysDictDataService;
import org.springframework.http.HttpStatus;
@@ -1,6 +1,6 @@
package cn.novalon.manage.sys.handler.dictionary;
import cn.novalon.manage.common.domain.Dictionary;
import cn.novalon.manage.sys.core.domain.Dictionary;
import cn.novalon.manage.sys.core.service.IDictionaryService;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
@@ -1,6 +1,6 @@
package cn.novalon.manage.sys.handler.file;
import cn.novalon.manage.common.domain.SysFile;
import cn.novalon.manage.sys.core.domain.SysFile;
import cn.novalon.manage.sys.core.service.ISysFileService;
import cn.novalon.manage.sys.dto.response.FilePreviewResponse;
import org.springframework.core.io.Resource;
@@ -1,7 +1,7 @@
package cn.novalon.manage.sys.handler.log;
import cn.novalon.manage.common.domain.SysLoginLog;
import cn.novalon.manage.common.domain.SysExceptionLog;
import cn.novalon.manage.sys.core.domain.SysLoginLog;
import cn.novalon.manage.sys.core.domain.SysExceptionLog;
import cn.novalon.manage.sys.core.service.ISysLoginLogService;
import cn.novalon.manage.sys.core.service.ISysExceptionLogService;
import cn.novalon.manage.common.dto.PageRequest;
@@ -0,0 +1,93 @@
package cn.novalon.manage.sys.handler.menu;
import cn.novalon.manage.sys.core.domain.SysMenu;
import cn.novalon.manage.sys.core.service.ISysMenuService;
import cn.novalon.manage.sys.dto.request.MenuCreateRequest;
import cn.novalon.manage.sys.dto.request.MenuUpdateRequest;
import cn.novalon.manage.sys.core.command.CreateMenuCommand;
import cn.novalon.manage.sys.core.command.UpdateMenuCommand;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
@Component
public class MenuHandler {
private final ISysMenuService menuService;
public MenuHandler(ISysMenuService menuService) {
this.menuService = menuService;
}
public Mono<ServerResponse> getAllMenus(ServerRequest request) {
return ServerResponse.ok()
.body(menuService.findAll(), SysMenu.class);
}
public Mono<ServerResponse> getMenuById(ServerRequest request) {
Long id = Long.valueOf(request.pathVariable("id"));
return menuService.findById(id)
.flatMap(menu -> ServerResponse.ok().bodyValue(menu))
.switchIfEmpty(ServerResponse.notFound().build());
}
public Mono<ServerResponse> getMenuTree(ServerRequest request) {
return ServerResponse.ok()
.body(menuService.buildMenuTree(menuService.findAll()), SysMenu.class);
}
public Mono<ServerResponse> getMenusByParent(ServerRequest request) {
Long parentId = request.queryParam("parentId")
.map(Long::valueOf)
.orElse(0L);
return ServerResponse.ok()
.body(menuService.findByParentId(parentId), SysMenu.class);
}
public Mono<ServerResponse> getMenusByType(ServerRequest request) {
String menuType = request.queryParam("menuType").orElse(null);
return ServerResponse.ok()
.body(menuService.findAll().filter(menu -> menuType == null || menuType.equals(menu.getMenuType())), SysMenu.class);
}
public Mono<ServerResponse> createMenu(ServerRequest request) {
return request.bodyToMono(MenuCreateRequest.class)
.map(req -> CreateMenuCommand.of(
req.getParentId(),
req.getMenuName(),
req.getMenuType(),
req.getOrderNum(),
req.getComponent(),
req.getPerms(),
req.getStatus()
))
.flatMap(menuService::createMenu)
.flatMap(menu -> ServerResponse.status(HttpStatus.CREATED).bodyValue(menu));
}
public Mono<ServerResponse> updateMenu(ServerRequest request) {
Long id = Long.valueOf(request.pathVariable("id"));
return request.bodyToMono(MenuUpdateRequest.class)
.map(req -> UpdateMenuCommand.of(
id,
req.getParentId(),
req.getMenuName(),
req.getMenuType(),
req.getOrderNum(),
req.getComponent(),
req.getPerms(),
req.getStatus()
))
.flatMap(menuService::updateMenu)
.flatMap(menu -> ServerResponse.ok().bodyValue(menu))
.switchIfEmpty(ServerResponse.notFound().build());
}
public Mono<ServerResponse> deleteMenu(ServerRequest request) {
Long id = Long.valueOf(request.pathVariable("id"));
return menuService.deleteMenu(id)
.then(ServerResponse.noContent().build());
}
}
@@ -1,6 +1,6 @@
package cn.novalon.manage.sys.handler.message;
import cn.novalon.manage.common.domain.SysUserMessage;
import cn.novalon.manage.sys.core.domain.SysUserMessage;
import cn.novalon.manage.sys.core.service.ISysUserMessageService;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
@@ -1,64 +0,0 @@
package cn.novalon.manage.sys.handler.notice;
import cn.novalon.manage.common.domain.SysNotice;
import cn.novalon.manage.sys.core.service.ISysNoticeService;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
@Component
public class SysNoticeHandler {
private final ISysNoticeService noticeService;
public SysNoticeHandler(ISysNoticeService noticeService) {
this.noticeService = noticeService;
}
public Mono<ServerResponse> getAllNotices(ServerRequest request) {
return ServerResponse.ok()
.body(noticeService.findAll(), SysNotice.class);
}
public Mono<ServerResponse> getNoticeById(ServerRequest request) {
Long id = Long.valueOf(request.pathVariable("id"));
return noticeService.findById(id)
.flatMap(notice -> ServerResponse.ok().bodyValue(notice))
.switchIfEmpty(ServerResponse.notFound().build());
}
public Mono<ServerResponse> getNoticesByStatus(ServerRequest request) {
String status = request.pathVariable("status");
return ServerResponse.ok()
.body(noticeService.findByStatus(status), SysNotice.class);
}
public Mono<ServerResponse> createNotice(ServerRequest request) {
return request.bodyToMono(SysNotice.class)
.flatMap(noticeService::save)
.flatMap(notice -> ServerResponse.status(HttpStatus.CREATED).bodyValue(notice));
}
public Mono<ServerResponse> updateNotice(ServerRequest request) {
Long id = Long.valueOf(request.pathVariable("id"));
return request.bodyToMono(SysNotice.class)
.flatMap(notice -> noticeService.findById(id)
.flatMap(existing -> {
existing.setNoticeTitle(notice.getNoticeTitle());
existing.setNoticeType(notice.getNoticeType());
existing.setNoticeContent(notice.getNoticeContent());
existing.setStatus(notice.getStatus());
return noticeService.save(existing);
}))
.flatMap(updatedNotice -> ServerResponse.ok().bodyValue(updatedNotice))
.switchIfEmpty(ServerResponse.notFound().build());
}
public Mono<ServerResponse> deleteNotice(ServerRequest request) {
Long id = Long.valueOf(request.pathVariable("id"));
return noticeService.deleteById(id)
.then(ServerResponse.noContent().build());
}
}
@@ -1,8 +1,12 @@
package cn.novalon.manage.sys.handler.role;
import cn.novalon.manage.common.domain.SysRole;
import cn.novalon.manage.sys.core.domain.SysRole;
import cn.novalon.manage.sys.core.service.ISysRoleService;
import cn.novalon.manage.common.dto.PageRequest;
import cn.novalon.manage.sys.dto.request.RoleCreateRequest;
import cn.novalon.manage.sys.dto.request.RoleUpdateRequest;
import cn.novalon.manage.sys.core.command.CreateRoleCommand;
import cn.novalon.manage.sys.core.command.UpdateRoleCommand;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.http.HttpStatus;
@@ -77,7 +81,13 @@ public class SysRoleHandler {
@Operation(summary = "创建角色", description = "创建新角色")
public Mono<ServerResponse> createRole(ServerRequest request) {
return request.bodyToMono(SysRole.class)
return request.bodyToMono(RoleCreateRequest.class)
.map(req -> CreateRoleCommand.of(
req.getRoleName(),
req.getRoleKey(),
req.getRoleSort(),
req.getStatus()
))
.flatMap(roleService::createRole)
.flatMap(role -> ServerResponse.status(HttpStatus.CREATED).bodyValue(role));
}
@@ -85,15 +95,15 @@ public class SysRoleHandler {
@Operation(summary = "更新角色", description = "更新角色信息")
public Mono<ServerResponse> updateRole(ServerRequest request) {
Long id = Long.valueOf(request.pathVariable("id"));
return request.bodyToMono(SysRole.class)
.flatMap(role -> roleService.findById(id)
.flatMap(existing -> {
if (role.getRoleName() != null) existing.setRoleName(role.getRoleName());
if (role.getRoleKey() != null) existing.setRoleKey(role.getRoleKey());
if (role.getRoleSort() != null) existing.setRoleSort(role.getRoleSort());
if (role.getStatus() != null) existing.setStatus(role.getStatus());
return roleService.updateRole(existing);
}))
return request.bodyToMono(RoleUpdateRequest.class)
.map(req -> UpdateRoleCommand.of(
id,
req.getRoleName(),
req.getRoleKey(),
req.getRoleSort(),
req.getStatus()
))
.flatMap(roleService::updateRole)
.flatMap(updatedRole -> ServerResponse.ok().bodyValue(updatedRole))
.switchIfEmpty(ServerResponse.notFound().build());
}
@@ -1,10 +1,13 @@
package cn.novalon.manage.sys.handler.user;
import cn.novalon.manage.common.domain.SysUser;
import cn.novalon.manage.sys.core.domain.SysUser;
import cn.novalon.manage.sys.core.service.ISysUserService;
import cn.novalon.manage.common.dto.PageRequest;
import cn.novalon.manage.sys.dto.request.PasswordChangeRequest;
import cn.novalon.manage.sys.dto.request.UserRegisterRequest;
import cn.novalon.manage.sys.dto.request.UserUpdateRequest;
import cn.novalon.manage.sys.core.command.CreateUserCommand;
import cn.novalon.manage.sys.core.command.UpdateUserCommand;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.http.HttpStatus;
@@ -15,6 +18,16 @@ import reactor.core.publisher.Mono;
import java.util.List;
/**
* 用户处理器
*
* 文件定义:处理用户相关的HTTP请求,将请求转换为Service层调用
* 涉及业务:用户查询、创建、更新、删除、密码修改等RESTful API操作
* 算法:使用WebFlux函数式编程模型处理响应式请求
*
* @author 张翔
* @date 2026-03-13
*/
@Component
@Tag(name = "用户管理", description = "用户相关操作")
public class SysUserHandler {
@@ -75,7 +88,14 @@ public class SysUserHandler {
@Operation(summary = "创建用户", description = "创建新用户")
public Mono<ServerResponse> createUser(ServerRequest request) {
return request.bodyToMono(SysUser.class)
return request.bodyToMono(UserRegisterRequest.class)
.map(req -> CreateUserCommand.of(
req.getUsername(),
req.getPassword(),
req.getEmail(),
null,
null
))
.flatMap(userService::createUser)
.flatMap(user -> ServerResponse.status(HttpStatus.CREATED).bodyValue(user));
}
@@ -84,16 +104,15 @@ public class SysUserHandler {
public Mono<ServerResponse> updateUser(ServerRequest request) {
Long id = Long.valueOf(request.pathVariable("id"));
return request.bodyToMono(UserUpdateRequest.class)
.flatMap(req -> userService.findById(id)
.flatMap(existing -> {
if (req.getEmail() != null)
existing.setEmail(req.getEmail());
if (req.getStatus() != null)
existing.setStatus(req.getStatus());
if (req.getRoleId() != null)
existing.setRoleId(req.getRoleId());
return userService.updateUser(existing);
}))
.map(req -> UpdateUserCommand.of(
id,
null,
null,
req.getEmail(),
req.getRoleId(),
req.getStatus()
))
.flatMap(userService::updateUser)
.flatMap(user -> ServerResponse.ok().bodyValue(user))
.switchIfEmpty(ServerResponse.notFound().build());
}
@@ -0,0 +1,66 @@
package cn.novalon.manage.sys.primitive;
import org.apache.commons.lang3.StringUtils;
import java.util.regex.Pattern;
/**
* 邮箱值对象
*
* @author 张翔
* @date 2026-03-13
*/
public final class Email {
private static final Pattern EMAIL_PATTERN = Pattern.compile("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$");
private final String value;
public String getValue() {
return value;
}
private Email(String value) {
this.value = value;
}
public static Email of(String value) {
if (StringUtils.isBlank(value)) {
throw new IllegalArgumentException("Email is required");
}
validate(value);
return new Email(value);
}
public static Email ofNullable(String value) {
if (StringUtils.isBlank(value)) {
return null;
}
validate(value);
return new Email(value);
}
private static void validate(String value) {
if (!EMAIL_PATTERN.matcher(value).matches()) {
throw new IllegalArgumentException("Invalid email format");
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Email email = (Email) o;
return value.equals(email.value);
}
@Override
public int hashCode() {
return value.hashCode();
}
@Override
public String toString() {
return value;
}
}
@@ -0,0 +1,66 @@
package cn.novalon.manage.sys.primitive;
import org.apache.commons.lang3.StringUtils;
/**
* 密码值对象
*
* @author 张翔
* @date 2026-03-13
*/
public final class Password {
private static final int MIN_LENGTH = 8;
private final String value;
public String getValue() {
return value;
}
private Password(String value) {
this.value = value;
}
public static Password of(String value) {
if (StringUtils.isBlank(value)) {
throw new IllegalArgumentException("Password is required");
}
validate(value);
return new Password(value);
}
private static void validate(String value) {
if (value.length() < MIN_LENGTH) {
throw new IllegalArgumentException("Password must be at least " + MIN_LENGTH + " characters long");
}
boolean hasUppercase = value.chars().anyMatch(Character::isUpperCase);
boolean hasLowercase = value.chars().anyMatch(Character::isLowerCase);
boolean hasDigit = value.chars().anyMatch(Character::isDigit);
boolean hasSpecial = value.chars().anyMatch(c -> !Character.isLetterOrDigit(c));
if (!hasUppercase || !hasLowercase || !hasDigit || !hasSpecial) {
throw new IllegalArgumentException(
"Password must contain at least one uppercase letter, one lowercase letter, one digit, and one special character");
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Password password = (Password) o;
return value.equals(password.value);
}
@Override
public int hashCode() {
return value.hashCode();
}
@Override
public String toString() {
return "********";
}
}
@@ -0,0 +1,70 @@
package cn.novalon.manage.sys.primitive;
import org.apache.commons.lang3.StringUtils;
import java.util.regex.Pattern;
/**
* 用户名值对象
*
* @author 张翔
* @date 2026-03-13
*/
public final class Username {
private static final int MIN_LENGTH = 3;
private static final int MAX_LENGTH = 50;
private static final Pattern USERNAME_PATTERN = Pattern.compile("^[a-zA-Z0-9_]+$");
private final String value;
public String getValue() {
return value;
}
private Username(String value) {
this.value = value;
}
public static Username of(String value) {
if (StringUtils.isBlank(value)) {
throw new IllegalArgumentException("Username is required");
}
validate(value);
return new Username(value);
}
private static void validate(String value) {
String trimmed = value.trim();
if (trimmed.length() < MIN_LENGTH) {
throw new IllegalArgumentException("Username must be at least " + MIN_LENGTH + " characters long");
}
if (trimmed.length() > MAX_LENGTH) {
throw new IllegalArgumentException("Username must be at most " + MAX_LENGTH + " characters long");
}
if (!USERNAME_PATTERN.matcher(trimmed).matches()) {
throw new IllegalArgumentException("Username can only contain letters, numbers, and underscores");
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Username username = (Username) o;
return value.equals(username.value);
}
@Override
public int hashCode() {
return value.hashCode();
}
@Override
public String toString() {
return value;
}
}
@@ -13,6 +13,12 @@ import reactor.core.publisher.Mono;
import java.util.Collections;
/**
* JWT认证过滤器
*
* @author 张翔
* @date 2026-03-13
*/
@Component
public class JwtAuthenticationFilter implements WebFilter {
@@ -12,6 +12,12 @@ import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* JWT Token提供者
*
* @author 张翔
* @date 2026-03-13
*/
@Component
public class JwtTokenProvider {
@@ -2,40 +2,110 @@ package cn.novalon.manage.sys.websocket;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.socket.WebSocketHandler;
import org.springframework.web.reactive.socket.WebSocketSession;
import reactor.core.publisher.Mono;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* WebSocket处理器
*
* 文件定义:处理WebSocket连接和消息推送
* 涉及业务:实时消息推送、系统公告通知、用户消息
* 算法:使用ConcurrentHashMap管理WebSocket会话,支持点对点和广播消息,添加心跳机制和超时处理
*
* @author 张翔
* @date 2026-03-13
*/
@Component
public class SysWebSocketHandler implements WebSocketHandler {
private final Map<String, WebSocketSession> sessions = new ConcurrentHashMap<>();
private final Map<String, LocalDateTime> lastActivityTime = new ConcurrentHashMap<>();
private final ObjectMapper objectMapper = new ObjectMapper();
@Value("${websocket.idle-timeout:300s}")
private Duration idleTimeout;
@Value("${websocket.heartbeat-interval:30s}")
private Duration heartbeatInterval;
@Override
public Mono<Void> handle(WebSocketSession session) {
String userId = extractUserId(session);
lastActivityTime.put(userId, LocalDateTime.now());
return session.receive()
.doOnNext(message -> {
String payload = message.getPayloadAsText();
handleIncomingMessage(session, userId, payload);
lastActivityTime.put(userId, LocalDateTime.now());
})
.doOnComplete(() -> {
sessions.remove(userId);
lastActivityTime.remove(userId);
System.out.println("WebSocket session closed for user: " + userId);
})
.doOnError(error -> {
sessions.remove(userId);
lastActivityTime.remove(userId);
System.err.println("WebSocket error for user " + userId + ": " + error.getMessage());
})
.then();
}
/**
* 定时清理空闲连接
*/
@Scheduled(fixedRate = 60000)
public void cleanupIdleConnections() {
LocalDateTime now = LocalDateTime.now();
lastActivityTime.entrySet().removeIf(entry -> {
LocalDateTime lastActivity = entry.getValue();
if (Duration.between(lastActivity, now).compareTo(idleTimeout) > 0) {
String userId = entry.getKey();
WebSocketSession session = sessions.get(userId);
if (session != null) {
try {
session.close();
System.out.println("Closed idle WebSocket connection for user: " + userId);
} catch (Exception e) {
System.err.println("Error closing idle connection for user " + userId + ": " + e.getMessage());
}
}
return true;
}
return false;
});
}
/**
* 定时发送心跳消息
*/
@Scheduled(fixedRate = 30000)
public void sendHeartbeat() {
sessions.forEach((userId, session) -> {
try {
if (session.isOpen()) {
String heartbeatMessage = objectMapper.writeValueAsString(Map.of(
"type", "heartbeat",
"timestamp", System.currentTimeMillis()
));
session.send(Mono.just(session.textMessage(heartbeatMessage))).subscribe();
}
} catch (Exception e) {
System.err.println("Error sending heartbeat to user " + userId + ": " + e.getMessage());
}
});
}
private String extractUserId(WebSocketSession session) {
String query = session.getHandshakeInfo().getUri().getQuery();
if (query != null && query.contains("userId=")) {
@@ -54,10 +124,17 @@ public class SysWebSocketHandler implements WebSocketHandler {
case "ping":
sendMessageToUser(userId, Map.of("type", "pong", "timestamp", System.currentTimeMillis()));
break;
case "pong":
lastActivityTime.put(userId, LocalDateTime.now());
break;
case "subscribe":
sessions.put(userId, session);
lastActivityTime.put(userId, LocalDateTime.now());
System.out.println("User " + userId + " subscribed to WebSocket");
break;
case "heartbeat":
lastActivityTime.put(userId, LocalDateTime.now());
break;
default:
System.out.println("Unknown message type: " + type);
}
@@ -68,7 +145,7 @@ public class SysWebSocketHandler implements WebSocketHandler {
public void sendMessageToUser(String userId, Object message) {
WebSocketSession session = sessions.get(userId);
if (session != null) {
if (session != null && session.isOpen()) {
try {
String json = objectMapper.writeValueAsString(message);
session.send(Mono.just(session.textMessage(json))).subscribe();
@@ -89,7 +166,9 @@ public class SysWebSocketHandler implements WebSocketHandler {
sessions.forEach((userId, session) -> {
try {
session.send(Mono.just(session.textMessage(json))).subscribe();
if (session.isOpen()) {
session.send(Mono.just(session.textMessage(json))).subscribe();
}
} catch (Exception e) {
System.err.println("Error broadcasting to user " + userId + ": " + e.getMessage());
}
@@ -1,5 +1,8 @@
server:
port: 8080
port: 8084
netty:
connection-timeout: 60s
idle-timeout: 300s
spring:
application:
@@ -30,6 +33,21 @@ jwt:
secret: novalon-manage-secret-key-change-in-production
expiration: 86400000
websocket:
enabled: true
heartbeat-interval: 30s
idle-timeout: 300s
max-text-message-buffer-size: 8192
max-binary-message-buffer-size: 8192
resilience4j:
ratelimiter:
instances:
apiRateLimiter:
limit-for-period: 100
limit-refresh-period: 1s
timeout-duration: 0
logging:
level:
cn.novalon.manage: DEBUG
@@ -1,9 +1,9 @@
package cn.novalon.manage.sys.core.service.impl;
import cn.novalon.manage.common.domain.Dictionary;
import cn.novalon.manage.sys.core.domain.Dictionary;
import cn.novalon.manage.sys.core.exception.DictionaryAlreadyExistsException;
import cn.novalon.manage.sys.core.service.IDictionaryService;
import cn.novalon.manage.db.repository.DictionaryRepository;
import cn.novalon.manage.sys.core.repository.IDictionaryRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -22,7 +22,7 @@ import static org.mockito.Mockito.*;
class DictionaryServiceTest {
@Mock
private DictionaryRepository repository;
private IDictionaryRepository repository;
private IDictionaryService service;
@@ -1,7 +1,7 @@
package cn.novalon.manage.sys.core.service.impl;
import cn.novalon.manage.common.domain.SysConfig;
import cn.novalon.manage.db.repository.ISysConfigRepository;
import cn.novalon.manage.sys.core.domain.SysConfig;
import cn.novalon.manage.sys.core.repository.ISysConfigRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

Some files were not shown because too many files have changed in this diff Show More