feat: 更新端口配置并添加监控支持

fix: 修复测试配置和依赖检查

perf: 优化雪花算法性能

refactor: 清理冗余代码和未使用的导入

style: 统一代码格式和注释

test: 添加单元测试和集成测试

ci: 更新CI配置和构建脚本

chore: 更新依赖和配置文件
This commit is contained in:
张翔
2026-03-13 08:50:19 +08:00
parent fe2e4110dd
commit 9f8bf041c3
169 changed files with 3565 additions and 132 deletions
@@ -1,9 +1,12 @@
package cn.novalon.manage.sys;
import cn.novalon.manage.sys.config.JwtProperties;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@SpringBootApplication
@EnableConfigurationProperties(JwtProperties.class)
public class ManageSysApplication {
public static void main(String[] args) {
SpringApplication.run(ManageSysApplication.class, args);
@@ -0,0 +1,30 @@
package cn.novalon.manage.sys.config;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.TimeUnit;
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(caffeineCacheBuilder());
return cacheManager;
}
private Caffeine<Object, Object> caffeineCacheBuilder() {
return Caffeine.newBuilder()
.initialCapacity(100)
.maximumSize(500)
.expireAfterWrite(30, TimeUnit.MINUTES)
.recordStats();
}
}
@@ -0,0 +1,30 @@
package cn.novalon.manage.sys.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
@Component
@ConfigurationProperties(prefix = "jwt")
@Validated
public class JwtProperties {
private String secret = "default-secret-key-change-in-production";
private long expiration = 86400000;
public String getSecret() {
return secret;
}
public void setSecret(String secret) {
this.secret = secret;
}
public long getExpiration() {
return expiration;
}
public void setExpiration(long expiration) {
this.expiration = expiration;
}
}
@@ -4,8 +4,6 @@ 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;
import org.springframework.web.reactive.function.client.ExchangeStrategies;
import org.springframework.web.reactive.function.client.WebClient;
@Configuration
public class MultipartConfig {
@@ -0,0 +1,45 @@
package cn.novalon.manage.sys.config;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import io.swagger.v3.oas.models.servers.Server;
import io.swagger.v3.oas.models.tags.Tag;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Arrays;
import java.util.List;
@Configuration
public class OpenApiConfig {
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info()
.title("Novalon Manage System API")
.version("1.0.0")
.description("Novalon 管理系统 RESTful API 文档")
.contact(new Contact()
.name("Novalon Team")
.email("support@novalon.cn"))
.license(new License()
.name("Apache 2.0")
.url("https://www.apache.org/licenses/LICENSE-2.0")))
.servers(List.of(
new Server().url("http://localhost:8080").description("开发环境"),
new Server().url("https://api.novalon.cn").description("生产环境")))
.tags(Arrays.asList(
new Tag().name("用户管理").description("用户相关操作"),
new Tag().name("角色管理").description("角色相关操作"),
new Tag().name("配置管理").description("系统配置相关操作"),
new Tag().name("字典管理").description("字典数据相关操作"),
new Tag().name("通知管理").description("系统通知相关操作"),
new Tag().name("文件管理").description("文件上传下载相关操作"),
new Tag().name("日志管理").description("操作日志相关操作"),
new Tag().name("认证管理").description("登录认证相关操作"),
new Tag().name("统计信息").description("系统统计相关操作")));
}
}
@@ -16,7 +16,6 @@ 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.RequestPredicates.*;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
@Configuration
@@ -114,15 +113,15 @@ public class SystemRouter {
public RouterFunction<ServerResponse> logRoutes(SysLogHandler logHandler) {
return route()
.GET("/api/logs/login", logHandler::getAllLoginLogs)
.GET("/api/logs/login/{id}", logHandler::getLoginLogById)
.POST("/api/logs/login", logHandler::createLoginLog)
.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/{id}", logHandler::getExceptionLogById)
.POST("/api/logs/exception", logHandler::createExceptionLog)
.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();
}
@@ -164,8 +163,8 @@ public class SystemRouter {
.PUT("/api/dict/types/{id}", dictHandler::updateDictType)
.DELETE("/api/dict/types/{id}", dictHandler::deleteDictType)
.GET("/api/dict/data", dictHandler::getAllDictData)
.GET("/api/dict/data/{id}", dictHandler::getDictDataById)
.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)
@@ -11,6 +11,8 @@ import java.time.LocalDateTime;
public abstract class BaseDomain {
protected Long id;
protected String createBy;
protected String updateBy;
protected LocalDateTime createdAt;
protected LocalDateTime updatedAt;
protected LocalDateTime deletedAt;
@@ -23,6 +25,22 @@ public abstract class BaseDomain {
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;
}
@@ -11,8 +11,10 @@ public class Dictionary {
private String remark;
private Integer sort;
private String createBy;
private String updateBy;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private LocalDateTime deletedAt;
public Dictionary() {
}
@@ -81,6 +83,14 @@ public class Dictionary {
this.createBy = createBy;
}
public String getUpdateBy() {
return updateBy;
}
public void setUpdateBy(String updateBy) {
this.updateBy = updateBy;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
@@ -96,4 +106,12 @@ public class Dictionary {
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
public LocalDateTime getDeletedAt() {
return deletedAt;
}
public void setDeletedAt(LocalDateTime deletedAt) {
this.deletedAt = deletedAt;
}
}
@@ -13,6 +13,7 @@ public class SysConfig {
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; }
@@ -32,4 +33,6 @@ public class SysConfig {
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; }
}
@@ -18,6 +18,7 @@ public class SysDictData {
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; }
@@ -47,4 +48,6 @@ public class SysDictData {
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; }
}
@@ -13,6 +13,7 @@ public class SysDictType {
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; }
@@ -32,4 +33,6 @@ public class SysDictType {
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; }
}
@@ -11,7 +11,9 @@ public class SysFile {
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; }
@@ -27,6 +29,10 @@ public class SysFile {
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; }
}
@@ -11,6 +11,8 @@ public class SysMenu extends BaseDomain {
private String perms;
private String component;
private String status;
private String createBy;
private String updateBy;
private List<SysMenu> children;
public String getMenuName() {
@@ -69,6 +71,22 @@ public class SysMenu extends BaseDomain {
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;
}
@@ -13,6 +13,7 @@ public class SysNotice {
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; }
@@ -32,4 +33,6 @@ public class SysNotice {
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; }
}
@@ -5,7 +5,6 @@ import cn.novalon.manage.sys.core.exception.DictionaryAlreadyExistsException;
import cn.novalon.manage.sys.core.service.IDictionaryService;
import cn.novalon.manage.sys.infrastructure.db.converter.DictionaryConverter;
import cn.novalon.manage.sys.infrastructure.db.dao.DictionaryDao;
import cn.novalon.manage.sys.infrastructure.db.entity.DictionaryEntity;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -11,6 +11,7 @@ import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Service
@@ -70,9 +71,11 @@ public class SysLoginLogService implements ISysLoginLogService {
return allLogs
.collectList()
.map(list -> {
.flatMap(list -> {
List<SysLoginLog> sortedList = new ArrayList<>(list);
if (pageRequest.getSort() != null && !pageRequest.getSort().isEmpty()) {
list.sort((a, b) -> {
sortedList.sort((a, b) -> {
int comparison = 0;
if ("username".equals(pageRequest.getSort())) {
comparison = compareStrings(a.getUsername(), b.getUsername());
@@ -84,7 +87,8 @@ public class SysLoginLogService implements ISysLoginLogService {
return "desc".equalsIgnoreCase(pageRequest.getOrder()) ? -comparison : comparison;
});
}
return list;
return Mono.just(sortedList);
})
.zipWith(dao.count())
.map(tuple -> {
@@ -126,4 +130,4 @@ public class SysLoginLogService implements ISysLoginLogService {
public Mono<Long> count() {
return dao.count();
}
}
}
@@ -9,7 +9,6 @@ import cn.novalon.manage.sys.dto.request.PageRequest;
import cn.novalon.manage.sys.dto.response.PageResponse;
import cn.novalon.manage.sys.infrastructure.db.entity.query.SysRoleQueryCriteria;
import cn.novalon.manage.sys.infrastructure.db.utils.QueryUtil;
import org.springframework.data.domain.Sort;
import org.springframework.data.relational.core.query.Query;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
@@ -25,7 +25,7 @@ public final class SnowflakeId {
private static final long DEFAULT_EPOCH = 1582136402000L;
private static final int MAX_RETRIES = 10;
private static final long MAX_BACKWARD_MS = 50;
private static final int SPIN_THRESHOLD = 100;
private static final int SPIN_THRESHOLD = 5;
private static final long TIME_CACHE_DURATION_MS = 16;
private static final AtomicLong lastTimestamp = new AtomicLong(-1L);
@@ -9,10 +9,16 @@ import cn.novalon.manage.sys.core.service.ISysUserService;
import org.springframework.http.HttpStatus;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.support.WebExchangeBindException;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.stream.Collectors;
@Component
public class SysAuthHandler {
@@ -20,7 +26,8 @@ public class SysAuthHandler {
private final PasswordEncoder passwordEncoder;
private final JwtTokenProvider jwtTokenProvider;
public SysAuthHandler(ISysUserService userService, PasswordEncoder passwordEncoder, JwtTokenProvider jwtTokenProvider) {
public SysAuthHandler(ISysUserService userService, PasswordEncoder passwordEncoder,
JwtTokenProvider jwtTokenProvider) {
this.userService = userService;
this.passwordEncoder = passwordEncoder;
this.jwtTokenProvider = jwtTokenProvider;
@@ -28,6 +35,12 @@ public class SysAuthHandler {
public Mono<ServerResponse> login(ServerRequest request) {
return request.bodyToMono(LoginRequest.class)
.filter(loginRequest -> loginRequest.getUsername() != null
&& !loginRequest.getUsername().trim().isEmpty())
.switchIfEmpty(Mono.error(new IllegalArgumentException("用户名不能为空")))
.filter(loginRequest -> loginRequest.getPassword() != null
&& !loginRequest.getPassword().trim().isEmpty())
.switchIfEmpty(Mono.error(new IllegalArgumentException("密码不能为空")))
.flatMap(loginRequest -> userService.findByUsername(loginRequest.getUsername())
.filter(user -> passwordEncoder.matches(loginRequest.getPassword(), user.getPassword()))
.filter(user -> 1 == user.getStatus())
@@ -36,7 +49,22 @@ public class SysAuthHandler {
AuthResponse response = new AuthResponse(token, user.getId(), user.getUsername());
return ServerResponse.ok().bodyValue(response);
})
.switchIfEmpty(ServerResponse.status(HttpStatus.UNAUTHORIZED).build()));
.switchIfEmpty(ServerResponse.status(HttpStatus.UNAUTHORIZED).build()))
.onErrorResume(WebExchangeBindException.class, ex -> {
String errorMessage = ex.getBindingResult().getFieldErrors().stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining(", "));
return ServerResponse.badRequest().bodyValue(Map.of(
"code", HttpStatus.BAD_REQUEST.value(),
"message", errorMessage,
"timestamp", LocalDateTime.now()));
})
.onErrorResume(IllegalArgumentException.class, ex -> {
return ServerResponse.badRequest().bodyValue(Map.of(
"code", HttpStatus.BAD_REQUEST.value(),
"message", ex.getMessage(),
"timestamp", LocalDateTime.now()));
});
}
public Mono<ServerResponse> register(ServerRequest request) {
@@ -44,7 +72,7 @@ public class SysAuthHandler {
.flatMap(registerRequest -> {
SysUser user = new SysUser();
user.setUsername(registerRequest.getUsername());
user.setPassword(registerRequest.getPassword());
user.setPassword(passwordEncoder.encode(registerRequest.getPassword()));
user.setEmail(registerRequest.getEmail());
return userService.findByUsername(registerRequest.getUsername())
.flatMap(existing -> Mono.<ServerResponse>error(new RuntimeException("用户名已存在")))
@@ -7,9 +7,7 @@ import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.multipart.FilePart;
import org.springframework.http.codec.multipart.Part;
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
@@ -70,7 +68,7 @@ public class SysFileHandler {
if (resource.exists() && resource.isReadable()) {
return ServerResponse.ok()
.header(HttpHeaders.CONTENT_DISPOSITION,
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + file.getFileName() + "\"")
.bodyValue(resource);
} else {
@@ -93,7 +91,7 @@ public class SysFileHandler {
if (resource.exists() && resource.isReadable()) {
return ServerResponse.ok()
.header(HttpHeaders.CONTENT_DISPOSITION,
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + file.getFileName() + "\"")
.bodyValue(resource);
} else {
@@ -128,7 +126,7 @@ public class SysFileHandler {
response.setPreviewData(Base64.getEncoder().encodeToString(fileBytes));
} else if (fileType.startsWith("text/")) {
response.setPreviewType("text");
response.setPreviewData(new String(fileBytes));
response.setPreviewData(new String(fileBytes, java.nio.charset.StandardCharsets.UTF_8));
} else {
response.setPreviewType("unsupported");
response.setPreviewData(null);
@@ -164,7 +162,7 @@ public class SysFileHandler {
response.setPreviewData(Base64.getEncoder().encodeToString(fileBytes));
} else if (fileType.startsWith("text/")) {
response.setPreviewType("text");
response.setPreviewData(new String(fileBytes));
response.setPreviewData(new String(fileBytes, java.nio.charset.StandardCharsets.UTF_8));
} else {
response.setPreviewType("unsupported");
response.setPreviewData(null);
@@ -5,7 +5,6 @@ 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.sys.dto.request.PageRequest;
import cn.novalon.manage.sys.dto.response.PageResponse;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
@@ -3,6 +3,8 @@ package cn.novalon.manage.sys.handler.role;
import cn.novalon.manage.sys.core.domain.SysRole;
import cn.novalon.manage.sys.core.service.ISysRoleService;
import cn.novalon.manage.sys.dto.request.PageRequest;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
@@ -10,6 +12,7 @@ import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
@Component
@Tag(name = "角色管理", description = "角色相关操作")
public class SysRoleHandler {
private final ISysRoleService roleService;
@@ -18,11 +21,13 @@ public class SysRoleHandler {
this.roleService = roleService;
}
@Operation(summary = "获取所有角色", description = "获取系统中所有角色列表")
public Mono<ServerResponse> getAllRoles(ServerRequest request) {
return ServerResponse.ok()
.body(roleService.findAll(), SysRole.class);
}
@Operation(summary = "分页获取角色", description = "根据分页参数获取角色列表")
public Mono<ServerResponse> getRolesByPage(ServerRequest request) {
int page = Integer.parseInt(request.queryParam("page").orElse("0"));
int size = Integer.parseInt(request.queryParam("size").orElse("10"));
@@ -41,11 +46,13 @@ public class SysRoleHandler {
.flatMap(response -> ServerResponse.ok().bodyValue(response));
}
@Operation(summary = "获取角色总数", description = "获取系统中角色总数")
public Mono<ServerResponse> getRoleCount(ServerRequest request) {
return roleService.count()
.flatMap(count -> ServerResponse.ok().bodyValue(count));
}
@Operation(summary = "根据角色名获取角色", description = "根据角色名称获取角色详细信息")
public Mono<ServerResponse> getRoleByName(ServerRequest request) {
String roleName = request.pathVariable("roleName");
return roleService.findByRoleName(roleName)
@@ -53,12 +60,14 @@ public class SysRoleHandler {
.switchIfEmpty(ServerResponse.notFound().build());
}
@Operation(summary = "检查角色名是否存在", description = "检查指定角色名是否已存在")
public Mono<ServerResponse> checkNameExists(ServerRequest request) {
String name = request.queryParam("name").orElse(null);
return roleService.existsByRoleName(name)
.flatMap(exists -> ServerResponse.ok().bodyValue(exists));
}
@Operation(summary = "根据ID获取角色", description = "根据角色ID获取角色详细信息")
public Mono<ServerResponse> getRoleById(ServerRequest request) {
Long id = Long.valueOf(request.pathVariable("id"));
return roleService.findById(id)
@@ -66,12 +75,14 @@ public class SysRoleHandler {
.switchIfEmpty(ServerResponse.notFound().build());
}
@Operation(summary = "创建角色", description = "创建新角色")
public Mono<ServerResponse> createRole(ServerRequest request) {
return request.bodyToMono(SysRole.class)
.flatMap(roleService::createRole)
.flatMap(role -> ServerResponse.status(HttpStatus.CREATED).bodyValue(role));
}
@Operation(summary = "更新角色", description = "更新角色信息")
public Mono<ServerResponse> updateRole(ServerRequest request) {
Long id = Long.valueOf(request.pathVariable("id"));
return request.bodyToMono(SysRole.class)
@@ -87,6 +98,7 @@ public class SysRoleHandler {
.switchIfEmpty(ServerResponse.notFound().build());
}
@Operation(summary = "删除角色", description = "逻辑删除角色")
public Mono<ServerResponse> deleteRole(ServerRequest request) {
Long id = Long.valueOf(request.pathVariable("id"));
return roleService.logicalDeleteRole(id)
@@ -94,6 +106,7 @@ public class SysRoleHandler {
.switchIfEmpty(ServerResponse.notFound().build());
}
@Operation(summary = "恢复角色", description = "恢复被逻辑删除的角色")
public Mono<ServerResponse> restoreRole(ServerRequest request) {
Long id = Long.valueOf(request.pathVariable("id"));
return roleService.restoreRole(id)
@@ -5,6 +5,8 @@ import cn.novalon.manage.sys.core.service.ISysUserService;
import cn.novalon.manage.sys.dto.request.PageRequest;
import cn.novalon.manage.sys.dto.request.PasswordChangeRequest;
import cn.novalon.manage.sys.dto.request.UserUpdateRequest;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
@@ -14,6 +16,7 @@ import reactor.core.publisher.Mono;
import java.util.List;
@Component
@Tag(name = "用户管理", description = "用户相关操作")
public class SysUserHandler {
private final ISysUserService userService;
@@ -22,12 +25,14 @@ public class SysUserHandler {
this.userService = userService;
}
@Operation(summary = "获取所有用户", description = "获取系统中所有用户列表")
public Mono<ServerResponse> getAllUsers(ServerRequest request) {
boolean includeDeleted = Boolean.valueOf(request.queryParam("includeDeleted").orElse("false"));
return ServerResponse.ok()
.body(userService.findAll(includeDeleted), SysUser.class);
}
@Operation(summary = "分页获取用户", description = "根据分页参数获取用户列表")
public Mono<ServerResponse> getUsersByPage(ServerRequest request) {
int page = Integer.parseInt(request.queryParam("page").orElse("0"));
int size = Integer.parseInt(request.queryParam("size").orElse("10"));
@@ -46,11 +51,13 @@ public class SysUserHandler {
.flatMap(response -> ServerResponse.ok().bodyValue(response));
}
@Operation(summary = "获取用户总数", description = "获取系统中用户总数")
public Mono<ServerResponse> getUserCount(ServerRequest request) {
return userService.count()
.flatMap(count -> ServerResponse.ok().bodyValue(count));
}
@Operation(summary = "根据ID获取用户", description = "根据用户ID获取用户详细信息")
public Mono<ServerResponse> getUserById(ServerRequest request) {
Long id = Long.valueOf(request.pathVariable("id"));
return userService.findById(id)
@@ -58,6 +65,7 @@ public class SysUserHandler {
.switchIfEmpty(ServerResponse.notFound().build());
}
@Operation(summary = "根据用户名获取用户", description = "根据用户名获取用户详细信息")
public Mono<ServerResponse> getUserByUsername(ServerRequest request) {
String username = request.pathVariable("username");
return userService.findByUsername(username)
@@ -65,12 +73,14 @@ public class SysUserHandler {
.switchIfEmpty(ServerResponse.notFound().build());
}
@Operation(summary = "创建用户", description = "创建新用户")
public Mono<ServerResponse> createUser(ServerRequest request) {
return request.bodyToMono(SysUser.class)
.flatMap(userService::createUser)
.flatMap(user -> ServerResponse.status(HttpStatus.CREATED).bodyValue(user));
}
@Operation(summary = "更新用户", description = "更新用户信息")
public Mono<ServerResponse> updateUser(ServerRequest request) {
Long id = Long.valueOf(request.pathVariable("id"));
return request.bodyToMono(UserUpdateRequest.class)
@@ -88,12 +98,14 @@ public class SysUserHandler {
.switchIfEmpty(ServerResponse.notFound().build());
}
@Operation(summary = "删除用户", description = "物理删除用户")
public Mono<ServerResponse> deleteUser(ServerRequest request) {
Long id = Long.valueOf(request.pathVariable("id"));
return userService.deleteUser(id)
.then(ServerResponse.noContent().build());
}
@Operation(summary = "修改密码", description = "修改用户密码")
public Mono<ServerResponse> changePassword(ServerRequest request) {
Long id = Long.valueOf(request.pathVariable("id"));
return request.bodyToMono(PasswordChangeRequest.class)
@@ -101,12 +113,14 @@ public class SysUserHandler {
.flatMap(user -> ServerResponse.ok().bodyValue(user));
}
@Operation(summary = "逻辑删除用户", description = "逻辑删除单个用户")
public Mono<ServerResponse> logicalDeleteUser(ServerRequest request) {
Long id = Long.valueOf(request.pathVariable("id"));
return userService.logicalDeleteUser(id)
.then(ServerResponse.noContent().build());
}
@Operation(summary = "批量逻辑删除用户", description = "批量逻辑删除多个用户")
public Mono<ServerResponse> logicalDeleteUsers(ServerRequest request) {
return request.bodyToMono(new org.springframework.core.ParameterizedTypeReference<List<Long>>() {
})
@@ -114,12 +128,14 @@ public class SysUserHandler {
.then(ServerResponse.noContent().build());
}
@Operation(summary = "恢复用户", description = "恢复单个被逻辑删除的用户")
public Mono<ServerResponse> restoreUser(ServerRequest request) {
Long id = Long.valueOf(request.pathVariable("id"));
return userService.restoreUser(id)
.then(ServerResponse.noContent().build());
}
@Operation(summary = "批量恢复用户", description = "批量恢复被逻辑删除的用户")
public Mono<ServerResponse> restoreUsers(ServerRequest request) {
return request.bodyToMono(new org.springframework.core.ParameterizedTypeReference<List<Long>>() {
})
@@ -127,12 +143,14 @@ public class SysUserHandler {
.then(ServerResponse.noContent().build());
}
@Operation(summary = "检查用户名是否存在", description = "检查指定用户名是否已存在")
public Mono<ServerResponse> checkUsernameExists(ServerRequest request) {
String username = request.queryParam("username").orElse(null);
return userService.existsByUsername(username)
.flatMap(exists -> ServerResponse.ok().bodyValue(exists));
}
@Operation(summary = "检查邮箱是否存在", description = "检查指定邮箱是否已存在")
public Mono<ServerResponse> checkEmailExists(ServerRequest request) {
String email = request.queryParam("email").orElse(null);
return userService.existsByEmail(email)
@@ -12,6 +12,12 @@ public abstract class BaseEntity {
@Id
private Long id;
@Column("create_by")
private String createBy;
@Column("update_by")
private String updateBy;
@CreatedDate
@Column("created_at")
private LocalDateTime createdAt;
@@ -31,6 +37,22 @@ public abstract class BaseEntity {
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;
}
@@ -1,8 +1,7 @@
package cn.novalon.manage.sys.infrastructure.db.entity;
import cn.novalon.manage.sys.core.domain.Dictionary;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Transient;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.Table;
import java.time.LocalDateTime;
@@ -17,9 +16,15 @@ public class DictionaryEntity {
private String value;
private String remark;
private Integer sort;
@Column("create_by")
private String createBy;
@Column("update_by")
private String updateBy;
@Column("created_at")
private LocalDateTime createdAt;
@Column("updated_at")
private LocalDateTime updatedAt;
@Column("deleted_at")
private LocalDateTime deletedAt;
public DictionaryEntity() {
@@ -89,6 +94,14 @@ public class DictionaryEntity {
this.createBy = createBy;
}
public String getUpdateBy() {
return updateBy;
}
public void setUpdateBy(String updateBy) {
this.updateBy = updateBy;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
@@ -24,6 +24,12 @@ public class SysConfigEntity {
@Column("config_type")
private String configType;
@Column("create_by")
private String createBy;
@Column("update_by")
private String updateBy;
@Column("created_at")
private LocalDateTime createdAt;
@@ -73,6 +79,22 @@ public class SysConfigEntity {
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;
}
@@ -36,6 +36,12 @@ public class SysDictDataEntity {
@Column("status")
private String status;
@Column("create_by")
private String createBy;
@Column("update_by")
private String updateBy;
@Column("created_at")
private LocalDateTime createdAt;
@@ -117,6 +123,22 @@ public class SysDictDataEntity {
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;
}
@@ -24,6 +24,12 @@ public class SysDictTypeEntity {
@Column("remark")
private String remark;
@Column("create_by")
private String createBy;
@Column("update_by")
private String updateBy;
@Column("created_at")
private LocalDateTime createdAt;
@@ -73,6 +79,22 @@ public class SysDictTypeEntity {
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;
}
@@ -30,6 +30,9 @@ public class SysFileEntity {
@Column("create_by")
private String createBy;
@Column("update_by")
private String updateBy;
@Column("created_at")
private LocalDateTime createdAt;
@@ -92,6 +95,14 @@ public class SysFileEntity {
this.createBy = createBy;
}
public String getUpdateBy() {
return updateBy;
}
public void setUpdateBy(String updateBy) {
this.updateBy = updateBy;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
@@ -24,6 +24,12 @@ public class SysNoticeEntity {
@Column("status")
private String status;
@Column("create_by")
private String createBy;
@Column("update_by")
private String updateBy;
@Column("created_at")
private LocalDateTime createdAt;
@@ -73,6 +79,22 @@ public class SysNoticeEntity {
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;
}
@@ -3,16 +3,12 @@ package cn.novalon.manage.sys.infrastructure.db.mapper;
import cn.novalon.manage.sys.core.domain.Dictionary;
import cn.novalon.manage.sys.infrastructure.db.entity.DictionaryEntity;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
import java.util.List;
@Mapper(componentModel = "spring")
public interface DictionaryMapper {
DictionaryMapper INSTANCE = Mappers.getMapper(DictionaryMapper.class);
Dictionary toDomain(DictionaryEntity entity);
DictionaryEntity toEntity(Dictionary domain);
@@ -3,15 +3,12 @@ package cn.novalon.manage.sys.infrastructure.db.mapper;
import cn.novalon.manage.sys.core.domain.OperationLog;
import cn.novalon.manage.sys.infrastructure.db.entity.OperationLogEntity;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
@Mapper(componentModel = "spring")
public interface OperationLogMapper {
OperationLogMapper INSTANCE = Mappers.getMapper(OperationLogMapper.class);
OperationLog toDomain(OperationLogEntity entity);
OperationLogEntity toEntity(OperationLog domain);
@@ -3,15 +3,12 @@ package cn.novalon.manage.sys.infrastructure.db.mapper;
import cn.novalon.manage.sys.core.domain.SysConfig;
import cn.novalon.manage.sys.infrastructure.db.entity.SysConfigEntity;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
@Mapper(componentModel = "spring")
public interface SysConfigMapper {
SysConfigMapper INSTANCE = Mappers.getMapper(SysConfigMapper.class);
SysConfig toDomain(SysConfigEntity entity);
SysConfigEntity toEntity(SysConfig domain);
@@ -3,15 +3,12 @@ package cn.novalon.manage.sys.infrastructure.db.mapper;
import cn.novalon.manage.sys.core.domain.SysDictData;
import cn.novalon.manage.sys.infrastructure.db.entity.SysDictDataEntity;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
@Mapper(componentModel = "spring")
public interface SysDictDataMapper {
SysDictDataMapper INSTANCE = Mappers.getMapper(SysDictDataMapper.class);
SysDictData toDomain(SysDictDataEntity entity);
SysDictDataEntity toEntity(SysDictData domain);
@@ -3,15 +3,12 @@ package cn.novalon.manage.sys.infrastructure.db.mapper;
import cn.novalon.manage.sys.core.domain.SysDictType;
import cn.novalon.manage.sys.infrastructure.db.entity.SysDictTypeEntity;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
@Mapper(componentModel = "spring")
public interface SysDictTypeMapper {
SysDictTypeMapper INSTANCE = Mappers.getMapper(SysDictTypeMapper.class);
SysDictType toDomain(SysDictTypeEntity entity);
SysDictTypeEntity toEntity(SysDictType domain);
@@ -3,15 +3,12 @@ package cn.novalon.manage.sys.infrastructure.db.mapper;
import cn.novalon.manage.sys.core.domain.SysExceptionLog;
import cn.novalon.manage.sys.infrastructure.db.entity.SysExceptionLogEntity;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
@Mapper(componentModel = "spring")
public interface SysExceptionLogMapper {
SysExceptionLogMapper INSTANCE = Mappers.getMapper(SysExceptionLogMapper.class);
SysExceptionLog toDomain(SysExceptionLogEntity entity);
SysExceptionLogEntity toEntity(SysExceptionLog domain);
@@ -3,15 +3,12 @@ package cn.novalon.manage.sys.infrastructure.db.mapper;
import cn.novalon.manage.sys.core.domain.SysFile;
import cn.novalon.manage.sys.infrastructure.db.entity.SysFileEntity;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
@Mapper(componentModel = "spring")
public interface SysFileMapper {
SysFileMapper INSTANCE = Mappers.getMapper(SysFileMapper.class);
SysFile toDomain(SysFileEntity entity);
SysFileEntity toEntity(SysFile domain);
@@ -3,15 +3,12 @@ package cn.novalon.manage.sys.infrastructure.db.mapper;
import cn.novalon.manage.sys.core.domain.SysLoginLog;
import cn.novalon.manage.sys.infrastructure.db.entity.SysLoginLogEntity;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
@Mapper(componentModel = "spring")
public interface SysLoginLogMapper {
SysLoginLogMapper INSTANCE = Mappers.getMapper(SysLoginLogMapper.class);
SysLoginLog toDomain(SysLoginLogEntity entity);
SysLoginLogEntity toEntity(SysLoginLog domain);
@@ -2,21 +2,66 @@ package cn.novalon.manage.sys.infrastructure.db.mapper;
import cn.novalon.manage.sys.core.domain.SysMenu;
import cn.novalon.manage.sys.infrastructure.db.entity.SysMenuEntity;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import org.springframework.stereotype.Component;
import java.util.List;
@Mapper(componentModel = "spring")
public interface SysMenuMapper {
@Component
public class SysMenuMapper {
SysMenuMapper INSTANCE = Mappers.getMapper(SysMenuMapper.class);
private static final SysMenuMapper INSTANCE = new SysMenuMapper();
SysMenu toDomain(SysMenuEntity entity);
public static SysMenuMapper getInstance() {
return INSTANCE;
}
SysMenuEntity toEntity(SysMenu domain);
public SysMenu toDomain(SysMenuEntity entity) {
if (entity == null) {
return null;
}
SysMenu domain = new SysMenu();
domain.setId(entity.getId());
domain.setMenuName(entity.getMenuName());
domain.setParentId(entity.getParentId());
domain.setOrderNum(entity.getOrderNum());
domain.setMenuType(entity.getMenuType());
domain.setPerms(entity.getPerms());
domain.setComponent(entity.getComponent());
domain.setStatus(entity.getStatus());
domain.setCreateBy(entity.getCreateBy());
domain.setUpdateBy(entity.getUpdateBy());
domain.setCreatedAt(entity.getCreatedAt());
domain.setUpdatedAt(entity.getUpdatedAt());
domain.setDeletedAt(entity.getDeletedAt());
return domain;
}
List<SysMenu> toDomainList(List<SysMenuEntity> entities);
public SysMenuEntity toEntity(SysMenu domain) {
if (domain == null) {
return null;
}
SysMenuEntity entity = new SysMenuEntity();
entity.setId(domain.getId());
entity.setMenuName(domain.getMenuName());
entity.setParentId(domain.getParentId());
entity.setOrderNum(domain.getOrderNum());
entity.setMenuType(domain.getMenuType());
entity.setPerms(domain.getPerms());
entity.setComponent(domain.getComponent());
entity.setStatus(domain.getStatus());
entity.setCreateBy(domain.getCreateBy());
entity.setUpdateBy(domain.getUpdateBy());
entity.setCreatedAt(domain.getCreatedAt());
entity.setUpdatedAt(domain.getUpdatedAt());
entity.setDeletedAt(domain.getDeletedAt());
return entity;
}
List<SysMenuEntity> toEntityList(List<SysMenu> domains);
public List<SysMenu> toDomainList(List<SysMenuEntity> entities) {
return entities.stream().map(this::toDomain).toList();
}
public List<SysMenuEntity> toEntityList(List<SysMenu> domains) {
return domains.stream().map(this::toEntity).toList();
}
}
@@ -3,15 +3,12 @@ package cn.novalon.manage.sys.infrastructure.db.mapper;
import cn.novalon.manage.sys.core.domain.SysNotice;
import cn.novalon.manage.sys.infrastructure.db.entity.SysNoticeEntity;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
@Mapper(componentModel = "spring")
public interface SysNoticeMapper {
SysNoticeMapper INSTANCE = Mappers.getMapper(SysNoticeMapper.class);
SysNotice toDomain(SysNoticeEntity entity);
SysNoticeEntity toEntity(SysNotice domain);
@@ -3,15 +3,12 @@ package cn.novalon.manage.sys.infrastructure.db.mapper;
import cn.novalon.manage.sys.core.domain.SysRole;
import cn.novalon.manage.sys.infrastructure.db.entity.SysRoleEntity;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
@Mapper(componentModel = "spring")
public interface SysRoleMapper {
SysRoleMapper INSTANCE = Mappers.getMapper(SysRoleMapper.class);
SysRole toDomain(SysRoleEntity entity);
SysRoleEntity toEntity(SysRole domain);
@@ -3,15 +3,12 @@ package cn.novalon.manage.sys.infrastructure.db.mapper;
import cn.novalon.manage.sys.core.domain.SysUser;
import cn.novalon.manage.sys.infrastructure.db.entity.SysUserEntity;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
@Mapper(componentModel = "spring")
public interface SysUserMapper {
SysUserMapper INSTANCE = Mappers.getMapper(SysUserMapper.class);
SysUser toDomain(SysUserEntity entity);
SysUserEntity toEntity(SysUser domain);
@@ -3,15 +3,12 @@ package cn.novalon.manage.sys.infrastructure.db.mapper;
import cn.novalon.manage.sys.core.domain.SysUserMessage;
import cn.novalon.manage.sys.infrastructure.db.entity.SysUserMessageEntity;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
@Mapper(componentModel = "spring")
public interface SysUserMessageMapper {
SysUserMessageMapper INSTANCE = Mappers.getMapper(SysUserMessageMapper.class);
SysUserMessage toDomain(SysUserMessageEntity entity);
SysUserMessageEntity toEntity(SysUserMessage domain);
@@ -3,7 +3,6 @@ package cn.novalon.manage.sys.infrastructure.db.repository;
import cn.novalon.manage.sys.core.domain.SysConfig;
import cn.novalon.manage.sys.infrastructure.db.mapper.SysConfigMapper;
import cn.novalon.manage.sys.infrastructure.db.dao.SysConfigDao;
import cn.novalon.manage.sys.infrastructure.db.entity.SysConfigEntity;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Repository;
import reactor.core.publisher.Flux;
@@ -3,7 +3,6 @@ package cn.novalon.manage.sys.infrastructure.db.repository;
import cn.novalon.manage.sys.core.domain.SysDictData;
import cn.novalon.manage.sys.infrastructure.db.converter.SysDictDataConverter;
import cn.novalon.manage.sys.infrastructure.db.dao.SysDictDataDao;
import cn.novalon.manage.sys.infrastructure.db.entity.SysDictDataEntity;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Repository;
import reactor.core.publisher.Flux;
@@ -3,7 +3,6 @@ package cn.novalon.manage.sys.infrastructure.db.repository;
import cn.novalon.manage.sys.core.domain.SysDictType;
import cn.novalon.manage.sys.infrastructure.db.converter.SysDictTypeConverter;
import cn.novalon.manage.sys.infrastructure.db.dao.SysDictTypeDao;
import cn.novalon.manage.sys.infrastructure.db.entity.SysDictTypeEntity;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Repository;
import reactor.core.publisher.Flux;
@@ -3,7 +3,6 @@ package cn.novalon.manage.sys.infrastructure.db.repository;
import cn.novalon.manage.sys.core.domain.SysExceptionLog;
import cn.novalon.manage.sys.infrastructure.db.converter.SysExceptionLogConverter;
import cn.novalon.manage.sys.infrastructure.db.dao.SysExceptionLogDao;
import cn.novalon.manage.sys.infrastructure.db.entity.SysExceptionLogEntity;
import org.springframework.stereotype.Repository;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -3,7 +3,6 @@ package cn.novalon.manage.sys.infrastructure.db.repository;
import cn.novalon.manage.sys.core.domain.SysFile;
import cn.novalon.manage.sys.infrastructure.db.mapper.SysFileMapper;
import cn.novalon.manage.sys.infrastructure.db.dao.SysFileDao;
import cn.novalon.manage.sys.infrastructure.db.entity.SysFileEntity;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Repository;
import reactor.core.publisher.Flux;
@@ -3,7 +3,6 @@ package cn.novalon.manage.sys.infrastructure.db.repository;
import cn.novalon.manage.sys.core.domain.SysLoginLog;
import cn.novalon.manage.sys.infrastructure.db.converter.SysLoginLogConverter;
import cn.novalon.manage.sys.infrastructure.db.dao.SysLoginLogDao;
import cn.novalon.manage.sys.infrastructure.db.entity.SysLoginLogEntity;
import org.springframework.stereotype.Repository;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -3,7 +3,6 @@ package cn.novalon.manage.sys.infrastructure.db.repository;
import cn.novalon.manage.sys.core.domain.SysNotice;
import cn.novalon.manage.sys.infrastructure.db.mapper.SysNoticeMapper;
import cn.novalon.manage.sys.infrastructure.db.dao.SysNoticeDao;
import cn.novalon.manage.sys.infrastructure.db.entity.SysNoticeEntity;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Repository;
import reactor.core.publisher.Flux;
@@ -3,7 +3,6 @@ package cn.novalon.manage.sys.infrastructure.db.repository;
import cn.novalon.manage.sys.core.domain.SysUserMessage;
import cn.novalon.manage.sys.infrastructure.db.converter.SysUserMessageConverter;
import cn.novalon.manage.sys.infrastructure.db.dao.SysUserMessageDao;
import cn.novalon.manage.sys.infrastructure.db.entity.SysUserMessageEntity;
import org.springframework.stereotype.Repository;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -27,7 +27,6 @@ public class JwtAuthenticationFilter implements WebFilter {
String token = extractToken(exchange.getRequest());
if (token != null && jwtTokenProvider.validateToken(token)) {
String username = jwtTokenProvider.getUsernameFromToken(token);
Long userId = jwtTokenProvider.getUserIdFromToken(token);
UsernamePasswordAuthenticationToken authentication =
@@ -1,9 +1,9 @@
package cn.novalon.manage.sys.security;
import cn.novalon.manage.sys.config.JwtProperties;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
@@ -15,14 +15,14 @@ import java.util.Map;
@Component
public class JwtTokenProvider {
@Value("${jwt.secret}")
private String jwtSecret;
private final JwtProperties jwtProperties;
@Value("${jwt.expiration}")
private long jwtExpiration;
public JwtTokenProvider(JwtProperties jwtProperties) {
this.jwtProperties = jwtProperties;
}
private SecretKey getSigningKey() {
return Keys.hmacShaKeyFor(jwtSecret.getBytes(StandardCharsets.UTF_8));
return Keys.hmacShaKeyFor(jwtProperties.getSecret().getBytes(StandardCharsets.UTF_8));
}
public String generateToken(String username, Long userId) {
@@ -34,7 +34,7 @@ public class JwtTokenProvider {
.setClaims(claims)
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + jwtExpiration))
.setExpiration(new Date(System.currentTimeMillis() + jwtProperties.getExpiration()))
.signWith(getSigningKey())
.compact();
}
@@ -1,9 +1,9 @@
package cn.novalon.manage.sys.websocket;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.socket.WebSocketHandler;
import org.springframework.web.reactive.socket.WebSocketMessage;
import org.springframework.web.reactive.socket.WebSocketSession;
import reactor.core.publisher.Mono;
@@ -46,7 +46,8 @@ public class SysWebSocketHandler implements WebSocketHandler {
private void handleIncomingMessage(WebSocketSession session, String userId, String payload) {
try {
Map<String, Object> message = objectMapper.readValue(payload, Map.class);
Map<String, Object> message = objectMapper.readValue(payload, new TypeReference<Map<String, Object>>() {
});
String type = (String) message.get("type");
switch (type) {
@@ -4,11 +4,11 @@ server:
spring:
application:
name: novalon-manage-api
servlet:
multipart:
enabled: true
max-file-size: 10MB
max-request-size: 10MB
servlet:
multipart:
enabled: true
max-file-size: 10MB
max-request-size: 10MB
datasource:
url: jdbc:postgresql://localhost:55432/manage_system
username: postgres
@@ -33,3 +33,18 @@ jwt:
logging:
level:
cn.novalon.manage: DEBUG
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
endpoint:
health:
show-details: always
metrics:
access: read-only
prometheus:
metrics:
export:
enabled: true
@@ -0,0 +1,209 @@
-- Novalon管理系统数据库初始化脚本
-- 版本: V1
-- 描述: 创建所有核心表
-- 用户表
CREATE TABLE IF NOT EXISTS users (
id BIGSERIAL PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
email VARCHAR(100),
phone VARCHAR(20),
role_id BIGINT,
status INTEGER DEFAULT 1,
create_by VARCHAR(50),
update_by VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
-- 角色表
CREATE TABLE IF NOT EXISTS roles (
id BIGSERIAL PRIMARY KEY,
role_name VARCHAR(100) NOT NULL,
role_key VARCHAR(100) NOT NULL UNIQUE,
role_sort INTEGER DEFAULT 0,
status INTEGER DEFAULT 1,
create_by VARCHAR(50),
update_by VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
-- 菜单表
CREATE TABLE IF NOT EXISTS menus (
id BIGSERIAL PRIMARY KEY,
menu_name VARCHAR(50) NOT NULL,
parent_id BIGINT DEFAULT 0,
order_num INTEGER DEFAULT 0,
menu_type VARCHAR(1) DEFAULT 'C',
perms VARCHAR(100),
component VARCHAR(200),
status INTEGER DEFAULT 1,
create_by VARCHAR(50),
update_by VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
-- 字典类型表
CREATE TABLE IF NOT EXISTS sys_dict_type (
id BIGSERIAL PRIMARY KEY,
dict_name VARCHAR(100) NOT NULL,
dict_type VARCHAR(100) NOT NULL UNIQUE,
status VARCHAR(1) DEFAULT '0',
remark VARCHAR(500),
create_by VARCHAR(50),
update_by VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
-- 字典数据表
CREATE TABLE IF NOT EXISTS sys_dict_data (
id BIGSERIAL PRIMARY KEY,
dict_sort INTEGER DEFAULT 0,
dict_label VARCHAR(100) NOT NULL,
dict_value VARCHAR(100) NOT NULL,
dict_type VARCHAR(100) NOT NULL,
css_class VARCHAR(100),
list_class VARCHAR(100),
is_default VARCHAR(1) DEFAULT 'N',
status VARCHAR(1) DEFAULT '0',
create_by VARCHAR(50),
update_by VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
-- 系统配置表
CREATE TABLE IF NOT EXISTS sys_config (
id BIGSERIAL PRIMARY KEY,
config_name VARCHAR(100) NOT NULL,
config_key VARCHAR(100) NOT NULL UNIQUE,
config_value VARCHAR(500) NOT NULL,
config_type VARCHAR(1) DEFAULT 'N',
create_by VARCHAR(50),
update_by VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
-- 登录日志表
CREATE TABLE IF NOT EXISTS sys_login_log (
id BIGSERIAL PRIMARY KEY,
username VARCHAR(50),
ip VARCHAR(50),
location VARCHAR(255),
browser VARCHAR(50),
os VARCHAR(50),
status VARCHAR(1),
message VARCHAR(255),
login_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 异常日志表
CREATE TABLE IF NOT EXISTS sys_exception_log (
id BIGSERIAL PRIMARY KEY,
username VARCHAR(50),
ip VARCHAR(50),
location VARCHAR(255),
browser VARCHAR(50),
os VARCHAR(50),
status VARCHAR(1),
message VARCHAR(255),
exception_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 系统公告表
CREATE TABLE IF NOT EXISTS sys_notice (
id BIGSERIAL PRIMARY KEY,
notice_title VARCHAR(50) NOT NULL,
notice_type VARCHAR(1) NOT NULL,
notice_content TEXT,
status VARCHAR(1) DEFAULT '0',
create_by VARCHAR(50),
update_by VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
-- 用户消息表
CREATE TABLE IF NOT EXISTS sys_user_message (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL,
notice_id BIGINT,
message_title VARCHAR(255),
message_content TEXT,
is_read VARCHAR(1) DEFAULT '0',
read_time TIMESTAMP,
create_by VARCHAR(50),
update_by VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
-- 文件管理表
CREATE TABLE IF NOT EXISTS sys_file (
id BIGSERIAL PRIMARY KEY,
file_name VARCHAR(255) NOT NULL,
file_path VARCHAR(500) NOT NULL,
file_size BIGINT,
file_type VARCHAR(100),
file_extension VARCHAR(10),
create_by VARCHAR(50),
update_by VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
-- OAuth2客户端表
CREATE TABLE IF NOT EXISTS oauth2_client (
id BIGSERIAL PRIMARY KEY,
client_id VARCHAR(100) NOT NULL UNIQUE,
client_secret VARCHAR(255) NOT NULL,
client_name VARCHAR(100),
web_server_redirect_uri VARCHAR(500),
scope VARCHAR(500),
authorized_grant_types VARCHAR(500),
access_token_validity_seconds INTEGER,
refresh_token_validity_seconds INTEGER,
auto_approve VARCHAR(1) DEFAULT 'false',
enabled VARCHAR(1) DEFAULT 'true',
create_by VARCHAR(50),
update_by VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
-- 插入初始管理员用户
INSERT INTO users (username, password, email, role_id, status, create_by, update_by)
VALUES ('admin', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iKTVKIUi', 'admin@novalon.com', 1, 1, 'system', 'system')
ON CONFLICT (username) DO NOTHING;
-- 插入初始角色
INSERT INTO roles (role_name, role_key, role_sort, status, create_by, update_by)
VALUES ('超级管理员', 'admin', 1, 1, 'system', 'system')
ON CONFLICT (role_key) DO NOTHING;
-- 插入初始字典类型
INSERT INTO sys_dict_type (dict_name, dict_type, status, remark, create_by, update_by)
VALUES ('用户状态', 'user_status', '0', '用户状态列表', 'system', 'system')
ON CONFLICT (dict_type) DO NOTHING;
-- 插入初始字典数据
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, status, create_by, update_by)
VALUES
(1, '正常', '1', 'user_status', '0', 'system', 'system'),
(2, '停用', '0', 'user_status', '0', 'system', 'system')
ON CONFLICT DO NOTHING;
@@ -0,0 +1,17 @@
package cn.novalon.manage.sys.config;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import io.r2dbc.spi.ConnectionFactory;
import org.mockito.Mockito;
@TestConfiguration
public class UnitTestConfig {
@Bean
@Primary
public ConnectionFactory testConnectionFactory() {
return Mockito.mock(ConnectionFactory.class);
}
}
@@ -1,9 +1,11 @@
package cn.novalon.manage.sys.core.service.impl;
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.sys.infrastructure.db.dao.DictionaryDao;
import cn.novalon.manage.sys.infrastructure.db.converter.DictionaryConverter;
import cn.novalon.manage.sys.infrastructure.db.entity.DictionaryEntity;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -13,9 +15,10 @@ import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.time.LocalDateTime;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class DictionaryServiceTest {
@@ -28,55 +31,212 @@ class DictionaryServiceTest {
private IDictionaryService service;
private Dictionary testDictionary;
private DictionaryEntity testEntity;
@BeforeEach
void setUp() {
service = new DictionaryService(dao, converter);
testDictionary = new Dictionary();
testDictionary.setId(1L);
testDictionary.setType("test_type");
testDictionary.setCode("test_code");
testDictionary.setName("Test Label");
testDictionary.setValue("test_value");
testDictionary.setSort(1);
testDictionary.setRemark("Test remark");
testDictionary.setCreatedAt(LocalDateTime.now());
testDictionary.setUpdatedAt(LocalDateTime.now());
testEntity = new DictionaryEntity();
testEntity.setId(1L);
testEntity.setType("test_type");
testEntity.setCode("test_code");
testEntity.setName("Test Label");
testEntity.setValue("test_value");
testEntity.setSort(1);
testEntity.setRemark("Test remark");
testEntity.setCreatedAt(LocalDateTime.now());
testEntity.setUpdatedAt(LocalDateTime.now());
}
@Test
void testFindAll() {
Dictionary dict1 = new Dictionary();
dict1.setId(1L);
dict1.setType("type1");
Dictionary dict2 = new Dictionary();
dict2.setId(2L);
dict2.setType("type2");
when(dao.findByDeletedAtIsNullOrderBySortAsc()).thenReturn(Flux.empty());
when(dao.findByDeletedAtIsNullOrderBySortAsc()).thenReturn(Flux.just(testEntity));
when(converter.toDomain(testEntity)).thenReturn(testDictionary);
StepVerifier.create(service.findAll())
.expectNext(testDictionary)
.verifyComplete();
verify(dao).findByDeletedAtIsNullOrderBySortAsc();
verify(converter).toDomain(testEntity);
}
@Test
void testFindById() {
Dictionary dict = new Dictionary();
dict.setId(1L);
dict.setType("type1");
when(dao.findById(1L)).thenReturn(Mono.empty());
when(dao.findById(1L)).thenReturn(Mono.just(testEntity));
when(converter.toDomain(testEntity)).thenReturn(testDictionary);
StepVerifier.create(service.findById(1L))
.expectNext(testDictionary)
.verifyComplete();
verify(dao).findById(1L);
verify(converter).toDomain(testEntity);
}
@Test
void testSave() {
Dictionary dict = new Dictionary();
dict.setId(1L);
dict.setType("type1");
void testFindById_NotFound() {
when(dao.findById(999L)).thenReturn(Mono.empty());
when(dao.save(any())).thenReturn(Mono.empty());
StepVerifier.create(service.save(dict))
StepVerifier.create(service.findById(999L))
.verifyComplete();
verify(dao).findById(999L);
verify(converter, never()).toDomain(any());
}
@Test
void testFindByType() {
when(dao.findByType("test_type")).thenReturn(Flux.just(testEntity));
when(converter.toDomain(testEntity)).thenReturn(testDictionary);
StepVerifier.create(service.findByType("test_type"))
.expectNext(testDictionary)
.verifyComplete();
verify(dao).findByType("test_type");
verify(converter).toDomain(testEntity);
}
@Test
void testCheckTypeAndCodeExists_True() {
when(dao.findByTypeAndCode("test_type", "test_code")).thenReturn(Mono.just(testEntity));
StepVerifier.create(service.checkTypeAndCodeExists("test_type", "test_code"))
.expectNext(true)
.verifyComplete();
verify(dao).findByTypeAndCode("test_type", "test_code");
}
@Test
void testCheckTypeAndCodeExists_False() {
when(dao.findByTypeAndCode("test_type", "test_code")).thenReturn(Mono.empty());
StepVerifier.create(service.checkTypeAndCodeExists("test_type", "test_code"))
.expectNext(false)
.verifyComplete();
verify(dao).findByTypeAndCode("test_type", "test_code");
}
@Test
void testSave_NewDictionary_Success() {
Dictionary newDict = new Dictionary();
newDict.setType("test_type");
newDict.setCode("test_code");
newDict.setName("Test Label");
newDict.setValue("test_value");
when(dao.findByTypeAndCode("test_type", "test_code")).thenReturn(Mono.empty());
when(converter.toEntity(any())).thenReturn(testEntity);
when(dao.save(any())).thenReturn(Mono.just(testEntity));
when(converter.toDomain(testEntity)).thenReturn(testDictionary);
StepVerifier.create(service.save(newDict))
.expectNextMatches(dict -> dict.getId() != null)
.verifyComplete();
verify(dao).findByTypeAndCode("test_type", "test_code");
verify(converter).toEntity(any());
verify(dao).save(any());
verify(converter).toDomain(testEntity);
}
@Test
void testSave_NewDictionary_AlreadyExists() {
Dictionary newDict = new Dictionary();
newDict.setType("test_type");
newDict.setCode("test_code");
when(dao.findByTypeAndCode("test_type", "test_code")).thenReturn(Mono.just(testEntity));
StepVerifier.create(service.save(newDict))
.expectError(DictionaryAlreadyExistsException.class)
.verify();
verify(dao).findByTypeAndCode("test_type", "test_code");
verify(converter, never()).toEntity(any());
verify(dao, never()).save(any());
}
@Test
void testSave_UpdateExistingDictionary() {
Dictionary existingDict = new Dictionary();
existingDict.setId(1L);
existingDict.setType("test_type");
existingDict.setCode("test_code");
when(converter.toEntity(existingDict)).thenReturn(testEntity);
when(dao.save(any())).thenReturn(Mono.just(testEntity));
when(converter.toDomain(testEntity)).thenReturn(testDictionary);
StepVerifier.create(service.save(existingDict))
.expectNextMatches(dict -> dict.getId() == 1L)
.verifyComplete();
verify(dao, never()).findByTypeAndCode(anyString(), anyString());
verify(converter).toEntity(existingDict);
verify(dao).save(any());
verify(converter).toDomain(testEntity);
}
@Test
void testUpdate() {
Dictionary updateDict = new Dictionary();
updateDict.setName("Updated Name");
updateDict.setValue("updated_value");
updateDict.setRemark("Updated remark");
updateDict.setSort(2);
DictionaryEntity existingEntity = new DictionaryEntity();
existingEntity.setId(1L);
existingEntity.setType("test_type");
existingEntity.setCode("test_code");
existingEntity.setName("Old Name");
existingEntity.setValue("old_value");
existingEntity.setRemark("Old remark");
existingEntity.setSort(1);
when(dao.findById(1L)).thenReturn(Mono.just(existingEntity));
when(dao.save(any())).thenReturn(Mono.just(existingEntity));
when(converter.toDomain(existingEntity)).thenReturn(testDictionary);
StepVerifier.create(service.update(1L, updateDict))
.expectNextMatches(dict -> dict.getId() == 1L)
.verifyComplete();
verify(dao).findById(1L);
verify(dao).save(any());
verify(converter).toDomain(existingEntity);
}
@Test
void testUpdate_NotFound() {
Dictionary updateDict = new Dictionary();
updateDict.setName("Updated Name");
when(dao.findById(999L)).thenReturn(Mono.empty());
StepVerifier.create(service.update(999L, updateDict))
.verifyComplete();
verify(dao).findById(999L);
verify(dao, never()).save(any());
verify(converter, never()).toDomain(any());
}
@Test
@@ -0,0 +1,160 @@
package cn.novalon.manage.sys.core.service.impl;
import cn.novalon.manage.sys.core.domain.SysConfig;
import cn.novalon.manage.sys.infrastructure.db.converter.SysConfigConverter;
import cn.novalon.manage.sys.infrastructure.db.dao.SysConfigDao;
import cn.novalon.manage.sys.infrastructure.db.entity.SysConfigEntity;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class SysConfigServiceTest {
@Mock
private SysConfigDao dao;
@Mock
private SysConfigConverter converter;
private SysConfigService configService;
private SysConfig testConfig;
private SysConfigEntity testEntity;
@BeforeEach
void setUp() {
configService = new SysConfigService(dao, converter);
testConfig = new SysConfig();
testConfig.setId(1L);
testConfig.setConfigKey("app.name");
testConfig.setConfigValue("Novalon Manage System");
testConfig.setConfigName("Application Name");
testConfig.setConfigType("system");
testEntity = new SysConfigEntity();
testEntity.setId(1L);
testEntity.setConfigKey("app.name");
testEntity.setConfigValue("Novalon Manage System");
testEntity.setConfigName("Application Name");
testEntity.setConfigType("system");
}
@Test
void testFindAll() {
when(dao.findByDeletedAtIsNull()).thenReturn(Flux.just(testEntity));
when(converter.toDomain(testEntity)).thenReturn(testConfig);
StepVerifier.create(configService.findAll())
.expectNext(testConfig)
.verifyComplete();
verify(dao).findByDeletedAtIsNull();
verify(converter).toDomain(testEntity);
}
@Test
void testFindById() {
when(dao.findById(1L)).thenReturn(Mono.just(testEntity));
when(converter.toDomain(testEntity)).thenReturn(testConfig);
StepVerifier.create(configService.findById(1L))
.expectNext(testConfig)
.verifyComplete();
verify(dao).findById(1L);
verify(converter).toDomain(testEntity);
}
@Test
void testFindById_NotFound() {
when(dao.findById(999L)).thenReturn(Mono.empty());
StepVerifier.create(configService.findById(999L))
.verifyComplete();
verify(dao).findById(999L);
verify(converter, never()).toDomain(any());
}
@Test
void testFindByConfigKey() {
when(dao.findByConfigKeyAndDeletedAtIsNull("app.name")).thenReturn(Mono.just(testEntity));
when(converter.toDomain(testEntity)).thenReturn(testConfig);
StepVerifier.create(configService.findByConfigKey("app.name"))
.expectNext(testConfig)
.verifyComplete();
verify(dao).findByConfigKeyAndDeletedAtIsNull("app.name");
verify(converter).toDomain(testEntity);
}
@Test
void testFindByConfigKey_NotFound() {
when(dao.findByConfigKeyAndDeletedAtIsNull("nonexistent")).thenReturn(Mono.empty());
StepVerifier.create(configService.findByConfigKey("nonexistent"))
.verifyComplete();
verify(dao).findByConfigKeyAndDeletedAtIsNull("nonexistent");
verify(converter, never()).toDomain(any());
}
@Test
void testSave() {
when(converter.toEntity(testConfig)).thenReturn(testEntity);
when(dao.save(testEntity)).thenReturn(Mono.just(testEntity));
when(converter.toDomain(testEntity)).thenReturn(testConfig);
StepVerifier.create(configService.save(testConfig))
.expectNext(testConfig)
.verifyComplete();
verify(converter).toEntity(testConfig);
verify(dao).save(testEntity);
verify(converter).toDomain(testEntity);
}
@Test
void testDeleteById() {
when(dao.deleteByIdAndDeletedAtIsNull(1L)).thenReturn(Mono.empty());
StepVerifier.create(configService.deleteById(1L))
.verifyComplete();
verify(dao).deleteByIdAndDeletedAtIsNull(1L);
}
@Test
void testGetConfigValue() {
when(dao.findByConfigKeyAndDeletedAtIsNull("app.name")).thenReturn(Mono.just(testEntity));
when(converter.toDomain(testEntity)).thenReturn(testConfig);
StepVerifier.create(configService.getConfigValue("app.name"))
.expectNext("Novalon Manage System")
.verifyComplete();
verify(dao).findByConfigKeyAndDeletedAtIsNull("app.name");
verify(converter).toDomain(testEntity);
}
@Test
void testGetConfigValue_NotFound() {
when(dao.findByConfigKeyAndDeletedAtIsNull("nonexistent")).thenReturn(Mono.empty());
StepVerifier.create(configService.getConfigValue("nonexistent"))
.verifyComplete();
verify(dao).findByConfigKeyAndDeletedAtIsNull("nonexistent");
}
}
@@ -0,0 +1,206 @@
package cn.novalon.manage.sys.core.service.impl;
import cn.novalon.manage.sys.core.constants.StatusConstants;
import cn.novalon.manage.sys.core.domain.SysRole;
import cn.novalon.manage.sys.core.repository.ISysRoleRepository;
import cn.novalon.manage.sys.dto.request.PageRequest;
import cn.novalon.manage.sys.dto.response.PageResponse;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.data.relational.core.query.Query;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.time.LocalDateTime;
import java.util.List;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class SysRoleServiceTest {
@Mock
private ISysRoleRepository roleRepository;
private SysRoleService roleService;
private SysRole testRole;
@BeforeEach
void setUp() {
roleService = new SysRoleService(roleRepository);
testRole = new SysRole();
testRole.setId(1L);
testRole.setRoleName("admin");
testRole.setRoleKey("admin");
testRole.setStatus(StatusConstants.ENABLED);
testRole.setCreatedAt(LocalDateTime.now());
testRole.setUpdatedAt(LocalDateTime.now());
}
@Test
void testFindById() {
when(roleRepository.findById(1L)).thenReturn(Mono.just(testRole));
StepVerifier.create(roleService.findById(1L))
.expectNext(testRole)
.verifyComplete();
verify(roleRepository).findById(1L);
}
@Test
void testFindAll() {
when(roleRepository.findAll()).thenReturn(Flux.just(testRole));
StepVerifier.create(roleService.findAll())
.expectNext(testRole)
.verifyComplete();
verify(roleRepository).findAll();
}
@Test
void testFindRolesByPage() {
PageRequest pageRequest = new PageRequest();
pageRequest.setPage(0);
pageRequest.setSize(10);
pageRequest.setKeyword("admin");
PageResponse<SysRole> pageResponse = new PageResponse<>();
pageResponse.setContent(List.of(testRole));
pageResponse.setTotalElements(1L);
when(roleRepository.findByQueryWithPagination(any(Query.class), eq(pageRequest)))
.thenReturn(Mono.just(pageResponse));
StepVerifier.create(roleService.findRolesByPage(pageRequest))
.expectNextMatches(response -> response.getTotalElements() == 1L)
.verifyComplete();
verify(roleRepository).findByQueryWithPagination(any(Query.class), eq(pageRequest));
}
@Test
void testCount() {
when(roleRepository.count()).thenReturn(Mono.just(5L));
StepVerifier.create(roleService.count())
.expectNext(5L)
.verifyComplete();
verify(roleRepository).count();
}
@Test
void testCreateRole() {
SysRole newRole = new SysRole();
newRole.setRoleName("user");
newRole.setRoleKey("user");
when(roleRepository.save(any(SysRole.class))).thenReturn(Mono.just(testRole));
StepVerifier.create(roleService.createRole(newRole))
.expectNextMatches(role ->
role.getStatus().equals(StatusConstants.ENABLED) &&
role.getCreatedAt() != null)
.verifyComplete();
verify(roleRepository).save(any(SysRole.class));
}
@Test
void testUpdateRole() {
SysRole updateRole = new SysRole();
updateRole.setId(1L);
updateRole.setRoleName("updated_admin");
when(roleRepository.save(any(SysRole.class))).thenReturn(Mono.just(testRole));
StepVerifier.create(roleService.updateRole(updateRole))
.expectNextMatches(role -> role.getUpdatedAt() != null)
.verifyComplete();
verify(roleRepository).save(any(SysRole.class));
}
@Test
void testDeleteRole() {
when(roleRepository.deleteById(1L)).thenReturn(Mono.empty());
StepVerifier.create(roleService.deleteRole(1L))
.verifyComplete();
verify(roleRepository).deleteById(1L);
}
@Test
void testFindByRoleName() {
when(roleRepository.findByRoleName("admin")).thenReturn(Mono.just(testRole));
StepVerifier.create(roleService.findByRoleName("admin"))
.expectNext(testRole)
.verifyComplete();
verify(roleRepository).findByRoleName("admin");
}
@Test
void testExistsByRoleName_True() {
when(roleRepository.existsByRoleName("admin")).thenReturn(Mono.just(true));
StepVerifier.create(roleService.existsByRoleName("admin"))
.expectNext(true)
.verifyComplete();
verify(roleRepository).existsByRoleName("admin");
}
@Test
void testExistsByRoleName_False() {
when(roleRepository.existsByRoleName("nonexistent")).thenReturn(Mono.just(false));
StepVerifier.create(roleService.existsByRoleName("nonexistent"))
.expectNext(false)
.verifyComplete();
verify(roleRepository).existsByRoleName("nonexistent");
}
@Test
void testLogicalDeleteRole() {
when(roleRepository.findByIdIncludingDeleted(1L)).thenReturn(Mono.just(testRole));
when(roleRepository.updateRole(any(SysRole.class))).thenReturn(Mono.just(testRole));
StepVerifier.create(roleService.logicalDeleteRole(1L))
.expectNextMatches(role -> role.getDeletedAt() != null)
.verifyComplete();
verify(roleRepository).findByIdIncludingDeleted(1L);
verify(roleRepository).updateRole(any(SysRole.class));
}
@Test
void testRestoreRole() {
SysRole deletedRole = new SysRole();
deletedRole.setId(1L);
deletedRole.setDeletedAt(LocalDateTime.now());
when(roleRepository.findByIdIncludingDeleted(1L)).thenReturn(Mono.just(deletedRole));
when(roleRepository.updateRole(any(SysRole.class))).thenReturn(Mono.just(testRole));
StepVerifier.create(roleService.restoreRole(1L))
.expectNextMatches(role -> role.getDeletedAt() == null)
.verifyComplete();
verify(roleRepository).findByIdIncludingDeleted(1L);
verify(roleRepository).updateRole(any(SysRole.class));
}
}
@@ -0,0 +1,334 @@
package cn.novalon.manage.sys.core.service.impl;
import cn.novalon.manage.sys.core.constants.StatusConstants;
import cn.novalon.manage.sys.core.domain.SysUser;
import cn.novalon.manage.sys.core.repository.ISysUserRepository;
import cn.novalon.manage.sys.dto.request.PageRequest;
import cn.novalon.manage.sys.dto.response.PageResponse;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.data.relational.core.query.Query;
import org.springframework.security.crypto.password.PasswordEncoder;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.time.LocalDateTime;
import java.util.List;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class SysUserServiceTest {
@Mock
private ISysUserRepository userRepository;
@Mock
private PasswordEncoder passwordEncoder;
private SysUserService userService;
private SysUser testUser;
@BeforeEach
void setUp() {
userService = new SysUserService(userRepository, passwordEncoder);
testUser = new SysUser();
testUser.setId(1L);
testUser.setUsername("testuser");
testUser.setPassword("encoded_password");
testUser.setEmail("test@example.com");
testUser.setRoleId(1L);
testUser.setStatus(StatusConstants.ENABLED);
testUser.setCreatedAt(LocalDateTime.now());
testUser.setUpdatedAt(LocalDateTime.now());
}
@Test
void testFindById() {
when(userRepository.findById(1L)).thenReturn(Mono.just(testUser));
StepVerifier.create(userService.findById(1L))
.expectNext(testUser)
.verifyComplete();
verify(userRepository).findById(1L);
}
@Test
void testFindAll() {
when(userRepository.findAll()).thenReturn(Flux.just(testUser));
StepVerifier.create(userService.findAll())
.expectNext(testUser)
.verifyComplete();
verify(userRepository).findAll();
}
@Test
void testFindAll_IncludeDeleted() {
when(userRepository.findAll()).thenReturn(Flux.just(testUser));
StepVerifier.create(userService.findAll(true))
.expectNext(testUser)
.verifyComplete();
verify(userRepository).findAll();
}
@Test
void testFindAll_ExcludeDeleted() {
when(userRepository.findByDeletedAtIsNull()).thenReturn(Flux.just(testUser));
StepVerifier.create(userService.findAll(false))
.expectNext(testUser)
.verifyComplete();
verify(userRepository).findByDeletedAtIsNull();
}
@Test
void testFindUsersByPage() {
PageRequest pageRequest = new PageRequest();
pageRequest.setPage(0);
pageRequest.setSize(10);
pageRequest.setKeyword("test");
PageResponse<SysUser> pageResponse = new PageResponse<>();
pageResponse.setContent(List.of(testUser));
pageResponse.setTotalElements(1L);
when(userRepository.findByQueryWithPagination(any(Query.class), eq(pageRequest)))
.thenReturn(Mono.just(pageResponse));
StepVerifier.create(userService.findUsersByPage(pageRequest))
.expectNextMatches(response -> response.getTotalElements() == 1L)
.verifyComplete();
verify(userRepository).findByQueryWithPagination(any(Query.class), eq(pageRequest));
}
@Test
void testFindUsersByPage_NoKeyword() {
PageRequest pageRequest = new PageRequest();
pageRequest.setPage(0);
pageRequest.setSize(10);
PageResponse<SysUser> pageResponse = new PageResponse<>();
pageResponse.setContent(List.of(testUser));
pageResponse.setTotalElements(1L);
when(userRepository.findByQueryWithPagination(any(Query.class), eq(pageRequest)))
.thenReturn(Mono.just(pageResponse));
StepVerifier.create(userService.findUsersByPage(pageRequest))
.expectNextMatches(response -> response.getTotalElements() == 1L)
.verifyComplete();
verify(userRepository).findByQueryWithPagination(any(Query.class), eq(pageRequest));
}
@Test
void testCount() {
when(userRepository.count()).thenReturn(Mono.just(10L));
StepVerifier.create(userService.count())
.expectNext(10L)
.verifyComplete();
verify(userRepository).count();
}
@Test
void testFindByUsername() {
when(userRepository.findByUsername("testuser")).thenReturn(Mono.just(testUser));
StepVerifier.create(userService.findByUsername("testuser"))
.expectNext(testUser)
.verifyComplete();
verify(userRepository).findByUsername("testuser");
}
@Test
void testCreateUser() {
SysUser newUser = new SysUser();
newUser.setUsername("newuser");
newUser.setPassword("raw_password");
newUser.setEmail("new@example.com");
when(passwordEncoder.encode("raw_password")).thenReturn("encoded_password");
when(userRepository.save(any(SysUser.class))).thenReturn(Mono.just(testUser));
StepVerifier.create(userService.createUser(newUser))
.expectNextMatches(user ->
user.getPassword().equals("encoded_password") &&
user.getStatus().equals(StatusConstants.ENABLED) &&
user.getCreatedAt() != null)
.verifyComplete();
ArgumentCaptor<SysUser> userCaptor = ArgumentCaptor.forClass(SysUser.class);
verify(userRepository).save(userCaptor.capture());
verify(passwordEncoder).encode("raw_password");
}
@Test
void testUpdateUser() {
SysUser updateUser = new SysUser();
updateUser.setId(1L);
updateUser.setUsername("updated_user");
when(userRepository.save(any(SysUser.class))).thenReturn(Mono.just(testUser));
StepVerifier.create(userService.updateUser(updateUser))
.expectNextMatches(user -> user.getUpdatedAt() != null)
.verifyComplete();
ArgumentCaptor<SysUser> userCaptor = ArgumentCaptor.forClass(SysUser.class);
verify(userRepository).save(userCaptor.capture());
}
@Test
void testDeleteUser() {
when(userRepository.deleteById(1L)).thenReturn(Mono.empty());
StepVerifier.create(userService.deleteUser(1L))
.verifyComplete();
verify(userRepository).deleteById(1L);
}
@Test
void testChangePassword_Success() {
when(userRepository.findById(1L)).thenReturn(Mono.just(testUser));
when(passwordEncoder.matches("old_password", "encoded_password")).thenReturn(true);
when(passwordEncoder.encode("new_password")).thenReturn("new_encoded_password");
when(userRepository.save(any(SysUser.class))).thenReturn(Mono.just(testUser));
StepVerifier.create(userService.changePassword(1L, "old_password", "new_password"))
.expectNextMatches(user -> user.getPassword().equals("new_encoded_password"))
.verifyComplete();
verify(passwordEncoder).matches("old_password", "encoded_password");
verify(passwordEncoder).encode("new_password");
verify(userRepository).save(any(SysUser.class));
}
@Test
void testChangePassword_WrongOldPassword() {
when(userRepository.findById(1L)).thenReturn(Mono.just(testUser));
when(passwordEncoder.matches("wrong_password", "encoded_password")).thenReturn(false);
StepVerifier.create(userService.changePassword(1L, "wrong_password", "new_password"))
.expectError(RuntimeException.class)
.verify();
verify(passwordEncoder).matches("wrong_password", "encoded_password");
verify(passwordEncoder, never()).encode(anyString());
verify(userRepository, never()).save(any(SysUser.class));
}
@Test
void testExistsByUsername_True() {
when(userRepository.findByUsername("testuser")).thenReturn(Mono.just(testUser));
StepVerifier.create(userService.existsByUsername("testuser"))
.expectNext(true)
.verifyComplete();
verify(userRepository).findByUsername("testuser");
}
@Test
void testExistsByUsername_False() {
when(userRepository.findByUsername("nonexistent")).thenReturn(Mono.empty());
StepVerifier.create(userService.existsByUsername("nonexistent"))
.expectNext(false)
.verifyComplete();
verify(userRepository).findByUsername("nonexistent");
}
@Test
void testExistsByEmail_True() {
when(userRepository.findByEmail("test@example.com")).thenReturn(Mono.just(testUser));
StepVerifier.create(userService.existsByEmail("test@example.com"))
.expectNext(true)
.verifyComplete();
verify(userRepository).findByEmail("test@example.com");
}
@Test
void testExistsByEmail_False() {
when(userRepository.findByEmail("nonexistent@example.com")).thenReturn(Mono.empty());
StepVerifier.create(userService.existsByEmail("nonexistent@example.com"))
.expectNext(false)
.verifyComplete();
verify(userRepository).findByEmail("nonexistent@example.com");
}
@Test
void testLogicalDeleteUser() {
when(userRepository.findByIdIncludingDeleted(1L)).thenReturn(Mono.just(testUser));
when(userRepository.save(any(SysUser.class))).thenReturn(Mono.just(testUser));
StepVerifier.create(userService.logicalDeleteUser(1L))
.verifyComplete();
ArgumentCaptor<SysUser> userCaptor = ArgumentCaptor.forClass(SysUser.class);
verify(userRepository).save(userCaptor.capture());
assert userCaptor.getValue().getDeletedAt() != null : "DeletedAt should be set";
}
@Test
void testLogicalDeleteUsers() {
List<Long> ids = List.of(1L, 2L, 3L);
when(userRepository.logicalDeleteByIds(ids)).thenReturn(Mono.empty());
StepVerifier.create(userService.logicalDeleteUsers(ids))
.verifyComplete();
verify(userRepository).logicalDeleteByIds(ids);
}
@Test
void testRestoreUser() {
SysUser deletedUser = new SysUser();
deletedUser.setId(1L);
deletedUser.setDeletedAt(LocalDateTime.now());
when(userRepository.findByIdIncludingDeleted(1L)).thenReturn(Mono.just(deletedUser));
when(userRepository.save(any(SysUser.class))).thenReturn(Mono.just(testUser));
StepVerifier.create(userService.restoreUser(1L))
.verifyComplete();
ArgumentCaptor<SysUser> userCaptor = ArgumentCaptor.forClass(SysUser.class);
verify(userRepository).save(userCaptor.capture());
}
@Test
void testRestoreUsers() {
List<Long> ids = List.of(1L, 2L, 3L);
when(userRepository.restoreByIds(ids)).thenReturn(Mono.empty());
StepVerifier.create(userService.restoreUsers(ids))
.verifyComplete();
verify(userRepository).restoreByIds(ids);
}
}
@@ -77,8 +77,10 @@ class DictionaryHandlerTest {
when(service.save(any())).thenReturn(Mono.just(dict));
Mono<ServerResponse> responseMono = handler.createDictionary(MockServerRequest.builder()
.build());
MockServerRequest request = MockServerRequest.builder()
.body(Mono.just(dict));
Mono<ServerResponse> responseMono = handler.createDictionary(request);
StepVerifier.create(responseMono)
.expectNextMatches(response -> response.statusCode().equals(HttpStatus.CREATED))
@@ -4,7 +4,6 @@ import cn.novalon.manage.sys.core.domain.Dictionary;
import cn.novalon.manage.sys.infrastructure.db.entity.DictionaryEntity;
import org.junit.jupiter.api.Test;
import org.mapstruct.factory.Mappers;
import java.util.Arrays;
import java.util.List;
@@ -12,7 +11,7 @@ import static org.junit.jupiter.api.Assertions.*;
class DictionaryMapperTest {
private final DictionaryMapper mapper = DictionaryMapper.INSTANCE;
private final DictionaryMapper mapper = Mappers.getMapper(DictionaryMapper.class);
@Test
void testToDomain() {
@@ -13,7 +13,6 @@ import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -0,0 +1,75 @@
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
stages: [
{ duration: '30s', target: 10 },
{ duration: '1m', target: 50 },
{ duration: '30s', target: 0 },
],
thresholds: {
http_req_duration: ['p(95)<500'],
http_req_failed: ['rate<0.01'],
},
};
const BASE_URL = __ENV.BASE_URL || 'http://localhost:8080';
export default function () {
const responses = http.batch([
['GET', `${BASE_URL}/api/users/page?page=0&size=10`, null, { tags: { name: 'UsersList' } }],
['GET', `${BASE_URL}/api/roles/page?page=0&size=10`, null, { tags: { name: 'RolesList' } }],
]);
check(responses[0], {
'users status is 200': (r) => r.status === 200,
'users response time < 500ms': (r) => r.timings.duration < 500,
});
check(responses[1], {
'roles status is 200': (r) => r.status === 200,
'roles response time < 500ms': (r) => r.timings.duration < 500,
});
const singleUserRes = http.get(`${BASE_URL}/api/users/1`);
check(singleUserRes, {
'single user status is 200 or 404': (r) => r.status === 200 || r.status === 404,
'single user response time < 300ms': (r) => r.timings.duration < 300,
});
const healthRes = http.get(`${BASE_URL}/actuator/health`);
check(healthRes, {
'health check status is 200': (r) => r.status === 200,
'health check response time < 100ms': (r) => r.timings.duration < 100,
});
sleep(1);
}
export function handleSummary(data) {
return {
'stdout': textSummary(data, { indent: ' ', enableColors: true }),
'performance-report.json': JSON.stringify(data, null, 2),
};
}
function textSummary(data, options) {
const indent = options?.indent || '';
const colors = options?.enableColors || false;
let summary = `\n${indent}📊 Performance Test Summary\n`;
summary += `${indent}============================\n\n`;
summary += `${indent}⏱️ HTTP Metrics:\n`;
summary += `${indent} - Total Requests: ${data.metrics.http_reqs?.values?.count || 0}\n`;
summary += `${indent} - Request Duration (p95): ${data.metrics.http_req_duration?.values?.['p(95)']?.toFixed(2) || 0}ms\n`;
summary += `${indent} - Request Failed Rate: ${(data.metrics.http_req_failed?.values?.rate * 100)?.toFixed(2) || 0}%\n`;
summary += `\n${indent}📈 Iterations:\n`;
summary += `${indent} - Total: ${data.metrics.iterations?.values?.count || 0}\n`;
summary += `${indent} - Rate: ${data.metrics.iterations?.values?.rate?.toFixed(2) || 0}/s\n`;
summary += `\n${indent}⏰ Test Duration: ${data.state?.testRunDurationMs ? (data.state.testRunDurationMs / 1000).toFixed(2) : 0}s\n`;
return summary;
}