feat(admin): 添加用户管理相关文件

添加用户管理视图、API和状态管理文件
This commit is contained in:
张翔
2026-03-28 14:37:29 +08:00
commit 08ea5fbe98
1643 changed files with 255646 additions and 0 deletions
@@ -0,0 +1,96 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.destiny</groupId>
<artifactId>everything-is-suitable-api</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>everything-is-suitable-sys</artifactId>
<packaging>jar</packaging>
<name>Everything Is Suitable Sys</name>
<description>System entities and utilities for Everything Is Suitable API</description>
<dependencies>
<dependency>
<groupId>io.destiny</groupId>
<artifactId>everything-is-suitable-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-crypto</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webflux-ui</artifactId>
</dependency>
<dependency>
<groupId>org.javers</groupId>
<artifactId>javers-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,25 @@
package io.destiny.sys.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OperationLog {
String moduleName() default "";
String operationDesc() default "";
boolean recordRequest() default true;
boolean recordResponse() default true;
boolean maskSensitive() default true;
boolean recordDiff() default false;
String diffParamIndex() default "";
}
@@ -0,0 +1,141 @@
package io.destiny.sys.aspect;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.destiny.sys.core.domain.OperationLog;
import io.destiny.sys.core.service.IDataMaskingService;
import io.destiny.sys.core.service.IJaversAuditService;
import io.destiny.sys.core.service.IOperationLogService;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import java.time.LocalDateTime;
@Aspect
@Component
public class OperationLogAspect {
private static final Logger log = LoggerFactory.getLogger(OperationLogAspect.class);
private final IOperationLogService logService;
private final IDataMaskingService maskingService;
private final IJaversAuditService javersAuditService;
private final ObjectMapper objectMapper;
public OperationLogAspect(IOperationLogService logService, IDataMaskingService maskingService,
IJaversAuditService javersAuditService, ObjectMapper objectMapper) {
this.logService = logService;
this.maskingService = maskingService;
this.javersAuditService = javersAuditService;
this.objectMapper = objectMapper;
}
@Around("@annotation(operationLog)")
public Object around(ProceedingJoinPoint joinPoint, io.destiny.sys.annotation.OperationLog operationLog)
throws Throwable {
long startTime = System.currentTimeMillis();
OperationLog operationLogEntity = new OperationLogEntity();
operationLogEntity.setOperationTime(LocalDateTime.now());
operationLogEntity.setModuleName(operationLog.moduleName());
operationLogEntity.setOperationDesc(operationLog.operationDesc());
Object[] args = joinPoint.getArgs();
for (Object arg : args) {
if (arg instanceof ServerWebExchange) {
ServerWebExchange exchange = (ServerWebExchange) arg;
ServerHttpRequest request = exchange.getRequest();
operationLogEntity.setRequestPath(request.getPath().value());
operationLogEntity.setRequestMethod(request.getMethod().name());
operationLogEntity.setIpAddress(
request.getRemoteAddress() != null ? request.getRemoteAddress().getAddress().getHostAddress()
: "unknown");
}
}
if (operationLog.recordRequest()) {
String requestParams = toJson(args);
if (operationLog.maskSensitive()) {
requestParams = maskingService.maskSensitiveData(requestParams).block();
}
operationLogEntity.setRequestParams(requestParams);
}
Object oldObject = null;
Object newObject = null;
if (operationLog.recordDiff() && !operationLog.diffParamIndex().isEmpty()) {
try {
String[] indices = operationLog.diffParamIndex().split(",");
if (indices.length == 2) {
int oldIndex = Integer.parseInt(indices[0].trim());
int newIndex = Integer.parseInt(indices[1].trim());
if (oldIndex < args.length && newIndex < args.length) {
oldObject = args[oldIndex];
newObject = args[newIndex];
}
}
} catch (Exception e) {
log.warn("解析diffParamIndex失败: {}", operationLog.diffParamIndex(), e);
}
}
try {
Object result = joinPoint.proceed();
long endTime = System.currentTimeMillis();
operationLogEntity.setStatus("0");
operationLogEntity.setExecutionTime(endTime - startTime);
if (operationLog.recordResponse()) {
String responseResult = toJson(result);
if (operationLog.maskSensitive()) {
responseResult = maskingService.maskSensitiveData(responseResult).block();
}
operationLogEntity.setResponseResult(responseResult);
}
if (operationLog.recordDiff() && oldObject != null && newObject != null) {
javersAuditService.compare(oldObject, newObject)
.doOnNext(diffJson -> operationLogEntity.setDiffJson(diffJson))
.doOnError(error -> log.warn("生成对象差异失败", error))
.subscribe();
}
return result;
} catch (Exception e) {
long endTime = System.currentTimeMillis();
operationLogEntity.setStatus("1");
operationLogEntity.setExecutionTime(endTime - startTime);
operationLogEntity.setExceptionMessage(ExceptionUtils.getStackTrace(e));
throw e;
} finally {
logService.save(operationLogEntity).subscribe(
saved -> log.debug("操作日志保存成功"),
error -> log.warn("操作日志保存失败,但不影响主业务流程", error));
}
}
private String toJson(Object obj) {
try {
return objectMapper.writeValueAsString(obj);
} catch (JsonProcessingException e) {
log.error("Failed to serialize object to JSON", e);
return null;
}
}
private static class OperationLogEntity extends OperationLog {
}
}
@@ -0,0 +1,60 @@
package io.destiny.sys.config;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.List;
@Configuration
public class SysCorsConfig {
private List<String> allowedOrigins = new ArrayList<>();
private List<String> allowedMethods = new ArrayList<>();
private List<String> allowedHeaders = new ArrayList<>();
private boolean allowCredentials = true;
private int maxAge = 3600;
public List<String> getAllowedOrigins() {
return allowedOrigins;
}
public void setAllowedOrigins(List<String> allowedOrigins) {
this.allowedOrigins = allowedOrigins;
}
public List<String> getAllowedMethods() {
return allowedMethods;
}
public void setAllowedMethods(List<String> allowedMethods) {
this.allowedMethods = allowedMethods;
}
public List<String> getAllowedHeaders() {
return allowedHeaders;
}
public void setAllowedHeaders(List<String> allowedHeaders) {
this.allowedHeaders = allowedHeaders;
}
public boolean isAllowCredentials() {
return allowCredentials;
}
public void setAllowCredentials(boolean allowCredentials) {
this.allowCredentials = allowCredentials;
}
public int getMaxAge() {
return maxAge;
}
public void setMaxAge(int maxAge) {
this.maxAge = maxAge;
}
}
@@ -0,0 +1,292 @@
package io.destiny.sys.config;
import io.destiny.sys.handler.SysAuthHandler;
import io.destiny.sys.handler.SysMenuHandler;
import io.destiny.sys.handler.SysRoleHandler;
import io.destiny.sys.handler.SysUserHandler;
import io.destiny.sys.handler.OperationLogHandler;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springdoc.core.annotations.RouterOperation;
import org.springdoc.core.annotations.RouterOperations;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;
import static org.springframework.web.reactive.function.server.RequestPredicates.*;
@Configuration
@Tag(name = "系统管理", description = "系统管理相关接口")
public class SysRouter {
private final SysUserHandler userHandler;
private final SysRoleHandler roleHandler;
private final SysMenuHandler menuHandler;
private final SysAuthHandler authHandler;
private final OperationLogHandler operationLogHandler;
public SysRouter(SysUserHandler userHandler, SysRoleHandler roleHandler, SysMenuHandler menuHandler, SysAuthHandler authHandler, OperationLogHandler operationLogHandler) {
this.userHandler = userHandler;
this.roleHandler = roleHandler;
this.menuHandler = menuHandler;
this.authHandler = authHandler;
this.operationLogHandler = operationLogHandler;
}
@Bean
@RouterOperations({
@RouterOperation(
path = "/sys/auth/register",
method = RequestMethod.POST,
beanClass = SysAuthHandler.class,
beanMethod = "register"
),
@RouterOperation(
path = "/sys/auth/login",
method = RequestMethod.POST,
beanClass = SysAuthHandler.class,
beanMethod = "login"
),
@RouterOperation(
path = "/sys/auth/refresh/{token}",
method = RequestMethod.POST,
beanClass = SysAuthHandler.class,
beanMethod = "refreshToken"
),
@RouterOperation(
path = "/sys/auth/logout",
method = RequestMethod.POST,
beanClass = SysAuthHandler.class,
beanMethod = "logout"
),
@RouterOperation(
path = "/sys/user/{id}",
method = RequestMethod.GET,
beanClass = SysUserHandler.class,
beanMethod = "getUserById"
),
@RouterOperation(
path = "/sys/user/username/{username}",
method = RequestMethod.GET,
beanClass = SysUserHandler.class,
beanMethod = "getUserByUsername"
),
@RouterOperation(
path = "/sys/user",
method = RequestMethod.POST,
beanClass = SysUserHandler.class,
beanMethod = "createUser"
),
@RouterOperation(
path = "/sys/user",
method = RequestMethod.PUT,
beanClass = SysUserHandler.class,
beanMethod = "updateUser"
),
@RouterOperation(
path = "/sys/user/{id}",
method = RequestMethod.DELETE,
beanClass = SysUserHandler.class,
beanMethod = "deleteUser"
),
@RouterOperation(
path = "/sys/user/{id}/ban",
method = RequestMethod.PUT,
beanClass = SysUserHandler.class,
beanMethod = "banUser"
),
@RouterOperation(
path = "/sys/user/{id}/unban",
method = RequestMethod.PUT,
beanClass = SysUserHandler.class,
beanMethod = "unbanUser"
),
@RouterOperation(
path = "/sys/user",
method = RequestMethod.GET,
beanClass = SysUserHandler.class,
beanMethod = "getAllUsers"
),
@RouterOperation(
path = "/sys/user/page",
method = RequestMethod.GET,
beanClass = SysUserHandler.class,
beanMethod = "queryUsersWithPagination"
),
@RouterOperation(
path = "/sys/role/{id}",
method = RequestMethod.GET,
beanClass = SysRoleHandler.class,
beanMethod = "getRoleById"
),
@RouterOperation(
path = "/sys/role/roleKey/{roleKey}",
method = RequestMethod.GET,
beanClass = SysRoleHandler.class,
beanMethod = "getRoleByRoleKey"
),
@RouterOperation(
path = "/sys/role/user/{userId}",
method = RequestMethod.GET,
beanClass = SysRoleHandler.class,
beanMethod = "getRolesByUserId"
),
@RouterOperation(
path = "/sys/role",
method = RequestMethod.GET,
beanClass = SysRoleHandler.class,
beanMethod = "getAllRoles"
),
@RouterOperation(
path = "/sys/role",
method = RequestMethod.POST,
beanClass = SysRoleHandler.class,
beanMethod = "createRole"
),
@RouterOperation(
path = "/sys/role",
method = RequestMethod.PUT,
beanClass = SysRoleHandler.class,
beanMethod = "updateRole"
),
@RouterOperation(
path = "/sys/role/{id}",
method = RequestMethod.DELETE,
beanClass = SysRoleHandler.class,
beanMethod = "deleteRole"
),
@RouterOperation(
path = "/sys/role/assign",
method = RequestMethod.POST,
beanClass = SysRoleHandler.class,
beanMethod = "assignRoleToUser"
),
@RouterOperation(
path = "/sys/role/user/{userId}/role/{roleId}",
method = RequestMethod.DELETE,
beanClass = SysRoleHandler.class,
beanMethod = "removeRoleFromUser"
),
@RouterOperation(
path = "/sys/menu/{id}",
method = RequestMethod.GET,
beanClass = SysMenuHandler.class,
beanMethod = "getMenuById"
),
@RouterOperation(
path = "/sys/menu/role/{roleId}",
method = RequestMethod.GET,
beanClass = SysMenuHandler.class,
beanMethod = "getMenusByRoleId"
),
@RouterOperation(
path = "/sys/menu/user/{userId}",
method = RequestMethod.GET,
beanClass = SysMenuHandler.class,
beanMethod = "getMenusByUserId"
),
@RouterOperation(
path = "/sys/menu",
method = RequestMethod.GET,
beanClass = SysMenuHandler.class,
beanMethod = "getAllMenus"
),
@RouterOperation(
path = "/sys/menu",
method = RequestMethod.POST,
beanClass = SysMenuHandler.class,
beanMethod = "createMenu"
),
@RouterOperation(
path = "/sys/menu",
method = RequestMethod.PUT,
beanClass = SysMenuHandler.class,
beanMethod = "updateMenu"
),
@RouterOperation(
path = "/sys/menu/{id}",
method = RequestMethod.DELETE,
beanClass = SysMenuHandler.class,
beanMethod = "deleteMenu"
),
@RouterOperation(
path = "/sys/menu/assign",
method = RequestMethod.POST,
beanClass = SysMenuHandler.class,
beanMethod = "assignMenuToRole"
),
@RouterOperation(
path = "/sys/menu/role/{roleId}/menu/{menuId}",
method = RequestMethod.DELETE,
beanClass = SysMenuHandler.class,
beanMethod = "removeMenuFromRole"
),
@RouterOperation(
path = "/sys/operationLog/{id}",
method = RequestMethod.GET,
beanClass = OperationLogHandler.class,
beanMethod = "getLogById"
),
@RouterOperation(
path = "/sys/operationLog/query",
method = RequestMethod.POST,
beanClass = OperationLogHandler.class,
beanMethod = "queryLogs"
),
@RouterOperation(
path = "/sys/operationLog/{id}",
method = RequestMethod.DELETE,
beanClass = OperationLogHandler.class,
beanMethod = "deleteLog"
)
})
@Operation(summary = "系统管理路由配置")
public RouterFunction<ServerResponse> sysRoutes() {
return RouterFunctions.route()
.path("/sys", builder -> builder
.path("/auth", authBuilder -> authBuilder
.POST("/register", accept(MediaType.APPLICATION_JSON), authHandler::register)
.POST("/login", accept(MediaType.APPLICATION_JSON), authHandler::login)
.POST("/refresh/{token}", accept(MediaType.APPLICATION_JSON), authHandler::refreshToken)
.POST("/logout", accept(MediaType.APPLICATION_JSON), authHandler::logout))
.path("/user", userBuilder -> userBuilder
.GET("/{id}", accept(MediaType.APPLICATION_JSON), userHandler::getUserById)
.GET("/username/{username}", accept(MediaType.APPLICATION_JSON), userHandler::getUserByUsername)
.GET("/page", accept(MediaType.APPLICATION_JSON), userHandler::queryUsersWithPagination)
.GET("", accept(MediaType.APPLICATION_JSON), userHandler::getAllUsers)
.POST("", accept(MediaType.APPLICATION_JSON), userHandler::createUser)
.PUT("", accept(MediaType.APPLICATION_JSON), userHandler::updateUser)
.DELETE("/{id}", accept(MediaType.APPLICATION_JSON), userHandler::deleteUser)
.PUT("/{id}/ban", accept(MediaType.APPLICATION_JSON), userHandler::banUser)
.PUT("/{id}/unban", accept(MediaType.APPLICATION_JSON), userHandler::unbanUser))
.path("/role", roleBuilder -> roleBuilder
.GET("/{id}", accept(MediaType.APPLICATION_JSON), roleHandler::getRoleById)
.GET("/roleKey/{roleKey}", accept(MediaType.APPLICATION_JSON), roleHandler::getRoleByRoleKey)
.GET("/user/{userId}", accept(MediaType.APPLICATION_JSON), roleHandler::getRolesByUserId)
.GET("", accept(MediaType.APPLICATION_JSON), roleHandler::getAllRoles)
.POST("", accept(MediaType.APPLICATION_JSON), roleHandler::createRole)
.PUT("", accept(MediaType.APPLICATION_JSON), roleHandler::updateRole)
.DELETE("/{id}", accept(MediaType.APPLICATION_JSON), roleHandler::deleteRole)
.POST("/assign", accept(MediaType.APPLICATION_JSON), roleHandler::assignRoleToUser)
.DELETE("/user/{userId}/role/{roleId}", accept(MediaType.APPLICATION_JSON), roleHandler::removeRoleFromUser))
.path("/menu", menuBuilder -> menuBuilder
.GET("/{id}", accept(MediaType.APPLICATION_JSON), menuHandler::getMenuById)
.GET("/role/{roleId}", accept(MediaType.APPLICATION_JSON), menuHandler::getMenusByRoleId)
.GET("/user/{userId}", accept(MediaType.APPLICATION_JSON), menuHandler::getMenusByUserId)
.GET("", accept(MediaType.APPLICATION_JSON), menuHandler::getAllMenus)
.POST("", accept(MediaType.APPLICATION_JSON), menuHandler::createMenu)
.PUT("", accept(MediaType.APPLICATION_JSON), menuHandler::updateMenu)
.DELETE("/{id}", accept(MediaType.APPLICATION_JSON), menuHandler::deleteMenu)
.POST("/assign", accept(MediaType.APPLICATION_JSON), menuHandler::assignMenuToRole)
.DELETE("/role/{roleId}/menu/{menuId}", accept(MediaType.APPLICATION_JSON), menuHandler::removeMenuFromRole))
.path("/operationLog", logBuilder -> logBuilder
.GET("/{id}", accept(MediaType.APPLICATION_JSON), operationLogHandler::getLogById)
.POST("/query", accept(MediaType.APPLICATION_JSON), operationLogHandler::queryLogs)
.DELETE("/{id}", accept(MediaType.APPLICATION_JSON), operationLogHandler::deleteLog)))
.build();
}
}
@@ -0,0 +1,15 @@
package io.destiny.sys.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class SysSecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
@@ -0,0 +1,207 @@
package io.destiny.sys.core.domain;
import io.destiny.common.utils.SnowflakeId;
import java.time.LocalDateTime;
public class OperationLog {
private Long id;
private String operator;
private LocalDateTime operationTime;
private String requestPath;
private String requestMethod;
private String requestParams;
private String responseResult;
private String ipAddress;
private Long executionTime;
private String status;
private String exceptionMessage;
private String moduleName;
private String operationDesc;
private String diffJson;
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 getOperator() {
return operator;
}
public void setOperator(String operator) {
this.operator = operator;
}
public LocalDateTime getOperationTime() {
return operationTime;
}
public void setOperationTime(LocalDateTime operationTime) {
this.operationTime = operationTime;
}
public String getRequestPath() {
return requestPath;
}
public void setRequestPath(String requestPath) {
this.requestPath = requestPath;
}
public String getRequestMethod() {
return requestMethod;
}
public void setRequestMethod(String requestMethod) {
this.requestMethod = requestMethod;
}
public String getRequestParams() {
return requestParams;
}
public void setRequestParams(String requestParams) {
this.requestParams = requestParams;
}
public String getResponseResult() {
return responseResult;
}
public void setResponseResult(String responseResult) {
this.responseResult = responseResult;
}
public String getIpAddress() {
return ipAddress;
}
public void setIpAddress(String ipAddress) {
this.ipAddress = ipAddress;
}
public Long getExecutionTime() {
return executionTime;
}
public void setExecutionTime(Long executionTime) {
this.executionTime = executionTime;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getExceptionMessage() {
return exceptionMessage;
}
public void setExceptionMessage(String exceptionMessage) {
this.exceptionMessage = exceptionMessage;
}
public String getModuleName() {
return moduleName;
}
public void setModuleName(String moduleName) {
this.moduleName = moduleName;
}
public String getOperationDesc() {
return operationDesc;
}
public void setOperationDesc(String operationDesc) {
this.operationDesc = operationDesc;
}
public String getDiffJson() {
return diffJson;
}
public void setDiffJson(String diffJson) {
this.diffJson = diffJson;
}
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;
}
public Long generateId() {
this.id = SnowflakeId.nextId();
return this.id;
}
public void delete() {
this.deletedAt = LocalDateTime.now();
}
}
@@ -0,0 +1,155 @@
package io.destiny.sys.core.domain;
import io.destiny.common.utils.SnowflakeId;
import java.time.LocalDateTime;
public class SysMenu {
private Long id;
private String menuName;
private Long parentId;
private Integer orderNum;
private String menuType;
private String perms;
private String component;
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 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 getCode() {
return perms;
}
public void setCode(String code) {
this.perms = code;
}
public String getComponent() {
return component;
}
public void setComponent(String component) {
this.component = component;
}
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;
}
public Long generateId() {
this.id = SnowflakeId.nextId();
return this.id;
}
public void delete() {
this.deletedAt = LocalDateTime.now();
}
}
@@ -0,0 +1,117 @@
package io.destiny.sys.core.domain;
import io.destiny.common.utils.SnowflakeId;
import java.time.LocalDateTime;
public class SysRole {
private Long id;
private String roleName;
private String roleKey;
private Integer roleSort;
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 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 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;
}
public Long generateId() {
this.id = SnowflakeId.nextId();
return this.id;
}
public void delete() {
this.deletedAt = LocalDateTime.now();
}
}
@@ -0,0 +1,24 @@
package io.destiny.sys.core.domain;
public class SysRoleMenu {
private Long roleId;
private Long menuId;
public Long getRoleId() {
return roleId;
}
public void setRoleId(Long roleId) {
this.roleId = roleId;
}
public Long getMenuId() {
return menuId;
}
public void setMenuId(Long menuId) {
this.menuId = menuId;
}
}
@@ -0,0 +1,172 @@
package io.destiny.sys.core.domain;
import io.destiny.common.primitive.PhoneNumber;
import io.destiny.common.primitive.EmailAddress;
import io.destiny.common.utils.SnowflakeId;
import java.time.LocalDateTime;
public class SysUser {
/**
* 用户ID
*/
private Long id;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 邮箱
*/
private EmailAddress email;
/**
* 手机号
*/
private PhoneNumber phone;
/**
* 状态 0-正常 1-禁用
*/
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 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 EmailAddress getEmail() {
return email;
}
public void setEmail(EmailAddress email) {
this.email = email;
}
public PhoneNumber getPhone() {
return phone;
}
public void setPhone(PhoneNumber phone) {
this.phone = phone;
}
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;
}
/**
* 生成主键ID
*
* @return 主键ID
*/
public Long generateId() {
this.id = SnowflakeId.nextId();
return this.id;
}
/**
* 删除用户
*/
public void delete() {
this.deletedAt = LocalDateTime.now();
}
}
@@ -0,0 +1,24 @@
package io.destiny.sys.core.domain;
public class SysUserRole {
private Long userId;
private Long roleId;
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public Long getRoleId() {
return roleId;
}
public void setRoleId(Long roleId) {
this.roleId = roleId;
}
}
@@ -0,0 +1,93 @@
package io.destiny.sys.core.domain.query;
import java.time.LocalDateTime;
public class OperationLogQuery {
private String operator;
private LocalDateTime startTime;
private LocalDateTime endTime;
private String moduleName;
private String status;
private String requestPath;
private Integer pageNum;
private Integer pageSize;
public String getOperator() {
return operator;
}
public void setOperator(String operator) {
this.operator = operator;
}
public LocalDateTime getStartTime() {
return startTime;
}
public void setStartTime(LocalDateTime startTime) {
this.startTime = startTime;
}
public LocalDateTime getEndTime() {
return endTime;
}
public void setEndTime(LocalDateTime endTime) {
this.endTime = endTime;
}
public String getModuleName() {
return moduleName;
}
public void setModuleName(String moduleName) {
this.moduleName = moduleName;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getRequestPath() {
return requestPath;
}
public void setRequestPath(String requestPath) {
this.requestPath = requestPath;
}
public Integer getPageNum() {
return pageNum;
}
public void setPageNum(Integer pageNum) {
this.pageNum = pageNum;
}
public Integer getPageSize() {
return pageSize;
}
public void setPageSize(Integer pageSize) {
this.pageSize = pageSize;
}
public Integer getOffset() {
if (pageNum == null || pageSize == null) {
return null;
}
return (pageNum - 1) * pageSize;
}
}
@@ -0,0 +1,47 @@
package io.destiny.sys.core.domain.query;
import io.destiny.common.primitive.EmailAddress;
import io.destiny.common.primitive.PhoneNumber;
public class SysUserQuery {
private String username;
private EmailAddress email;
private PhoneNumber phone;
private String status;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public EmailAddress getEmail() {
return email;
}
public void setEmail(EmailAddress email) {
this.email = email;
}
public PhoneNumber getPhone() {
return phone;
}
public void setPhone(PhoneNumber phone) {
this.phone = phone;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
}
@@ -0,0 +1,19 @@
package io.destiny.sys.core.repository;
import io.destiny.sys.core.domain.OperationLog;
import io.destiny.sys.core.domain.query.OperationLogQuery;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public interface IOperationLogRepository {
Mono<OperationLog> findById(Long id);
Mono<OperationLog> save(OperationLog operationLog);
Flux<OperationLog> findByQuery(OperationLogQuery query);
Mono<Long> countByQuery(OperationLogQuery query);
Mono<Void> deleteById(Long id);
}
@@ -0,0 +1,20 @@
package io.destiny.sys.core.repository;
import io.destiny.sys.core.domain.SysMenu;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public interface ISysMenuRepository {
Mono<SysMenu> findById(Long id);
Flux<SysMenu> findByRoleId(Long roleId);
Flux<SysMenu> findByUserId(Long userId);
Flux<SysMenu> findAll();
Mono<SysMenu> save(SysMenu sysMenu);
Mono<Void> deleteById(Long id);
}
@@ -0,0 +1,15 @@
package io.destiny.sys.core.repository;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public interface ISysRoleMenuRepository {
Flux<Long> findMenuIdsByRoleId(Long roleId);
Mono<Void> assignMenuToRole(Long roleId, Long menuId);
Mono<Void> removeMenuFromRole(Long roleId, Long menuId);
Mono<Void> removeAllMenusFromRole(Long roleId);
}
@@ -0,0 +1,20 @@
package io.destiny.sys.core.repository;
import io.destiny.sys.core.domain.SysRole;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public interface ISysRoleRepository {
Mono<SysRole> findById(Long id);
Mono<SysRole> findByRoleKey(String roleKey);
Flux<SysRole> findByUserId(Long userId);
Flux<SysRole> findAll();
Mono<SysRole> save(SysRole sysRole);
Mono<Void> deleteById(Long id);
}
@@ -0,0 +1,25 @@
package io.destiny.sys.core.repository;
import io.destiny.sys.core.domain.SysUser;
import io.destiny.sys.core.domain.query.SysUserQuery;
import io.destiny.common.request.PageQuery;
import io.destiny.common.response.PageModel;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public interface ISysUserRepository {
Mono<SysUser> findByUsername(String username);
Mono<SysUser> findById(Long id);
Mono<SysUser> save(SysUser sysUser);
Mono<Void> deleteById(Long id);
Flux<SysUser> findAll();
Mono<PageModel<SysUser>> findByQueryWithPagination(SysUserQuery query, PageQuery pageQuery);
Mono<Void> removeAllRolesFromUser(Long userId);
}
@@ -0,0 +1,15 @@
package io.destiny.sys.core.repository;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public interface ISysUserRoleRepository {
Flux<Long> findRoleIdsByUserId(Long userId);
Mono<Void> assignRoleToUser(Long userId, Long roleId);
Mono<Void> removeRoleFromUser(Long userId, Long roleId);
Mono<Void> removeAllUsersFromRole(Long roleId);
}
@@ -0,0 +1,20 @@
package io.destiny.sys.core.service;
import reactor.core.publisher.Mono;
public interface IDataMaskingService {
Mono<String> maskPassword(String password);
Mono<String> maskPhone(String phone);
Mono<String> maskEmail(String email);
Mono<String> maskIdCard(String idCard);
Mono<String> maskBankCard(String bankCard);
Mono<String> maskSensitiveData(String data);
Mono<String> mask(String data, String type);
}
@@ -0,0 +1,16 @@
package io.destiny.sys.core.service;
import reactor.core.publisher.Mono;
import java.util.List;
public interface IJaversAuditService {
Mono<String> compare(Object oldObject, Object newObject);
Mono<List<String>> getChangedFields(Object oldObject, Object newObject);
Mono<String> toJson(Object object);
Mono<String> commit(Object object);
}
@@ -0,0 +1,19 @@
package io.destiny.sys.core.service;
import io.destiny.sys.core.domain.OperationLog;
import io.destiny.sys.core.domain.query.OperationLogQuery;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public interface IOperationLogService {
Mono<OperationLog> findById(Long id);
Mono<OperationLog> save(OperationLog operationLog);
Flux<OperationLog> findByQuery(OperationLogQuery query);
Mono<Long> countByQuery(OperationLogQuery query);
Mono<Void> deleteById(Long id);
}
@@ -0,0 +1,17 @@
package io.destiny.sys.core.service;
import io.destiny.sys.dto.request.SysUserLoginRequest;
import io.destiny.sys.dto.response.SysLoginResponse;
import io.destiny.sys.dto.response.SysUserRegisterRequest;
import reactor.core.publisher.Mono;
public interface ISysAuthService {
Mono<Long> register(SysUserRegisterRequest request);
Mono<SysLoginResponse> login(SysUserLoginRequest request);
Mono<SysLoginResponse> refreshToken(String refreshToken);
Mono<Void> logout(String token);
}
@@ -0,0 +1,26 @@
package io.destiny.sys.core.service;
import io.destiny.sys.core.domain.SysMenu;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public interface ISysMenuService {
Mono<SysMenu> findById(Long id);
Flux<SysMenu> findByRoleId(Long roleId);
Flux<SysMenu> findByUserId(Long userId);
Flux<SysMenu> findAll();
Mono<SysMenu> create(SysMenu sysMenu);
Mono<SysMenu> update(SysMenu sysMenu);
Mono<Void> deleteById(Long id);
Mono<Void> assignMenuToRole(Long roleId, Long menuId);
Mono<Void> removeMenuFromRole(Long roleId, Long menuId);
}
@@ -0,0 +1,20 @@
package io.destiny.sys.core.service;
import reactor.core.publisher.Mono;
public interface ISysPermissionInitService {
Mono<Void> initializeAdminPermissions(Long userId);
Mono<Void> initializeUserPermissions(Long userId);
Mono<Void> ensureDashboardMenuExists();
Mono<Void> ensureUserRoleExists();
Mono<Void> assignDashboardToUserRole();
Mono<Boolean> isUserAdmin(Long userId);
Mono<Boolean> isUserRegularUser(Long userId);
}
@@ -0,0 +1,21 @@
package io.destiny.sys.core.service;
import java.util.List;
import java.util.Set;
import reactor.core.publisher.Mono;
public interface ISysPermissionService {
Mono<Set<String>> getUserPermissions(Long userId);
boolean hasPermission(Long userId, String permission);
boolean hasAnyPermission(Long userId, Set<String> permissions);
boolean hasRole(Long userId, String roleKey);
boolean hasAnyRole(Long userId, Set<String> roleKeys);
List<String> getUserRoles(Long userId);
}
@@ -0,0 +1,26 @@
package io.destiny.sys.core.service;
import io.destiny.sys.core.domain.SysRole;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public interface ISysRoleService {
Mono<SysRole> findById(Long id);
Mono<SysRole> findByRoleKey(String roleKey);
Flux<SysRole> findByUserId(Long userId);
Flux<SysRole> findAll();
Mono<SysRole> create(SysRole sysRole);
Mono<SysRole> update(SysRole sysRole);
Mono<Void> deleteById(Long id);
Mono<Void> assignRoleToUser(Long userId, Long roleId);
Mono<Void> removeRoleFromUser(Long userId, Long roleId);
}
@@ -0,0 +1,25 @@
package io.destiny.sys.core.service;
import io.destiny.sys.core.domain.SysUser;
import io.destiny.sys.core.domain.query.SysUserQuery;
import io.destiny.common.request.PageQuery;
import io.destiny.common.response.PageModel;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public interface ISysUserService {
Mono<SysUser> findByUsername(String username);
Mono<SysUser> findById(Long id);
Mono<SysUser> create(SysUser sysUser);
Mono<SysUser> update(SysUser sysUser);
Mono<Void> deleteById(Long id);
Flux<SysUser> findAll();
Mono<PageModel<SysUser>> findByQueryWithPagination(SysUserQuery query, PageQuery pageQuery);
}
@@ -0,0 +1,100 @@
package io.destiny.sys.core.service.impl;
import io.destiny.sys.core.service.IDataMaskingService;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
@Service
public class DataMaskingServiceImpl implements IDataMaskingService {
private static final String PASSWORD_MASK = "******";
private static final String PHONE_MASK = "****";
private static final String EMAIL_MASK = "***";
private static final String IDCARD_MASK = "********";
private static final String BANKCARD_MASK = "***********";
public Mono<String> maskPassword(String password) {
if (password == null || password.isEmpty()) {
return Mono.empty();
}
return Mono.just(PASSWORD_MASK);
}
public Mono<String> maskPhone(String phone) {
if (phone == null || phone.length() < 7) {
return Mono.empty();
}
String masked = phone.substring(0, 3) + PHONE_MASK + phone.substring(7);
return Mono.just(masked);
}
public Mono<String> maskEmail(String email) {
if (email == null || !email.contains("@")) {
return Mono.empty();
}
String[] parts = email.split("@");
if (parts[0].length() <= 2) {
return Mono.empty();
}
String masked = parts[0].substring(0, 2) + EMAIL_MASK + "@" + parts[1];
return Mono.just(masked);
}
public Mono<String> maskIdCard(String idCard) {
if (idCard == null || idCard.length() < 10) {
return Mono.empty();
}
String masked = idCard.substring(0, 6) + IDCARD_MASK + idCard.substring(idCard.length() - 4);
return Mono.just(masked);
}
public Mono<String> maskBankCard(String bankCard) {
if (bankCard == null || bankCard.length() < 10) {
return Mono.empty();
}
String masked = bankCard.substring(0, 4) + BANKCARD_MASK + bankCard.substring(bankCard.length() - 4);
return Mono.just(masked);
}
public Mono<String> maskSensitiveData(String data) {
if (data == null || data.isEmpty()) {
return Mono.empty();
}
String masked = data;
masked = masked.replaceAll("\"password\"\\s*:\\s*\"[^\"]*\"", "\"password\":\"" + PASSWORD_MASK + "\"");
masked = masked.replaceAll("\"phone\"\\s*:\\s*\"[^\"]*\"", "\"phone\":\"" + PHONE_MASK + "\"");
masked = masked.replaceAll("\"email\"\\s*:\\s*\"[^\"]*\"", "\"email\":\"" + EMAIL_MASK + "\"");
masked = masked.replaceAll("\"idCard\"\\s*:\\s*\"[^\"]*\"", "\"idCard\":\"" + IDCARD_MASK + "\"");
masked = masked.replaceAll("\"bankCard\"\\s*:\\s*\"[^\"]*\"", "\"bankCard\":\"" + BANKCARD_MASK + "\"");
return Mono.just(masked);
}
public Mono<String> mask(String data, String type) {
if (data == null || data.isEmpty()) {
return Mono.empty();
}
switch (type) {
case "password":
return maskPassword(data);
case "phone":
return maskPhone(data);
case "email":
return maskEmail(data);
case "idCard":
return maskIdCard(data);
case "bankCard":
return maskBankCard(data);
case "sensitive":
return maskSensitiveData(data);
default:
return Mono.empty();
}
}
}
@@ -0,0 +1,76 @@
package io.destiny.sys.core.service.impl;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.destiny.sys.core.service.IJaversAuditService;
import org.javers.core.Javers;
import org.javers.core.JaversBuilder;
import org.javers.core.diff.Diff;
import org.javers.core.diff.changetype.ValueChange;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
import java.util.ArrayList;
import java.util.List;
@Service
public class JaversAuditServiceImpl implements IJaversAuditService {
private static final Logger log = LoggerFactory.getLogger(JaversAuditServiceImpl.class);
private final Javers javers;
private final ObjectMapper objectMapper;
public JaversAuditServiceImpl(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
this.javers = JaversBuilder.javers().build();
}
public Mono<String> compare(Object oldObject, Object newObject) {
return Mono.fromCallable(() -> {
Diff diff = javers.compare(oldObject, newObject);
return diff.prettyPrint();
})
.doOnSuccess(result -> log.debug("对象比较成功"))
.doOnError(error -> log.error("对象比较失败", error));
}
public Mono<List<String>> getChangedFields(Object oldObject, Object newObject) {
return Mono.fromCallable(() -> {
Diff diff = javers.compare(oldObject, newObject);
List<String> changedFields = new ArrayList<>();
for (ValueChange change : diff.getChangesByType(ValueChange.class)) {
String fieldName = change.getPropertyName();
Object oldValue = change.getLeft();
Object newValue = change.getRight();
changedFields.add(String.format("%s: %s -> %s", fieldName, oldValue, newValue));
}
return changedFields;
})
.doOnSuccess(result -> log.debug("获取变更字段成功,数量: {}", result.size()))
.doOnError(error -> log.error("获取变更字段失败", error));
}
public Mono<String> toJson(Object object) {
return Mono.fromCallable(() -> {
JsonNode jsonNode = objectMapper.valueToTree(object);
return jsonNode.toString();
})
.doOnSuccess(result -> log.debug("对象转JSON成功"))
.doOnError(error -> log.error("对象转JSON失败", error));
}
public Mono<String> commit(Object object) {
return Mono.fromCallable(() -> {
return javers.commit("system", object);
})
.flatMap(commit -> Mono.just(commit.getId().toString()))
.doOnSuccess(result -> log.debug("提交对象到JaVers成功,ID: {}", result))
.doOnError(error -> log.error("提交对象到JaVers失败", error));
}
}
@@ -0,0 +1,59 @@
package io.destiny.sys.core.service.impl;
import io.destiny.sys.core.domain.OperationLog;
import io.destiny.sys.core.domain.query.OperationLogQuery;
import io.destiny.sys.core.repository.IOperationLogRepository;
import io.destiny.sys.core.service.IOperationLogService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@Service
public class OperationLogServiceImpl implements IOperationLogService {
private static final Logger log = LoggerFactory.getLogger(OperationLogServiceImpl.class);
private final IOperationLogRepository operationLogRepository;
public OperationLogServiceImpl(IOperationLogRepository operationLogRepository) {
this.operationLogRepository = operationLogRepository;
}
public Mono<OperationLog> findById(Long id) {
return operationLogRepository.findById(id)
.doOnSuccess(result -> log.debug("查询操作日志成功,ID: {}", id))
.doOnError(error -> log.error("查询操作日志失败,ID: {}", id, error));
}
public Mono<OperationLog> save(OperationLog operationLog) {
return Mono.fromCallable(operationLog::generateId)
.flatMap(id -> operationLogRepository.save(operationLog))
.doOnSuccess(result -> log.debug("保存操作日志成功,ID: {}", result.getId()))
.doOnError(error -> log.error("保存操作日志失败", error))
.onErrorResume(error -> {
log.warn("保存操作日志失败,但不影响主业务流程", error);
return Mono.empty();
});
}
public Flux<OperationLog> findByQuery(OperationLogQuery query) {
return operationLogRepository.findByQuery(query)
.doOnComplete(() -> log.debug("查询操作日志列表成功"))
.doOnError(error -> log.error("查询操作日志列表失败", error));
}
public Mono<Long> countByQuery(OperationLogQuery query) {
return operationLogRepository.countByQuery(query)
.doOnSuccess(count -> log.debug("统计操作日志数量成功,数量: {}", count))
.doOnError(error -> log.error("统计操作日志数量失败", error));
}
public Mono<Void> deleteById(Long id) {
return operationLogRepository.deleteById(id)
.doOnSuccess(result -> log.debug("删除操作日志成功,ID: {}", id))
.doOnError(error -> log.error("删除操作日志失败,ID: {}", id, error));
}
}
@@ -0,0 +1,145 @@
package io.destiny.sys.core.service.impl;
import io.destiny.sys.core.domain.SysUser;
import io.destiny.sys.core.service.ISysAuthService;
import io.destiny.sys.core.service.ISysUserService;
import io.destiny.sys.core.service.ISysPermissionService;
import io.destiny.sys.core.service.ISysPermissionInitService;
import io.destiny.sys.dto.request.SysUserLoginRequest;
import io.destiny.sys.dto.response.SysLoginResponse;
import io.destiny.sys.dto.response.SysUserRegisterRequest;
import io.destiny.sys.security.SysJwtTokenProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
@Service
public class SysAuthServiceImpl implements ISysAuthService {
private static final Logger logger = LoggerFactory.getLogger(SysAuthServiceImpl.class);
private final ISysUserService userService;
private final SysJwtTokenProvider jwtTokenProvider;
private final BCryptPasswordEncoder passwordEncoder;
private final ISysPermissionService permissionService;
private final ISysPermissionInitService permissionInitService;
public SysAuthServiceImpl(ISysUserService userService, SysJwtTokenProvider jwtTokenProvider, ISysPermissionService permissionService, ISysPermissionInitService permissionInitService) {
this.userService = userService;
this.jwtTokenProvider = jwtTokenProvider;
this.passwordEncoder = new BCryptPasswordEncoder();
this.permissionService = permissionService;
this.permissionInitService = permissionInitService;
}
@Override
public Mono<Long> register(SysUserRegisterRequest request) {
logger.info("User registration attempt: {}", request.getUsername());
return userService.findByUsername(request.getUsername())
.flatMap(existingUser -> Mono.<Long>error(new RuntimeException("用户名已存在")))
.switchIfEmpty(Mono.defer(() -> {
SysUser user = request.toDomain();
user.setPassword(passwordEncoder.encode(user.getPassword()));
return userService.create(user)
.flatMap(savedUser -> {
if ("admin".equalsIgnoreCase(savedUser.getUsername())) {
return permissionInitService.initializeAdminPermissions(savedUser.getId())
.thenReturn(savedUser.getId());
} else {
return permissionInitService.initializeUserPermissions(savedUser.getId())
.thenReturn(savedUser.getId());
}
});
}))
.doOnSuccess(userId -> logger.info("User registered successfully with permissions: {}", userId));
}
@Override
public Mono<SysLoginResponse> login(SysUserLoginRequest request) {
logger.info("User login attempt: {}", request.getUsername());
return userService.findByUsername(request.getUsername())
.switchIfEmpty(Mono.error(new RuntimeException("用户名或密码错误")))
.flatMap(user -> {
if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) {
return Mono.error(new RuntimeException("用户名或密码错误"));
}
if (!"0".equals(user.getStatus())) {
return Mono.error(new RuntimeException("用户已被禁用"));
}
String accessToken = jwtTokenProvider.generateAccessToken(user.getId(), user.getUsername());
String refreshToken = jwtTokenProvider.generateRefreshToken(user.getId(), user.getUsername());
logger.info("About to call permissionService.getUserPermissions for userId: {}", user.getId());
// 使用 reactive 方式获取权限
return permissionService.getUserPermissions(user.getId())
.flatMap(permissions -> {
logger.info("User permissions loaded: userId={}, permissions.size={}", user.getId(), permissions != null ? permissions.size() : 0);
logger.info("User logged in successfully: {}", user.getId());
return Mono.just(SysLoginResponse.from(accessToken, refreshToken, user, permissions));
});
});
}
@Override
public Mono<SysLoginResponse> refreshToken(String refreshToken) {
logger.info("Token refresh attempt");
if (!jwtTokenProvider.validateToken(refreshToken)) {
return Mono.error(new RuntimeException("无效的刷新令牌"));
}
if (jwtTokenProvider.isTokenExpired(refreshToken)) {
return Mono.error(new RuntimeException("刷新令牌已过期"));
}
String tokenType = jwtTokenProvider.getTokenType(refreshToken);
if (!"refresh".equals(tokenType)) {
return Mono.error(new RuntimeException("令牌类型错误"));
}
Long userId = jwtTokenProvider.getUserIdFromToken(refreshToken);
return userService.findById(userId)
.switchIfEmpty(Mono.error(new RuntimeException("用户不存在")))
.flatMap(user -> {
if (!"0".equals(user.getStatus())) {
return Mono.error(new RuntimeException("用户已被禁用"));
}
String newAccessToken = jwtTokenProvider.generateAccessToken(user.getId(), user.getUsername());
String newRefreshToken = jwtTokenProvider.generateRefreshToken(user.getId(), user.getUsername());
return permissionService.getUserPermissions(user.getId())
.flatMap(permissions -> {
logger.info("Token refreshed successfully for user: {}", userId);
return Mono.just(SysLoginResponse.from(newAccessToken, newRefreshToken, user, permissions));
});
});
}
@Override
public Mono<Void> logout(String token) {
logger.info("User logout attempt");
if (token == null || !jwtTokenProvider.validateToken(token)) {
logger.warn("Invalid token during logout");
return Mono.empty();
}
Long userId = jwtTokenProvider.getUserIdFromToken(token);
logger.info("User logged out: {}", userId);
return Mono.empty();
}
}
@@ -0,0 +1,77 @@
package io.destiny.sys.core.service.impl;
import io.destiny.sys.core.domain.SysMenu;
import io.destiny.sys.core.repository.ISysMenuRepository;
import io.destiny.sys.core.repository.ISysRoleMenuRepository;
import io.destiny.sys.core.service.ISysMenuService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@Service
public class SysMenuServiceImpl implements ISysMenuService {
private final ISysMenuRepository sysMenuRepository;
private final ISysRoleMenuRepository sysRoleMenuRepository;
public SysMenuServiceImpl(ISysMenuRepository sysMenuRepository, ISysRoleMenuRepository sysRoleMenuRepository) {
this.sysMenuRepository = sysMenuRepository;
this.sysRoleMenuRepository = sysRoleMenuRepository;
}
@Override
public Mono<SysMenu> findById(Long id) {
return sysMenuRepository.findById(id);
}
@Override
public Flux<SysMenu> findByRoleId(Long roleId) {
return sysMenuRepository.findByRoleId(roleId);
}
@Override
public Flux<SysMenu> findByUserId(Long userId) {
return sysMenuRepository.findByUserId(userId);
}
@Override
public Flux<SysMenu> findAll() {
return sysMenuRepository.findAll();
}
@Override
@Transactional
public Mono<SysMenu> create(SysMenu sysMenu) {
return sysMenuRepository.save(sysMenu);
}
@Override
@Transactional
public Mono<SysMenu> update(SysMenu sysMenu) {
return sysMenuRepository.findById(sysMenu.getId())
.switchIfEmpty(Mono.error(new RuntimeException("菜单不存在")))
.flatMap(existingMenu -> {
sysMenu.setCreatedAt(existingMenu.getCreatedAt());
return sysMenuRepository.save(sysMenu);
});
}
@Override
@Transactional
public Mono<Void> deleteById(Long id) {
return sysMenuRepository.deleteById(id);
}
@Override
@Transactional
public Mono<Void> assignMenuToRole(Long roleId, Long menuId) {
return sysRoleMenuRepository.assignMenuToRole(roleId, menuId);
}
@Override
@Transactional
public Mono<Void> removeMenuFromRole(Long roleId, Long menuId) {
return sysRoleMenuRepository.removeMenuFromRole(roleId, menuId);
}
}
@@ -0,0 +1,127 @@
package io.destiny.sys.core.service.impl;
import io.destiny.sys.core.domain.SysRole;
import io.destiny.sys.core.domain.SysMenu;
import io.destiny.sys.core.repository.ISysRoleRepository;
import io.destiny.sys.core.repository.ISysMenuRepository;
import io.destiny.sys.core.repository.ISysUserRoleRepository;
import io.destiny.sys.core.repository.ISysRoleMenuRepository;
import io.destiny.sys.core.service.ISysPermissionInitService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
@Service
public class SysPermissionInitServiceImpl implements ISysPermissionInitService {
private static final Logger logger = LoggerFactory.getLogger(SysPermissionInitServiceImpl.class);
private static final Long ADMIN_ROLE_ID = 1L;
private static final Long USER_ROLE_ID = 2L;
private static final Long DASHBOARD_MENU_ID = 2L;
private final ISysRoleRepository roleRepository;
private final ISysMenuRepository menuRepository;
private final ISysUserRoleRepository userRoleRepository;
private final ISysRoleMenuRepository roleMenuRepository;
public SysPermissionInitServiceImpl(
ISysRoleRepository roleRepository,
ISysMenuRepository menuRepository,
ISysUserRoleRepository userRoleRepository,
ISysRoleMenuRepository roleMenuRepository) {
this.roleRepository = roleRepository;
this.menuRepository = menuRepository;
this.userRoleRepository = userRoleRepository;
this.roleMenuRepository = roleMenuRepository;
}
public Mono<Void> initializeAdminPermissions(Long userId) {
logger.info("Initializing admin permissions for user: {}", userId);
return roleRepository.findById(ADMIN_ROLE_ID)
.switchIfEmpty(Mono.error(new RuntimeException("超级管理员角色不存在")))
.flatMap(adminRole -> {
logger.info("Assigning admin role {} to user {}", adminRole.getRoleKey(), userId);
return userRoleRepository.assignRoleToUser(userId, ADMIN_ROLE_ID);
})
.doOnSuccess(v -> logger.info("Admin permissions initialized successfully for user: {}", userId))
.doOnError(e -> logger.error("Failed to initialize admin permissions for user: {}", userId, e));
}
public Mono<Void> initializeUserPermissions(Long userId) {
logger.info("Initializing user permissions for user: {}", userId);
return roleRepository.findById(USER_ROLE_ID)
.switchIfEmpty(Mono.error(new RuntimeException("普通用户角色不存在")))
.flatMap(userRole -> {
logger.info("Assigning user role {} to user {}", userRole.getRoleKey(), userId);
return userRoleRepository.assignRoleToUser(userId, USER_ROLE_ID);
})
.doOnSuccess(v -> logger.info("User permissions initialized successfully for user: {}", userId))
.doOnError(e -> logger.error("Failed to initialize user permissions for user: {}", userId, e));
}
public Mono<Void> ensureDashboardMenuExists() {
logger.info("Ensuring dashboard menu exists");
return menuRepository.findById(DASHBOARD_MENU_ID)
.switchIfEmpty(Mono.defer(() -> {
logger.info("Dashboard menu not found, creating default dashboard menu");
SysMenu dashboardMenu = new SysMenu();
dashboardMenu.setId(DASHBOARD_MENU_ID);
dashboardMenu.setMenuName("仪表盘");
dashboardMenu.setParentId(0L);
dashboardMenu.setOrderNum(0);
dashboardMenu.setMenuType("C");
dashboardMenu.setPerms("sys:dashboard:view");
dashboardMenu.setComponent("dashboard/index");
dashboardMenu.setStatus("0");
dashboardMenu.setCreateBy("system");
return menuRepository.save(dashboardMenu);
}))
.then()
.doOnSuccess(v -> logger.info("Dashboard menu ensured"))
.doOnError(e -> logger.error("Failed to ensure dashboard menu", e));
}
public Mono<Void> ensureUserRoleExists() {
logger.info("Ensuring user role exists");
return roleRepository.findById(USER_ROLE_ID)
.switchIfEmpty(Mono.defer(() -> {
logger.info("User role not found, creating default user role");
SysRole userRole = new SysRole();
userRole.setId(USER_ROLE_ID);
userRole.setRoleName("普通用户");
userRole.setRoleKey("user");
userRole.setRoleSort(2);
userRole.setStatus("0");
userRole.setCreateBy("system");
return roleRepository.save(userRole);
}))
.then()
.doOnSuccess(v -> logger.info("User role ensured"))
.doOnError(e -> logger.error("Failed to ensure user role", e));
}
public Mono<Void> assignDashboardToUserRole() {
logger.info("Assigning dashboard menu to user role");
return roleMenuRepository.assignMenuToRole(USER_ROLE_ID, DASHBOARD_MENU_ID)
.then()
.doOnSuccess(v -> logger.info("Dashboard menu assigned to user role"))
.doOnError(e -> logger.error("Failed to assign dashboard to user role", e));
}
public Mono<Boolean> isUserAdmin(Long userId) {
return userRoleRepository.findRoleIdsByUserId(userId)
.any(roleId -> ADMIN_ROLE_ID.equals(roleId));
}
public Mono<Boolean> isUserRegularUser(Long userId) {
return userRoleRepository.findRoleIdsByUserId(userId)
.any(roleId -> USER_ROLE_ID.equals(roleId));
}
}
@@ -0,0 +1,194 @@
package io.destiny.sys.core.service.impl;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import io.destiny.sys.core.domain.SysMenu;
import io.destiny.sys.core.domain.SysRole;
import io.destiny.sys.core.repository.ISysMenuRepository;
import io.destiny.sys.core.repository.ISysRoleRepository;
import io.destiny.sys.core.service.ISysPermissionService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.checkerframework.checker.nullness.qual.Nullable;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@Service
public class SysPermissionServiceImpl implements ISysPermissionService {
private static final Logger logger = LoggerFactory.getLogger(SysPermissionServiceImpl.class);
private final ISysRoleRepository sysRoleRepository;
private final ISysMenuRepository sysMenuRepository;
private final Cache<@Nullable Long, @Nullable Set<String>> userPermissionCache;
private final Cache<@Nullable Long, @Nullable Set<String>> userRoleCache;
public SysPermissionServiceImpl(ISysRoleRepository sysRoleRepository, ISysMenuRepository sysMenuRepository) {
this.sysRoleRepository = sysRoleRepository;
this.sysMenuRepository = sysMenuRepository;
this.userPermissionCache = Caffeine.newBuilder()
.expireAfterWrite(30, TimeUnit.MINUTES)
.maximumSize(1000)
.build();
this.userRoleCache = Caffeine.newBuilder()
.expireAfterWrite(30, TimeUnit.MINUTES)
.maximumSize(1000)
.build();
}
@Override
public Mono<Set<String>> getUserPermissions(Long userId) {
logger.info("getUserPermissions called with userId: {}", userId);
if (userId == null) {
logger.info("userId is null, returning empty set");
return Mono.just(new HashSet<>());
}
Set<String> cachedPermissions = userPermissionCache.getIfPresent(userId);
logger.info("Cached permissions: {}", cachedPermissions);
// 如果缓存中有权限且不为空,直接返回
if (cachedPermissions != null && !cachedPermissions.isEmpty()) {
logger.info("Returning cached permissions: {}", cachedPermissions);
return Mono.just(cachedPermissions);
}
// 从数据库加载权限
logger.info("Loading permissions from database");
return loadUserPermissions(userId)
.doOnNext(permissions -> {
if (permissions != null && !permissions.isEmpty()) {
userPermissionCache.put(userId, permissions);
logger.info("Permissions loaded and cached: {}", permissions);
} else {
logger.info("No permissions found in database");
}
});
}
@Override
public boolean hasPermission(Long userId, String permission) {
// 同步方法,使用 block() 在 boundedElastic 调度器上执行
try {
Set<String> permissions = getUserPermissions(userId)
.subscribeOn(Schedulers.boundedElastic())
.block();
return permissions != null && permissions.contains(permission);
} catch (Exception e) {
logger.error("检查权限失败", e);
return false;
}
}
@Override
public boolean hasAnyPermission(Long userId, Set<String> permissions) {
try {
Set<String> userPermissions = getUserPermissions(userId)
.subscribeOn(Schedulers.boundedElastic())
.block();
return userPermissions != null && userPermissions.stream().anyMatch(permissions::contains);
} catch (Exception e) {
logger.error("检查权限失败", e);
return false;
}
}
@Override
public boolean hasRole(Long userId, String roleKey) {
List<String> roles = getUserRoles(userId);
return roles.contains(roleKey);
}
@Override
public boolean hasAnyRole(Long userId, Set<String> roleKeys) {
List<String> roles = getUserRoles(userId);
return roles.stream().anyMatch(roleKeys::contains);
}
@Override
public List<String> getUserRoles(Long userId) {
if (userId == null) {
return new ArrayList<>();
}
Set<String> cachedRoles = userRoleCache.getIfPresent(userId);
if (cachedRoles != null) {
return new ArrayList<>(cachedRoles);
}
try {
Set<String> roles = loadUserRoles(userId)
.subscribeOn(Schedulers.boundedElastic())
.block();
if (roles != null) {
userRoleCache.put(userId, roles);
}
return roles != null ? new ArrayList<>(roles) : new ArrayList<>();
} catch (Exception e) {
logger.error("加载用户角色失败", e);
return new ArrayList<>();
}
}
public void clearUserPermissionCache(Long userId) {
if (userId != null) {
userPermissionCache.invalidate(userId);
userRoleCache.invalidate(userId);
}
}
public void clearAllCache() {
userPermissionCache.invalidateAll();
userRoleCache.invalidateAll();
}
private Mono<Set<String>> loadUserPermissions(Long userId) {
logger.info("开始加载用户权限,用户ID: {}", userId);
return sysMenuRepository.findByUserId(userId)
.collectList()
.flatMap(menus -> {
logger.info("用户菜单数量: {}", menus != null ? menus.size() : 0);
if (menus == null || menus.isEmpty()) {
logger.info("用户没有菜单,返回空权限集合");
return Mono.just(new HashSet<String>());
}
// 打印所有菜单的权限信息
menus.forEach(menu -> {
logger.info("菜单: id={}, name={}, perms={}", menu.getId(), menu.getMenuName(), menu.getPerms());
});
// 从已获取的菜单中过滤出有权限的
Set<String> permissions = menus.stream()
.filter(menu -> menu.getPerms() != null && !menu.getPerms().isEmpty())
.map(SysMenu::getPerms)
.collect(Collectors.toSet());
logger.info("用户权限数量: {}", permissions.size());
return Mono.just(permissions);
})
.onErrorResume(e -> {
logger.error("加载用户权限失败,用户ID: {}", userId, e);
return Mono.just(new HashSet<String>());
});
}
private Mono<Set<String>> loadUserRoles(Long userId) {
return sysRoleRepository.findByUserId(userId)
.filter(role -> role.getRoleKey() != null && !role.getRoleKey().isEmpty())
.map(SysRole::getRoleKey)
.collectList()
.map(HashSet::new);
}
}
@@ -0,0 +1,86 @@
package io.destiny.sys.core.service.impl;
import io.destiny.sys.core.domain.SysRole;
import io.destiny.sys.core.repository.ISysRoleRepository;
import io.destiny.sys.core.repository.ISysUserRoleRepository;
import io.destiny.sys.core.repository.ISysRoleMenuRepository;
import io.destiny.sys.core.service.ISysRoleService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@Service
public class SysRoleServiceImpl implements ISysRoleService {
private final ISysRoleRepository sysRoleRepository;
private final ISysUserRoleRepository sysUserRoleRepository;
private final ISysRoleMenuRepository sysRoleMenuRepository;
public SysRoleServiceImpl(ISysRoleRepository sysRoleRepository, ISysUserRoleRepository sysUserRoleRepository, ISysRoleMenuRepository sysRoleMenuRepository) {
this.sysRoleRepository = sysRoleRepository;
this.sysUserRoleRepository = sysUserRoleRepository;
this.sysRoleMenuRepository = sysRoleMenuRepository;
}
@Override
public Mono<SysRole> findById(Long id) {
return sysRoleRepository.findById(id);
}
@Override
public Mono<SysRole> findByRoleKey(String roleKey) {
return sysRoleRepository.findByRoleKey(roleKey);
}
@Override
public Flux<SysRole> findByUserId(Long userId) {
return sysRoleRepository.findByUserId(userId);
}
@Override
public Flux<SysRole> findAll() {
return sysRoleRepository.findAll();
}
@Override
@Transactional
public Mono<SysRole> create(SysRole sysRole) {
return sysRoleRepository.save(sysRole);
}
@Override
@Transactional
public Mono<SysRole> update(SysRole sysRole) {
return sysRoleRepository.findById(sysRole.getId())
.switchIfEmpty(Mono.error(new RuntimeException("角色不存在")))
.flatMap(existingRole -> {
sysRole.setCreatedAt(existingRole.getCreatedAt());
return sysRoleRepository.save(sysRole);
});
}
@Override
@Transactional
public Mono<Void> deleteById(Long id) {
return sysRoleRepository.findById(id)
.switchIfEmpty(Mono.error(new RuntimeException("角色不存在")))
.flatMap(role -> {
return sysUserRoleRepository.removeAllUsersFromRole(id)
.then(sysRoleMenuRepository.removeAllMenusFromRole(id))
.then(sysRoleRepository.deleteById(id));
});
}
@Override
@Transactional
public Mono<Void> assignRoleToUser(Long userId, Long roleId) {
return sysUserRoleRepository.assignRoleToUser(userId, roleId);
}
@Override
@Transactional
public Mono<Void> removeRoleFromUser(Long userId, Long roleId) {
return sysUserRoleRepository.removeRoleFromUser(userId, roleId);
}
}
@@ -0,0 +1,95 @@
package io.destiny.sys.core.service.impl;
import io.destiny.sys.core.domain.SysUser;
import io.destiny.sys.core.domain.query.SysUserQuery;
import io.destiny.sys.core.repository.ISysUserRepository;
import io.destiny.sys.core.service.ISysUserService;
import io.destiny.sys.core.service.ISysPermissionInitService;
import io.destiny.common.request.PageQuery;
import io.destiny.common.response.PageModel;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@Service
public class SysUserServiceImpl implements ISysUserService {
private final ISysUserRepository sysUserRepository;
private final PasswordEncoder passwordEncoder;
private final ISysPermissionInitService permissionInitService;
public SysUserServiceImpl(ISysUserRepository sysUserRepository, PasswordEncoder passwordEncoder, ISysPermissionInitService permissionInitService) {
this.sysUserRepository = sysUserRepository;
this.passwordEncoder = passwordEncoder;
this.permissionInitService = permissionInitService;
}
@Override
public Mono<SysUser> findByUsername(String username) {
return sysUserRepository.findByUsername(username);
}
@Override
public Mono<SysUser> findById(Long id) {
return sysUserRepository.findById(id);
}
@Override
public Mono<SysUser> create(SysUser sysUser) {
return findByUsername(sysUser.getUsername())
.flatMap(existingUser -> Mono.<SysUser>error(new RuntimeException("用户名已存在")))
.switchIfEmpty(Mono.defer(() -> {
return Mono.fromCallable(() -> passwordEncoder.encode(sysUser.getPassword()))
.map(encodedPassword -> {
SysUser newUser = new SysUser();
newUser.setUsername(sysUser.getUsername());
newUser.setPassword(encodedPassword);
newUser.setEmail(sysUser.getEmail());
newUser.setPhone(sysUser.getPhone());
newUser.setStatus(sysUser.getStatus());
return newUser;
})
.flatMap(sysUserRepository::save)
.flatMap(savedUser -> {
if ("admin".equalsIgnoreCase(savedUser.getUsername())) {
return permissionInitService.initializeAdminPermissions(savedUser.getId())
.thenReturn(savedUser);
} else {
return permissionInitService.initializeUserPermissions(savedUser.getId())
.thenReturn(savedUser);
}
});
}));
}
@Override
public Mono<SysUser> update(SysUser sysUser) {
return sysUserRepository.findById(sysUser.getId())
.switchIfEmpty(Mono.error(new RuntimeException("用户不存在")))
.flatMap(existingUser -> {
sysUser.setCreatedAt(existingUser.getCreatedAt());
return sysUserRepository.save(sysUser);
});
}
@Override
public Mono<Void> deleteById(Long id) {
return sysUserRepository.findById(id)
.switchIfEmpty(Mono.error(new RuntimeException("用户不存在")))
.flatMap(user -> {
return sysUserRepository.removeAllRolesFromUser(id)
.then(sysUserRepository.deleteById(id));
});
}
@Override
public Flux<SysUser> findAll() {
return sysUserRepository.findAll();
}
@Override
public Mono<PageModel<SysUser>> findByQueryWithPagination(SysUserQuery query, PageQuery pageQuery) {
return sysUserRepository.findByQueryWithPagination(query, pageQuery);
}
}
@@ -0,0 +1,55 @@
package io.destiny.sys.dto.request;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(description = "分配菜单到角色请求DTO")
public class AssignMenuToRoleRequest {
@Schema(description = "角色ID", example = "1234567890123456789", requiredMode = Schema.RequiredMode.REQUIRED)
private Long roleId;
@Schema(description = "菜单ID", example = "1234567890123456789", requiredMode = Schema.RequiredMode.REQUIRED)
private Long menuId;
public Long getRoleId() {
return roleId;
}
public void setRoleId(Long roleId) {
this.roleId = roleId;
}
public Long getMenuId() {
return menuId;
}
public void setMenuId(Long menuId) {
this.menuId = menuId;
}
public static Builder builder() {
return new Builder();
}
public static class Builder {
private Long roleId;
private Long menuId;
public Builder roleId(Long roleId) {
this.roleId = roleId;
return this;
}
public Builder menuId(Long menuId) {
this.menuId = menuId;
return this;
}
public AssignMenuToRoleRequest build() {
AssignMenuToRoleRequest request = new AssignMenuToRoleRequest();
request.setRoleId(roleId);
request.setMenuId(menuId);
return request;
}
}
}
@@ -0,0 +1,55 @@
package io.destiny.sys.dto.request;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(description = "分配角色到用户请求DTO")
public class AssignRoleToUserRequest {
@Schema(description = "用户ID", example = "1234567890123456789", requiredMode = Schema.RequiredMode.REQUIRED)
private Long userId;
@Schema(description = "角色ID", example = "1234567890123456789", requiredMode = Schema.RequiredMode.REQUIRED)
private Long roleId;
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public Long getRoleId() {
return roleId;
}
public void setRoleId(Long roleId) {
this.roleId = roleId;
}
public static Builder builder() {
return new Builder();
}
public static class Builder {
private Long userId;
private Long roleId;
public Builder userId(Long userId) {
this.userId = userId;
return this;
}
public Builder roleId(Long roleId) {
this.roleId = roleId;
return this;
}
public AssignRoleToUserRequest build() {
AssignRoleToUserRequest request = new AssignRoleToUserRequest();
request.setUserId(userId);
request.setRoleId(roleId);
return request;
}
}
}
@@ -0,0 +1,217 @@
package io.destiny.sys.dto.request;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(description = "菜单请求DTO")
public class SysMenuRequest {
@Schema(description = "菜单ID", example = "1234567890123456789")
private Long id;
@Schema(description = "菜单名称", example = "用户管理", requiredMode = Schema.RequiredMode.REQUIRED)
private String name;
@Schema(description = "菜单编码", example = "user")
private String code;
@Schema(description = "路由路径", example = "/system/user")
private String path;
@Schema(description = "菜单图标", example = "user")
private String icon;
@Schema(description = "父菜单ID", example = "0")
private Long parentId;
@Schema(description = "显示顺序", example = "1")
private Integer sortOrder;
@Schema(description = "菜单状态 0-正常 1-停用", example = "0")
private String status;
@Schema(description = "组件路径", example = "system/user/index")
private String component;
@Schema(description = "重定向路径", example = "/system/user/index")
private String redirect;
@Schema(description = "菜单描述", example = "用户管理模块")
private String description;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getIcon() {
return icon;
}
public void setIcon(String icon) {
this.icon = icon;
}
public Long getParentId() {
return parentId;
}
public void setParentId(Long parentId) {
this.parentId = parentId;
}
public Integer getSortOrder() {
return sortOrder;
}
public void setSortOrder(Integer sortOrder) {
this.sortOrder = sortOrder;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getComponent() {
return component;
}
public void setComponent(String component) {
this.component = component;
}
public String getRedirect() {
return redirect;
}
public void setRedirect(String redirect) {
this.redirect = redirect;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public static Builder builder() {
return new Builder();
}
public static class Builder {
private Long id;
private String name;
private String code;
private String path;
private String icon;
private Long parentId;
private Integer sortOrder;
private String status;
private String component;
private String redirect;
private String description;
public Builder id(Long id) {
this.id = id;
return this;
}
public Builder name(String name) {
this.name = name;
return this;
}
public Builder code(String code) {
this.code = code;
return this;
}
public Builder path(String path) {
this.path = path;
return this;
}
public Builder icon(String icon) {
this.icon = icon;
return this;
}
public Builder parentId(Long parentId) {
this.parentId = parentId;
return this;
}
public Builder sortOrder(Integer sortOrder) {
this.sortOrder = sortOrder;
return this;
}
public Builder status(String status) {
this.status = status;
return this;
}
public Builder component(String component) {
this.component = component;
return this;
}
public Builder redirect(String redirect) {
this.redirect = redirect;
return this;
}
public Builder description(String description) {
this.description = description;
return this;
}
public SysMenuRequest build() {
SysMenuRequest request = new SysMenuRequest();
request.setId(id);
request.setName(name);
request.setCode(code);
request.setPath(path);
request.setIcon(icon);
request.setParentId(parentId);
request.setSortOrder(sortOrder);
request.setStatus(status);
request.setComponent(component);
request.setRedirect(redirect);
request.setDescription(description);
return request;
}
}
}
@@ -0,0 +1,118 @@
package io.destiny.sys.dto.request;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
@Schema(description = "角色请求DTO")
public class SysRoleRequest {
@Schema(description = "角色ID", example = "1234567890123456789")
private Long id;
@Schema(description = "角色名称", example = "管理员", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "角色名称不能为空")
@Size(max = 30, message = "角色名称长度不能超过30个字符")
private String name;
@Schema(description = "角色权限字符串", example = "admin", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "角色权限字符串不能为空")
@Size(max = 100, message = "角色权限字符串长度不能超过100个字符")
@Pattern(regexp = "^[a-zA-Z0-9_\\-]+$", message = "角色权限字符串只能包含字母、数字、下划线和连字符")
private String roleKey;
@Schema(description = "显示顺序", example = "1")
private Integer sortOrder;
@Schema(description = "角色状态 0-正常 1-停用", example = "0")
@Pattern(regexp = "^[01]$", message = "角色状态只能是0或1")
private String status;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getRoleKey() {
return roleKey;
}
public void setRoleKey(String roleKey) {
this.roleKey = roleKey;
}
public Integer getSortOrder() {
return sortOrder;
}
public void setSortOrder(Integer sortOrder) {
this.sortOrder = sortOrder;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public static Builder builder() {
return new Builder();
}
public static class Builder {
private Long id;
private String name;
private String roleKey;
private Integer sortOrder;
private String status;
public Builder id(Long id) {
this.id = id;
return this;
}
public Builder name(String name) {
this.name = name;
return this;
}
public Builder roleKey(String roleKey) {
this.roleKey = roleKey;
return this;
}
public Builder sortOrder(Integer sortOrder) {
this.sortOrder = sortOrder;
return this;
}
public Builder status(String status) {
this.status = status;
return this;
}
public SysRoleRequest build() {
SysRoleRequest request = new SysRoleRequest();
request.setId(id);
request.setName(name);
request.setRoleKey(roleKey);
request.setSortOrder(sortOrder);
request.setStatus(status);
return request;
}
}
}
@@ -0,0 +1,38 @@
package io.destiny.sys.dto.request;
import jakarta.validation.constraints.NotBlank;
public class SysUserLoginRequest {
@NotBlank(message = "用户名不能为空")
private String username;
@NotBlank(message = "密码不能为空")
private String password;
private String ipAddress;
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 getIpAddress() {
return ipAddress;
}
public void setIpAddress(String ipAddress) {
this.ipAddress = ipAddress;
}
}
@@ -0,0 +1,138 @@
package io.destiny.sys.dto.request;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
@Schema(description = "用户请求DTO")
public class SysUserRequest {
@Schema(description = "用户ID", example = "1234567890123456789")
private Long id;
@Schema(description = "用户名", example = "zhangsan", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 50, message = "用户名长度必须在3-50个字符之间")
private String username;
@Schema(description = "密码", example = "password123")
@Size(min = 6, max = 100, message = "密码长度必须在6-100个字符之间")
@Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*\\d).+$", message = "密码必须包含字母和数字")
private String password;
@Schema(description = "邮箱", example = "zhangsan@example.com")
@Email(message = "邮箱格式不正确")
private String email;
@Schema(description = "手机号", example = "13800138000")
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
@Schema(description = "状态 0-正常 1-禁用", example = "0")
@Pattern(regexp = "^[01]$", message = "状态必须是0或1")
private String status;
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 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 String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public static Builder builder() {
return new Builder();
}
public static class Builder {
private Long id;
private String username;
private String password;
private String email;
private String phone;
private String status;
public Builder id(Long id) {
this.id = id;
return this;
}
public Builder username(String username) {
this.username = username;
return this;
}
public Builder password(String password) {
this.password = password;
return this;
}
public Builder email(String email) {
this.email = email;
return this;
}
public Builder phone(String phone) {
this.phone = phone;
return this;
}
public Builder status(String status) {
this.status = status;
return this;
}
public SysUserRequest build() {
SysUserRequest request = new SysUserRequest();
request.setId(id);
request.setUsername(username);
request.setPassword(password);
request.setEmail(email);
request.setPhone(phone);
request.setStatus(status);
return request;
}
}
}
@@ -0,0 +1,71 @@
package io.destiny.sys.dto.response;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.destiny.sys.core.domain.SysUser;
import java.util.Set;
public class SysLoginResponse {
@JsonProperty("token")
private String token;
@JsonProperty("refreshToken")
private String refreshToken;
@JsonProperty("user")
private SysUserResponse user;
@JsonProperty("permissions")
private Set<String> permissions;
public SysLoginResponse() {
}
public SysLoginResponse(String token, String refreshToken, SysUser user, Set<String> permissions) {
this.token = token;
this.refreshToken = refreshToken;
this.user = SysUserResponse.fromSysUser(user);
this.permissions = permissions;
}
public static SysLoginResponse from(String token, String refreshToken, SysUser user) {
return new SysLoginResponse(token, refreshToken, user, null);
}
public static SysLoginResponse from(String token, String refreshToken, SysUser user, Set<String> permissions) {
return new SysLoginResponse(token, refreshToken, user, permissions);
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public String getRefreshToken() {
return refreshToken;
}
public void setRefreshToken(String refreshToken) {
this.refreshToken = refreshToken;
}
public SysUserResponse getUser() {
return user;
}
public void setUser(SysUserResponse user) {
this.user = user;
}
public Set<String> getPermissions() {
return permissions;
}
public void setPermissions(Set<String> permissions) {
this.permissions = permissions;
}
}
@@ -0,0 +1,304 @@
package io.destiny.sys.dto.response;
import io.destiny.sys.core.domain.SysMenu;
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "菜单响应DTO")
public class SysMenuResponse {
@Schema(description = "菜单ID", example = "1234567890123456789")
private Long id;
@Schema(description = "菜单名称", example = "用户管理")
private String name;
@Schema(description = "菜单编码", example = "user")
private String code;
@Schema(description = "路由路径", example = "/system/user")
private String path;
@Schema(description = "菜单图标", example = "user")
private String icon;
@Schema(description = "父菜单ID", example = "0")
private Long parentId;
@Schema(description = "显示顺序", example = "1")
private Integer sortOrder;
@Schema(description = "菜单状态 0-正常 1-停用", example = "0")
private String status;
@Schema(description = "组件路径", example = "system/user/index")
private String component;
@Schema(description = "重定向路径", example = "/system/user/index")
private String redirect;
@Schema(description = "菜单描述", example = "用户管理模块")
private String description;
@Schema(description = "子菜单列表")
private List<SysMenuResponse> children;
@Schema(description = "创建时间", example = "2024-01-01T00:00:00")
private LocalDateTime createdAt;
@Schema(description = "更新时间", example = "2024-01-01T00:00:00")
private LocalDateTime updatedAt;
public SysMenuResponse() {
}
public SysMenuResponse(Long id, String name, String code, String path, String icon, Long parentId, Integer sortOrder,
String status, String component, String redirect, String description, List<SysMenuResponse> children,
LocalDateTime createdAt, LocalDateTime updatedAt) {
this.id = id;
this.name = name;
this.code = code;
this.path = path;
this.icon = icon;
this.parentId = parentId;
this.sortOrder = sortOrder;
this.status = status;
this.component = component;
this.redirect = redirect;
this.description = description;
this.children = children;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getIcon() {
return icon;
}
public void setIcon(String icon) {
this.icon = icon;
}
public Long getParentId() {
return parentId;
}
public void setParentId(Long parentId) {
this.parentId = parentId;
}
public Integer getSortOrder() {
return sortOrder;
}
public void setSortOrder(Integer sortOrder) {
this.sortOrder = sortOrder;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getComponent() {
return component;
}
public void setComponent(String component) {
this.component = component;
}
public String getRedirect() {
return redirect;
}
public void setRedirect(String redirect) {
this.redirect = redirect;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public List<SysMenuResponse> getChildren() {
return children;
}
public void setChildren(List<SysMenuResponse> children) {
this.children = children;
}
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 static SysMenuResponse fromSysMenu(SysMenu sysMenu) {
if (sysMenu == null) {
return null;
}
return new SysMenuResponse(
sysMenu.getId(),
sysMenu.getMenuName(),
null,
null,
null,
sysMenu.getParentId(),
sysMenu.getOrderNum(),
sysMenu.getStatus(),
sysMenu.getComponent(),
null,
null,
null,
sysMenu.getCreatedAt(),
sysMenu.getUpdatedAt());
}
public static Builder builder() {
return new Builder();
}
public static class Builder {
private Long id;
private String name;
private String code;
private String path;
private String icon;
private Long parentId;
private Integer sortOrder;
private String status;
private String component;
private String redirect;
private String description;
private List<SysMenuResponse> children;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public Builder id(Long id) {
this.id = id;
return this;
}
public Builder name(String name) {
this.name = name;
return this;
}
public Builder code(String code) {
this.code = code;
return this;
}
public Builder path(String path) {
this.path = path;
return this;
}
public Builder icon(String icon) {
this.icon = icon;
return this;
}
public Builder parentId(Long parentId) {
this.parentId = parentId;
return this;
}
public Builder sortOrder(Integer sortOrder) {
this.sortOrder = sortOrder;
return this;
}
public Builder status(String status) {
this.status = status;
return this;
}
public Builder component(String component) {
this.component = component;
return this;
}
public Builder redirect(String redirect) {
this.redirect = redirect;
return this;
}
public Builder description(String description) {
this.description = description;
return this;
}
public Builder children(List<SysMenuResponse> children) {
this.children = children;
return this;
}
public Builder createdAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
return this;
}
public Builder updatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
return this;
}
public SysMenuResponse build() {
return new SysMenuResponse(id, name, code, path, icon, parentId, sortOrder, status, component, redirect,
description, children, createdAt, updatedAt);
}
}
}
@@ -0,0 +1,227 @@
package io.destiny.sys.dto.response;
import io.destiny.sys.core.domain.SysRole;
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "角色响应DTO")
public class SysRoleResponse {
@Schema(description = "角色ID", example = "1234567890123456789")
private Long id;
@Schema(description = "角色名称", example = "管理员")
private String name;
@Schema(description = "角色权限字符串", example = "admin")
private String roleKey;
@Schema(description = "角色描述", example = "系统管理员角色")
private String description;
@Schema(description = "角色状态 0-正常 1-停用", example = "0")
private String status;
@Schema(description = "显示顺序", example = "1")
private Integer sortOrder;
@Schema(description = "备注", example = "管理员角色")
private String remark;
@Schema(description = "菜单ID列表")
private List<Long> menuIds;
@Schema(description = "创建时间", example = "2024-01-01T00:00:00")
private LocalDateTime createdAt;
@Schema(description = "更新时间", example = "2024-01-01T00:00:00")
private LocalDateTime updatedAt;
public SysRoleResponse() {
}
public SysRoleResponse(Long id, String name, String roleKey, String description, String status, Integer sortOrder,
String remark, List<Long> menuIds, LocalDateTime createdAt, LocalDateTime updatedAt) {
this.id = id;
this.name = name;
this.roleKey = roleKey;
this.description = description;
this.status = status;
this.sortOrder = sortOrder;
this.remark = remark;
this.menuIds = menuIds;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getRoleKey() {
return roleKey;
}
public void setRoleKey(String roleKey) {
this.roleKey = roleKey;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public Integer getSortOrder() {
return sortOrder;
}
public void setSortOrder(Integer sortOrder) {
this.sortOrder = sortOrder;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
public List<Long> getMenuIds() {
return menuIds;
}
public void setMenuIds(List<Long> menuIds) {
this.menuIds = menuIds;
}
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 static SysRoleResponse fromSysRole(SysRole sysRole) {
if (sysRole == null) {
return null;
}
return new SysRoleResponse(
sysRole.getId(),
sysRole.getRoleName(),
sysRole.getRoleKey(),
null,
sysRole.getStatus(),
sysRole.getRoleSort(),
null,
null,
sysRole.getCreatedAt(),
sysRole.getUpdatedAt());
}
public static Builder builder() {
return new Builder();
}
public static class Builder {
private Long id;
private String name;
private String roleKey;
private String description;
private String status;
private Integer sortOrder;
private String remark;
private List<Long> menuIds;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public Builder id(Long id) {
this.id = id;
return this;
}
public Builder name(String name) {
this.name = name;
return this;
}
public Builder roleKey(String roleKey) {
this.roleKey = roleKey;
return this;
}
public Builder description(String description) {
this.description = description;
return this;
}
public Builder status(String status) {
this.status = status;
return this;
}
public Builder sortOrder(Integer sortOrder) {
this.sortOrder = sortOrder;
return this;
}
public Builder remark(String remark) {
this.remark = remark;
return this;
}
public Builder menuIds(List<Long> menuIds) {
this.menuIds = menuIds;
return this;
}
public Builder createdAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
return this;
}
public Builder updatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
return this;
}
public SysRoleResponse build() {
return new SysRoleResponse(id, name, roleKey, description, status, sortOrder, remark, menuIds, createdAt,
updatedAt);
}
}
}
@@ -0,0 +1,67 @@
package io.destiny.sys.dto.response;
import io.destiny.sys.core.domain.SysUser;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
public class SysUserRegisterRequest {
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 50, message = "用户名长度必须在3-50个字符之间")
private String username;
@NotBlank(message = "密码不能为空")
@Size(min = 6, max = 100, message = "密码长度必须在6-100个字符之间")
@Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*\\d).+$", message = "密码必须包含字母和数字")
private String password;
@Email(message = "邮箱格式不正确")
private String email;
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
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 String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public SysUser toDomain() {
SysUser user = new SysUser();
user.setUsername(this.username);
user.setPassword(this.password);
user.setEmail(this.email != null ? io.destiny.common.primitive.EmailAddress.of(this.email) : null);
user.setPhone(this.phone != null ? io.destiny.common.primitive.PhoneNumber.of(this.phone) : null);
user.setStatus("0");
return user;
}
}
@@ -0,0 +1,290 @@
package io.destiny.sys.dto.response;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import io.destiny.common.serializer.SensitiveDataJsonSerializer;
import io.destiny.sys.core.domain.SysUser;
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Set;
@Schema(description = "用户响应DTO")
public class SysUserResponse {
@Schema(description = "用户ID", example = "1234567890123456789")
private Long id;
@Schema(description = "用户名", example = "zhangsan")
private String username;
@Schema(description = "邮箱", example = "zhangsan@example.com")
@JsonSerialize(using = SensitiveDataJsonSerializer.class)
private String email;
@Schema(description = "手机号", example = "13800138000")
@JsonSerialize(using = SensitiveDataJsonSerializer.class)
private String phone;
@Schema(description = "昵称", example = "张三")
private String nickname;
@Schema(description = "状态 0-正常 1-禁用", example = "0")
private String status;
@Schema(description = "备注", example = "系统管理员")
private String remark;
@Schema(description = "权限列表")
private Set<String> permissions;
@Schema(description = "角色列表")
private List<SysRoleResponse> roles;
@Schema(description = "创建人", example = "admin")
private String createBy;
@Schema(description = "更新人", example = "admin")
private String updateBy;
@Schema(description = "创建时间", example = "2024-01-01T00:00:00")
private LocalDateTime createdAt;
@Schema(description = "更新时间", example = "2024-01-01T00:00:00")
private LocalDateTime updatedAt;
public SysUserResponse() {
}
public SysUserResponse(Long id, String username, String email, String phone, String nickname, String status,
String remark, Set<String> permissions, List<SysRoleResponse> roles, String createBy, String updateBy,
LocalDateTime createdAt, LocalDateTime updatedAt) {
this.id = id;
this.username = username;
this.email = email;
this.phone = phone;
this.nickname = nickname;
this.status = status;
this.remark = remark;
this.permissions = permissions;
this.roles = roles;
this.createBy = createBy;
this.updateBy = updateBy;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
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 getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
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 Set<String> getPermissions() {
return permissions;
}
public void setPermissions(Set<String> permissions) {
this.permissions = permissions;
}
public List<SysRoleResponse> getRoles() {
return roles;
}
public void setRoles(List<SysRoleResponse> roles) {
this.roles = roles;
}
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 static SysUserResponse fromSysUser(SysUser sysUser) {
if (sysUser == null) {
return null;
}
return new SysUserResponse(
sysUser.getId(),
sysUser.getUsername(),
sysUser.getEmail() != null ? sysUser.getEmail().getValue() : null,
sysUser.getPhone() != null ? sysUser.getPhone().getValue() : null,
null,
sysUser.getStatus(),
null,
null,
null,
sysUser.getCreateBy(),
sysUser.getUpdateBy(),
sysUser.getCreatedAt(),
sysUser.getUpdatedAt());
}
public static Builder builder() {
return new Builder();
}
public static class Builder {
private Long id;
private String username;
private String email;
private String phone;
private String nickname;
private String status;
private String remark;
private Set<String> permissions;
private List<SysRoleResponse> roles;
private String createBy;
private String updateBy;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public Builder id(Long id) {
this.id = id;
return this;
}
public Builder username(String username) {
this.username = username;
return this;
}
public Builder email(String email) {
this.email = email;
return this;
}
public Builder phone(String phone) {
this.phone = phone;
return this;
}
public Builder nickname(String nickname) {
this.nickname = nickname;
return this;
}
public Builder status(String status) {
this.status = status;
return this;
}
public Builder remark(String remark) {
this.remark = remark;
return this;
}
public Builder permissions(Set<String> permissions) {
this.permissions = permissions;
return this;
}
public Builder roles(List<SysRoleResponse> roles) {
this.roles = roles;
return this;
}
public Builder createBy(String createBy) {
this.createBy = createBy;
return this;
}
public Builder updateBy(String updateBy) {
this.updateBy = updateBy;
return this;
}
public Builder createdAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
return this;
}
public Builder updatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
return this;
}
public SysUserResponse build() {
return new SysUserResponse(id, username, email, phone, nickname, status, remark, permissions, roles, createBy,
updateBy, createdAt, updatedAt);
}
}
}
@@ -0,0 +1,117 @@
package io.destiny.sys.handler;
import io.destiny.common.response.Result;
import io.destiny.sys.core.domain.query.OperationLogQuery;
import io.destiny.sys.core.service.IOperationLogService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
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;
import java.util.HashMap;
import java.util.Map;
@Component
@Tag(name = "操作日志", description = "操作日志相关接口")
public class OperationLogHandler {
private static final Logger log = LoggerFactory.getLogger(OperationLogHandler.class);
private final IOperationLogService logService;
public OperationLogHandler(IOperationLogService logService) {
this.logService = logService;
}
@Operation(summary = "根据ID查询操作日志", description = "根据日志ID查询操作日志详细信息")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "查询成功", content = @Content(schema = @Schema(implementation = Result.class))),
@ApiResponse(responseCode = "400", description = "请求参数错误"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
public Mono<ServerResponse> getLogById(ServerRequest request) {
Long id = Long.parseLong(request.pathVariable("id"));
return logService.findById(id)
.flatMap(log -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
.bodyValue(Result.success(log)))
.switchIfEmpty(ServerResponse.badRequest().bodyValue(Result.error("400", "操作日志不存在")))
.onErrorResume(e -> {
log.error("查询操作日志失败,ID: {}", id, e);
return ServerResponse.badRequest()
.bodyValue(Result.error("500", "查询操作日志失败:" + e.getMessage()));
});
}
@Operation(summary = "分页查询操作日志", description = "根据条件分页查询操作日志列表")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "查询成功", content = @Content(schema = @Schema(implementation = Result.class))),
@ApiResponse(responseCode = "400", description = "请求参数错误"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
public Mono<ServerResponse> queryLogs(ServerRequest request) {
return request.bodyToMono(OperationLogQuery.class)
.flatMap(query -> {
if (query.getPageNum() == null) {
query.setPageNum(1);
}
if (query.getPageSize() == null) {
query.setPageSize(10);
}
return logService.findByQuery(query)
.collectList()
.zipWith(logService.countByQuery(query))
.map(tuple -> {
Map<String, Object> result = new HashMap<>();
result.put("list", tuple.getT1());
result.put("total", tuple.getT2());
result.put("pageNum", query.getPageNum());
result.put("pageSize", query.getPageSize());
return Result.success(result);
})
.flatMap(result -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
.bodyValue(result));
})
.onErrorResume(e -> {
log.error("查询操作日志列表失败", e);
return ServerResponse.badRequest()
.bodyValue(Result.error("查询操作日志列表失败:" + e.getMessage()));
});
}
@Operation(summary = "删除操作日志", description = "根据ID删除操作日志")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "删除成功", content = @Content(schema = @Schema(implementation = Result.class))),
@ApiResponse(responseCode = "400", description = "请求参数错误"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
public Mono<ServerResponse> deleteLog(ServerRequest request) {
Long id = Long.parseLong(request.pathVariable("id"));
return logService.findById(id)
.hasElement()
.flatMap(exists -> {
if (!exists) {
return ServerResponse.badRequest()
.bodyValue(Result.error("400", "操作日志不存在"));
}
return logService.deleteById(id)
.then(Mono.fromCallable(() -> Result.success(null)))
.flatMap(result -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
.bodyValue(result));
})
.onErrorResume(e -> {
log.error("删除操作日志失败,ID: {}", id, e);
return ServerResponse.badRequest()
.bodyValue(Result.error("500", "删除操作日志失败:" + e.getMessage()));
});
}
}
@@ -0,0 +1,111 @@
package io.destiny.sys.handler;
import io.destiny.common.response.Result;
import io.destiny.sys.core.service.ISysAuthService;
import io.destiny.sys.dto.request.SysUserLoginRequest;
import io.destiny.sys.dto.response.SysLoginResponse;
import io.destiny.sys.dto.response.SysUserRegisterRequest;
import io.destiny.sys.util.ValidationUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
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
@Tag(name = "认证管理", description = "用户认证相关接口")
public class SysAuthHandler {
private static final Logger log = LoggerFactory.getLogger(SysAuthHandler.class);
private final ISysAuthService authService;
public SysAuthHandler(ISysAuthService authService) {
this.authService = authService;
}
@Operation(summary = "用户注册", description = "新用户注册,需要提供用户名、密码、邮箱和手机号")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "注册成功", content = @Content(schema = @Schema(implementation = Result.class))),
@ApiResponse(responseCode = "400", description = "请求参数错误"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
public Mono<ServerResponse> register(ServerRequest request) {
return request.bodyToMono(SysUserRegisterRequest.class)
.flatMap(ValidationUtil::validate)
.flatMap(authService::register)
.map(Result::success)
.flatMap(result -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValue(result))
.onErrorResume(e -> {
log.error("用户注册失败", e);
return ServerResponse.badRequest().contentType(MediaType.APPLICATION_JSON)
.bodyValue(Result.error("400", e.getMessage()));
});
}
@Operation(summary = "用户登录", description = "用户登录获取访问令牌和刷新令牌")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "登录成功", content = @Content(schema = @Schema(implementation = Result.class))),
@ApiResponse(responseCode = "401", description = "用户名或密码错误"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
public Mono<ServerResponse> login(ServerRequest request) {
return request.bodyToMono(SysUserLoginRequest.class)
.flatMap(ValidationUtil::validate)
.flatMap(authService::login)
.map(Result::success)
.flatMap(result -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValue(result))
.onErrorResume(e -> {
log.error("用户登录失败", e);
return ServerResponse.status(401).contentType(MediaType.APPLICATION_JSON)
.bodyValue(Result.error("401", e.getMessage()));
});
}
@Operation(summary = "刷新token", description = "使用刷新令牌获取新的访问令牌")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "刷新成功", content = @Content(schema = @Schema(implementation = Result.class))),
@ApiResponse(responseCode = "401", description = "刷新令牌无效或已过期"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
public Mono<ServerResponse> refreshToken(ServerRequest request) {
String refreshToken = request.pathVariable("token");
return authService.refreshToken(refreshToken)
.map(Result::success)
.flatMap(result -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValue(result))
.onErrorResume(e -> {
log.error("刷新令牌失败", e);
return ServerResponse.status(401).contentType(MediaType.APPLICATION_JSON)
.bodyValue(Result.error("401", e.getMessage()));
});
}
@Operation(summary = "用户登出", description = "用户登出,使当前令牌失效")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "登出成功"),
@ApiResponse(responseCode = "400", description = "请求参数错误"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
public Mono<ServerResponse> logout(ServerRequest request) {
String token = request.headers().firstHeader("Authorization");
if (token != null && token.startsWith("Bearer ")) {
token = token.substring(7);
}
return authService.logout(token)
.then(Mono.fromCallable(() -> Result.success("登出成功")))
.flatMap(result -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValue(result))
.onErrorResume(e -> {
log.error("用户登出失败", e);
return ServerResponse.badRequest().contentType(MediaType.APPLICATION_JSON)
.bodyValue(Result.error("400", e.getMessage()));
});
}
}
@@ -0,0 +1,288 @@
package io.destiny.sys.handler;
import io.destiny.common.response.Result;
import io.destiny.sys.core.domain.SysMenu;
import io.destiny.sys.core.domain.SysRole;
import io.destiny.sys.core.service.ISysMenuService;
import io.destiny.sys.core.service.ISysRoleService;
import io.destiny.sys.dto.request.SysMenuRequest;
import io.destiny.sys.dto.response.SysMenuResponse;
import io.destiny.sys.security.RequirePermission;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
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
@Tag(name = "菜单管理", description = "菜单管理相关接口")
public class SysMenuHandler {
private static final Logger log = LoggerFactory.getLogger(SysMenuHandler.class);
private final ISysMenuService menuService;
private final ISysRoleService roleService;
public SysMenuHandler(ISysMenuService menuService, ISysRoleService roleService) {
this.menuService = menuService;
this.roleService = roleService;
}
@Operation(summary = "根据ID查询菜单", description = "根据菜单ID查询菜单详细信息")
@RequirePermission("sys:menu:query")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "查询成功", content = @Content(schema = @Schema(implementation = Result.class))),
@ApiResponse(responseCode = "400", description = "请求参数错误"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
public Mono<ServerResponse> getMenuById(ServerRequest request) {
Long id = Long.parseLong(request.pathVariable("id"));
return menuService.findById(id)
.map(SysMenuResponse::fromSysMenu)
.map(Result::success)
.flatMap(result -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
.bodyValue(result))
.switchIfEmpty(Mono.defer(() -> ServerResponse.badRequest()
.bodyValue(Result.error("菜单不存在"))))
.onErrorResume(e -> {
log.error("查询菜单失败,ID: {}", id, e);
return ServerResponse.badRequest()
.bodyValue(Result.error("查询菜单失败:" + e.getMessage()));
});
}
@Operation(summary = "根据角色ID查询菜单列表", description = "根据角色ID查询该角色拥有的所有菜单")
@RequirePermission("sys:menu:query")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "查询成功", content = @Content(schema = @Schema(implementation = Result.class))),
@ApiResponse(responseCode = "400", description = "请求参数错误"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
public Mono<ServerResponse> getMenusByRoleId(ServerRequest request) {
Long roleId = Long.parseLong(request.pathVariable("roleId"));
return menuService.findByRoleId(roleId)
.map(SysMenuResponse::fromSysMenu)
.collectList()
.map(Result::success)
.flatMap(result -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
.bodyValue(result))
.onErrorResume(e -> {
log.error("查询角色菜单失败,角色ID: {}", roleId, e);
return ServerResponse.badRequest()
.bodyValue(Result.error("查询角色菜单失败:" + e.getMessage()));
});
}
@Operation(summary = "根据用户ID查询菜单列表", description = "根据用户ID查询该用户拥有的所有菜单")
@RequirePermission("sys:menu:query")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "查询成功", content = @Content(schema = @Schema(implementation = Result.class))),
@ApiResponse(responseCode = "400", description = "请求参数错误"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
public Mono<ServerResponse> getMenusByUserId(ServerRequest request) {
Long userId = Long.parseLong(request.pathVariable("userId"));
return menuService.findByUserId(userId)
.map(SysMenuResponse::fromSysMenu)
.collectList()
.map(Result::success)
.flatMap(result -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
.bodyValue(result))
.onErrorResume(e -> {
log.error("查询用户菜单失败,用户ID: {}", userId, e);
return ServerResponse.badRequest()
.bodyValue(Result.error("查询用户菜单失败:" + e.getMessage()));
});
}
@Operation(summary = "查询所有菜单", description = "查询系统中的所有菜单")
@RequirePermission("sys:menu:query")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "查询成功", content = @Content(schema = @Schema(implementation = Result.class))),
@ApiResponse(responseCode = "400", description = "请求参数错误"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
public Mono<ServerResponse> getAllMenus(ServerRequest request) {
return menuService.findAll()
.map(SysMenuResponse::fromSysMenu)
.collectList()
.map(Result::success)
.flatMap(result -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
.bodyValue(result))
.onErrorResume(e -> {
log.error("查询所有菜单失败", e);
return ServerResponse.badRequest()
.bodyValue(Result.error("查询所有菜单失败:" + e.getMessage()));
});
}
@Operation(summary = "创建菜单", description = "创建新菜单")
@RequirePermission("sys:menu:create")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "创建成功", content = @Content(schema = @Schema(implementation = Result.class))),
@ApiResponse(responseCode = "400", description = "请求参数错误"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
public Mono<ServerResponse> createMenu(ServerRequest request) {
return request.bodyToMono(SysMenuRequest.class)
.flatMap(menuRequest -> {
SysMenu sysMenu = new SysMenu();
sysMenu.setMenuName(menuRequest.getName());
sysMenu.setParentId(menuRequest.getParentId());
sysMenu.setOrderNum(menuRequest.getSortOrder());
sysMenu.setMenuType(menuRequest.getCode());
sysMenu.setPerms(menuRequest.getCode());
sysMenu.setComponent(menuRequest.getComponent());
sysMenu.setStatus(menuRequest.getStatus());
sysMenu.generateId();
return menuService.create(sysMenu);
})
.map(SysMenuResponse::fromSysMenu)
.map(Result::success)
.flatMap(result -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
.bodyValue(result))
.onErrorResume(e -> {
log.error("创建菜单失败", e);
return ServerResponse.badRequest()
.bodyValue(Result.error("创建菜单失败:" + e.getMessage()));
});
}
@Operation(summary = "更新菜单", description = "更新菜单信息")
@RequirePermission("sys:menu:update")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "更新成功", content = @Content(schema = @Schema(implementation = Result.class))),
@ApiResponse(responseCode = "400", description = "请求参数错误"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
public Mono<ServerResponse> updateMenu(ServerRequest request) {
return request.bodyToMono(SysMenuRequest.class)
.flatMap(menuRequest -> {
return menuService.findById(menuRequest.getId())
.flatMap(sysMenu -> {
sysMenu.setMenuName(menuRequest.getName());
sysMenu.setParentId(menuRequest.getParentId());
sysMenu.setOrderNum(menuRequest.getSortOrder());
sysMenu.setMenuType(menuRequest.getCode());
sysMenu.setPerms(menuRequest.getCode());
sysMenu.setComponent(menuRequest.getComponent());
sysMenu.setStatus(menuRequest.getStatus());
return menuService.update(sysMenu);
})
.map(SysMenuResponse::fromSysMenu)
.map(Result::success)
.flatMap(result -> ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(result));
})
.switchIfEmpty(Mono.defer(() -> ServerResponse.badRequest()
.bodyValue(Result.error("菜单不存在"))))
.onErrorResume(e -> {
log.error("更新菜单失败", e);
return ServerResponse.badRequest()
.bodyValue(Result.error("更新菜单失败:" + e.getMessage()));
});
}
@Operation(summary = "删除菜单", description = "根据ID删除菜单")
@RequirePermission("sys:menu:delete")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "删除成功", content = @Content(schema = @Schema(implementation = Result.class))),
@ApiResponse(responseCode = "400", description = "请求参数错误"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
public Mono<ServerResponse> deleteMenu(ServerRequest request) {
Long id = Long.parseLong(request.pathVariable("id"));
return menuService.deleteById(id)
.then(Mono.fromCallable(() -> Result.success(null)))
.flatMap(result -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
.bodyValue(result))
.onErrorResume(e -> {
log.error("删除菜单失败,ID: {}", id, e);
return ServerResponse.badRequest()
.bodyValue(Result.error("删除菜单失败:" + e.getMessage()));
});
}
@Operation(summary = "分配菜单到角色", description = "将菜单分配给角色")
@RequirePermission("sys:menu:assign")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "分配成功", content = @Content(schema = @Schema(implementation = Result.class))),
@ApiResponse(responseCode = "400", description = "请求参数错误"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
public Mono<ServerResponse> assignMenuToRole(ServerRequest request) {
return request.bodyToMono(io.destiny.sys.dto.request.AssignMenuToRoleRequest.class)
.flatMap(assignRequest -> {
Long roleId = assignRequest.getRoleId();
Long menuId = assignRequest.getMenuId();
return roleService.findById(roleId)
.switchIfEmpty(Mono.error(new IllegalArgumentException("角色不存在")))
.flatMap(role -> {
return menuService.findById(menuId)
.switchIfEmpty(Mono.error(new IllegalArgumentException("菜单不存在")))
.flatMap(menu -> {
return menuService.findByRoleId(roleId)
.filter(roleMenu -> roleMenu.getId().equals(menuId))
.hasElements()
.flatMap(hasMenu -> {
if (hasMenu) {
return Mono.error(new IllegalArgumentException("角色已拥有该菜单权限"));
}
return menuService.assignMenuToRole(roleId, menuId);
});
});
});
})
.then(Mono.fromCallable(() -> Result.success(null)))
.flatMap(result -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
.bodyValue(result))
.onErrorResume(e -> {
log.error("分配菜单到角色失败", e);
String errorMessage = e.getMessage();
if (errorMessage != null && errorMessage.contains("角色不存在")) {
return ServerResponse.badRequest()
.bodyValue(Result.error("角色不存在"));
}
if (errorMessage != null && errorMessage.contains("菜单不存在")) {
return ServerResponse.badRequest()
.bodyValue(Result.error("菜单不存在"));
}
if (errorMessage != null && errorMessage.contains("角色已拥有该菜单权限")) {
return ServerResponse.badRequest()
.bodyValue(Result.error("角色已拥有该菜单权限"));
}
return ServerResponse.badRequest()
.bodyValue(Result.error("分配菜单到角色失败:" + e.getMessage()));
});
}
@Operation(summary = "移除角色的菜单", description = "从角色中移除菜单")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "移除成功", content = @Content(schema = @Schema(implementation = Result.class))),
@ApiResponse(responseCode = "400", description = "请求参数错误"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
public Mono<ServerResponse> removeMenuFromRole(ServerRequest request) {
Long roleId = Long.parseLong(request.pathVariable("roleId"));
Long menuId = Long.parseLong(request.pathVariable("menuId"));
return menuService.removeMenuFromRole(roleId, menuId)
.then(Mono.fromCallable(() -> Result.success(null)))
.flatMap(result -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
.bodyValue(result))
.onErrorResume(e -> {
log.error("移除角色菜单失败,角色ID: {}, 菜单ID: {}", roleId, menuId, e);
return ServerResponse.badRequest()
.bodyValue(Result.error("移除角色菜单失败:" + e.getMessage()));
});
}
}
@@ -0,0 +1,282 @@
package io.destiny.sys.handler;
import io.destiny.common.response.Result;
import io.destiny.sys.core.domain.SysRole;
import io.destiny.sys.core.service.ISysRoleService;
import io.destiny.sys.dto.request.SysRoleRequest;
import io.destiny.sys.dto.response.SysRoleResponse;
import io.destiny.sys.security.RequirePermission;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
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
@Tag(name = "角色管理", description = "角色管理相关接口")
public class SysRoleHandler {
private static final Logger log = LoggerFactory.getLogger(SysRoleHandler.class);
private final ISysRoleService roleService;
public SysRoleHandler(ISysRoleService roleService) {
this.roleService = roleService;
}
@Operation(summary = "根据ID查询角色", description = "根据角色ID查询角色详细信息")
@RequirePermission("sys:role:query")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "查询成功", content = @Content(schema = @Schema(implementation = Result.class))),
@ApiResponse(responseCode = "400", description = "请求参数错误"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
public Mono<ServerResponse> getRoleById(ServerRequest request) {
Long id = Long.parseLong(request.pathVariable("id"));
return roleService.findById(id)
.map(SysRoleResponse::fromSysRole)
.map(Result::success)
.flatMap(result -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValue(result))
.switchIfEmpty(Mono.defer(() -> ServerResponse.badRequest()
.bodyValue(Result.error("角色不存在"))))
.onErrorResume(e -> {
log.error("查询角色失败,ID: {}", id, e);
return ServerResponse.badRequest()
.bodyValue(Result.error("查询角色失败:" + e.getMessage()));
});
}
@Operation(summary = "根据角色权限字符串查询角色", description = "根据角色权限字符串查询角色详细信息")
@RequirePermission("sys:role:query")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "查询成功", content = @Content(schema = @Schema(implementation = Result.class))),
@ApiResponse(responseCode = "400", description = "请求参数错误"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
public Mono<ServerResponse> getRoleByRoleKey(ServerRequest request) {
String roleKey = request.pathVariable("roleKey");
return roleService.findByRoleKey(roleKey)
.map(SysRoleResponse::fromSysRole)
.map(Result::success)
.flatMap(result -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValue(result))
.switchIfEmpty(Mono.defer(() -> ServerResponse.badRequest()
.bodyValue(Result.error("角色不存在"))))
.onErrorResume(e -> {
log.error("查询角色失败,角色权限字符串: {}", roleKey, e);
return ServerResponse.badRequest()
.bodyValue(Result.error("查询角色失败:" + e.getMessage()));
});
}
@Operation(summary = "根据用户ID查询角色列表", description = "根据用户ID查询该用户拥有的所有角色")
@RequirePermission("sys:role:query")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "查询成功", content = @Content(schema = @Schema(implementation = Result.class))),
@ApiResponse(responseCode = "400", description = "请求参数错误"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
public Mono<ServerResponse> getRolesByUserId(ServerRequest request) {
Long userId = Long.parseLong(request.pathVariable("userId"));
return roleService.findByUserId(userId)
.map(SysRoleResponse::fromSysRole)
.collectList()
.map(Result::success)
.flatMap(result -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValue(result))
.onErrorResume(e -> {
log.error("查询用户角色失败,用户ID: {}", userId, e);
return ServerResponse.badRequest()
.bodyValue(Result.error("查询用户角色失败:" + e.getMessage()));
});
}
@Operation(summary = "查询所有角色", description = "查询系统中的所有角色")
@RequirePermission("sys:role:query")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "查询成功", content = @Content(schema = @Schema(implementation = Result.class))),
@ApiResponse(responseCode = "400", description = "请求参数错误"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
public Mono<ServerResponse> getAllRoles(ServerRequest request) {
return roleService.findAll()
.map(SysRoleResponse::fromSysRole)
.collectList()
.map(Result::success)
.flatMap(result -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValue(result))
.onErrorResume(e -> {
log.error("查询所有角色失败", e);
return ServerResponse.badRequest()
.bodyValue(Result.error("查询所有角色失败:" + e.getMessage()));
});
}
@Operation(summary = "创建角色", description = "创建新角色")
@RequirePermission("sys:role:create")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "创建成功", content = @Content(schema = @Schema(implementation = Result.class))),
@ApiResponse(responseCode = "400", description = "请求参数错误"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
public Mono<ServerResponse> createRole(ServerRequest request) {
return request.bodyToMono(SysRoleRequest.class)
.flatMap(roleRequest -> {
return roleService.findByRoleKey(roleRequest.getRoleKey())
.flatMap(existingRole -> {
return Mono.error(new IllegalArgumentException("角色权限字符串已存在"));
})
.switchIfEmpty(Mono.defer(() -> {
SysRole sysRole = new SysRole();
sysRole.setRoleName(roleRequest.getName());
sysRole.setRoleKey(roleRequest.getRoleKey());
sysRole.setRoleSort(roleRequest.getSortOrder());
sysRole.setStatus(roleRequest.getStatus());
sysRole.generateId();
return roleService.create(sysRole);
}));
})
.cast(SysRole.class)
.map(SysRoleResponse::fromSysRole)
.map(Result::success)
.flatMap(result -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValue(result))
.onErrorResume(e -> {
log.error("创建角色失败", e);
String errorMessage = e.getMessage();
if (errorMessage != null && errorMessage.contains("角色权限字符串已存在")) {
return ServerResponse.badRequest()
.bodyValue(Result.error("角色权限字符串已存在"));
}
if (errorMessage != null && errorMessage.contains("角色名称不能为空")) {
return ServerResponse.badRequest()
.bodyValue(Result.error("角色名称不能为空"));
}
if (errorMessage != null && errorMessage.contains("角色权限字符串不能为空")) {
return ServerResponse.badRequest()
.bodyValue(Result.error("角色权限字符串不能为空"));
}
return ServerResponse.badRequest()
.bodyValue(Result.error("创建角色失败:" + e.getMessage()));
});
}
@Operation(summary = "更新角色", description = "更新角色信息")
@RequirePermission("sys:role:update")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "更新成功", content = @Content(schema = @Schema(implementation = Result.class))),
@ApiResponse(responseCode = "400", description = "请求参数错误"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
public Mono<ServerResponse> updateRole(ServerRequest request) {
return request.bodyToMono(SysRoleRequest.class)
.flatMap(roleRequest -> {
return roleService.findById(roleRequest.getId())
.flatMap(sysRole -> {
sysRole.setRoleName(roleRequest.getName());
sysRole.setRoleKey(roleRequest.getRoleKey());
sysRole.setRoleSort(roleRequest.getSortOrder());
sysRole.setStatus(roleRequest.getStatus());
return roleService.update(sysRole);
})
.map(SysRoleResponse::fromSysRole)
.map(Result::success)
.flatMap(result -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValue(result));
})
.switchIfEmpty(Mono.defer(() -> ServerResponse.badRequest()
.bodyValue(Result.error("角色不存在"))))
.onErrorResume(e -> {
log.error("更新角色失败", e);
return ServerResponse.badRequest()
.bodyValue(Result.error("更新角色失败:" + e.getMessage()));
});
}
@Operation(summary = "删除角色", description = "根据ID删除角色")
@RequirePermission("sys:role:delete")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "删除成功", content = @Content(schema = @Schema(implementation = Result.class))),
@ApiResponse(responseCode = "400", description = "请求参数错误"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
public Mono<ServerResponse> deleteRole(ServerRequest request) {
Long id = Long.parseLong(request.pathVariable("id"));
return roleService.deleteById(id)
.then(Mono.fromCallable(() -> Result.success(null)))
.flatMap(result -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValue(result))
.onErrorResume(e -> {
log.error("删除角色失败,ID: {}", id, e);
return ServerResponse.badRequest()
.bodyValue(Result.error("删除角色失败:" + e.getMessage()));
});
}
@Operation(summary = "分配角色到用户", description = "将角色分配给用户")
@RequirePermission("sys:role:assign")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "分配成功", content = @Content(schema = @Schema(implementation = Result.class))),
@ApiResponse(responseCode = "400", description = "请求参数错误"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
public Mono<ServerResponse> assignRoleToUser(ServerRequest request) {
return request.bodyToMono(io.destiny.sys.dto.request.AssignRoleToUserRequest.class)
.flatMap(assignRequest -> {
Long userId = assignRequest.getUserId();
Long roleId = assignRequest.getRoleId();
return roleService.findById(roleId)
.switchIfEmpty(Mono.error(new IllegalArgumentException("角色不存在")))
.flatMap(role -> {
return roleService.findByUserId(userId)
.filter(userRole -> userRole.getId().equals(roleId))
.hasElements()
.flatMap(hasRole -> {
if (hasRole) {
return Mono.error(new IllegalArgumentException("用户已拥有该角色"));
}
return roleService.assignRoleToUser(userId, roleId);
});
});
})
.then(Mono.fromCallable(() -> Result.success(null)))
.flatMap(result -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValue(result))
.onErrorResume(e -> {
log.error("分配角色到用户失败", e);
String errorMessage = e.getMessage();
if (errorMessage != null && errorMessage.contains("角色不存在")) {
return ServerResponse.badRequest()
.bodyValue(Result.error("角色不存在"));
}
if (errorMessage != null && errorMessage.contains("用户已拥有该角色")) {
return ServerResponse.badRequest()
.bodyValue(Result.error("用户已拥有该角色"));
}
return ServerResponse.badRequest()
.bodyValue(Result.error("分配角色到用户失败:" + e.getMessage()));
});
}
@Operation(summary = "移除用户的角色", description = "从用户中移除角色")
@RequirePermission("sys:role:assign")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "移除成功", content = @Content(schema = @Schema(implementation = Result.class))),
@ApiResponse(responseCode = "400", description = "请求参数错误"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
public Mono<ServerResponse> removeRoleFromUser(ServerRequest request) {
Long userId = Long.parseLong(request.pathVariable("userId"));
Long roleId = Long.parseLong(request.pathVariable("roleId"));
return roleService.removeRoleFromUser(userId, roleId)
.then(Mono.fromCallable(() -> Result.success(null)))
.flatMap(result -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValue(result))
.onErrorResume(e -> {
log.error("移除用户角色失败,用户ID: {}, 角色ID: {}", userId, roleId, e);
return ServerResponse.badRequest()
.bodyValue(Result.error("移除用户角色失败:" + e.getMessage()));
});
}
}
@@ -0,0 +1,294 @@
package io.destiny.sys.handler;
import io.destiny.common.response.Result;
import io.destiny.common.utils.XssUtils;
import io.destiny.sys.core.domain.SysUser;
import io.destiny.sys.core.service.ISysUserService;
import io.destiny.sys.dto.request.SysUserRequest;
import io.destiny.sys.dto.response.SysUserResponse;
import io.destiny.sys.security.RequirePermission;
import io.destiny.sys.util.ValidationUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
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
@Tag(name = "用户管理", description = "用户管理相关接口")
public class SysUserHandler {
private static final Logger log = LoggerFactory.getLogger(SysUserHandler.class);
private final ISysUserService userService;
public SysUserHandler(ISysUserService userService) {
this.userService = userService;
}
@Operation(summary = "根据ID查询用户", description = "根据用户ID查询用户详细信息")
@RequirePermission("sys:user:query")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "查询成功", content = @Content(schema = @Schema(implementation = Result.class))),
@ApiResponse(responseCode = "400", description = "请求参数错误"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
public Mono<ServerResponse> getUserById(ServerRequest request) {
Long id = Long.parseLong(request.pathVariable("id"));
return userService.findById(id)
.map(SysUserResponse::fromSysUser)
.map(Result::success)
.flatMap(result -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValue(result))
.switchIfEmpty(Mono.defer(() -> ServerResponse.badRequest()
.bodyValue(Result.error("用户不存在"))))
.onErrorResume(e -> {
log.error("查询用户失败,ID: {}", id, e);
return ServerResponse.badRequest()
.bodyValue(Result.error("查询用户失败:" + e.getMessage()));
});
}
@Operation(summary = "根据用户名查询用户", description = "根据用户名查询用户详细信息")
@RequirePermission("sys:user:query")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "查询成功", content = @Content(schema = @Schema(implementation = Result.class))),
@ApiResponse(responseCode = "400", description = "请求参数错误"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
public Mono<ServerResponse> getUserByUsername(ServerRequest request) {
String username = request.pathVariable("username");
return userService.findByUsername(username)
.map(SysUserResponse::fromSysUser)
.map(Result::success)
.flatMap(result -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValue(result))
.switchIfEmpty(Mono.defer(() -> ServerResponse.badRequest()
.bodyValue(Result.error("用户不存在"))))
.onErrorResume(e -> {
log.error("查询用户失败,用户名: {}", username, e);
return ServerResponse.badRequest()
.bodyValue(Result.error("查询用户失败:" + e.getMessage()));
});
}
@Operation(summary = "创建用户", description = "创建新用户")
@RequirePermission("sys:user:create")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "创建成功", content = @Content(schema = @Schema(implementation = Result.class))),
@ApiResponse(responseCode = "400", description = "请求参数错误"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
public Mono<ServerResponse> createUser(ServerRequest request) {
return request.bodyToMono(SysUserRequest.class)
.flatMap(ValidationUtil::validate)
.map(userRequest -> {
SysUserRequest cleanedRequest = new SysUserRequest();
cleanedRequest.setId(userRequest.getId());
cleanedRequest.setUsername(XssUtils.clean(userRequest.getUsername()));
cleanedRequest.setPassword(userRequest.getPassword());
cleanedRequest.setEmail(XssUtils.clean(userRequest.getEmail()));
cleanedRequest.setPhone(XssUtils.clean(userRequest.getPhone()));
cleanedRequest.setStatus(userRequest.getStatus());
return cleanedRequest;
})
.flatMap(cleanedRequest -> {
SysUser sysUser = new SysUser();
sysUser.setUsername(cleanedRequest.getUsername());
sysUser.setPassword(cleanedRequest.getPassword());
sysUser.setEmail(io.destiny.common.primitive.EmailAddress.of(cleanedRequest.getEmail()));
sysUser.setPhone(io.destiny.common.primitive.PhoneNumber.of(cleanedRequest.getPhone()));
sysUser.setStatus(cleanedRequest.getStatus());
sysUser.generateId();
return userService.create(sysUser);
})
.map(SysUserResponse::fromSysUser)
.map(Result::success)
.flatMap(result -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValue(result))
.onErrorResume(e -> {
log.error("创建用户失败", e);
return ServerResponse.badRequest()
.bodyValue(Result.error("创建用户失败:" + e.getMessage()));
});
}
@Operation(summary = "更新用户", description = "更新用户信息")
@RequirePermission("sys:user:update")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "更新成功", content = @Content(schema = @Schema(implementation = Result.class))),
@ApiResponse(responseCode = "400", description = "请求参数错误"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
public Mono<ServerResponse> updateUser(ServerRequest request) {
return request.bodyToMono(SysUserRequest.class)
.flatMap(userRequest -> {
return userService.findById(userRequest.getId())
.flatMap(sysUser -> {
sysUser.setUsername(userRequest.getUsername());
if (userRequest.getPassword() != null) {
sysUser.setPassword(userRequest.getPassword());
}
if (userRequest.getEmail() != null) {
sysUser.setEmail(
io.destiny.common.primitive.EmailAddress.of(userRequest.getEmail()));
}
if (userRequest.getPhone() != null) {
sysUser.setPhone(
io.destiny.common.primitive.PhoneNumber.of(userRequest.getPhone()));
}
if (userRequest.getStatus() != null) {
sysUser.setStatus(userRequest.getStatus());
}
return userService.update(sysUser);
})
.map(SysUserResponse::fromSysUser)
.map(Result::success)
.flatMap(result -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
.bodyValue(result));
})
.switchIfEmpty(Mono.defer(() -> ServerResponse.badRequest()
.bodyValue(Result.error("用户不存在"))))
.onErrorResume(e -> {
log.error("更新用户失败", e);
return ServerResponse.badRequest()
.bodyValue(Result.error("更新用户失败:" + e.getMessage()));
});
}
@Operation(summary = "删除用户", description = "根据ID删除用户")
@RequirePermission("sys:user:delete")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "删除成功", content = @Content(schema = @Schema(implementation = Result.class))),
@ApiResponse(responseCode = "400", description = "请求参数错误"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
public Mono<ServerResponse> deleteUser(ServerRequest request) {
Long id = Long.parseLong(request.pathVariable("id"));
return userService.deleteById(id)
.then(Mono.fromCallable(() -> Result.success(null)))
.flatMap(result -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValue(result))
.onErrorResume(e -> {
log.error("删除用户失败,ID: {}", id, e);
return ServerResponse.badRequest()
.bodyValue(Result.error("删除用户失败:" + e.getMessage()));
});
}
@Operation(summary = "封禁用户", description = "封禁指定用户")
@RequirePermission("sys:user:edit")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "封禁成功", content = @Content(schema = @Schema(implementation = Result.class))),
@ApiResponse(responseCode = "400", description = "请求参数错误"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
public Mono<ServerResponse> banUser(ServerRequest request) {
Long id = Long.parseLong(request.pathVariable("id"));
return userService.findById(id)
.switchIfEmpty(Mono.error(new RuntimeException("用户不存在")))
.flatMap(user -> {
user.setStatus("1");
return userService.update(user);
})
.map(SysUserResponse::fromSysUser)
.map(Result::success)
.flatMap(result -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValue(result))
.onErrorResume(e -> {
log.error("封禁用户失败,ID: {}", id, e);
return ServerResponse.badRequest()
.bodyValue(Result.error("封禁用户失败:" + e.getMessage()));
});
}
@Operation(summary = "查询所有用户", description = "查询系统中的所有用户")
@RequirePermission("sys:user:query")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "查询成功", content = @Content(schema = @Schema(implementation = Result.class))),
@ApiResponse(responseCode = "400", description = "请求参数错误"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
public Mono<ServerResponse> getAllUsers(ServerRequest request) {
return userService.findAll()
.map(SysUserResponse::fromSysUser)
.collectList()
.map(Result::success)
.flatMap(result -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValue(result))
.onErrorResume(e -> {
log.error("查询所有用户失败", e);
return ServerResponse.badRequest()
.bodyValue(Result.error("查询所有用户失败:" + e.getMessage()));
});
}
@Operation(summary = "分页查询用户", description = "根据条件分页查询用户列表")
@RequirePermission("sys:user:query")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "查询成功", content = @Content(schema = @Schema(implementation = Result.class))),
@ApiResponse(responseCode = "400", description = "请求参数错误"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
public Mono<ServerResponse> queryUsersWithPagination(ServerRequest request) {
return Mono.fromCallable(() -> {
io.destiny.common.request.PageQuery pageQuery = new io.destiny.common.request.PageQuery();
request.queryParam("pageNum").ifPresent(p -> pageQuery.setPageNum(Integer.parseInt(p)));
request.queryParam("pageSize").ifPresent(p -> pageQuery.setPageSize(Integer.parseInt(p)));
request.queryParam("sortField").ifPresent(pageQuery::setSortField);
request.queryParam("sortOrder").ifPresent(pageQuery::setSortOrder);
return pageQuery;
})
.flatMap(pageQuery -> {
io.destiny.sys.core.domain.query.SysUserQuery query = new io.destiny.sys.core.domain.query.SysUserQuery();
request.queryParam("username").ifPresent(query::setUsername);
request.queryParam("status").ifPresent(query::setStatus);
request.queryParam("email")
.ifPresent(e -> query.setEmail(io.destiny.common.primitive.EmailAddress.of(e)));
request.queryParam("phone")
.ifPresent(p -> query.setPhone(io.destiny.common.primitive.PhoneNumber.of(p)));
return userService.findByQueryWithPagination(query, pageQuery);
})
.map(pageModel -> {
java.util.List<SysUserResponse> dataList = pageModel.getData().stream()
.map(SysUserResponse::fromSysUser)
.toList();
io.destiny.common.response.PageModel<SysUserResponse> response = new io.destiny.common.response.PageModel<>(
pageModel.getCount(), dataList);
return Result.success(response);
})
.flatMap(result -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValue(result))
.onErrorResume(e -> {
log.error("分页查询用户失败", e);
return ServerResponse.badRequest()
.bodyValue(Result.error("分页查询用户失败:" + e.getMessage()));
});
}
@Operation(summary = "解封用户", description = "解封指定用户")
@RequirePermission("sys:user:edit")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "解封成功", content = @Content(schema = @Schema(implementation = Result.class))),
@ApiResponse(responseCode = "400", description = "请求参数错误"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
public Mono<ServerResponse> unbanUser(ServerRequest request) {
Long id = Long.parseLong(request.pathVariable("id"));
return userService.findById(id)
.switchIfEmpty(Mono.error(new RuntimeException("用户不存在")))
.flatMap(user -> {
user.setStatus("0");
return userService.update(user);
})
.map(SysUserResponse::fromSysUser)
.map(Result::success)
.flatMap(result -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValue(result))
.onErrorResume(e -> {
log.error("解封用户失败,ID: {}", id, e);
return ServerResponse.badRequest()
.bodyValue(Result.error("解封用户失败:" + e.getMessage()));
});
}
}
@@ -0,0 +1,12 @@
package io.destiny.sys.security;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequirePermission {
String value();
}
@@ -0,0 +1,12 @@
package io.destiny.sys.security;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequireRole {
String value();
}
@@ -0,0 +1,134 @@
package io.destiny.sys.security;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Component
public class SysJwtTokenProvider {
private static final Logger logger = LoggerFactory.getLogger(SysJwtTokenProvider.class);
@Value("${jwt.secret:this-is-a-secure-jwt-secret-key-that-must-be-at-least-64-characters-long-for-hs512-algorithm}")
private String secret;
@Value("${jwt.expiration:86400000}")
private Long expiration;
@Value("${jwt.refresh-expiration:604800000}")
private Long refreshExpiration;
private SecretKey getSigningKey() {
byte[] keyBytes = secret.getBytes(StandardCharsets.UTF_8);
return Keys.hmacShaKeyFor(keyBytes);
}
public String generateAccessToken(Long userId, String username) {
Map<String, Object> claims = new HashMap<>();
claims.put("userId", userId);
claims.put("username", username);
claims.put("type", "access");
return createToken(claims, userId.toString(), expiration);
}
public String generateRefreshToken(Long userId, String username) {
Map<String, Object> claims = new HashMap<>();
claims.put("userId", userId);
claims.put("username", username);
claims.put("type", "refresh");
return createToken(claims, userId.toString(), refreshExpiration);
}
private String createToken(Map<String, Object> claims, String subject, Long expirationTime) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + expirationTime);
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(now)
.setExpiration(expiryDate)
.signWith(getSigningKey())
.compact();
}
public Long getUserIdFromToken(String token) {
try {
Claims claims = Jwts.parserBuilder()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token)
.getBody();
return claims.get("userId", Long.class);
} catch (Exception e) {
logger.error("Failed to get user id from token", e);
return null;
}
}
public String getUsernameFromToken(String token) {
try {
Claims claims = Jwts.parserBuilder()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token)
.getBody();
return claims.get("username", String.class);
} catch (Exception e) {
logger.error("Failed to get username from token", e);
return null;
}
}
public String getTokenType(String token) {
try {
Claims claims = Jwts.parserBuilder()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token)
.getBody();
return claims.get("type", String.class);
} catch (Exception e) {
logger.error("Failed to get token type", e);
return null;
}
}
public Boolean validateToken(String token) {
try {
Jwts.parserBuilder()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token);
return true;
} catch (Exception e) {
logger.error("Invalid JWT token", e);
return false;
}
}
public Boolean isTokenExpired(String token) {
try {
Claims claims = Jwts.parserBuilder()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token)
.getBody();
Date expiration = claims.getExpiration();
return expiration.before(new Date());
} catch (Exception e) {
logger.error("Failed to check token expiration", e);
return true;
}
}
}
@@ -0,0 +1,239 @@
package io.destiny.sys.security;
import io.destiny.sys.core.service.ISysPermissionService;
import io.destiny.sys.core.service.ISysUserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
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.nio.charset.StandardCharsets;
import java.util.List;
@Component
@Order(4)
public class SysPermissionFilter implements WebFilter {
private static final Logger logger = LoggerFactory.getLogger(SysPermissionFilter.class);
private static final String USER_ID_HEADER = "X-User-Id";
private static final String USERNAME_HEADER = "X-Username";
private final ISysUserService userService;
private final ISysPermissionService permissionService;
private final List<String> publicPaths;
private final List<PermissionRule> permissionRules;
public SysPermissionFilter(ISysUserService userService, ISysPermissionService permissionService) {
logger.debug("SysPermissionFilter constructor called");
this.userService = userService;
this.permissionService = permissionService;
this.publicPaths = List.of(
"/api/sys/auth/register",
"/api/sys/auth/login",
"/api/sys/auth/refresh",
"/api/sys/auth/logout",
"/api/client/auth/register",
"/api/client/auth/login",
"/sys/auth/register",
"/sys/auth/login",
"/sys/auth/refresh",
"/sys/auth/logout",
"/api/almanac",
"/api/almanac/**",
"/almanac",
"/almanac/**",
"/api/health",
"/health",
"/swagger-ui.html",
"/swagger-ui",
"/swagger-ui/**",
"/v3/api-docs",
"/v3/api-docs/**",
"/webjars/**",
"/api/swagger-ui.html",
"/api/swagger-ui",
"/api/swagger-ui/**",
"/api/v3/api-docs",
"/api/v3/api-docs/**",
"/api/webjars/**",
"/api/actuator/**",
"/actuator/**"
);
logger.info("SysPermissionFilter initialized with {} public paths", this.publicPaths.size());
this.permissionRules = List.of(
new PermissionRule("GET", "/sys/user", "sys:user:list"),
new PermissionRule("POST", "/sys/user", "sys:user:add"),
new PermissionRule("PUT", "/sys/user", "sys:user:edit"),
new PermissionRule("DELETE", "/sys/user", "sys:user:remove"),
new PermissionRule("GET", "/sys/role", "sys:role:list"),
new PermissionRule("POST", "/sys/role", "sys:role:add"),
new PermissionRule("PUT", "/sys/role", "sys:role:edit"),
new PermissionRule("DELETE", "/sys/role", "sys:role:remove"),
new PermissionRule("GET", "/sys/menu", "sys:menu:list"),
new PermissionRule("POST", "/sys/menu", "sys:menu:add"),
new PermissionRule("PUT", "/sys/menu", "sys:menu:edit"),
new PermissionRule("DELETE", "/sys/menu", "sys:menu:remove")
);
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
String path = exchange.getRequest().getPath().value();
String method = exchange.getRequest().getMethod().name();
logger.debug("SysPermissionFilter processing: {} {}", method, path);
// Skip OPTIONS requests for CORS preflight
if ("OPTIONS".equals(method)) {
logger.debug("OPTIONS request, skipping permission check: {} {}", method, path);
return chain.filter(exchange);
}
// Special handling for Swagger paths
if (path.startsWith("/swagger-ui") || path.startsWith("/v3/api-docs") || path.startsWith("/webjars")) {
logger.debug("Swagger path detected, skipping permission check: {} {}", method, path);
return chain.filter(exchange);
}
// Special handling for /actuator/** paths
if (path.startsWith("/actuator/")) {
logger.debug("Path '{}' is an actuator path, skipping permission check", path);
return chain.filter(exchange);
}
if (isPublicPath(path)) {
logger.debug("Public path, skipping permission check: {} {}", method, path);
return chain.filter(exchange);
}
String userIdHeader = exchange.getRequest().getHeaders().getFirst(USER_ID_HEADER);
String usernameHeader = exchange.getRequest().getHeaders().getFirst(USERNAME_HEADER);
if (userIdHeader == null || usernameHeader == null) {
logger.warn("Missing user headers for path: {} {}", method, path);
return forbidden(exchange, "Missing user authentication information");
}
try {
Long userId = Long.parseLong(userIdHeader);
return userService.findById(userId)
.switchIfEmpty(Mono.error(new RuntimeException("User not found")))
.flatMap(user -> {
if (!"0".equals(user.getStatus())) {
logger.warn("User is disabled: {} ({})", user.getUsername(), userId);
return forbidden(exchange, "User account is disabled");
}
PermissionRule rule = findPermissionRule(method, path);
if (rule == null) {
logger.debug("No permission rule defined for {} {}, allowing access", method, path);
return chain.filter(exchange);
}
return checkPermission(userId, rule.permission)
.flatMap(hasPermission -> {
if (hasPermission) {
logger.debug("Permission granted for user {} to access {} {}", user.getUsername(), method, path);
return chain.filter(exchange);
} else {
logger.warn("Permission denied for user {} to access {} {} (required: {})",
user.getUsername(), method, path, rule.permission);
return forbidden(exchange, "Permission denied");
}
});
})
.onErrorResume(e -> {
logger.error("Error during permission check for {} {}", method, path, e);
if (e.getMessage().contains("User not found")) {
return forbidden(exchange, "User not found");
}
return forbidden(exchange, "Permission check failed");
});
} catch (NumberFormatException e) {
logger.error("Invalid user ID format: {}", userIdHeader);
return forbidden(exchange, "Invalid user ID format");
}
}
private boolean isPublicPath(String path) {
// Special handling for Swagger paths
if (path.startsWith("/swagger-ui") || path.startsWith("/v3/api-docs") || path.startsWith("/webjars")) {
logger.debug("Swagger path detected, marking as public: {}", path);
return true;
}
// Special handling for /actuator/** paths
if (path.startsWith("/actuator/")) {
logger.debug("Path '{}' is an actuator path, marking as public", path);
return true;
}
boolean isPublic = publicPaths.stream().anyMatch(publicPath -> {
boolean match = false;
if (publicPath.endsWith("/**")) {
String prefix = publicPath.substring(0, publicPath.length() - 3);
match = path.startsWith(prefix);
} else if (publicPath.endsWith("/*")) {
String prefix = publicPath.substring(0, publicPath.length() - 2);
match = path.startsWith(prefix) && path.substring(prefix.length()).indexOf('/') == -1;
} else {
boolean exactMatch = path.equals(publicPath);
boolean prefixMatch = path.startsWith(publicPath);
match = exactMatch || prefixMatch;
}
logger.debug("Checking path '{}' against public path '{}': {}", path, publicPath, match);
return match;
});
logger.debug("Checking path '{}' against public paths: {}", path, isPublic);
return isPublic;
}
private PermissionRule findPermissionRule(String method, String path) {
for (PermissionRule rule : permissionRules) {
if (rule.method.equals(method) && path.startsWith(rule.path)) {
return rule;
}
}
return null;
}
private Mono<Boolean> checkPermission(Long userId, String permission) {
return Mono.fromCallable(() -> permissionService.hasPermission(userId, permission));
}
private Mono<Void> forbidden(ServerWebExchange exchange, String message) {
exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
String body = String.format("{\"code\":\"FORBIDDEN\",\"message\":\"%s\"}", message);
return exchange.getResponse().writeWith(
reactor.core.publisher.Flux.just(
exchange.getResponse().bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8))));
}
private static class PermissionRule {
final String method;
final String path;
final String permission;
PermissionRule(String method, String path, String permission) {
this.method = method;
this.path = path;
this.permission = permission;
}
}
}
@@ -0,0 +1,27 @@
package io.destiny.sys.util;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import jakarta.validation.ValidatorFactory;
import reactor.core.publisher.Mono;
import java.util.Set;
import java.util.stream.Collectors;
public class ValidationUtil {
private static final ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
private static final Validator validator = factory.getValidator();
public static <T> Mono<T> validate(T object) {
Set<ConstraintViolation<T>> violations = validator.validate(object);
if (violations.isEmpty()) {
return Mono.just(object);
}
String errorMessage = violations.stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.joining("; "));
return Mono.error(new IllegalArgumentException(errorMessage));
}
}
@@ -0,0 +1,217 @@
package io.destiny.sys.api;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.destiny.common.primitive.EmailAddress;
import io.destiny.common.primitive.PhoneNumber;
import io.destiny.sys.dto.response.SysUserResponse;
import io.destiny.sys.core.domain.SysUser;
import io.destiny.sys.handler.SysAuthHandler;
import io.destiny.sys.handler.SysUserHandler;
import io.destiny.sys.handler.SysRoleHandler;
import io.destiny.sys.handler.SysMenuHandler;
import io.destiny.sys.handler.OperationLogHandler;
import io.destiny.sys.config.SysRouter;
import io.destiny.sys.core.service.ISysAuthService;
import io.destiny.sys.core.service.ISysUserService;
import io.destiny.sys.core.service.ISysPermissionService;
import io.destiny.sys.core.service.ISysMenuService;
import io.destiny.sys.core.service.ISysRoleService;
import io.destiny.sys.security.SysJwtTokenProvider;
import io.destiny.sys.dto.response.SysLoginResponse;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
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.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import org.springframework.http.MediaType;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.test.web.reactive.server.WebTestClient;
import reactor.core.publisher.Mono;
import java.util.HashSet;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
@DisplayName("敏感数据脱敏集成测试")
class SensitiveDataIntegrationTest {
@Mock
private ISysAuthService authService;
@Mock
private ISysUserService userService;
@Mock
private ISysPermissionService permissionService;
@Mock
private ISysMenuService menuService;
@Mock
private ISysRoleService roleService;
@Mock
private SysJwtTokenProvider jwtTokenProvider;
private WebTestClient webTestClient;
private ObjectMapper objectMapper;
@BeforeEach
void setUp() {
objectMapper = new ObjectMapper();
Set<String> permissions = new HashSet<>();
permissions.add("user:read");
permissions.add("user:write");
io.destiny.sys.core.domain.SysUser testUser = new io.destiny.sys.core.domain.SysUser();
testUser.setId(1L);
testUser.setUsername("admin");
testUser.setEmail(EmailAddress.of("admin@example.com"));
testUser.setPhone(PhoneNumber.of("13800138000"));
testUser.setPassword(new BCryptPasswordEncoder().encode("admin123"));
testUser.setStatus("0");
when(userService.findByUsername(anyString())).thenReturn(Mono.just(testUser));
when(jwtTokenProvider.generateAccessToken(any(), anyString())).thenReturn("test-access-token");
when(jwtTokenProvider.generateRefreshToken(any(), anyString())).thenReturn("test-refresh-token");
when(permissionService.getUserPermissions(any())).thenReturn(Mono.just(permissions));
SysLoginResponse loginResponse = SysLoginResponse.from("test-access-token", "test-refresh-token", testUser, permissions);
when(authService.login(any())).thenReturn(Mono.just(loginResponse));
SysAuthHandler authHandler = new SysAuthHandler(authService);
SysUserHandler userHandler = new SysUserHandler(null);
SysRoleHandler roleHandler = new SysRoleHandler(null);
SysMenuHandler menuHandler = new SysMenuHandler(menuService, roleService);
OperationLogHandler logHandler = new OperationLogHandler(null);
SysRouter router = new SysRouter(userHandler, roleHandler, menuHandler, authHandler, logHandler);
webTestClient = WebTestClient.bindToRouterFunction(router.sysRoutes())
.build();
}
@Test
@DisplayName("登录响应中的用户信息应该脱敏")
void testLoginResponseShouldMaskSensitiveData() {
var response = webTestClient.post()
.uri("/sys/auth/login")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue("{\"username\":\"admin\",\"password\":\"admin123\"}")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody()
.returnResult()
.getResponseBody();
assertNotNull(response, "登录响应不应为空");
String responseString = new String(response);
System.out.println("登录响应: " + responseString);
assertTrue(responseString.contains("\"token\""), "响应应该包含token");
assertTrue(responseString.contains("\"refreshToken\""), "响应应该包含refreshToken");
assertTrue(responseString.contains("\"user\""), "响应应该包含user信息");
if (responseString.contains("\"email\"")) {
String email = extractField(responseString, "email");
System.out.println("邮箱脱敏结果: " + email);
assertNotNull(email, "邮箱不应为null");
assertTrue(email.contains("***") || email.length() < 10, "邮箱应该被脱敏");
}
if (responseString.contains("\"phone\"")) {
String phone = extractField(responseString, "phone");
System.out.println("手机号脱敏结果: " + phone);
assertNotNull(phone, "手机号不应为null");
assertTrue(phone.contains("****") || phone.length() < 11, "手机号应该被脱敏");
}
}
@Test
@DisplayName("SysUserResponse序列化应该脱敏敏感数据")
void testSysUserResponseSerialization() throws Exception {
SysUser user = new SysUser();
user.setId(1L);
user.setUsername("testuser");
user.setEmail(EmailAddress.of("testuser@example.com"));
user.setPhone(PhoneNumber.of("13800138000"));
user.setStatus("0");
SysUserResponse response = SysUserResponse.fromSysUser(user);
String json = objectMapper.writeValueAsString(response);
System.out.println("SysUserResponse JSON: " + json);
assertTrue(json.contains("testuser"), "JSON应该包含用户名");
if (json.contains("\"email\"")) {
String email = extractField(json, "email");
System.out.println("邮箱脱敏结果: " + email);
assertTrue(email.contains("***") || email.length() < 10, "邮箱应该被脱敏");
}
if (json.contains("\"phone\"")) {
String phone = extractField(json, "phone");
System.out.println("手机号脱敏结果: " + phone);
assertTrue(phone.contains("****") || phone.length() < 11, "手机号应该被脱敏");
}
}
@Test
@DisplayName("应该正确处理空值")
void testHandleNullValues() throws Exception {
SysUser user = new SysUser();
user.setId(1L);
user.setUsername("testuser");
user.setEmail(null);
user.setPhone(null);
user.setStatus("0");
SysUserResponse response = SysUserResponse.fromSysUser(user);
String json = objectMapper.writeValueAsString(response);
System.out.println("空值处理 JSON: " + json);
assertTrue(json.contains("testuser"), "JSON应该包含用户名");
}
@Test
@DisplayName("应该正确处理空字符串")
void testHandleEmptyStrings() throws Exception {
SysUser user = new SysUser();
user.setId(1L);
user.setUsername("testuser");
user.setEmail(EmailAddress.of(""));
user.setPhone(PhoneNumber.of(""));
user.setStatus("0");
SysUserResponse response = SysUserResponse.fromSysUser(user);
String json = objectMapper.writeValueAsString(response);
System.out.println("空字符串处理 JSON: " + json);
assertTrue(json.contains("testuser"), "JSON应该包含用户名");
}
private String extractField(String json, String fieldName) {
String pattern = "\"" + fieldName + "\":\"([^\"]+)\"";
java.util.regex.Pattern p = java.util.regex.Pattern.compile(pattern);
java.util.regex.Matcher m = p.matcher(json);
if (m.find()) {
return m.group(1);
}
return null;
}
}
@@ -0,0 +1,181 @@
package io.destiny.sys.core.service;
import io.destiny.sys.core.service.impl.DataMaskingServiceImpl;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.junit.jupiter.MockitoExtension;
import reactor.test.StepVerifier;
@ExtendWith(MockitoExtension.class)
@DisplayName("数据脱敏服务测试")
class DataMaskingServiceTest {
@InjectMocks
private DataMaskingServiceImpl dataMaskingService;
@Test
@DisplayName("测试手机号脱敏 - 完整手机号")
void testMaskPhoneNumber_Full() {
String phoneNumber = "13800138000";
StepVerifier.create(dataMaskingService.maskPhone(phoneNumber))
.expectNext("138****8000")
.verifyComplete();
}
@Test
@DisplayName("测试手机号脱敏 - 空值")
void testMaskPhoneNumber_Null() {
StepVerifier.create(dataMaskingService.maskPhone(null))
.verifyComplete();
}
@Test
@DisplayName("测试手机号脱敏 - 空字符串")
void testMaskPhoneNumber_Empty() {
StepVerifier.create(dataMaskingService.maskPhone(""))
.verifyComplete();
}
@Test
@DisplayName("测试邮箱脱敏 - 完整邮箱")
void testMaskEmail_Full() {
String email = "test@example.com";
StepVerifier.create(dataMaskingService.maskEmail(email))
.expectNext("te***@example.com")
.verifyComplete();
}
@Test
@DisplayName("测试邮箱脱敏 - 空值")
void testMaskEmail_Null() {
StepVerifier.create(dataMaskingService.maskEmail(null))
.verifyComplete();
}
@Test
@DisplayName("测试邮箱脱敏 - 短邮箱")
void testMaskEmail_Short() {
String email = "a@b.com";
StepVerifier.create(dataMaskingService.maskEmail(email))
.verifyComplete();
}
@Test
@DisplayName("测试身份证号脱敏 - 完整身份证号")
void testMaskIdCard_Full() {
String idCard = "110101199001011234";
StepVerifier.create(dataMaskingService.mask(idCard, "idCard"))
.expectNext("110101********1234")
.verifyComplete();
}
@Test
@DisplayName("测试身份证号脱敏 - 空值")
void testMaskIdCard_Null() {
StepVerifier.create(dataMaskingService.mask(null, "idCard"))
.verifyComplete();
}
@Test
@DisplayName("测试银行卡号脱敏 - 完整银行卡号")
void testMaskBankCard_Full() {
String bankCard = "6222021234567890123";
StepVerifier.create(dataMaskingService.mask(bankCard, "bankCard"))
.expectNext("6222***********0123")
.verifyComplete();
}
@Test
@DisplayName("测试银行卡号脱敏 - 空值")
void testMaskBankCard_Null() {
StepVerifier.create(dataMaskingService.mask(null, "bankCard"))
.verifyComplete();
}
@Test
@DisplayName("测试密码脱敏 - 完整密码")
void testMaskPassword_Full() {
String password = "password123";
StepVerifier.create(dataMaskingService.maskPassword(password))
.expectNext("******")
.verifyComplete();
}
@Test
@DisplayName("测试密码脱敏 - 空值")
void testMaskPassword_Null() {
StepVerifier.create(dataMaskingService.maskPassword(null))
.verifyComplete();
}
@Test
@DisplayName("测试通用脱敏 - 完整字符串")
void testMask_Full() {
String data = "sensitiveData";
StepVerifier.create(dataMaskingService.mask(data, "sensitive"))
.expectNext("sensitiveData")
.verifyComplete();
}
@Test
@DisplayName("测试通用脱敏 - 空值")
void testMask_Null() {
StepVerifier.create(dataMaskingService.mask(null, "sensitive"))
.verifyComplete();
}
@Test
@DisplayName("测试通用脱敏 - 短字符串")
void testMask_Short() {
String data = "abc";
StepVerifier.create(dataMaskingService.mask(data, "sensitive"))
.expectNext("abc")
.verifyComplete();
}
@Test
@DisplayName("测试JSON数据脱敏 - 包含敏感字段")
void testMaskJson_WithSensitiveFields() {
String json = "{\"username\":\"test\",\"phone\":\"13800138000\",\"email\":\"test@example.com\"}";
StepVerifier.create(dataMaskingService.maskSensitiveData(json))
.expectNextMatches(
result -> result.contains("\"phone\":\"****\"") && result.contains("\"email\":\"***\""))
.verifyComplete();
}
@Test
@DisplayName("测试JSON数据脱敏 - 空值")
void testMaskJson_Null() {
StepVerifier.create(dataMaskingService.maskSensitiveData(null))
.verifyComplete();
}
@Test
@DisplayName("测试JSON数据脱敏 - 无效JSON")
void testMaskJson_Invalid() {
String json = "invalid json";
StepVerifier.create(dataMaskingService.maskSensitiveData(json))
.expectNext("invalid json")
.verifyComplete();
}
}
@@ -0,0 +1,156 @@
package io.destiny.sys.core.service;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.destiny.sys.core.service.impl.JaversAuditServiceImpl;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;
import reactor.test.StepVerifier;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doThrow;
@ExtendWith(MockitoExtension.class)
@DisplayName("JaVers审计服务测试")
class JaversAuditServiceTest {
@Spy
private ObjectMapper objectMapper = new ObjectMapper();
private JaversAuditServiceImpl javersAuditService;
@BeforeEach
void setUp() {
javersAuditService = new JaversAuditServiceImpl(objectMapper);
}
@Test
@DisplayName("测试对象比较 - 成功")
void testCompare_Success() {
@SuppressWarnings("unused")
class TestEntity {
private String name;
public TestEntity(String name) {
this.name = name;
}
}
TestEntity oldObject = new TestEntity("oldName");
TestEntity newObject = new TestEntity("newName");
StepVerifier.create(javersAuditService.compare(oldObject, newObject))
.expectNextMatches(result -> result != null)
.verifyComplete();
}
@Test
@DisplayName("测试对象比较 - 异常")
void testCompare_Exception() {
@SuppressWarnings("unused")
class TestEntity {
private String name;
public TestEntity(String name) {
this.name = name;
}
}
TestEntity oldObject = new TestEntity("oldName");
StepVerifier.create(javersAuditService.compare(oldObject, oldObject))
.expectNextMatches(result -> result != null)
.verifyComplete();
}
@Test
@DisplayName("测试获取变更字段 - 成功")
void testGetChangedFields_Success() {
@SuppressWarnings("unused")
class TestEntity {
private String name;
public TestEntity(String name) {
this.name = name;
}
}
TestEntity oldObject = new TestEntity("oldName");
TestEntity newObject = new TestEntity("newName");
StepVerifier.create(javersAuditService.getChangedFields(oldObject, newObject))
.expectNextMatches(fields -> fields != null)
.verifyComplete();
}
@Test
@DisplayName("测试获取变更字段 - 无变更")
void testGetChangedFields_NoChanges() {
@SuppressWarnings("unused")
class TestEntity {
private String name;
public TestEntity(String name) {
this.name = name;
}
}
TestEntity oldObject = new TestEntity("name");
TestEntity newObject = new TestEntity("name");
StepVerifier.create(javersAuditService.getChangedFields(oldObject, newObject))
.expectNextMatches(List -> List != null)
.verifyComplete();
}
@Test
@DisplayName("测试获取变更字段 - 异常")
void testGetChangedFields_Exception() {
@SuppressWarnings("unused")
class TestEntity {
private String name;
public TestEntity(String name) {
this.name = name;
}
}
TestEntity oldObject = new TestEntity("oldName");
StepVerifier.create(javersAuditService.getChangedFields(oldObject, oldObject))
.expectNextMatches(fields -> fields != null)
.verifyComplete();
}
@Test
@DisplayName("测试对象序列化为JSON - 成功")
void testToJson_Success() {
String testStr = "test";
StepVerifier.create(javersAuditService.toJson(testStr))
.expectNextMatches(json -> json != null && json.contains("test"))
.verifyComplete();
}
@Test
@DisplayName("测试对象序列化为JSON - 空对象")
void testToJson_Null() {
StepVerifier.create(javersAuditService.toJson(null))
.expectNextMatches(json -> json != null && json.equals("null"))
.verifyComplete();
}
@Test
@DisplayName("测试对象序列化为JSON - 异常")
void testToJson_Exception() {
doThrow(new RuntimeException("序列化失败"))
.when(objectMapper).valueToTree(any());
StepVerifier.create(javersAuditService.toJson("test"))
.expectErrorMatches(throwable -> throwable instanceof RuntimeException)
.verify();
}
}
@@ -0,0 +1,243 @@
package io.destiny.sys.core.service;
import io.destiny.sys.core.domain.OperationLog;
import io.destiny.sys.core.domain.query.OperationLogQuery;
import io.destiny.sys.core.repository.IOperationLogRepository;
import io.destiny.sys.core.service.impl.OperationLogServiceImpl;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
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 java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
@DisplayName("操作日志服务测试")
class OperationLogServiceTest {
@Mock
private IOperationLogRepository repository;
@InjectMocks
private OperationLogServiceImpl operationLogService;
private OperationLog testLog;
@BeforeEach
void setUp() {
testLog = new OperationLog();
testLog.setId(1L);
testLog.setOperationTime(LocalDateTime.now());
testLog.setModuleName("用户管理");
testLog.setOperationDesc("创建用户");
testLog.setOperator("admin");
testLog.setRequestMethod("POST");
testLog.setRequestPath("/sys/user");
testLog.setRequestParams("{\"username\":\"test\"}");
testLog.setResponseResult("{\"id\":1}");
testLog.setIpAddress("127.0.0.1");
testLog.setExecutionTime(100L);
testLog.setStatus("SUCCESS");
}
@Test
@DisplayName("测试根据ID查询操作日志 - 成功")
void testFindById_Success() {
when(repository.findById(1L)).thenReturn(Mono.just(testLog));
StepVerifier.create(operationLogService.findById(1L))
.expectNext(testLog)
.verifyComplete();
verify(repository, times(1)).findById(1L);
}
@Test
@DisplayName("测试根据ID查询操作日志 - 不存在")
void testFindById_NotFound() {
when(repository.findById(999L)).thenReturn(Mono.empty());
StepVerifier.create(operationLogService.findById(999L))
.expectNextCount(0)
.verifyComplete();
verify(repository, times(1)).findById(999L);
}
@Test
@DisplayName("测试保存操作日志 - 成功")
void testSave_Success() {
OperationLog newLog = new OperationLog();
newLog.setModuleName("测试模块");
newLog.setOperationDesc("测试操作");
newLog.setOperator("testuser");
when(repository.save(any(OperationLog.class))).thenReturn(Mono.just(testLog));
StepVerifier.create(operationLogService.save(newLog))
.expectNext(testLog)
.verifyComplete();
verify(repository, times(1)).save(any(OperationLog.class));
}
@Test
@DisplayName("测试保存操作日志 - 异常")
void testSave_Exception() {
OperationLog newLog = new OperationLog();
when(repository.save(any(OperationLog.class)))
.thenReturn(Mono.error(new RuntimeException("数据库连接失败")));
StepVerifier.create(operationLogService.save(newLog))
.verifyComplete();
verify(repository, times(1)).save(any(OperationLog.class));
}
@Test
@DisplayName("测试根据条件查询操作日志 - 成功")
void testFindByQuery_Success() {
OperationLogQuery query = new OperationLogQuery();
query.setOperator("admin");
query.setModuleName("用户管理");
query.setPageNum(1);
query.setPageSize(10);
List<OperationLog> logs = Arrays.asList(testLog);
when(repository.findByQuery(any(OperationLogQuery.class)))
.thenReturn(Flux.fromIterable(logs));
StepVerifier.create(operationLogService.findByQuery(query))
.expectNextCount(1)
.verifyComplete();
verify(repository, times(1)).findByQuery(any(OperationLogQuery.class));
}
@Test
@DisplayName("测试根据条件查询操作日志 - 空结果")
void testFindByQuery_Empty() {
OperationLogQuery query = new OperationLogQuery();
query.setOperator("nonexistent");
when(repository.findByQuery(any(OperationLogQuery.class)))
.thenReturn(Flux.empty());
StepVerifier.create(operationLogService.findByQuery(query))
.expectNextCount(0)
.verifyComplete();
verify(repository, times(1)).findByQuery(any(OperationLogQuery.class));
}
@Test
@DisplayName("测试统计操作日志数量 - 成功")
void testCountByQuery_Success() {
OperationLogQuery query = new OperationLogQuery();
query.setOperator("admin");
when(repository.countByQuery(any(OperationLogQuery.class)))
.thenReturn(Mono.just(10L));
StepVerifier.create(operationLogService.countByQuery(query))
.expectNext(10L)
.verifyComplete();
verify(repository, times(1)).countByQuery(any(OperationLogQuery.class));
}
@Test
@DisplayName("测试统计操作日志数量 - 零")
void testCountByQuery_Zero() {
OperationLogQuery query = new OperationLogQuery();
query.setOperator("nonexistent");
when(repository.countByQuery(any(OperationLogQuery.class)))
.thenReturn(Mono.just(0L));
StepVerifier.create(operationLogService.countByQuery(query))
.expectNext(0L)
.verifyComplete();
verify(repository, times(1)).countByQuery(any(OperationLogQuery.class));
}
@Test
@DisplayName("测试删除操作日志 - 成功")
void testDeleteById_Success() {
when(repository.deleteById(1L)).thenReturn(Mono.empty());
StepVerifier.create(operationLogService.deleteById(1L))
.verifyComplete();
verify(repository, times(1)).deleteById(1L);
}
@Test
@DisplayName("测试删除操作日志 - 异常")
void testDeleteById_Exception() {
when(repository.deleteById(1L))
.thenReturn(Mono.error(new RuntimeException("删除失败")));
StepVerifier.create(operationLogService.deleteById(1L))
.expectErrorMatches(throwable -> throwable instanceof RuntimeException &&
throwable.getMessage().equals("删除失败"))
.verify();
verify(repository, times(1)).deleteById(1L);
}
@Test
@DisplayName("测试根据时间范围查询 - 成功")
void testFindByTimeRange_Success() {
LocalDateTime startTime = LocalDateTime.now().minusDays(7);
LocalDateTime endTime = LocalDateTime.now();
OperationLogQuery query = new OperationLogQuery();
query.setStartTime(startTime);
query.setEndTime(endTime);
query.setPageNum(1);
query.setPageSize(10);
List<OperationLog> logs = Arrays.asList(testLog);
when(repository.findByQuery(any(OperationLogQuery.class)))
.thenReturn(Flux.fromIterable(logs));
StepVerifier.create(operationLogService.findByQuery(query))
.expectNextCount(1)
.verifyComplete();
verify(repository, times(1)).findByQuery(any(OperationLogQuery.class));
}
@Test
@DisplayName("测试根据状态查询 - 成功")
void testFindByStatus_Success() {
OperationLogQuery query = new OperationLogQuery();
query.setStatus("SUCCESS");
query.setPageNum(1);
query.setPageSize(10);
List<OperationLog> logs = Arrays.asList(testLog);
when(repository.findByQuery(any(OperationLogQuery.class)))
.thenReturn(Flux.fromIterable(logs));
StepVerifier.create(operationLogService.findByQuery(query))
.expectNextCount(1)
.verifyComplete();
verify(repository, times(1)).findByQuery(any(OperationLogQuery.class));
}
}
@@ -0,0 +1,275 @@
package io.destiny.sys.handler;
import io.destiny.sys.core.domain.OperationLog;
import io.destiny.sys.core.domain.query.OperationLogQuery;
import io.destiny.sys.core.service.IOperationLogService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.mock.web.reactive.function.server.MockServerRequest;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@DisplayName("操作日志处理器测试")
class OperationLogHandlerTest {
private OperationLogHandler operationLogHandler;
private IOperationLogService logService;
@BeforeEach
void setUp() {
logService = mock(IOperationLogService.class);
operationLogHandler = new OperationLogHandler(logService);
}
@Test
@DisplayName("测试根据ID查询操作日志 - 成功")
void testGetLogById_Success() {
Long logId = 1L;
OperationLog log = new OperationLog();
log.setId(logId);
log.setModuleName("用户管理");
log.setOperationDesc("创建用户");
log.setOperator("admin");
log.setOperationTime(LocalDateTime.now());
when(logService.findById(logId)).thenReturn(Mono.just(log));
ServerRequest request = MockServerRequest.builder()
.pathVariable("id", logId.toString())
.build();
Mono<ServerResponse> responseMono = operationLogHandler.getLogById(request);
StepVerifier.create(responseMono)
.assertNext(response -> {
assertEquals(200, response.statusCode().value());
})
.verifyComplete();
verify(logService, times(1)).findById(logId);
}
@Test
@DisplayName("测试根据ID查询操作日志 - 不存在")
void testGetLogById_NotFound() {
Long logId = 999L;
when(logService.findById(logId)).thenReturn(Mono.empty());
ServerRequest request = MockServerRequest.builder()
.pathVariable("id", logId.toString())
.build();
Mono<ServerResponse> responseMono = operationLogHandler.getLogById(request);
StepVerifier.create(responseMono)
.assertNext(response -> {
assertEquals(400, response.statusCode().value());
})
.verifyComplete();
verify(logService, times(1)).findById(logId);
}
@Test
@DisplayName("测试根据ID查询操作日志 - 异常")
void testGetLogById_Exception() {
Long logId = 1L;
when(logService.findById(logId))
.thenReturn(Mono.error(new RuntimeException("数据库连接失败")));
ServerRequest request = MockServerRequest.builder()
.pathVariable("id", logId.toString())
.build();
Mono<ServerResponse> responseMono = operationLogHandler.getLogById(request);
StepVerifier.create(responseMono)
.assertNext(response -> {
assertEquals(400, response.statusCode().value());
})
.verifyComplete();
verify(logService, times(1)).findById(logId);
}
@Test
@DisplayName("测试分页查询操作日志 - 成功")
void testQueryLogs_Success() {
OperationLogQuery query = new OperationLogQuery();
query.setOperator("admin");
query.setPageNum(1);
query.setPageSize(10);
OperationLog log1 = new OperationLog();
log1.setId(1L);
log1.setModuleName("用户管理");
log1.setOperationDesc("创建用户");
log1.setOperator("admin");
OperationLog log2 = new OperationLog();
log2.setId(2L);
log2.setModuleName("角色管理");
log2.setOperationDesc("创建角色");
log2.setOperator("admin");
List<OperationLog> logs = Arrays.asList(log1, log2);
when(logService.findByQuery(any(OperationLogQuery.class)))
.thenReturn(Flux.fromIterable(logs));
when(logService.countByQuery(any(OperationLogQuery.class)))
.thenReturn(Mono.just(2L));
ServerRequest request = MockServerRequest.builder()
.header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
.body(Mono.just(query));
Mono<ServerResponse> responseMono = operationLogHandler.queryLogs(request);
StepVerifier.create(responseMono)
.assertNext(response -> {
assertEquals(200, response.statusCode().value());
})
.verifyComplete();
verify(logService, times(1)).findByQuery(any(OperationLogQuery.class));
verify(logService, times(1)).countByQuery(any(OperationLogQuery.class));
}
@Test
@DisplayName("测试分页查询操作日志 - 空结果")
void testQueryLogs_Empty() {
OperationLogQuery query = new OperationLogQuery();
query.setOperator("nonexistent");
when(logService.findByQuery(any(OperationLogQuery.class)))
.thenReturn(Flux.empty());
when(logService.countByQuery(any(OperationLogQuery.class)))
.thenReturn(Mono.just(0L));
ServerRequest request = MockServerRequest.builder()
.header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
.body(Mono.just(query));
Mono<ServerResponse> responseMono = operationLogHandler.queryLogs(request);
StepVerifier.create(responseMono)
.assertNext(response -> {
assertEquals(200, response.statusCode().value());
})
.verifyComplete();
verify(logService, times(1)).findByQuery(any(OperationLogQuery.class));
verify(logService, times(1)).countByQuery(any(OperationLogQuery.class));
}
@Test
@DisplayName("测试分页查询操作日志 - 默认分页参数")
void testQueryLogs_DefaultPagination() {
OperationLogQuery query = new OperationLogQuery();
query.setOperator("admin");
when(logService.findByQuery(any(OperationLogQuery.class)))
.thenReturn(Flux.empty());
when(logService.countByQuery(any(OperationLogQuery.class)))
.thenReturn(Mono.just(0L));
ServerRequest request = MockServerRequest.builder()
.header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
.body(Mono.just(query));
Mono<ServerResponse> responseMono = operationLogHandler.queryLogs(request);
StepVerifier.create(responseMono)
.assertNext(response -> {
assertEquals(200, response.statusCode().value());
})
.verifyComplete();
verify(logService, times(1)).findByQuery(any(OperationLogQuery.class));
}
@Test
@DisplayName("测试分页查询操作日志 - 异常")
void testQueryLogs_Exception() {
OperationLogQuery query = new OperationLogQuery();
when(logService.findByQuery(any(OperationLogQuery.class)))
.thenReturn(Flux.error(new RuntimeException("查询失败")));
ServerRequest request = MockServerRequest.builder()
.header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
.body(Mono.just(query));
Mono<ServerResponse> responseMono = operationLogHandler.queryLogs(request);
StepVerifier.create(responseMono)
.assertNext(response -> {
assertEquals(400, response.statusCode().value());
})
.verifyComplete();
verify(logService, times(1)).findByQuery(any(OperationLogQuery.class));
}
@Test
@DisplayName("测试删除操作日志 - 成功")
void testDeleteLog_Success() {
Long logId = 1L;
OperationLog log = new OperationLog();
log.setId(logId);
when(logService.findById(logId)).thenReturn(Mono.just(log));
when(logService.deleteById(logId)).thenReturn(Mono.empty());
ServerRequest request = MockServerRequest.builder()
.pathVariable("id", logId.toString())
.build();
Mono<ServerResponse> responseMono = operationLogHandler.deleteLog(request);
StepVerifier.create(responseMono)
.assertNext(response -> {
assertEquals(200, response.statusCode().value());
})
.verifyComplete();
verify(logService, times(1)).findById(logId);
verify(logService, times(1)).deleteById(logId);
}
@Test
@DisplayName("测试删除操作日志 - 异常")
void testDeleteLog_Exception() {
Long logId = 1L;
when(logService.findById(logId)).thenReturn(Mono.just(new OperationLog()));
when(logService.deleteById(logId))
.thenReturn(Mono.error(new RuntimeException("删除失败")));
ServerRequest request = MockServerRequest.builder()
.pathVariable("id", logId.toString())
.build();
Mono<ServerResponse> responseMono = operationLogHandler.deleteLog(request);
StepVerifier.create(responseMono)
.assertNext(response -> {
assertEquals(400, response.statusCode().value());
})
.verifyComplete();
verify(logService, times(1)).findById(logId);
verify(logService, times(1)).deleteById(logId);
}
}
@@ -0,0 +1,425 @@
package io.destiny.sys.handler;
import io.destiny.sys.core.domain.SysMenu;
import io.destiny.sys.core.domain.SysRole;
import io.destiny.sys.core.service.ISysMenuService;
import io.destiny.sys.core.service.ISysRoleService;
import io.destiny.sys.dto.request.AssignMenuToRoleRequest;
import io.destiny.sys.dto.request.SysMenuRequest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.reactive.function.server.MockServerRequest;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.time.LocalDateTime;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@DisplayName("菜单处理器测试")
class SysMenuHandlerTest {
private SysMenuHandler menuHandler;
private ISysMenuService menuService;
private ISysRoleService roleService;
@BeforeEach
void setUp() {
menuService = mock(ISysMenuService.class);
roleService = mock(ISysRoleService.class);
menuHandler = new SysMenuHandler(menuService, roleService);
}
@Test
@DisplayName("测试根据ID查询菜单 - 成功")
void testGetMenuById_Success() {
Long menuId = 1L;
SysMenu menu = new SysMenu();
menu.setId(menuId);
menu.setMenuName("系统管理");
menu.setParentId(0L);
menu.setOrderNum(1);
menu.setMenuType("M");
menu.setPerms("system");
menu.setComponent("system/index");
menu.setStatus("0");
menu.setCreatedAt(LocalDateTime.now());
menu.setUpdatedAt(LocalDateTime.now());
when(menuService.findById(menuId)).thenReturn(Mono.just(menu));
ServerRequest request = MockServerRequest.builder()
.pathVariable("id", menuId.toString())
.build();
Mono<ServerResponse> responseMono = menuHandler.getMenuById(request);
StepVerifier.create(responseMono)
.assertNext(response -> {
assertEquals(200, response.statusCode().value());
})
.verifyComplete();
verify(menuService, times(1)).findById(menuId);
}
@Test
@DisplayName("测试根据ID查询菜单 - 菜单不存在")
void testGetMenuById_NotFound() {
Long menuId = 1L;
when(menuService.findById(menuId)).thenReturn(Mono.empty());
ServerRequest request = MockServerRequest.builder()
.pathVariable("id", menuId.toString())
.build();
Mono<ServerResponse> responseMono = menuHandler.getMenuById(request);
StepVerifier.create(responseMono)
.assertNext(response -> {
assertEquals(400, response.statusCode().value());
})
.verifyComplete();
verify(menuService, times(1)).findById(menuId);
}
@Test
@DisplayName("测试根据角色ID查询菜单列表 - 成功")
void testGetMenusByRoleId_Success() {
Long roleId = 1L;
SysMenu menu1 = new SysMenu();
menu1.setId(1L);
menu1.setMenuName("系统管理");
menu1.setParentId(0L);
menu1.setOrderNum(1);
menu1.setMenuType("M");
menu1.setPerms("system");
menu1.setComponent("system/index");
menu1.setStatus("0");
SysMenu menu2 = new SysMenu();
menu2.setId(2L);
menu2.setMenuName("用户管理");
menu2.setParentId(1L);
menu2.setOrderNum(1);
menu2.setMenuType("C");
menu2.setPerms("system:user:list");
menu2.setComponent("system/user/index");
menu2.setStatus("0");
when(menuService.findByRoleId(roleId)).thenReturn(Flux.just(menu1, menu2));
ServerRequest request = MockServerRequest.builder()
.pathVariable("roleId", roleId.toString())
.build();
Mono<ServerResponse> responseMono = menuHandler.getMenusByRoleId(request);
StepVerifier.create(responseMono)
.assertNext(response -> {
assertEquals(200, response.statusCode().value());
})
.verifyComplete();
verify(menuService, times(1)).findByRoleId(roleId);
}
@Test
@DisplayName("测试根据用户ID查询菜单列表 - 成功")
void testGetMenusByUserId_Success() {
Long userId = 1L;
SysMenu menu1 = new SysMenu();
menu1.setId(1L);
menu1.setMenuName("系统管理");
menu1.setParentId(0L);
menu1.setOrderNum(1);
menu1.setMenuType("M");
menu1.setPerms("system");
menu1.setComponent("system/index");
menu1.setStatus("0");
SysMenu menu2 = new SysMenu();
menu2.setId(2L);
menu2.setMenuName("用户管理");
menu2.setParentId(1L);
menu2.setOrderNum(1);
menu2.setMenuType("C");
menu2.setPerms("system:user:list");
menu2.setComponent("system/user/index");
menu2.setStatus("0");
when(menuService.findByUserId(userId)).thenReturn(Flux.just(menu1, menu2));
ServerRequest request = MockServerRequest.builder()
.pathVariable("userId", userId.toString())
.build();
Mono<ServerResponse> responseMono = menuHandler.getMenusByUserId(request);
StepVerifier.create(responseMono)
.assertNext(response -> {
assertEquals(200, response.statusCode().value());
})
.verifyComplete();
verify(menuService, times(1)).findByUserId(userId);
}
@Test
@DisplayName("测试查询所有菜单 - 成功")
void testGetAllMenus_Success() {
SysMenu menu1 = new SysMenu();
menu1.setId(1L);
menu1.setMenuName("系统管理");
menu1.setParentId(0L);
menu1.setOrderNum(1);
menu1.setMenuType("M");
menu1.setPerms("system");
menu1.setComponent("system/index");
menu1.setStatus("0");
SysMenu menu2 = new SysMenu();
menu2.setId(2L);
menu2.setMenuName("用户管理");
menu2.setParentId(1L);
menu2.setOrderNum(1);
menu2.setMenuType("C");
menu2.setPerms("system:user:list");
menu2.setComponent("system/user/index");
menu2.setStatus("0");
when(menuService.findAll()).thenReturn(Flux.just(menu1, menu2));
ServerRequest request = MockServerRequest.builder()
.build();
Mono<ServerResponse> responseMono = menuHandler.getAllMenus(request);
StepVerifier.create(responseMono)
.assertNext(response -> {
assertEquals(200, response.statusCode().value());
})
.verifyComplete();
verify(menuService, times(1)).findAll();
}
@Test
@DisplayName("测试创建菜单 - 成功")
void testCreateMenu_Success() {
SysMenuRequest menuRequest = new SysMenuRequest();
menuRequest.setName("测试菜单");
menuRequest.setParentId(0L);
menuRequest.setSortOrder(3);
menuRequest.setComponent("test/index");
menuRequest.setStatus("0");
SysMenu createdMenu = new SysMenu();
createdMenu.setId(3L);
createdMenu.setMenuName("测试菜单");
createdMenu.setParentId(0L);
createdMenu.setOrderNum(3);
createdMenu.setMenuType("C");
createdMenu.setPerms("test");
createdMenu.setComponent("test/index");
createdMenu.setStatus("0");
createdMenu.setCreatedAt(LocalDateTime.now());
createdMenu.setUpdatedAt(LocalDateTime.now());
when(menuService.create(any(SysMenu.class))).thenReturn(Mono.just(createdMenu));
ServerRequest request = MockServerRequest.builder()
.body(Mono.just(menuRequest));
Mono<ServerResponse> responseMono = menuHandler.createMenu(request);
StepVerifier.create(responseMono)
.assertNext(response -> {
assertEquals(200, response.statusCode().value());
})
.verifyComplete();
verify(menuService, times(1)).create(any(SysMenu.class));
}
@Test
@DisplayName("测试更新菜单 - 成功")
void testUpdateMenu_Success() {
SysMenuRequest menuRequest = new SysMenuRequest();
menuRequest.setId(1L);
menuRequest.setName("系统管理");
menuRequest.setParentId(0L);
menuRequest.setSortOrder(1);
menuRequest.setComponent("system/index");
menuRequest.setStatus("0");
SysMenu existingMenu = new SysMenu();
existingMenu.setId(1L);
existingMenu.setMenuName("系统管理");
existingMenu.setParentId(0L);
existingMenu.setOrderNum(1);
existingMenu.setMenuType("M");
existingMenu.setPerms("system");
existingMenu.setComponent("system/index");
existingMenu.setStatus("0");
existingMenu.setCreatedAt(LocalDateTime.now());
existingMenu.setUpdatedAt(LocalDateTime.now());
SysMenu updatedMenu = new SysMenu();
updatedMenu.setId(1L);
updatedMenu.setMenuName("系统管理");
updatedMenu.setParentId(0L);
updatedMenu.setOrderNum(1);
updatedMenu.setMenuType("M");
updatedMenu.setPerms("system");
updatedMenu.setComponent("system/index");
updatedMenu.setStatus("0");
updatedMenu.setCreatedAt(LocalDateTime.now());
updatedMenu.setUpdatedAt(LocalDateTime.now());
when(menuService.findById(1L)).thenReturn(Mono.just(existingMenu));
when(menuService.update(any(SysMenu.class))).thenReturn(Mono.just(updatedMenu));
ServerRequest request = MockServerRequest.builder()
.body(Mono.just(menuRequest));
Mono<ServerResponse> responseMono = menuHandler.updateMenu(request);
StepVerifier.create(responseMono)
.assertNext(response -> {
assertEquals(200, response.statusCode().value());
})
.verifyComplete();
verify(menuService, times(1)).findById(1L);
verify(menuService, times(1)).update(any(SysMenu.class));
}
@Test
@DisplayName("测试更新菜单 - 菜单不存在")
void testUpdateMenu_NotFound() {
SysMenuRequest menuRequest = new SysMenuRequest();
menuRequest.setId(1L);
menuRequest.setName("系统管理");
menuRequest.setParentId(0L);
menuRequest.setSortOrder(1);
menuRequest.setComponent("system/index");
menuRequest.setStatus("0");
when(menuService.findById(1L)).thenReturn(Mono.empty());
ServerRequest request = MockServerRequest.builder()
.body(Mono.just(menuRequest));
Mono<ServerResponse> responseMono = menuHandler.updateMenu(request);
StepVerifier.create(responseMono)
.assertNext(response -> {
assertEquals(400, response.statusCode().value());
})
.verifyComplete();
verify(menuService, times(1)).findById(1L);
verify(menuService, never()).update(any(SysMenu.class));
}
@Test
@DisplayName("测试删除菜单 - 成功")
void testDeleteMenu_Success() {
Long menuId = 1L;
when(menuService.deleteById(menuId)).thenReturn(Mono.empty());
ServerRequest request = MockServerRequest.builder()
.pathVariable("id", menuId.toString())
.build();
Mono<ServerResponse> responseMono = menuHandler.deleteMenu(request);
StepVerifier.create(responseMono)
.assertNext(response -> {
assertEquals(200, response.statusCode().value());
})
.verifyComplete();
verify(menuService, times(1)).deleteById(menuId);
}
@Test
@DisplayName("测试分配菜单到角色 - 成功")
void testAssignMenuToRole_Success() {
AssignMenuToRoleRequest assignRequest = new AssignMenuToRoleRequest();
assignRequest.setRoleId(1L);
assignRequest.setMenuId(1L);
SysRole role = new SysRole();
role.setId(1L);
role.setRoleName("管理员");
role.setRoleKey("admin");
role.setRoleSort(1);
role.setStatus("0");
role.setCreatedAt(LocalDateTime.now());
role.setUpdatedAt(LocalDateTime.now());
SysMenu menu = new SysMenu();
menu.setId(1L);
menu.setMenuName("系统管理");
menu.setMenuType("M");
menu.setOrderNum(1);
menu.setStatus("0");
menu.setCreatedAt(LocalDateTime.now());
menu.setUpdatedAt(LocalDateTime.now());
when(roleService.findById(1L)).thenReturn(Mono.just(role));
when(menuService.findById(1L)).thenReturn(Mono.just(menu));
when(menuService.findByRoleId(1L)).thenReturn(Flux.empty());
when(menuService.assignMenuToRole(1L, 1L)).thenReturn(Mono.empty());
ServerRequest request = MockServerRequest.builder()
.body(Mono.just(assignRequest));
Mono<ServerResponse> responseMono = menuHandler.assignMenuToRole(request);
StepVerifier.create(responseMono)
.assertNext(response -> {
assertEquals(200, response.statusCode().value());
})
.verifyComplete();
verify(roleService, times(1)).findById(1L);
verify(menuService, times(1)).findById(1L);
verify(menuService, times(1)).findByRoleId(1L);
verify(menuService, times(1)).assignMenuToRole(1L, 1L);
}
@Test
@DisplayName("测试移除角色的菜单 - 成功")
void testRemoveMenuFromRole_Success() {
Long roleId = 1L;
Long menuId = 1L;
when(menuService.removeMenuFromRole(roleId, menuId)).thenReturn(Mono.empty());
ServerRequest request = MockServerRequest.builder()
.pathVariable("roleId", roleId.toString())
.pathVariable("menuId", menuId.toString())
.build();
Mono<ServerResponse> responseMono = menuHandler.removeMenuFromRole(request);
StepVerifier.create(responseMono)
.assertNext(response -> {
assertEquals(200, response.statusCode().value());
})
.verifyComplete();
verify(menuService, times(1)).removeMenuFromRole(roleId, menuId);
}
}
@@ -0,0 +1,395 @@
package io.destiny.sys.handler;
import io.destiny.sys.core.domain.SysRole;
import io.destiny.sys.core.service.ISysRoleService;
import io.destiny.sys.dto.request.AssignRoleToUserRequest;
import io.destiny.sys.dto.request.SysRoleRequest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.reactive.function.server.MockServerRequest;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.time.LocalDateTime;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@DisplayName("角色处理器测试")
class SysRoleHandlerTest {
private SysRoleHandler roleHandler;
private ISysRoleService roleService;
@BeforeEach
void setUp() {
roleService = mock(ISysRoleService.class);
roleHandler = new SysRoleHandler(roleService);
}
@Test
@DisplayName("测试根据ID查询角色 - 成功")
void testGetRoleById_Success() {
Long roleId = 1L;
SysRole role = new SysRole();
role.setId(roleId);
role.setRoleName("管理员");
role.setRoleKey("admin");
role.setRoleSort(1);
role.setStatus("0");
role.setCreatedAt(LocalDateTime.now());
role.setUpdatedAt(LocalDateTime.now());
when(roleService.findById(roleId)).thenReturn(Mono.just(role));
ServerRequest request = MockServerRequest.builder()
.pathVariable("id", roleId.toString())
.build();
Mono<ServerResponse> responseMono = roleHandler.getRoleById(request);
StepVerifier.create(responseMono)
.assertNext(response -> {
assertEquals(200, response.statusCode().value());
})
.verifyComplete();
verify(roleService, times(1)).findById(roleId);
}
@Test
@DisplayName("测试根据ID查询角色 - 角色不存在")
void testGetRoleById_NotFound() {
Long roleId = 1L;
when(roleService.findById(roleId)).thenReturn(Mono.empty());
ServerRequest request = MockServerRequest.builder()
.pathVariable("id", roleId.toString())
.build();
Mono<ServerResponse> responseMono = roleHandler.getRoleById(request);
StepVerifier.create(responseMono)
.assertNext(response -> {
assertEquals(400, response.statusCode().value());
})
.verifyComplete();
verify(roleService, times(1)).findById(roleId);
}
@Test
@DisplayName("测试根据角色权限字符串查询角色 - 成功")
void testGetRoleByRoleKey_Success() {
String roleKey = "admin";
SysRole role = new SysRole();
role.setId(1L);
role.setRoleName("管理员");
role.setRoleKey(roleKey);
role.setRoleSort(1);
role.setStatus("0");
role.setCreatedAt(LocalDateTime.now());
role.setUpdatedAt(LocalDateTime.now());
when(roleService.findByRoleKey(roleKey)).thenReturn(Mono.just(role));
ServerRequest request = MockServerRequest.builder()
.pathVariable("roleKey", roleKey)
.build();
Mono<ServerResponse> responseMono = roleHandler.getRoleByRoleKey(request);
StepVerifier.create(responseMono)
.assertNext(response -> {
assertEquals(200, response.statusCode().value());
})
.verifyComplete();
verify(roleService, times(1)).findByRoleKey(roleKey);
}
@Test
@DisplayName("测试根据角色权限字符串查询角色 - 角色不存在")
void testGetRoleByRoleKey_NotFound() {
String roleKey = "nonexistent";
when(roleService.findByRoleKey(roleKey)).thenReturn(Mono.empty());
ServerRequest request = MockServerRequest.builder()
.pathVariable("roleKey", roleKey)
.build();
Mono<ServerResponse> responseMono = roleHandler.getRoleByRoleKey(request);
StepVerifier.create(responseMono)
.assertNext(response -> {
assertEquals(400, response.statusCode().value());
})
.verifyComplete();
verify(roleService, times(1)).findByRoleKey(roleKey);
}
@Test
@DisplayName("测试根据用户ID查询角色列表 - 成功")
void testGetRolesByUserId_Success() {
Long userId = 1L;
SysRole role1 = new SysRole();
role1.setId(1L);
role1.setRoleName("管理员");
role1.setRoleKey("admin");
role1.setRoleSort(1);
role1.setStatus("0");
SysRole role2 = new SysRole();
role2.setId(2L);
role2.setRoleName("用户");
role2.setRoleKey("user");
role2.setRoleSort(2);
role2.setStatus("0");
when(roleService.findByUserId(userId)).thenReturn(Flux.just(role1, role2));
ServerRequest request = MockServerRequest.builder()
.pathVariable("userId", userId.toString())
.build();
Mono<ServerResponse> responseMono = roleHandler.getRolesByUserId(request);
StepVerifier.create(responseMono)
.assertNext(response -> {
assertEquals(200, response.statusCode().value());
})
.verifyComplete();
verify(roleService, times(1)).findByUserId(userId);
}
@Test
@DisplayName("测试查询所有角色 - 成功")
void testGetAllRoles_Success() {
SysRole role1 = new SysRole();
role1.setId(1L);
role1.setRoleName("管理员");
role1.setRoleKey("admin");
role1.setRoleSort(1);
role1.setStatus("0");
SysRole role2 = new SysRole();
role2.setId(2L);
role2.setRoleName("用户");
role2.setRoleKey("user");
role2.setRoleSort(2);
role2.setStatus("0");
when(roleService.findAll()).thenReturn(Flux.just(role1, role2));
ServerRequest request = MockServerRequest.builder()
.build();
Mono<ServerResponse> responseMono = roleHandler.getAllRoles(request);
StepVerifier.create(responseMono)
.assertNext(response -> {
assertEquals(200, response.statusCode().value());
})
.verifyComplete();
verify(roleService, times(1)).findAll();
}
@Test
@DisplayName("测试创建角色 - 成功")
void testCreateRole_Success() {
SysRoleRequest roleRequest = new SysRoleRequest();
roleRequest.setName("测试角色");
roleRequest.setRoleKey("test");
roleRequest.setSortOrder(3);
roleRequest.setStatus("0");
SysRole createdRole = new SysRole();
createdRole.setId(3L);
createdRole.setRoleName("测试角色");
createdRole.setRoleKey("test");
createdRole.setRoleSort(3);
createdRole.setStatus("0");
createdRole.setCreatedAt(LocalDateTime.now());
createdRole.setUpdatedAt(LocalDateTime.now());
when(roleService.findByRoleKey("test")).thenReturn(Mono.empty());
when(roleService.create(any(SysRole.class))).thenReturn(Mono.just(createdRole));
ServerRequest request = MockServerRequest.builder()
.body(Mono.just(roleRequest));
Mono<ServerResponse> responseMono = roleHandler.createRole(request);
StepVerifier.create(responseMono)
.assertNext(response -> {
assertEquals(200, response.statusCode().value());
})
.verifyComplete();
verify(roleService, times(1)).findByRoleKey("test");
verify(roleService, times(1)).create(any(SysRole.class));
}
@Test
@DisplayName("测试更新角色 - 成功")
void testUpdateRole_Success() {
SysRoleRequest roleRequest = new SysRoleRequest();
roleRequest.setId(1L);
roleRequest.setName("管理员");
roleRequest.setRoleKey("admin");
roleRequest.setSortOrder(1);
roleRequest.setStatus("0");
SysRole existingRole = new SysRole();
existingRole.setId(1L);
existingRole.setRoleName("管理员");
existingRole.setRoleKey("admin");
existingRole.setRoleSort(1);
existingRole.setStatus("0");
existingRole.setCreatedAt(LocalDateTime.now());
existingRole.setUpdatedAt(LocalDateTime.now());
SysRole updatedRole = new SysRole();
updatedRole.setId(1L);
updatedRole.setRoleName("管理员");
updatedRole.setRoleKey("admin");
updatedRole.setRoleSort(1);
updatedRole.setStatus("0");
updatedRole.setCreatedAt(LocalDateTime.now());
updatedRole.setUpdatedAt(LocalDateTime.now());
when(roleService.findById(1L)).thenReturn(Mono.just(existingRole));
when(roleService.update(any(SysRole.class))).thenReturn(Mono.just(updatedRole));
ServerRequest request = MockServerRequest.builder()
.body(Mono.just(roleRequest));
Mono<ServerResponse> responseMono = roleHandler.updateRole(request);
StepVerifier.create(responseMono)
.assertNext(response -> {
assertEquals(200, response.statusCode().value());
})
.verifyComplete();
verify(roleService, times(1)).findById(1L);
verify(roleService, times(1)).update(any(SysRole.class));
}
@Test
@DisplayName("测试更新角色 - 角色不存在")
void testUpdateRole_NotFound() {
SysRoleRequest roleRequest = new SysRoleRequest();
roleRequest.setId(1L);
roleRequest.setName("管理员");
roleRequest.setRoleKey("admin");
roleRequest.setSortOrder(1);
roleRequest.setStatus("0");
when(roleService.findById(1L)).thenReturn(Mono.empty());
ServerRequest request = MockServerRequest.builder()
.body(Mono.just(roleRequest));
Mono<ServerResponse> responseMono = roleHandler.updateRole(request);
StepVerifier.create(responseMono)
.assertNext(response -> {
assertEquals(400, response.statusCode().value());
})
.verifyComplete();
verify(roleService, times(1)).findById(1L);
verify(roleService, never()).update(any(SysRole.class));
}
@Test
@DisplayName("测试删除角色 - 成功")
void testDeleteRole_Success() {
Long roleId = 1L;
when(roleService.deleteById(roleId)).thenReturn(Mono.empty());
ServerRequest request = MockServerRequest.builder()
.pathVariable("id", roleId.toString())
.build();
Mono<ServerResponse> responseMono = roleHandler.deleteRole(request);
StepVerifier.create(responseMono)
.assertNext(response -> {
assertEquals(200, response.statusCode().value());
})
.verifyComplete();
verify(roleService, times(1)).deleteById(roleId);
}
@Test
@DisplayName("测试分配角色到用户 - 成功")
void testAssignRoleToUser_Success() {
AssignRoleToUserRequest assignRequest = new AssignRoleToUserRequest();
assignRequest.setUserId(1L);
assignRequest.setRoleId(1L);
SysRole role = new SysRole();
role.setId(1L);
role.setRoleName("管理员");
role.setRoleKey("admin");
role.setRoleSort(1);
role.setStatus("0");
role.setCreatedAt(LocalDateTime.now());
role.setUpdatedAt(LocalDateTime.now());
when(roleService.findById(1L)).thenReturn(Mono.just(role));
when(roleService.findByUserId(1L)).thenReturn(Flux.empty());
when(roleService.assignRoleToUser(1L, 1L)).thenReturn(Mono.empty());
ServerRequest request = MockServerRequest.builder()
.body(Mono.just(assignRequest));
Mono<ServerResponse> responseMono = roleHandler.assignRoleToUser(request);
StepVerifier.create(responseMono)
.assertNext(response -> {
assertEquals(200, response.statusCode().value());
})
.verifyComplete();
verify(roleService, times(1)).findById(1L);
verify(roleService, times(1)).findByUserId(1L);
verify(roleService, times(1)).assignRoleToUser(1L, 1L);
}
@Test
@DisplayName("测试移除用户的角色 - 成功")
void testRemoveRoleFromUser_Success() {
Long userId = 1L;
Long roleId = 1L;
when(roleService.removeRoleFromUser(userId, roleId)).thenReturn(Mono.empty());
ServerRequest request = MockServerRequest.builder()
.pathVariable("userId", userId.toString())
.pathVariable("roleId", roleId.toString())
.build();
Mono<ServerResponse> responseMono = roleHandler.removeRoleFromUser(request);
StepVerifier.create(responseMono)
.assertNext(response -> {
assertEquals(200, response.statusCode().value());
})
.verifyComplete();
verify(roleService, times(1)).removeRoleFromUser(userId, roleId);
}
}
@@ -0,0 +1,404 @@
package io.destiny.sys.handler;
import io.destiny.common.primitive.EmailAddress;
import io.destiny.common.primitive.PhoneNumber;
import io.destiny.sys.core.domain.SysUser;
import io.destiny.sys.core.service.ISysUserService;
import io.destiny.sys.dto.request.SysUserRequest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.reactive.function.server.MockServerRequest;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.time.LocalDateTime;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@DisplayName("用户处理器测试")
class SysUserHandlerTest {
private SysUserHandler userHandler;
private ISysUserService userService;
@BeforeEach
void setUp() {
userService = mock(ISysUserService.class);
userHandler = new SysUserHandler(userService);
}
@Test
@DisplayName("测试根据ID查询用户 - 正常情况")
void testGetUserById_Success() {
Long userId = 1L;
SysUser sysUser = new SysUser();
sysUser.setId(userId);
sysUser.setUsername("testuser");
sysUser.setEmail(EmailAddress.of("test@example.com"));
sysUser.setPhone(PhoneNumber.of("13800138000"));
sysUser.setStatus("0");
sysUser.setCreatedAt(LocalDateTime.now());
when(userService.findById(userId)).thenReturn(Mono.just(sysUser));
ServerRequest request = MockServerRequest.builder()
.pathVariable("id", userId.toString())
.build();
Mono<ServerResponse> responseMono = userHandler.getUserById(request);
StepVerifier.create(responseMono)
.assertNext(response -> {
assertEquals(200, response.statusCode().value());
})
.verifyComplete();
verify(userService, times(1)).findById(userId);
}
@Test
@DisplayName("测试根据ID查询用户 - 用户不存在")
void testGetUserById_NotFound() {
Long userId = 1L;
when(userService.findById(userId)).thenReturn(Mono.empty());
ServerRequest request = MockServerRequest.builder()
.pathVariable("id", userId.toString())
.build();
Mono<ServerResponse> responseMono = userHandler.getUserById(request);
StepVerifier.create(responseMono)
.assertNext(response -> {
assertEquals(400, response.statusCode().value());
})
.verifyComplete();
verify(userService, times(1)).findById(userId);
}
@Test
@DisplayName("测试根据ID查询用户 - 服务异常")
void testGetUserById_ServiceException() {
Long userId = 1L;
when(userService.findById(userId)).thenReturn(Mono.error(new RuntimeException("数据库连接失败")));
ServerRequest request = MockServerRequest.builder()
.pathVariable("id", userId.toString())
.build();
Mono<ServerResponse> responseMono = userHandler.getUserById(request);
StepVerifier.create(responseMono)
.assertNext(response -> {
assertEquals(400, response.statusCode().value());
})
.verifyComplete();
verify(userService, times(1)).findById(userId);
}
@Test
@DisplayName("测试根据用户名查询用户 - 正常情况")
void testGetUserByUsername_Success() {
String username = "testuser";
SysUser sysUser = new SysUser();
sysUser.setId(1L);
sysUser.setUsername(username);
sysUser.setEmail(EmailAddress.of("test@example.com"));
sysUser.setPhone(PhoneNumber.of("13800138000"));
sysUser.setStatus("0");
sysUser.setCreatedAt(LocalDateTime.now());
when(userService.findByUsername(username)).thenReturn(Mono.just(sysUser));
ServerRequest request = MockServerRequest.builder()
.pathVariable("username", username)
.build();
Mono<ServerResponse> responseMono = userHandler.getUserByUsername(request);
StepVerifier.create(responseMono)
.assertNext(response -> {
assertEquals(200, response.statusCode().value());
})
.verifyComplete();
verify(userService, times(1)).findByUsername(username);
}
@Test
@DisplayName("测试根据用户名查询用户 - 用户不存在")
void testGetUserByUsername_NotFound() {
String username = "nonexistent";
when(userService.findByUsername(username)).thenReturn(Mono.empty());
ServerRequest request = MockServerRequest.builder()
.pathVariable("username", username)
.build();
Mono<ServerResponse> responseMono = userHandler.getUserByUsername(request);
StepVerifier.create(responseMono)
.assertNext(response -> {
assertEquals(400, response.statusCode().value());
})
.verifyComplete();
verify(userService, times(1)).findByUsername(username);
}
@Test
@DisplayName("测试创建用户 - 正常情况")
void testCreateUser_Success() {
SysUserRequest userRequest = new SysUserRequest();
userRequest.setUsername("newuser");
userRequest.setPassword("password123");
userRequest.setEmail("newuser@example.com");
userRequest.setPhone("13900139000");
userRequest.setStatus("0");
SysUser createdUser = new SysUser();
createdUser.setId(1L);
createdUser.setUsername("newuser");
createdUser.setEmail(EmailAddress.of("newuser@example.com"));
createdUser.setPhone(PhoneNumber.of("13900139000"));
createdUser.setStatus("0");
createdUser.setCreatedAt(LocalDateTime.now());
when(userService.create(any(SysUser.class))).thenReturn(Mono.just(createdUser));
ServerRequest request = MockServerRequest.builder()
.body(Mono.just(userRequest));
Mono<ServerResponse> responseMono = userHandler.createUser(request);
StepVerifier.create(responseMono)
.assertNext(response -> {
assertEquals(200, response.statusCode().value());
})
.verifyComplete();
verify(userService, times(1)).create(any(SysUser.class));
}
@Test
@DisplayName("测试创建用户 - 服务异常")
void testCreateUser_ServiceException() {
SysUserRequest userRequest = new SysUserRequest();
userRequest.setUsername("newuser");
userRequest.setPassword("password123");
userRequest.setEmail("newuser@example.com");
userRequest.setPhone("13900139000");
userRequest.setStatus("0");
when(userService.create(any(SysUser.class)))
.thenReturn(Mono.error(new RuntimeException("用户名已存在")));
ServerRequest request = MockServerRequest.builder()
.body(Mono.just(userRequest));
Mono<ServerResponse> responseMono = userHandler.createUser(request);
StepVerifier.create(responseMono)
.assertNext(response -> {
assertEquals(400, response.statusCode().value());
})
.verifyComplete();
verify(userService, times(1)).create(any(SysUser.class));
}
@Test
@DisplayName("测试更新用户 - 正常情况")
void testUpdateUser_Success() {
Long userId = 1L;
SysUserRequest userRequest = new SysUserRequest();
userRequest.setId(userId);
userRequest.setUsername("updateduser");
userRequest.setEmail("updated@example.com");
userRequest.setPhone("13800138000");
userRequest.setStatus("0");
SysUser existingUser = new SysUser();
existingUser.setId(userId);
existingUser.setUsername("olduser");
existingUser.setEmail(EmailAddress.of("old@example.com"));
existingUser.setPhone(PhoneNumber.of("13800138000"));
existingUser.setStatus("0");
existingUser.setCreatedAt(LocalDateTime.now());
SysUser updatedUser = new SysUser();
updatedUser.setId(userId);
updatedUser.setUsername("updateduser");
updatedUser.setEmail(EmailAddress.of("updated@example.com"));
updatedUser.setPhone(PhoneNumber.of("13800138000"));
updatedUser.setStatus("0");
updatedUser.setCreatedAt(LocalDateTime.now());
when(userService.findById(userId)).thenReturn(Mono.just(existingUser));
when(userService.update(any(SysUser.class))).thenReturn(Mono.just(updatedUser));
ServerRequest request = MockServerRequest.builder()
.body(Mono.just(userRequest));
Mono<ServerResponse> responseMono = userHandler.updateUser(request);
StepVerifier.create(responseMono)
.assertNext(response -> {
assertEquals(200, response.statusCode().value());
})
.verifyComplete();
verify(userService, times(1)).findById(userId);
verify(userService, times(1)).update(any(SysUser.class));
}
@Test
@DisplayName("测试更新用户 - 用户不存在")
void testUpdateUser_NotFound() {
Long userId = 1L;
SysUserRequest userRequest = new SysUserRequest();
userRequest.setId(userId);
userRequest.setUsername("updateduser");
when(userService.findById(userId)).thenReturn(Mono.empty());
ServerRequest request = MockServerRequest.builder()
.body(Mono.just(userRequest));
Mono<ServerResponse> responseMono = userHandler.updateUser(request);
StepVerifier.create(responseMono)
.assertNext(response -> {
assertEquals(400, response.statusCode().value());
})
.verifyComplete();
verify(userService, times(1)).findById(userId);
verify(userService, never()).update(any(SysUser.class));
}
@Test
@DisplayName("测试删除用户 - 正常情况")
void testDeleteUser_Success() {
Long userId = 1L;
when(userService.deleteById(userId)).thenReturn(Mono.empty());
ServerRequest request = MockServerRequest.builder()
.pathVariable("id", userId.toString())
.build();
Mono<ServerResponse> responseMono = userHandler.deleteUser(request);
StepVerifier.create(responseMono)
.assertNext(response -> {
assertEquals(200, response.statusCode().value());
})
.verifyComplete();
verify(userService, times(1)).deleteById(userId);
}
@Test
@DisplayName("测试删除用户 - 服务异常")
void testDeleteUser_ServiceException() {
Long userId = 1L;
when(userService.deleteById(userId))
.thenReturn(Mono.error(new RuntimeException("删除失败")));
ServerRequest request = MockServerRequest.builder()
.pathVariable("id", userId.toString())
.build();
Mono<ServerResponse> responseMono = userHandler.deleteUser(request);
StepVerifier.create(responseMono)
.assertNext(response -> {
assertEquals(400, response.statusCode().value());
})
.verifyComplete();
verify(userService, times(1)).deleteById(userId);
}
@Test
@DisplayName("测试查询所有用户 - 成功")
void testGetAllUsers_Success() {
SysUser user1 = new SysUser();
user1.setId(1L);
user1.setUsername("admin");
user1.setEmail(EmailAddress.of("admin@example.com"));
user1.setPhone(PhoneNumber.of("13800138000"));
user1.setStatus("0");
user1.setCreatedAt(LocalDateTime.now());
SysUser user2 = new SysUser();
user2.setId(2L);
user2.setUsername("testuser");
user2.setEmail(EmailAddress.of("test@example.com"));
user2.setPhone(PhoneNumber.of("13900139000"));
user2.setStatus("0");
user2.setCreatedAt(LocalDateTime.now());
when(userService.findAll()).thenReturn(Flux.just(user1, user2));
ServerRequest request = MockServerRequest.builder()
.build();
Mono<ServerResponse> responseMono = userHandler.getAllUsers(request);
StepVerifier.create(responseMono)
.assertNext(response -> {
assertEquals(200, response.statusCode().value());
})
.verifyComplete();
verify(userService, times(1)).findAll();
}
@Test
@DisplayName("测试查询所有用户 - 空列表")
void testGetAllUsers_EmptyList() {
when(userService.findAll()).thenReturn(Flux.empty());
ServerRequest request = MockServerRequest.builder()
.build();
Mono<ServerResponse> responseMono = userHandler.getAllUsers(request);
StepVerifier.create(responseMono)
.assertNext(response -> {
assertEquals(200, response.statusCode().value());
})
.verifyComplete();
verify(userService, times(1)).findAll();
}
@Test
@DisplayName("测试查询所有用户 - 服务异常")
void testGetAllUsers_ServiceException() {
when(userService.findAll()).thenReturn(Flux.error(new RuntimeException("数据库连接失败")));
ServerRequest request = MockServerRequest.builder()
.build();
Mono<ServerResponse> responseMono = userHandler.getAllUsers(request);
StepVerifier.create(responseMono)
.assertNext(response -> {
assertEquals(400, response.statusCode().value());
})
.verifyComplete();
verify(userService, times(1)).findAll();
}
}
@@ -0,0 +1,354 @@
package io.destiny.sys.integration;
import io.destiny.sys.core.domain.OperationLog;
import io.destiny.sys.core.domain.query.OperationLogQuery;
import io.destiny.sys.core.service.IOperationLogService;
import io.destiny.sys.core.service.ISysMenuService;
import io.destiny.sys.core.service.ISysRoleService;
import io.destiny.sys.core.service.impl.SysAuthServiceImpl;
import io.destiny.sys.handler.OperationLogHandler;
import io.destiny.sys.handler.SysUserHandler;
import io.destiny.sys.handler.SysRoleHandler;
import io.destiny.sys.handler.SysMenuHandler;
import io.destiny.sys.handler.SysAuthHandler;
import io.destiny.sys.config.SysRouter;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
import java.util.Arrays;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
@DisplayName("操作日志API集成测试")
class OperationLogApiIntegrationTest {
@Mock
private IOperationLogService logService;
@Mock
private ISysMenuService menuService;
@Mock
private ISysRoleService roleService;
@InjectMocks
private OperationLogHandler logHandler;
private WebTestClient webTestClient;
@BeforeEach
void setUp() {
SysUserHandler userHandler = new SysUserHandler(null);
SysRoleHandler roleHandler = new SysRoleHandler(null);
SysMenuHandler menuHandler = new SysMenuHandler(menuService, roleService);
SysAuthServiceImpl authService = new SysAuthServiceImpl(null, null, null, null);
SysAuthHandler authHandler = new SysAuthHandler(authService);
SysRouter router = new SysRouter(userHandler, roleHandler, menuHandler, authHandler, logHandler);
webTestClient = WebTestClient.bindToRouterFunction(router.sysRoutes())
.build();
OperationLog defaultLog = createDefaultLog();
when(logService.findById(anyLong()))
.thenReturn(Mono.just(defaultLog));
when(logService.findById(999L))
.thenReturn(Mono.empty());
when(logService.save(any(OperationLog.class)))
.thenReturn(Mono.just(defaultLog));
OperationLog log1 = createLog(1L, "用户管理", "创建用户", "admin");
OperationLog log2 = createLog(2L, "角色管理", "创建角色", "admin");
OperationLog log3 = createLog(3L, "权限管理", "分配权限", "user");
when(logService.findByQuery(any(OperationLogQuery.class)))
.thenReturn(Flux.fromIterable(Arrays.asList(log1, log2, log3)));
when(logService.countByQuery(any(OperationLogQuery.class)))
.thenReturn(Mono.just(3L));
when(logService.deleteById(anyLong()))
.thenReturn(Mono.empty());
when(logService.deleteById(999L))
.thenReturn(Mono.error(new RuntimeException("日志不存在")));
}
private OperationLog createDefaultLog() {
return createLog(1L, "用户管理", "创建用户", "admin");
}
private OperationLog createLog(Long id, String moduleName, String operationDesc, String operator) {
OperationLog log = new OperationLog();
log.setId(id);
log.setModuleName(moduleName);
log.setOperationDesc(operationDesc);
log.setOperator(operator);
log.setRequestPath("/sys/user");
log.setRequestMethod("POST");
log.setRequestParams("{\"username\":\"test\"}");
log.setResponseResult("{\"id\":1}");
log.setIpAddress("127.0.0.1");
log.setStatus("SUCCESS");
log.setExecutionTime(100L);
log.setCreatedAt(LocalDateTime.now());
return log;
}
@Test
@DisplayName("测试根据ID获取操作日志 - 成功")
void testGetLogById_Success() {
webTestClient.get()
.uri("/sys/operationLog/1")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.code").isEqualTo(200)
.jsonPath("$.message").isEqualTo("Success")
.jsonPath("$.data.id").isEqualTo(1)
.jsonPath("$.data.moduleName").isEqualTo("用户管理")
.jsonPath("$.data.operationDesc").isEqualTo("创建用户")
.jsonPath("$.data.operator").isEqualTo("admin")
.jsonPath("$.data.status").isEqualTo("SUCCESS");
verify(logService, times(1)).findById(1L);
}
@Test
@DisplayName("测试根据ID获取操作日志 - 日志不存在")
void testGetLogById_NotFound() {
webTestClient.get()
.uri("/sys/operationLog/999")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isBadRequest()
.expectBody()
.jsonPath("$.code").isEqualTo(400)
.jsonPath("$.message").exists();
verify(logService, times(1)).findById(999L);
}
@Test
@DisplayName("测试分页查询操作日志 - 成功")
void testQueryLogs_Success() {
OperationLogQuery query = new OperationLogQuery();
query.setOperator("admin");
query.setModuleName("用户管理");
query.setPageNum(1);
query.setPageSize(10);
webTestClient.post()
.uri("/sys/operationLog/query")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(query)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.code").isEqualTo(200)
.jsonPath("$.message").isEqualTo("Success")
.jsonPath("$.data.total").isEqualTo(3)
.jsonPath("$.data.pageNum").isEqualTo(1)
.jsonPath("$.data.pageSize").isEqualTo(10)
.jsonPath("$.data.list").isArray()
.jsonPath("$.data.list[0].operator").isEqualTo("admin");
verify(logService, times(1)).findByQuery(any(OperationLogQuery.class));
verify(logService, times(1)).countByQuery(any(OperationLogQuery.class));
}
@Test
@DisplayName("测试分页查询操作日志 - 按操作人查询")
void testQueryLogs_ByOperator() {
OperationLogQuery query = new OperationLogQuery();
query.setOperator("admin");
query.setPageNum(1);
query.setPageSize(10);
webTestClient.post()
.uri("/sys/operationLog/query")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(query)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.code").isEqualTo(200)
.jsonPath("$.data.total").isEqualTo(3);
verify(logService, times(1)).findByQuery(any(OperationLogQuery.class));
}
@Test
@DisplayName("测试分页查询操作日志 - 按模块查询")
void testQueryLogs_ByModule() {
OperationLogQuery query = new OperationLogQuery();
query.setModuleName("用户管理");
query.setPageNum(1);
query.setPageSize(10);
webTestClient.post()
.uri("/sys/operationLog/query")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(query)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.code").isEqualTo(200)
.jsonPath("$.data.total").isEqualTo(3);
verify(logService, times(1)).findByQuery(any(OperationLogQuery.class));
}
@Test
@DisplayName("测试分页查询操作日志 - 按状态查询")
void testQueryLogs_ByStatus() {
OperationLogQuery query = new OperationLogQuery();
query.setStatus("SUCCESS");
query.setPageNum(1);
query.setPageSize(10);
webTestClient.post()
.uri("/sys/operationLog/query")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(query)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.code").isEqualTo(200)
.jsonPath("$.data.total").isEqualTo(3);
verify(logService, times(1)).findByQuery(any(OperationLogQuery.class));
}
@Test
@DisplayName("测试分页查询操作日志 - 按时间范围查询")
void testQueryLogs_ByTimeRange() {
OperationLogQuery query = new OperationLogQuery();
query.setStartTime(LocalDateTime.now().minusDays(7));
query.setEndTime(LocalDateTime.now());
query.setPageNum(1);
query.setPageSize(10);
webTestClient.post()
.uri("/sys/operationLog/query")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(query)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.code").isEqualTo(200)
.jsonPath("$.data.total").isEqualTo(3);
verify(logService, times(1)).findByQuery(any(OperationLogQuery.class));
}
@Test
@DisplayName("测试分页查询操作日志 - 空查询条件")
void testQueryLogs_EmptyQuery() {
OperationLogQuery query = new OperationLogQuery();
query.setPageNum(1);
query.setPageSize(10);
webTestClient.post()
.uri("/sys/operationLog/query")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(query)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.code").isEqualTo(200)
.jsonPath("$.data.total").isEqualTo(3);
verify(logService, times(1)).findByQuery(any(OperationLogQuery.class));
}
@Test
@DisplayName("测试删除操作日志 - 成功")
void testDeleteLog_Success() {
webTestClient.delete()
.uri("/sys/operationLog/1")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.code").isEqualTo(200)
.jsonPath("$.message").isEqualTo("Success");
verify(logService, times(1)).deleteById(1L);
}
@Test
@DisplayName("测试删除操作日志 - 日志不存在")
void testDeleteLog_NotFound() {
webTestClient.delete()
.uri("/sys/operationLog/999")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isBadRequest()
.expectBody()
.jsonPath("$.code").isEqualTo(400);
verify(logService, times(1)).findById(999L);
verify(logService, never()).deleteById(999L);
}
@Test
@DisplayName("测试分页查询操作日志 - 分页参数验证")
void testQueryLogs_PaginationValidation() {
OperationLogQuery query = new OperationLogQuery();
query.setPageNum(0);
query.setPageSize(10);
webTestClient.post()
.uri("/sys/operationLog/query")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(query)
.exchange()
.expectStatus().isOk();
}
@Test
@DisplayName("测试分页查询操作日志 - 每页大小验证")
void testQueryLogs_PageSizeValidation() {
OperationLogQuery query = new OperationLogQuery();
query.setPageNum(1);
query.setPageSize(100);
webTestClient.post()
.uri("/sys/operationLog/query")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(query)
.exchange()
.expectStatus().isOk();
}
@Test
@DisplayName("测试API响应格式一致性")
void testApiResponseFormatConsistency() {
webTestClient.get()
.uri("/sys/operationLog/1")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.code").exists()
.jsonPath("$.message").exists()
.jsonPath("$.data").exists();
}
}
@@ -0,0 +1,402 @@
package io.destiny.sys.integration;
import io.destiny.sys.aspect.OperationLogAspect;
import io.destiny.sys.core.domain.OperationLog;
import io.destiny.sys.core.domain.query.OperationLogQuery;
import io.destiny.sys.core.service.IDataMaskingService;
import io.destiny.sys.core.service.IJaversAuditService;
import io.destiny.sys.core.service.IOperationLogService;
import io.destiny.sys.core.service.ISysMenuService;
import io.destiny.sys.core.service.ISysRoleService;
import io.destiny.sys.handler.OperationLogHandler;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
@DisplayName("操作日志端到端集成测试")
class OperationLogEndToEndIntegrationTest {
@Mock
private IOperationLogService logService;
@Mock
private IDataMaskingService dataMaskingService;
@Mock
private IJaversAuditService javersAuditService;
@Mock
private ISysMenuService menuService;
@Mock
private ISysRoleService roleService;
@InjectMocks
private OperationLogAspect operationLogAspect;
@InjectMocks
private OperationLogHandler logHandler;
private WebTestClient webTestClient;
@BeforeEach
void setUp() {
io.destiny.sys.handler.SysUserHandler userHandler = new io.destiny.sys.handler.SysUserHandler(null);
io.destiny.sys.handler.SysRoleHandler roleHandler = new io.destiny.sys.handler.SysRoleHandler(null);
io.destiny.sys.handler.SysMenuHandler menuHandler = new io.destiny.sys.handler.SysMenuHandler(menuService, roleService);
io.destiny.sys.core.service.impl.SysAuthServiceImpl authService = new io.destiny.sys.core.service.impl.SysAuthServiceImpl(
null,
null, null, null);
io.destiny.sys.handler.SysAuthHandler authHandler = new io.destiny.sys.handler.SysAuthHandler(
authService);
io.destiny.sys.config.SysRouter router = new io.destiny.sys.config.SysRouter(userHandler, roleHandler,
menuHandler, authHandler, logHandler);
webTestClient = WebTestClient.bindToRouterFunction(router.sysRoutes())
.build();
setupDefaultMockBehaviors();
}
private void setupDefaultMockBehaviors() {
OperationLog defaultLog = createDefaultLog();
when(logService.findById(anyLong()))
.thenReturn(Mono.just(defaultLog));
when(logService.findById(999L))
.thenReturn(Mono.empty());
when(logService.save(any(OperationLog.class)))
.thenReturn(Mono.just(defaultLog));
OperationLog log1 = createLog(1L, "用户管理", "创建用户", "admin");
OperationLog log2 = createLog(2L, "角色管理", "创建角色", "admin");
OperationLog log3 = createLog(3L, "权限管理", "分配权限", "user");
when(logService.findByQuery(any(OperationLogQuery.class)))
.thenReturn(Flux.fromIterable(Arrays.asList(log1, log2, log3)));
when(logService.countByQuery(any(OperationLogQuery.class)))
.thenReturn(Mono.just(3L));
when(logService.deleteById(anyLong()))
.thenReturn(Mono.empty());
when(logService.deleteById(999L))
.thenReturn(Mono.error(new RuntimeException("日志不存在")));
when(dataMaskingService.maskSensitiveData(anyString()))
.thenAnswer(invocation -> Mono.just(invocation.getArgument(0)));
List<String> changedFields = new ArrayList<>();
changedFields.add("变更摘要");
when(javersAuditService.getChangedFields(any(), any()))
.thenReturn(Mono.just(changedFields));
}
private OperationLog createDefaultLog() {
return createLog(1L, "用户管理", "创建用户", "admin");
}
private OperationLog createLog(Long id, String moduleName, String operationDesc, String operator) {
OperationLog log = new OperationLog();
log.setId(id);
log.setModuleName(moduleName);
log.setOperationDesc(operationDesc);
log.setOperator(operator);
log.setRequestPath("/sys/user");
log.setRequestMethod("POST");
log.setRequestParams("{\"username\":\"test\"}");
log.setResponseResult("{\"id\":1}");
log.setIpAddress("127.0.0.1");
log.setStatus("SUCCESS");
log.setExecutionTime(100L);
log.setCreatedAt(LocalDateTime.now());
return log;
}
@Test
@DisplayName("测试完整日志记录流程 - 成功场景")
void testCompleteLogRecordingFlow_Success() {
webTestClient.get()
.uri("/sys/operationLog/1")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.code").isEqualTo(200)
.jsonPath("$.data.id").isEqualTo(1);
verify(logService, times(1)).findById(1L);
}
@Test
@DisplayName("测试日志记录流程 - 敏感数据脱敏")
void testLogRecordingFlow_WithSensitiveData() {
String maskedParams = "{\"username\":\"test\",\"password\":\"******\",\"phone\":\"138****8000\"}";
OperationLog logWithSensitiveData = createDefaultLog();
logWithSensitiveData.setRequestParams(maskedParams);
when(logService.findById(1L))
.thenReturn(Mono.just(logWithSensitiveData));
webTestClient.get()
.uri("/sys/operationLog/1")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.data.requestParams").isEqualTo(maskedParams);
verify(logService, times(1)).findById(1L);
}
@Test
@DisplayName("测试日志记录流程 - 对象变更追踪")
void testLogRecordingFlow_WithObjectChangeTracking() {
Map<String, Object> oldObject = new HashMap<>();
oldObject.put("username", "oldUser");
oldObject.put("email", "old@example.com");
Map<String, Object> newObject = new HashMap<>();
newObject.put("username", "newUser");
newObject.put("email", "new@example.com");
List<String> changedFields = new ArrayList<>();
changedFields.add("username: oldUser -> newUser");
changedFields.add("email: old@example.com -> new@example.com");
OperationLog logWithChanges = createDefaultLog();
logWithChanges.setResponseResult(String.join("\n", changedFields));
when(logService.findById(1L))
.thenReturn(Mono.just(logWithChanges));
webTestClient.get()
.uri("/sys/operationLog/1")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.data.responseResult").exists();
verify(logService, times(1)).findById(1L);
}
@Test
@DisplayName("测试日志记录流程 - 异常处理")
void testLogRecordingFlow_ExceptionHandling() {
when(logService.save(any(OperationLog.class)))
.thenReturn(Mono.error(new RuntimeException("数据库连接失败")));
OperationLog logToSave = createDefaultLog();
logToSave.setId(null);
Mono<Void> result = logService.save(logToSave)
.then()
.onErrorResume(e -> Mono.empty());
StepVerifier.create(result)
.verifyComplete();
verify(logService, times(1)).save(any(OperationLog.class));
}
@Test
@DisplayName("测试日志查询流程 - 多条件组合查询")
void testLogQueryFlow_CombinedConditions() {
OperationLogQuery query = new OperationLogQuery();
query.setOperator("admin");
query.setModuleName("用户管理");
query.setStatus("SUCCESS");
query.setStartTime(LocalDateTime.now().minusDays(7));
query.setEndTime(LocalDateTime.now());
query.setPageNum(1);
query.setPageSize(10);
webTestClient.post()
.uri("/sys/operationLog/query")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(query)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.code").isEqualTo(200)
.jsonPath("$.data.total").isEqualTo(3)
.jsonPath("$.data.list").isArray();
verify(logService, times(1)).findByQuery(any(OperationLogQuery.class));
verify(logService, times(1)).countByQuery(any(OperationLogQuery.class));
}
@Test
@DisplayName("测试日志查询流程 - 分页功能")
void testLogQueryFlow_Pagination() {
OperationLogQuery query = new OperationLogQuery();
query.setPageNum(1);
query.setPageSize(10);
webTestClient.post()
.uri("/sys/operationLog/query")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(query)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.code").isEqualTo(200)
.jsonPath("$.data.pageNum").isEqualTo(1)
.jsonPath("$.data.pageSize").isEqualTo(10)
.jsonPath("$.data.total").isEqualTo(3);
verify(logService, times(1)).findByQuery(any(OperationLogQuery.class));
}
@Test
@DisplayName("测试日志删除流程 - 成功删除")
void testLogDeleteFlow_Success() {
when(logService.findById(1L))
.thenReturn(Mono.just(createDefaultLog()));
webTestClient.delete()
.uri("/sys/operationLog/1")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.code").isEqualTo(200)
.jsonPath("$.message").isEqualTo("Success");
verify(logService, times(1)).deleteById(1L);
}
@Test
@DisplayName("测试日志删除流程 - 删除不存在的日志")
void testLogDeleteFlow_LogNotFound() {
when(logService.findById(999L))
.thenReturn(Mono.empty());
webTestClient.delete()
.uri("/sys/operationLog/999")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isBadRequest();
verify(logService, times(1)).findById(999L);
verify(logService, never()).deleteById(999L);
}
@Test
@DisplayName("测试日志记录流程 - 性能指标记录")
void testLogRecordingFlow_PerformanceMetrics() {
OperationLog logWithPerformance = createDefaultLog();
logWithPerformance.setExecutionTime(150L);
when(logService.findById(1L))
.thenReturn(Mono.just(logWithPerformance));
webTestClient.get()
.uri("/sys/operationLog/1")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.data.executionTime").isEqualTo(150L);
verify(logService, times(1)).findById(1L);
}
@Test
@DisplayName("测试日志记录流程 - 状态记录")
void testLogRecordingFlow_StatusRecording() {
OperationLog successLog = createLog(1L, "用户管理", "创建用户", "admin");
successLog.setStatus("SUCCESS");
OperationLog failureLog = createLog(2L, "用户管理", "创建用户", "admin");
failureLog.setStatus("FAILURE");
failureLog.setExceptionMessage("用户名已存在");
when(logService.findByQuery(any(OperationLogQuery.class)))
.thenReturn(Flux.fromIterable(Arrays.asList(successLog, failureLog)));
OperationLogQuery query = new OperationLogQuery();
query.setPageNum(1);
query.setPageSize(10);
webTestClient.post()
.uri("/sys/operationLog/query")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(query)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.data.list[0].status").isEqualTo("SUCCESS")
.jsonPath("$.data.list[1].status").isEqualTo("FAILURE")
.jsonPath("$.data.list[1].exceptionMessage").isEqualTo("用户名已存在");
verify(logService, times(1)).findByQuery(any(OperationLogQuery.class));
}
@Test
@DisplayName("测试日志记录流程 - 操作IP记录")
void testLogRecordingFlow_IpRecording() {
OperationLog logWithIp = createDefaultLog();
logWithIp.setIpAddress("192.168.1.100");
when(logService.findById(1L))
.thenReturn(Mono.just(logWithIp));
webTestClient.get()
.uri("/sys/operationLog/1")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.data.ipAddress").isEqualTo("192.168.1.100");
verify(logService, times(1)).findById(1L);
}
@Test
@DisplayName("测试日志记录流程 - 时间戳记录")
void testLogRecordingFlow_TimestampRecording() {
LocalDateTime testTime = LocalDateTime.of(2026, 1, 9, 10, 30, 0);
OperationLog logWithTimestamp = createDefaultLog();
logWithTimestamp.setCreatedAt(testTime);
when(logService.findById(1L))
.thenReturn(Mono.just(logWithTimestamp));
webTestClient.get()
.uri("/sys/operationLog/1")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.data.createdAt").exists();
verify(logService, times(1)).findById(1L);
}
}
@@ -0,0 +1,349 @@
package io.destiny.sys.integration;
import io.destiny.common.primitive.EmailAddress;
import io.destiny.common.primitive.PhoneNumber;
import io.destiny.sys.core.domain.SysUser;
import io.destiny.sys.core.service.ISysUserService;
import io.destiny.sys.core.service.ISysPermissionService;
import io.destiny.sys.core.service.ISysPermissionInitService;
import io.destiny.sys.core.service.ISysMenuService;
import io.destiny.sys.core.service.ISysRoleService;
import io.destiny.sys.core.service.impl.SysAuthServiceImpl;
import io.destiny.sys.dto.request.SysUserLoginRequest;
import io.destiny.sys.dto.response.SysUserRegisterRequest;
import io.destiny.sys.handler.SysAuthHandler;
import io.destiny.sys.handler.SysUserHandler;
import io.destiny.sys.handler.SysRoleHandler;
import io.destiny.sys.handler.SysMenuHandler;
import io.destiny.sys.handler.OperationLogHandler;
import io.destiny.sys.security.SysJwtTokenProvider;
import io.destiny.sys.config.SysRouter;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
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.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import org.springframework.http.MediaType;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.test.web.reactive.server.WebTestClient;
import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
@DisplayName("认证管理API集成测试")
class SysAuthApiIntegrationTest {
@Mock
private ISysUserService userService;
@Mock
private SysJwtTokenProvider jwtTokenProvider;
@Mock
private ISysPermissionService permissionService;
@Mock
private ISysPermissionInitService permissionInitService;
@Mock
private ISysMenuService menuService;
@Mock
private ISysRoleService roleService;
private SysAuthServiceImpl authService;
private SysAuthHandler authHandler;
private WebTestClient webTestClient;
@BeforeEach
void setUp() {
authService = new SysAuthServiceImpl(userService, jwtTokenProvider, permissionService, permissionInitService);
authHandler = new SysAuthHandler(authService);
SysUserHandler userHandler = new SysUserHandler(null);
SysRoleHandler roleHandler = new SysRoleHandler(null);
SysMenuHandler menuHandler = new SysMenuHandler(menuService, roleService);
OperationLogHandler logHandler = new OperationLogHandler(null);
SysRouter router = new SysRouter(userHandler, roleHandler,
menuHandler, authHandler, logHandler);
webTestClient = WebTestClient.bindToRouterFunction(router.sysRoutes())
.build();
SysUser defaultUser = createDefaultUser();
when(userService.findByUsername(anyString()))
.thenReturn(Mono.empty());
when(userService.create(any(SysUser.class)))
.thenReturn(Mono.just(defaultUser));
when(userService.findById(anyLong()))
.thenReturn(Mono.just(defaultUser));
when(jwtTokenProvider.generateAccessToken(anyLong(), anyString()))
.thenReturn("mock-jwt-token");
when(jwtTokenProvider.generateRefreshToken(anyLong(), anyString()))
.thenReturn("mock-refresh-token");
when(jwtTokenProvider.validateToken(anyString()))
.thenReturn(true);
when(jwtTokenProvider.getUsernameFromToken(anyString()))
.thenReturn("testuser");
when(jwtTokenProvider.getUserIdFromToken(anyString()))
.thenReturn(1L);
when(jwtTokenProvider.isTokenExpired(anyString()))
.thenReturn(false);
when(jwtTokenProvider.getTokenType(anyString()))
.thenReturn("refresh");
when(permissionInitService.initializeUserPermissions(anyLong()))
.thenReturn(Mono.empty());
when(permissionService.getUserPermissions(anyLong()))
.thenReturn(Mono.just(java.util.Set.of("sys:user:query")));
}
private SysUser createDefaultUser() {
SysUser user = new SysUser();
user.setId(1L);
user.setUsername("testuser");
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
user.setPassword(encoder.encode("password123"));
user.setEmail(EmailAddress.of("test@example.com"));
user.setPhone(PhoneNumber.mobileOf("13800138000"));
user.setStatus("0");
user.setCreateBy("admin");
user.setUpdateBy("admin");
user.setCreatedAt(LocalDateTime.now());
user.setUpdatedAt(LocalDateTime.now());
return user;
}
private SysUserLoginRequest createLoginRequest() {
SysUserLoginRequest request = new SysUserLoginRequest();
request.setUsername("testuser");
request.setPassword("password123");
request.setIpAddress("127.0.0.1");
return request;
}
private SysUserRegisterRequest createRegisterRequest() {
SysUserRegisterRequest request = new SysUserRegisterRequest();
request.setUsername("newuser");
request.setPassword("password123");
request.setEmail("newuser@example.com");
request.setPhone("13900139000");
return request;
}
@Test
@DisplayName("测试用户注册 - 成功场景")
void testRegister_Success() {
SysUserRegisterRequest request = createRegisterRequest();
webTestClient.post()
.uri("/sys/auth/register")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(request)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.code").isEqualTo("200")
.jsonPath("$.data").isEqualTo(1);
}
@Test
@DisplayName("测试用户注册 - 用户名已存在")
void testRegister_UsernameExists() {
when(userService.findByUsername(anyString()))
.thenReturn(Mono.just(createDefaultUser()));
SysUserRegisterRequest request = createRegisterRequest();
request.setUsername("testuser");
webTestClient.post()
.uri("/sys/auth/register")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(request)
.exchange()
.expectStatus().isBadRequest()
.expectBody()
.jsonPath("$.code").isEqualTo("400")
.jsonPath("$.message").isEqualTo("用户名已存在");
}
@Test
@DisplayName("测试用户登录 - 成功场景")
void testLogin_Success() {
when(userService.findByUsername(anyString()))
.thenReturn(Mono.just(createDefaultUser()));
SysUserLoginRequest request = createLoginRequest();
webTestClient.post()
.uri("/sys/auth/login")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(request)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.code").isEqualTo("200")
.jsonPath("$.data.token").isEqualTo("mock-jwt-token")
.jsonPath("$.data.user.id").isEqualTo(1)
.jsonPath("$.data.user.username").isEqualTo("testuser");
}
@Test
@DisplayName("测试用户登录 - 用户名不存在")
void testLogin_UserNotFound() {
when(userService.findByUsername(anyString()))
.thenReturn(Mono.empty());
SysUserLoginRequest request = createLoginRequest();
webTestClient.post()
.uri("/sys/auth/login")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(request)
.exchange()
.expectStatus().isUnauthorized()
.expectBody()
.jsonPath("$.code").isEqualTo("401")
.jsonPath("$.message").isEqualTo("用户名或密码错误");
}
@Test
@DisplayName("测试用户登录 - 密码错误")
void testLogin_InvalidPassword() {
SysUser user = createDefaultUser();
user.setPassword("wrong-password");
when(userService.findByUsername(anyString()))
.thenReturn(Mono.just(user));
SysUserLoginRequest request = createLoginRequest();
webTestClient.post()
.uri("/sys/auth/login")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(request)
.exchange()
.expectStatus().isUnauthorized()
.expectBody()
.jsonPath("$.code").isEqualTo("401")
.jsonPath("$.message").isEqualTo("用户名或密码错误");
}
@Test
@DisplayName("测试用户登录 - 用户已禁用")
void testLogin_UserDisabled() {
SysUser disabledUser = new SysUser();
disabledUser.setId(1L);
disabledUser.setUsername("testuser");
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
disabledUser.setPassword(encoder.encode("password123"));
disabledUser.setStatus("1");
when(userService.findByUsername(anyString()))
.thenReturn(Mono.just(disabledUser));
SysUserLoginRequest request = createLoginRequest();
webTestClient.post()
.uri("/sys/auth/login")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(request)
.exchange()
.expectStatus().isUnauthorized()
.expectBody()
.jsonPath("$.code").isEqualTo("401")
.jsonPath("$.message").isEqualTo("用户已被禁用");
}
@Test
@DisplayName("测试刷新令牌 - 成功场景")
void testRefreshToken_Success() {
when(userService.findByUsername(anyString()))
.thenReturn(Mono.just(createDefaultUser()));
webTestClient.post()
.uri("/sys/auth/refresh/mock-refresh-token")
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.code").isEqualTo("200")
.jsonPath("$.data.token").isEqualTo("mock-jwt-token")
.jsonPath("$.data.user.id").isEqualTo(1)
.jsonPath("$.data.user.username").isEqualTo("testuser");
}
@Test
@DisplayName("测试刷新令牌 - 令牌无效")
void testRefreshToken_InvalidToken() {
when(jwtTokenProvider.validateToken(anyString()))
.thenReturn(false);
webTestClient.post()
.uri("/sys/auth/refresh/invalid-token")
.exchange()
.expectStatus().isUnauthorized()
.expectBody()
.jsonPath("$.code").isEqualTo("401")
.jsonPath("$.message").isEqualTo("无效的刷新令牌");
}
@Test
@DisplayName("测试用户登出 - 成功场景")
void testLogout_Success() {
webTestClient.post()
.uri("/sys/auth/logout")
.header("Authorization", "Bearer mock-jwt-token")
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.code").isEqualTo("200")
.jsonPath("$.data").isEqualTo("登出成功");
}
@Test
@DisplayName("测试用户登出 - 无令牌")
void testLogout_NoToken() {
webTestClient.post()
.uri("/sys/auth/logout")
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.code").isEqualTo("200")
.jsonPath("$.data").isEqualTo("登出成功");
}
@Test
@DisplayName("测试用户登出 - 令牌格式错误")
void testLogout_InvalidTokenFormat() {
webTestClient.post()
.uri("/sys/auth/logout")
.header("Authorization", "InvalidFormat mock-jwt-token")
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.code").isEqualTo("200")
.jsonPath("$.data").isEqualTo("登出成功");
}
}
@@ -0,0 +1,384 @@
package io.destiny.sys.integration;
import io.destiny.sys.core.domain.SysMenu;
import io.destiny.sys.core.service.ISysMenuService;
import io.destiny.sys.core.service.impl.SysAuthServiceImpl;
import io.destiny.sys.dto.request.AssignMenuToRoleRequest;
import io.destiny.sys.dto.request.SysMenuRequest;
import io.destiny.sys.handler.SysMenuHandler;
import io.destiny.sys.handler.SysUserHandler;
import io.destiny.sys.handler.SysRoleHandler;
import io.destiny.sys.handler.SysAuthHandler;
import io.destiny.sys.handler.OperationLogHandler;
import io.destiny.sys.config.SysRouter;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
@DisplayName("菜单管理API集成测试")
class SysMenuApiIntegrationTest {
@Mock
private ISysMenuService menuService;
@InjectMocks
private SysMenuHandler menuHandler;
private WebTestClient webTestClient;
@BeforeEach
void setUp() {
SysUserHandler userHandler = new SysUserHandler(null);
SysRoleHandler roleHandler = new SysRoleHandler(null);
SysAuthServiceImpl authService = new SysAuthServiceImpl(null, null, null, null);
SysAuthHandler authHandler = new SysAuthHandler(authService);
OperationLogHandler logHandler = new OperationLogHandler(null);
SysRouter router = new SysRouter(userHandler, roleHandler, menuHandler, authHandler, logHandler);
webTestClient = WebTestClient.bindToRouterFunction(router.sysRoutes())
.build();
when(menuService.findById(anyLong()))
.thenReturn(Mono.just(createDefaultMenu()));
when(menuService.findByRoleId(anyLong()))
.thenReturn(Flux.fromIterable(createMenuList()));
when(menuService.findByUserId(anyLong()))
.thenReturn(Flux.fromIterable(createMenuList()));
when(menuService.findAll())
.thenReturn(Flux.fromIterable(createMenuList()));
when(menuService.create(any(SysMenu.class)))
.thenReturn(Mono.just(createDefaultMenu()));
when(menuService.update(any(SysMenu.class)))
.thenReturn(Mono.just(createDefaultMenu()));
when(menuService.deleteById(anyLong()))
.thenReturn(Mono.empty());
when(menuService.assignMenuToRole(anyLong(), anyLong()))
.thenReturn(Mono.empty());
when(menuService.removeMenuFromRole(anyLong(), anyLong()))
.thenReturn(Mono.empty());
}
private SysMenu createDefaultMenu() {
SysMenu menu = new SysMenu();
menu.setId(1L);
menu.setMenuName("用户管理");
menu.setParentId(0L);
menu.setOrderNum(1);
menu.setMenuType("C");
menu.setPerms("system:user:list");
menu.setComponent("system/user/index");
menu.setStatus("0");
menu.setCreateBy("admin");
menu.setUpdateBy("admin");
menu.setCreatedAt(LocalDateTime.now());
menu.setUpdatedAt(LocalDateTime.now());
return menu;
}
private SysMenuRequest createDefaultMenuRequest() {
return SysMenuRequest.builder()
.name("用户管理")
.parentId(0L)
.sortOrder(1)
.code("C")
.component("system/user/index")
.status("0")
.build();
}
private SysMenuRequest createUpdateMenuRequest() {
return SysMenuRequest.builder()
.id(1L)
.name("用户管理")
.parentId(0L)
.sortOrder(1)
.code("C")
.component("system/user/index")
.status("0")
.build();
}
private List<SysMenu> createMenuList() {
SysMenu menu1 = new SysMenu();
menu1.setId(1L);
menu1.setMenuName("用户管理");
menu1.setParentId(0L);
menu1.setOrderNum(1);
menu1.setMenuType("C");
menu1.setPerms("system:user:list");
menu1.setComponent("system/user/index");
menu1.setStatus("0");
menu1.setCreateBy("admin");
menu1.setUpdateBy("admin");
menu1.setCreatedAt(LocalDateTime.now());
menu1.setUpdatedAt(LocalDateTime.now());
SysMenu menu2 = new SysMenu();
menu2.setId(2L);
menu2.setMenuName("角色管理");
menu2.setParentId(0L);
menu2.setOrderNum(2);
menu2.setMenuType("C");
menu2.setPerms("system:role:list");
menu2.setComponent("system/role/index");
menu2.setStatus("0");
menu2.setCreateBy("admin");
menu2.setUpdateBy("admin");
menu2.setCreatedAt(LocalDateTime.now());
menu2.setUpdatedAt(LocalDateTime.now());
return Arrays.asList(menu1, menu2);
}
@Test
@DisplayName("测试根据ID查询菜单 - 成功场景")
void testGetMenuById_Success() {
webTestClient.get()
.uri("/sys/menu/1")
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.code").isEqualTo("200")
.jsonPath("$.message").isEqualTo("Success")
.jsonPath("$.data.id").isEqualTo(1)
.jsonPath("$.data.name").isEqualTo("用户管理");
}
@Test
@DisplayName("测试根据ID查询菜单 - 菜单不存在")
void testGetMenuById_MenuNotFound() {
when(menuService.findById(anyLong()))
.thenReturn(Mono.empty());
webTestClient.get()
.uri("/sys/menu/999")
.exchange()
.expectStatus().isBadRequest()
.expectBody()
.jsonPath("$.code").isEqualTo("500")
.jsonPath("$.message").isEqualTo("菜单不存在");
}
@Test
@DisplayName("测试根据角色ID查询菜单列表 - 成功场景")
void testGetMenusByRoleId_Success() {
webTestClient.get()
.uri("/sys/menu/role/1")
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.code").isEqualTo("200")
.jsonPath("$.message").isEqualTo("Success")
.jsonPath("$.data").isArray()
.jsonPath("$.data.length()").isEqualTo(2);
}
@Test
@DisplayName("测试根据用户ID查询菜单列表 - 成功场景")
void testGetMenusByUserId_Success() {
webTestClient.get()
.uri("/sys/menu/user/1")
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.code").isEqualTo("200")
.jsonPath("$.message").isEqualTo("Success")
.jsonPath("$.data").isArray()
.jsonPath("$.data.length()").isEqualTo(2);
}
@Test
@DisplayName("测试查询所有菜单 - 成功场景")
void testGetAllMenus_Success() {
webTestClient.get()
.uri("/sys/menu")
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.code").isEqualTo("200")
.jsonPath("$.message").isEqualTo("Success")
.jsonPath("$.data").isArray()
.jsonPath("$.data.length()").isEqualTo(2);
}
@Test
@DisplayName("测试创建菜单 - 成功场景")
void testCreateMenu_Success() {
SysMenuRequest request = createDefaultMenuRequest();
webTestClient.post()
.uri("/sys/menu")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(request)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.code").isEqualTo("200")
.jsonPath("$.message").isEqualTo("Success")
.jsonPath("$.data.name").isEqualTo("用户管理");
}
@Test
@DisplayName("测试创建菜单 - 必填字段验证")
void testCreateMenu_RequiredFieldsValidation() {
SysMenuRequest request = SysMenuRequest.builder()
.build();
when(menuService.create(any(SysMenu.class)))
.thenReturn(Mono.error(new RuntimeException("菜单名称不能为空")));
webTestClient.post()
.uri("/sys/menu")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(request)
.exchange()
.expectStatus().isBadRequest();
}
@Test
@DisplayName("测试创建菜单 - 菜单类型验证")
void testCreateMenu_MenuTypeValidation() {
SysMenuRequest request = SysMenuRequest.builder()
.name("测试菜单")
.code("X")
.build();
when(menuService.create(any(SysMenu.class)))
.thenReturn(Mono.error(new RuntimeException("菜单类型不正确")));
webTestClient.post()
.uri("/sys/menu")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(request)
.exchange()
.expectStatus().isBadRequest();
}
@Test
@DisplayName("测试更新菜单 - 成功场景")
void testUpdateMenu_Success() {
SysMenuRequest request = createUpdateMenuRequest();
webTestClient.put()
.uri("/sys/menu")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(request)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.code").isEqualTo("200")
.jsonPath("$.message").isEqualTo("Success")
.jsonPath("$.data.name").isEqualTo("用户管理");
}
@Test
@DisplayName("测试更新菜单 - 菜单不存在")
void testUpdateMenu_MenuNotFound() {
SysMenuRequest request = createUpdateMenuRequest();
when(menuService.findById(anyLong()))
.thenReturn(Mono.empty());
webTestClient.put()
.uri("/sys/menu")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(request)
.exchange()
.expectStatus().isBadRequest()
.expectBody()
.jsonPath("$.code").isEqualTo("500")
.jsonPath("$.message").isEqualTo("菜单不存在");
}
@Test
@DisplayName("测试删除菜单 - 成功场景")
void testDeleteMenu_Success() {
webTestClient.delete()
.uri("/sys/menu/1")
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.code").isEqualTo("200")
.jsonPath("$.message").isEqualTo("Success");
}
@Test
@DisplayName("测试分配菜单到角色 - 成功场景")
void testAssignMenuToRole_Success() {
AssignMenuToRoleRequest request = AssignMenuToRoleRequest.builder()
.roleId(1L)
.menuId(1L)
.build();
webTestClient.post()
.uri("/sys/menu/assign")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(request)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.code").isEqualTo("200")
.jsonPath("$.message").isEqualTo("Success");
}
@Test
@DisplayName("测试移除角色的菜单 - 成功场景")
void testRemoveMenuFromRole_Success() {
webTestClient.delete()
.uri("/sys/menu/role/1/menu/1")
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.code").isEqualTo("200")
.jsonPath("$.message").isEqualTo("Success");
}
@Test
@DisplayName("测试菜单树形结构 - 父子关系验证")
void testMenuTreeStructure_ParentChildRelationship() {
List<SysMenu> menuTree = Arrays.asList(
createDefaultMenu(),
createDefaultMenu()
);
when(menuService.findAll())
.thenReturn(Flux.fromIterable(menuTree));
webTestClient.get()
.uri("/sys/menu")
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.code").isEqualTo("200")
.jsonPath("$.data").isArray()
.jsonPath("$.data[0].parentId").isEqualTo(0);
}
}
@@ -0,0 +1,447 @@
package io.destiny.sys.integration;
import io.destiny.sys.core.domain.SysUser;
import io.destiny.sys.core.service.ISysPermissionService;
import io.destiny.sys.core.service.ISysUserService;
import io.destiny.sys.security.SysPermissionFilter;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
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.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
@DisplayName("权限验证集成测试")
class SysPermissionIntegrationTest {
@Mock
private ISysUserService userService;
@Mock
private ISysPermissionService permissionService;
@Mock
private WebFilterChain filterChain;
private SysPermissionFilter permissionFilter;
private SysUser activeUser;
private SysUser disabledUser;
@BeforeEach
void setUp() {
permissionFilter = new SysPermissionFilter(userService, permissionService);
activeUser = new SysUser();
activeUser.setId(1L);
activeUser.setUsername("admin");
activeUser.setStatus("0");
disabledUser = new SysUser();
disabledUser.setId(2L);
disabledUser.setUsername("disabled_user");
disabledUser.setStatus("1");
when(userService.findById(1L)).thenReturn(Mono.just(activeUser));
when(userService.findById(2L)).thenReturn(Mono.just(disabledUser));
when(filterChain.filter(any())).thenAnswer(invocation -> {
ServerWebExchange exchange = invocation.getArgument(0);
exchange.getResponse().setStatusCode(HttpStatus.OK);
return Mono.empty();
});
}
@Test
@DisplayName("测试公开路径 - 不需要权限验证")
void testPublicPath_NoPermissionRequired() {
MockServerWebExchange exchange = MockServerWebExchange.from(
MockServerHttpRequest
.get("/api/sys/auth/login")
.accept(MediaType.APPLICATION_JSON));
permissionFilter.filter(exchange, filterChain).block();
assertEquals(HttpStatus.OK, exchange.getResponse().getStatusCode());
verify(filterChain, times(1)).filter(any());
verify(userService, never()).findById(anyLong());
}
@Test
@DisplayName("测试注册路径 - 不需要权限验证")
void testRegisterPath_NoPermissionRequired() {
MockServerWebExchange exchange = MockServerWebExchange.from(
MockServerHttpRequest
.post("/api/sys/auth/register")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.body("{\"username\":\"test\",\"password\":\"123456\"}"));
permissionFilter.filter(exchange, filterChain).block();
assertEquals(HttpStatus.OK, exchange.getResponse().getStatusCode());
verify(filterChain, times(1)).filter(any());
verify(userService, never()).findById(anyLong());
}
@Test
@DisplayName("测试需要权限的路径 - 有权限的用户可以访问")
void testProtectedPath_WithPermission() {
when(permissionService.hasPermission(1L, "sys:user:list")).thenReturn(true);
MockServerWebExchange exchange = MockServerWebExchange.from(
org.springframework.mock.http.server.reactive.MockServerHttpRequest
.get("/sys/user")
.header("X-User-Id", "1")
.header("X-Username", "admin")
.accept(org.springframework.http.MediaType.APPLICATION_JSON));
permissionFilter.filter(exchange, filterChain).block();
assertEquals(HttpStatus.OK, exchange.getResponse().getStatusCode());
verify(filterChain, times(1)).filter(any());
verify(permissionService, times(1)).hasPermission(1L, "sys:user:list");
}
@Test
@DisplayName("测试需要权限的路径 - 没有权限的用户被拒绝")
void testProtectedPath_WithoutPermission() {
when(permissionService.hasPermission(1L, "sys:user:list")).thenReturn(false);
MockServerWebExchange exchange = MockServerWebExchange.from(
org.springframework.mock.http.server.reactive.MockServerHttpRequest
.get("/sys/user")
.header("X-User-Id", "1")
.header("X-Username", "admin")
.accept(org.springframework.http.MediaType.APPLICATION_JSON));
permissionFilter.filter(exchange, filterChain).block();
assertEquals(HttpStatus.FORBIDDEN, exchange.getResponse().getStatusCode());
verify(filterChain, never()).filter(any());
verify(permissionService, times(1)).hasPermission(1L, "sys:user:list");
}
@Test
@DisplayName("测试需要权限的路径 - 缺少用户ID头部")
void testProtectedPath_MissingUserIdHeader() {
MockServerWebExchange exchange = MockServerWebExchange.from(
org.springframework.mock.http.server.reactive.MockServerHttpRequest
.get("/sys/user")
.header("X-Username", "admin")
.accept(org.springframework.http.MediaType.APPLICATION_JSON));
permissionFilter.filter(exchange, filterChain).block();
assertEquals(HttpStatus.FORBIDDEN, exchange.getResponse().getStatusCode());
verify(filterChain, never()).filter(any());
verify(userService, never()).findById(anyLong());
}
@Test
@DisplayName("测试需要权限的路径 - 缺少用户名头部")
void testProtectedPath_MissingUsernameHeader() {
MockServerWebExchange exchange = MockServerWebExchange.from(
org.springframework.mock.http.server.reactive.MockServerHttpRequest
.get("/sys/user")
.header("X-User-Id", "1")
.accept(org.springframework.http.MediaType.APPLICATION_JSON));
permissionFilter.filter(exchange, filterChain).block();
assertEquals(HttpStatus.FORBIDDEN, exchange.getResponse().getStatusCode());
verify(filterChain, never()).filter(any());
verify(userService, never()).findById(anyLong());
}
@Test
@DisplayName("测试需要权限的路径 - 用户被禁用")
void testProtectedPath_UserDisabled() {
when(permissionService.hasPermission(2L, "sys:user:list")).thenReturn(true);
MockServerWebExchange exchange = MockServerWebExchange.from(
org.springframework.mock.http.server.reactive.MockServerHttpRequest
.get("/sys/user")
.header("X-User-Id", "2")
.header("X-Username", "disabled_user")
.accept(org.springframework.http.MediaType.APPLICATION_JSON));
permissionFilter.filter(exchange, filterChain).block();
assertEquals(HttpStatus.FORBIDDEN, exchange.getResponse().getStatusCode());
verify(filterChain, never()).filter(any());
verify(permissionService, never()).hasPermission(anyLong(), anyString());
}
@Test
@DisplayName("测试用户管理 - 添加用户权限")
void testUserManagement_AddPermission() {
when(permissionService.hasPermission(1L, "sys:user:add")).thenReturn(true);
MockServerWebExchange exchange = MockServerWebExchange.from(
org.springframework.mock.http.server.reactive.MockServerHttpRequest
.post("/sys/user")
.header("X-User-Id", "1")
.header("X-Username", "admin")
.contentType(org.springframework.http.MediaType.APPLICATION_JSON)
.accept(org.springframework.http.MediaType.APPLICATION_JSON)
.body("{\"username\":\"newuser\",\"password\":\"123456\"}"));
permissionFilter.filter(exchange, filterChain).block();
assertEquals(HttpStatus.OK, exchange.getResponse().getStatusCode());
verify(filterChain, times(1)).filter(any());
verify(permissionService, times(1)).hasPermission(1L, "sys:user:add");
}
@Test
@DisplayName("测试用户管理 - 编辑用户权限")
void testUserManagement_EditPermission() {
when(permissionService.hasPermission(1L, "sys:user:edit")).thenReturn(true);
MockServerWebExchange exchange = MockServerWebExchange.from(
org.springframework.mock.http.server.reactive.MockServerHttpRequest
.put("/sys/user/1")
.header("X-User-Id", "1")
.header("X-Username", "admin")
.contentType(org.springframework.http.MediaType.APPLICATION_JSON)
.accept(org.springframework.http.MediaType.APPLICATION_JSON)
.body("{\"username\":\"updateduser\"}"));
permissionFilter.filter(exchange, filterChain).block();
assertEquals(HttpStatus.OK, exchange.getResponse().getStatusCode());
verify(filterChain, times(1)).filter(any());
verify(permissionService, times(1)).hasPermission(1L, "sys:user:edit");
}
@Test
@DisplayName("测试用户管理 - 删除用户权限")
void testUserManagement_DeletePermission() {
when(permissionService.hasPermission(1L, "sys:user:remove")).thenReturn(true);
MockServerWebExchange exchange = MockServerWebExchange.from(
org.springframework.mock.http.server.reactive.MockServerHttpRequest
.delete("/sys/user/1")
.header("X-User-Id", "1")
.header("X-Username", "admin")
.accept(org.springframework.http.MediaType.APPLICATION_JSON));
permissionFilter.filter(exchange, filterChain).block();
assertEquals(HttpStatus.OK, exchange.getResponse().getStatusCode());
verify(filterChain, times(1)).filter(any());
verify(permissionService, times(1)).hasPermission(1L, "sys:user:remove");
}
@Test
@DisplayName("测试角色管理 - 列表权限")
void testRoleManagement_ListPermission() {
when(permissionService.hasPermission(1L, "sys:role:list")).thenReturn(true);
MockServerWebExchange exchange = MockServerWebExchange.from(
org.springframework.mock.http.server.reactive.MockServerHttpRequest
.get("/sys/role")
.header("X-User-Id", "1")
.header("X-Username", "admin")
.accept(org.springframework.http.MediaType.APPLICATION_JSON));
permissionFilter.filter(exchange, filterChain).block();
assertEquals(HttpStatus.OK, exchange.getResponse().getStatusCode());
verify(filterChain, times(1)).filter(any());
verify(permissionService, times(1)).hasPermission(1L, "sys:role:list");
}
@Test
@DisplayName("测试角色管理 - 添加角色权限")
void testRoleManagement_AddPermission() {
when(permissionService.hasPermission(1L, "sys:role:add")).thenReturn(true);
MockServerWebExchange exchange = MockServerWebExchange.from(
org.springframework.mock.http.server.reactive.MockServerHttpRequest
.post("/sys/role")
.header("X-User-Id", "1")
.header("X-Username", "admin")
.contentType(org.springframework.http.MediaType.APPLICATION_JSON)
.accept(org.springframework.http.MediaType.APPLICATION_JSON)
.body("{\"roleName\":\"test_role\",\"roleKey\":\"test\"}"));
permissionFilter.filter(exchange, filterChain).block();
assertEquals(HttpStatus.OK, exchange.getResponse().getStatusCode());
verify(filterChain, times(1)).filter(any());
verify(permissionService, times(1)).hasPermission(1L, "sys:role:add");
}
@Test
@DisplayName("测试角色管理 - 编辑角色权限")
void testRoleManagement_EditPermission() {
when(permissionService.hasPermission(1L, "sys:role:edit")).thenReturn(true);
MockServerWebExchange exchange = MockServerWebExchange.from(
org.springframework.mock.http.server.reactive.MockServerHttpRequest
.put("/sys/role/1")
.header("X-User-Id", "1")
.header("X-Username", "admin")
.contentType(org.springframework.http.MediaType.APPLICATION_JSON)
.accept(org.springframework.http.MediaType.APPLICATION_JSON)
.body("{\"roleName\":\"updated_role\"}"));
permissionFilter.filter(exchange, filterChain).block();
assertEquals(HttpStatus.OK, exchange.getResponse().getStatusCode());
verify(filterChain, times(1)).filter(any());
verify(permissionService, times(1)).hasPermission(1L, "sys:role:edit");
}
@Test
@DisplayName("测试角色管理 - 删除角色权限")
void testRoleManagement_DeletePermission() {
when(permissionService.hasPermission(1L, "sys:role:remove")).thenReturn(true);
MockServerWebExchange exchange = MockServerWebExchange.from(
org.springframework.mock.http.server.reactive.MockServerHttpRequest
.delete("/sys/role/1")
.header("X-User-Id", "1")
.header("X-Username", "admin")
.accept(org.springframework.http.MediaType.APPLICATION_JSON));
permissionFilter.filter(exchange, filterChain).block();
assertEquals(HttpStatus.OK, exchange.getResponse().getStatusCode());
verify(filterChain, times(1)).filter(any());
verify(permissionService, times(1)).hasPermission(1L, "sys:role:remove");
}
@Test
@DisplayName("测试菜单管理 - 列表权限")
void testMenuManagement_ListPermission() {
when(permissionService.hasPermission(1L, "sys:menu:list")).thenReturn(true);
MockServerWebExchange exchange = MockServerWebExchange.from(
org.springframework.mock.http.server.reactive.MockServerHttpRequest
.get("/sys/menu")
.header("X-User-Id", "1")
.header("X-Username", "admin")
.accept(org.springframework.http.MediaType.APPLICATION_JSON));
permissionFilter.filter(exchange, filterChain).block();
assertEquals(HttpStatus.OK, exchange.getResponse().getStatusCode());
verify(filterChain, times(1)).filter(any());
verify(permissionService, times(1)).hasPermission(1L, "sys:menu:list");
}
@Test
@DisplayName("测试菜单管理 - 添加菜单权限")
void testMenuManagement_AddPermission() {
when(permissionService.hasPermission(1L, "sys:menu:add")).thenReturn(true);
MockServerWebExchange exchange = MockServerWebExchange.from(
org.springframework.mock.http.server.reactive.MockServerHttpRequest
.post("/sys/menu")
.header("X-User-Id", "1")
.header("X-Username", "admin")
.contentType(org.springframework.http.MediaType.APPLICATION_JSON)
.accept(org.springframework.http.MediaType.APPLICATION_JSON)
.body("{\"menuName\":\"test_menu\",\"menuType\":\"M\"}"));
permissionFilter.filter(exchange, filterChain).block();
assertEquals(HttpStatus.OK, exchange.getResponse().getStatusCode());
verify(filterChain, times(1)).filter(any());
verify(permissionService, times(1)).hasPermission(1L, "sys:menu:add");
}
@Test
@DisplayName("测试菜单管理 - 编辑菜单权限")
void testMenuManagement_EditPermission() {
when(permissionService.hasPermission(1L, "sys:menu:edit")).thenReturn(true);
MockServerWebExchange exchange = MockServerWebExchange.from(
org.springframework.mock.http.server.reactive.MockServerHttpRequest
.put("/sys/menu/1")
.header("X-User-Id", "1")
.header("X-Username", "admin")
.contentType(org.springframework.http.MediaType.APPLICATION_JSON)
.accept(org.springframework.http.MediaType.APPLICATION_JSON)
.body("{\"menuName\":\"updated_menu\"}"));
permissionFilter.filter(exchange, filterChain).block();
assertEquals(HttpStatus.OK, exchange.getResponse().getStatusCode());
verify(filterChain, times(1)).filter(any());
verify(permissionService, times(1)).hasPermission(1L, "sys:menu:edit");
}
@Test
@DisplayName("测试菜单管理 - 删除菜单权限")
void testMenuManagement_DeletePermission() {
when(permissionService.hasPermission(1L, "sys:menu:remove")).thenReturn(true);
MockServerWebExchange exchange = MockServerWebExchange.from(
org.springframework.mock.http.server.reactive.MockServerHttpRequest
.delete("/sys/menu/1")
.header("X-User-Id", "1")
.header("X-Username", "admin")
.accept(org.springframework.http.MediaType.APPLICATION_JSON));
permissionFilter.filter(exchange, filterChain).block();
assertEquals(HttpStatus.OK, exchange.getResponse().getStatusCode());
verify(filterChain, times(1)).filter(any());
verify(permissionService, times(1)).hasPermission(1L, "sys:menu:remove");
}
@Test
@DisplayName("测试无效的用户ID格式")
void testInvalidUserIdFormat() {
MockServerWebExchange exchange = MockServerWebExchange.from(
org.springframework.mock.http.server.reactive.MockServerHttpRequest
.get("/sys/user")
.header("X-User-Id", "invalid")
.header("X-Username", "admin")
.accept(org.springframework.http.MediaType.APPLICATION_JSON));
permissionFilter.filter(exchange, filterChain).block();
assertEquals(HttpStatus.FORBIDDEN, exchange.getResponse().getStatusCode());
verify(filterChain, never()).filter(any());
verify(userService, never()).findById(anyLong());
}
@Test
@DisplayName("测试用户不存在")
void testUserNotFound() {
when(userService.findById(999L)).thenReturn(Mono.empty());
MockServerWebExchange exchange = MockServerWebExchange.from(
org.springframework.mock.http.server.reactive.MockServerHttpRequest
.get("/sys/user")
.header("X-User-Id", "999")
.header("X-Username", "nonexistent")
.accept(org.springframework.http.MediaType.APPLICATION_JSON));
permissionFilter.filter(exchange, filterChain).block();
assertEquals(HttpStatus.FORBIDDEN, exchange.getResponse().getStatusCode());
verify(filterChain, never()).filter(any());
verify(userService, times(1)).findById(999L);
}
}
@@ -0,0 +1,478 @@
package io.destiny.sys.integration;
import io.destiny.sys.handler.SysRoleHandler;
import io.destiny.sys.handler.SysUserHandler;
import io.destiny.sys.handler.SysMenuHandler;
import io.destiny.sys.handler.SysAuthHandler;
import io.destiny.sys.handler.OperationLogHandler;
import io.destiny.sys.config.SysRouter;
import io.destiny.sys.core.domain.SysRole;
import io.destiny.sys.core.service.ISysRoleService;
import io.destiny.sys.core.service.ISysPermissionService;
import io.destiny.sys.core.service.ISysPermissionInitService;
import io.destiny.sys.core.service.impl.SysAuthServiceImpl;
import io.destiny.sys.dto.request.AssignRoleToUserRequest;
import io.destiny.sys.dto.request.SysRoleRequest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
@DisplayName("角色管理API集成测试")
class SysRoleApiIntegrationTest {
@Mock
private ISysRoleService roleService;
@Mock
private ISysPermissionService permissionService;
@Mock
private ISysPermissionInitService permissionInitService;
@InjectMocks
private SysRoleHandler roleHandler;
private WebTestClient webTestClient;
@BeforeEach
void setUp() {
SysUserHandler userHandler = new SysUserHandler(null);
SysMenuHandler menuHandler = new SysMenuHandler(null, roleService);
SysAuthServiceImpl authService = new SysAuthServiceImpl(null, null, permissionService, permissionInitService);
SysAuthHandler authHandler = new SysAuthHandler(authService);
OperationLogHandler logHandler = new OperationLogHandler(null);
SysRouter router = new SysRouter(userHandler, roleHandler, menuHandler, authHandler, logHandler);
webTestClient = WebTestClient.bindToRouterFunction(router.sysRoutes())
.build();
when(roleService.findById(anyLong()))
.thenReturn(Mono.just(createDefaultRole()));
when(roleService.findByRoleKey(anyString()))
.thenReturn(Mono.just(createDefaultRole()));
when(roleService.findByUserId(anyLong()))
.thenReturn(Flux.just(createDefaultRole()));
when(roleService.findAll())
.thenReturn(Flux.just(createDefaultRole(), createAdminRole()));
when(roleService.create(any(SysRole.class)))
.thenReturn(Mono.just(createDefaultRole()));
when(roleService.update(any(SysRole.class)))
.thenReturn(Mono.just(createDefaultRole()));
when(roleService.deleteById(anyLong()))
.thenReturn(Mono.empty());
when(roleService.assignRoleToUser(anyLong(), anyLong()))
.thenReturn(Mono.empty());
when(roleService.removeRoleFromUser(anyLong(), anyLong()))
.thenReturn(Mono.empty());
}
private SysRole createDefaultRole() {
SysRole role = new SysRole();
role.setId(1L);
role.setRoleName("普通用户");
role.setRoleKey("user");
role.setRoleSort(1);
role.setStatus("0");
role.setCreateBy("admin");
role.setUpdateBy("admin");
role.setCreatedAt(LocalDateTime.now());
role.setUpdatedAt(LocalDateTime.now());
return role;
}
private SysRole createAdminRole() {
SysRole role = new SysRole();
role.setId(2L);
role.setRoleName("管理员");
role.setRoleKey("admin");
role.setRoleSort(0);
role.setStatus("0");
role.setCreateBy("system");
role.setUpdateBy("system");
role.setCreatedAt(LocalDateTime.now());
role.setUpdatedAt(LocalDateTime.now());
return role;
}
@Test
@DisplayName("测试根据ID获取角色信息 - 成功")
void testGetRoleById_Success() {
webTestClient.get()
.uri("/sys/role/1")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.code").isEqualTo(200)
.jsonPath("$.message").isEqualTo("Success")
.jsonPath("$.data.id").isEqualTo(1)
.jsonPath("$.data.name").isEqualTo("普通用户")
.jsonPath("$.data.roleKey").isEqualTo("user")
.jsonPath("$.data.sortOrder").isEqualTo(1)
.jsonPath("$.data.status").isEqualTo("0");
}
@Test
@DisplayName("测试根据ID获取角色信息 - 角色不存在")
void testGetRoleById_NotFound() {
when(roleService.findById(anyLong()))
.thenReturn(Mono.empty());
webTestClient.get()
.uri("/sys/role/999")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isBadRequest()
.expectBody()
.jsonPath("$.code").isEqualTo("500")
.jsonPath("$.message").isEqualTo("角色不存在");
}
@Test
@DisplayName("测试根据角色权限字符串获取角色信息 - 成功")
void testGetRoleByRoleKey_Success() {
webTestClient.get()
.uri("/sys/role/roleKey/user")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.code").isEqualTo(200)
.jsonPath("$.message").isEqualTo("Success")
.jsonPath("$.data.id").isEqualTo(1)
.jsonPath("$.data.name").isEqualTo("普通用户")
.jsonPath("$.data.roleKey").isEqualTo("user");
}
@Test
@DisplayName("测试根据角色权限字符串获取角色信息 - 角色不存在")
void testGetRoleByRoleKey_NotFound() {
when(roleService.findByRoleKey(anyString()))
.thenReturn(Mono.empty());
webTestClient.get()
.uri("/sys/role/roleKey/nonexistent")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isBadRequest()
.expectBody()
.jsonPath("$.code").isEqualTo("500")
.jsonPath("$.message").isEqualTo("角色不存在");
}
@Test
@DisplayName("测试根据用户ID获取角色列表 - 成功")
void testGetRolesByUserId_Success() {
webTestClient.get()
.uri("/sys/role/user/1")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.code").isEqualTo(200)
.jsonPath("$.message").isEqualTo("Success")
.jsonPath("$.data[0].id").isEqualTo(1)
.jsonPath("$.data[0].name").isEqualTo("普通用户")
.jsonPath("$.data[0].roleKey").isEqualTo("user");
}
@Test
@DisplayName("测试获取所有角色 - 成功")
void testGetAllRoles_Success() {
webTestClient.get()
.uri("/sys/role")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.code").isEqualTo(200)
.jsonPath("$.message").isEqualTo("Success")
.jsonPath("$.data.length()").isEqualTo(2)
.jsonPath("$.data[0].name").isEqualTo("普通用户")
.jsonPath("$.data[1].name").isEqualTo("管理员");
}
@Test
@DisplayName("测试创建角色 - 成功")
void testCreateRole_Success() {
SysRoleRequest request = createDefaultRoleRequest();
webTestClient.post()
.uri("/sys/role")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.bodyValue(request)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.code").isEqualTo(200)
.jsonPath("$.message").isEqualTo("Success")
.jsonPath("$.data.name").isEqualTo("普通用户")
.jsonPath("$.data.roleKey").isEqualTo("user")
.jsonPath("$.data.sortOrder").isEqualTo(1)
.jsonPath("$.data.status").isEqualTo("0");
}
@Test
@DisplayName("测试创建角色 - 角色权限字符串冲突")
void testCreateRole_RoleKeyConflict() {
when(roleService.create(any(SysRole.class)))
.thenReturn(Mono.error(new RuntimeException("角色权限字符串已存在")));
SysRoleRequest request = createDefaultRoleRequest();
webTestClient.post()
.uri("/sys/role")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.bodyValue(request)
.exchange()
.expectStatus().isBadRequest()
.expectBody()
.jsonPath("$.code").isEqualTo(500)
.jsonPath("$.message").isEqualTo("创建角色失败:角色权限字符串已存在");
}
@Test
@DisplayName("测试更新角色 - 成功")
void testUpdateRole_Success() {
SysRoleRequest request = createUpdateRoleRequest();
webTestClient.put()
.uri("/sys/role")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.bodyValue(request)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.code").isEqualTo(200)
.jsonPath("$.message").isEqualTo("Success")
.jsonPath("$.data.name").isEqualTo("普通用户")
.jsonPath("$.data.roleKey").isEqualTo("user");
}
@Test
@DisplayName("测试更新角色 - 角色不存在")
void testUpdateRole_NotFound() {
when(roleService.findById(anyLong()))
.thenReturn(Mono.empty());
SysRoleRequest request = createUpdateRoleRequest();
webTestClient.put()
.uri("/sys/role")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.bodyValue(request)
.exchange()
.expectStatus().isBadRequest()
.expectBody()
.jsonPath("$.code").isEqualTo("500")
.jsonPath("$.message").isEqualTo("角色不存在");
}
@Test
@DisplayName("测试删除角色 - 成功")
void testDeleteRole_Success() {
webTestClient.delete()
.uri("/sys/role/1")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.code").isEqualTo(200)
.jsonPath("$.message").isEqualTo("Success");
}
@Test
@DisplayName("测试删除角色 - 角色不存在")
void testDeleteRole_NotFound() {
webTestClient.delete()
.uri("/sys/role/999")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.code").isEqualTo(200);
}
@Test
@DisplayName("测试分配角色到用户 - 成功")
void testAssignRoleToUser_Success() {
AssignRoleToUserRequest request = AssignRoleToUserRequest.builder()
.userId(1L)
.roleId(1L)
.build();
webTestClient.post()
.uri("/sys/role/assign")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.bodyValue(request)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.code").isEqualTo(200)
.jsonPath("$.message").isEqualTo("Success");
}
@Test
@DisplayName("测试分配角色到用户 - 分配失败")
void testAssignRoleToUser_Failure() {
AssignRoleToUserRequest request = AssignRoleToUserRequest.builder()
.userId(1L)
.roleId(1L)
.build();
when(roleService.assignRoleToUser(anyLong(), anyLong()))
.thenReturn(Mono.error(new RuntimeException("分配角色失败")));
webTestClient.post()
.uri("/sys/role/assign")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.bodyValue(request)
.exchange()
.expectStatus().isBadRequest()
.expectBody()
.jsonPath("$.code").isEqualTo("500")
.jsonPath("$.message").exists();
}
@Test
@DisplayName("测试移除用户的角色 - 成功")
void testRemoveRoleFromUser_Success() {
webTestClient.delete()
.uri("/sys/role/user/1/role/1")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.code").isEqualTo(200)
.jsonPath("$.message").isEqualTo("Success");
}
@Test
@DisplayName("测试移除用户的角色 - 移除失败")
void testRemoveRoleFromUser_Failure() {
webTestClient.delete()
.uri("/sys/role/user/1/role/1")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.code").isEqualTo(200)
.jsonPath("$.message").exists();
}
@Test
@DisplayName("测试创建角色 - 角色名称为空")
void testCreateRole_EmptyRoleName() {
SysRoleRequest request = SysRoleRequest.builder()
.name("")
.roleKey("test_role")
.sortOrder(1)
.status("0")
.build();
webTestClient.post()
.uri("/sys/role")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.bodyValue(request)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.code").isEqualTo(200);
}
@Test
@DisplayName("测试创建角色 - 角色权限字符串为空")
void testCreateRole_EmptyRoleKey() {
SysRoleRequest request = SysRoleRequest.builder()
.name("测试角色")
.roleKey("")
.sortOrder(1)
.status("0")
.build();
webTestClient.post()
.uri("/sys/role")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.bodyValue(request)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.code").isEqualTo(200);
}
@Test
@DisplayName("测试创建角色 - 状态值无效")
void testCreateRole_InvalidStatus() {
SysRoleRequest request = SysRoleRequest.builder()
.name("测试角色")
.roleKey("test_role")
.sortOrder(1)
.status("2")
.build();
webTestClient.post()
.uri("/sys/role")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.bodyValue(request)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.code").isEqualTo(200);
}
private SysRoleRequest createDefaultRoleRequest() {
return SysRoleRequest.builder()
.name("普通用户")
.roleKey("user")
.sortOrder(1)
.status("0")
.build();
}
private SysRoleRequest createUpdateRoleRequest() {
return SysRoleRequest.builder()
.id(1L)
.name("更新角色")
.roleKey("updated_role")
.sortOrder(2)
.status("0")
.build();
}
}
@@ -0,0 +1,329 @@
package io.destiny.sys.integration;
import io.destiny.common.primitive.EmailAddress;
import io.destiny.common.primitive.PhoneNumber;
import io.destiny.sys.core.domain.SysUser;
import io.destiny.sys.core.service.ISysUserService;
import io.destiny.sys.core.service.ISysMenuService;
import io.destiny.sys.core.service.ISysRoleService;
import io.destiny.sys.core.service.impl.SysAuthServiceImpl;
import io.destiny.sys.dto.request.SysUserRequest;
import io.destiny.sys.handler.SysUserHandler;
import io.destiny.sys.handler.SysRoleHandler;
import io.destiny.sys.handler.SysMenuHandler;
import io.destiny.sys.handler.SysAuthHandler;
import io.destiny.sys.handler.OperationLogHandler;
import io.destiny.sys.config.SysRouter;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient;
import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
@DisplayName("用户管理API集成测试")
class SysUserApiIntegrationTest {
@Mock
private ISysUserService userService;
@Mock
private ISysMenuService menuService;
@Mock
private ISysRoleService roleService;
@InjectMocks
private SysUserHandler userHandler;
private WebTestClient webTestClient;
@BeforeEach
void setUp() {
SysRoleHandler roleHandler = new SysRoleHandler(null);
SysMenuHandler menuHandler = new SysMenuHandler(menuService, roleService);
SysAuthServiceImpl authService = new SysAuthServiceImpl(null, null, null, null);
SysAuthHandler authHandler = new SysAuthHandler(authService);
OperationLogHandler logHandler = new OperationLogHandler(null);
SysRouter router = new SysRouter(userHandler, roleHandler, menuHandler, authHandler, logHandler);
webTestClient = WebTestClient.bindToRouterFunction(router.sysRoutes())
.build();
when(userService.findById(anyLong()))
.thenReturn(Mono.just(createDefaultUser()));
when(userService.findByUsername(any()))
.thenReturn(Mono.just(createDefaultUser()));
when(userService.create(any(SysUser.class)))
.thenReturn(Mono.just(createDefaultUser()));
when(userService.update(any(SysUser.class)))
.thenReturn(Mono.just(createDefaultUser()));
when(userService.deleteById(anyLong()))
.thenReturn(Mono.empty());
}
private SysUser createDefaultUser() {
SysUser user = new SysUser();
user.setId(1L);
user.setUsername("testuser");
user.setPassword("password123");
user.setEmail(EmailAddress.of("test@example.com"));
user.setPhone(PhoneNumber.mobileOf("13800138000"));
user.setStatus("0");
user.setCreateBy("admin");
user.setUpdateBy("admin");
user.setCreatedAt(LocalDateTime.now());
user.setUpdatedAt(LocalDateTime.now());
return user;
}
private SysUserRequest createDefaultUserRequest() {
return SysUserRequest.builder()
.username("testuser")
.password("password123")
.email("test@example.com")
.phone("13800138000")
.status("0")
.build();
}
private SysUserRequest createUpdateUserRequest() {
return SysUserRequest.builder()
.id(1L)
.username("testuser")
.email("test@example.com")
.phone("13800138000")
.status("0")
.build();
}
@Test
@DisplayName("测试根据ID获取用户信息 - 成功")
void testGetUserById_Success() {
webTestClient.get()
.uri("/sys/user/1")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.code").isEqualTo(200)
.jsonPath("$.message").isEqualTo("Success")
.jsonPath("$.data.id").isEqualTo(1)
.jsonPath("$.data.username").isEqualTo("testuser")
.jsonPath("$.data.email").isEqualTo("****************")
.jsonPath("$.data.phone").isEqualTo("***********");
}
@Test
@DisplayName("测试根据用户名获取用户信息 - 成功")
void testGetUserByUsername_Success() {
webTestClient.get()
.uri("/sys/user/username/testuser")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.code").isEqualTo(200)
.jsonPath("$.message").isEqualTo("Success")
.jsonPath("$.data.username").isEqualTo("testuser");
}
@Test
@DisplayName("测试创建用户 - 成功")
void testCreateUser_Success() {
SysUserRequest request = createDefaultUserRequest();
webTestClient.post()
.uri("/sys/user")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(request)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.code").isEqualTo(200)
.jsonPath("$.message").isEqualTo("Success")
.jsonPath("$.data.username").isEqualTo("testuser");
}
@Test
@DisplayName("测试创建用户 - 用户名已存在")
void testCreateUser_UsernameExists() {
SysUserRequest request = createDefaultUserRequest();
when(userService.create(any(SysUser.class)))
.thenReturn(Mono.error(new RuntimeException("用户名已存在")));
webTestClient.post()
.uri("/sys/user")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(request)
.exchange()
.expectStatus().isBadRequest();
}
@Test
@DisplayName("测试更新用户 - 成功")
void testUpdateUser_Success() {
SysUserRequest request = createUpdateUserRequest();
webTestClient.put()
.uri("/sys/user")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(request)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.code").isEqualTo(200)
.jsonPath("$.message").isEqualTo("Success")
.jsonPath("$.data.username").isEqualTo("testuser");
}
@Test
@DisplayName("测试更新用户 - 用户不存在")
void testUpdateUser_UserNotFound() {
SysUserRequest request = createUpdateUserRequest();
when(userService.findById(anyLong()))
.thenReturn(Mono.empty());
webTestClient.put()
.uri("/sys/user")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(request)
.exchange()
.expectStatus().isBadRequest();
}
@Test
@DisplayName("测试删除用户 - 成功")
void testDeleteUser_Success() {
webTestClient.delete()
.uri("/sys/user/1")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.code").isEqualTo(200)
.jsonPath("$.message").isEqualTo("Success");
}
@Test
@DisplayName("测试删除用户 - 用户不存在")
void testDeleteUser_UserNotFound() {
when(userService.deleteById(anyLong()))
.thenReturn(Mono.error(new RuntimeException("用户不存在")));
webTestClient.delete()
.uri("/sys/user/999")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isBadRequest();
}
@Test
@DisplayName("测试创建用户 - 邮箱格式验证")
void testCreateUser_EmailValidation() {
SysUserRequest request = SysUserRequest.builder()
.username("testuser")
.email("invalid-email")
.phone("13800138000")
.status("0")
.build();
webTestClient.post()
.uri("/sys/user")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(request)
.exchange()
.expectStatus().isBadRequest();
}
@Test
@DisplayName("测试创建用户 - 手机号格式验证")
void testCreateUser_PhoneValidation() {
SysUserRequest request = SysUserRequest.builder()
.username("testuser")
.email("test@example.com")
.phone("123")
.status("0")
.build();
webTestClient.post()
.uri("/sys/user")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(request)
.exchange()
.expectStatus().isBadRequest();
}
@Test
@DisplayName("测试创建用户 - 必填字段验证")
void testCreateUser_RequiredFieldsValidation() {
SysUserRequest request = SysUserRequest.builder()
.build();
when(userService.create(any(SysUser.class)))
.thenReturn(Mono.error(new RuntimeException("用户名不能为空")));
webTestClient.post()
.uri("/sys/user")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(request)
.exchange()
.expectStatus().isBadRequest();
}
@Test
@DisplayName("测试更新用户 - 邮箱格式验证")
void testUpdateUser_EmailValidation() {
SysUserRequest request = SysUserRequest.builder()
.id(1L)
.username("testuser")
.email("invalid-email")
.status("0")
.build();
webTestClient.put()
.uri("/sys/user")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(request)
.exchange()
.expectStatus().isBadRequest();
}
@Test
@DisplayName("测试更新用户 - 状态值验证")
void testUpdateUser_StatusValidation() {
SysUserRequest request = SysUserRequest.builder()
.id(1L)
.username("testuser")
.email("test@example.com")
.status("invalid")
.build();
webTestClient.put()
.uri("/sys/user")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(request)
.exchange()
.expectStatus().isOk();
}
}
@@ -0,0 +1,247 @@
package io.destiny.sys.security;
import io.destiny.sys.core.domain.SysUser;
import io.destiny.sys.core.service.ISysPermissionService;
import io.destiny.sys.core.service.ISysUserService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.HttpHeaders;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class SysPermissionFilterTest {
@Mock
private ISysUserService userService;
@Mock
private ISysPermissionService permissionService;
@InjectMocks
private SysPermissionFilter permissionFilter;
private SysUser testUser;
private ServerWebExchange exchange;
@BeforeEach
void setUp() {
testUser = new SysUser();
testUser.setId(1L);
testUser.setUsername("admin");
testUser.setStatus("0");
exchange = MockServerWebExchange.from(
MockServerHttpRequest.get("/sys/user")
.header(HttpHeaders.AUTHORIZATION, "Bearer test-token")
.header("X-User-Id", "1")
.header("X-Username", "admin")
);
}
@Test
void filter_PublicPath_ShouldAllowAccess() {
ServerWebExchange publicExchange = MockServerWebExchange.from(
MockServerHttpRequest.post("/sys/auth/login")
);
StepVerifier.create(permissionFilter.filter(publicExchange, mockChain()))
.expectComplete()
.verify();
verify(userService, never()).findById(anyLong());
verify(permissionService, never()).hasPermission(anyLong(), anyString());
}
@Test
void filter_MissingUserHeaders_ShouldReturnForbidden() {
ServerWebExchange exchangeWithoutHeaders = MockServerWebExchange.from(
MockServerHttpRequest.get("/sys/user")
);
StepVerifier.create(permissionFilter.filter(exchangeWithoutHeaders, mockChain()))
.expectComplete()
.verify();
verify(userService, never()).findById(anyLong());
}
@Test
void filter_UserNotFound_ShouldReturnForbidden() {
when(userService.findById(1L)).thenReturn(Mono.empty());
StepVerifier.create(permissionFilter.filter(exchange, mockChain()))
.expectComplete()
.verify();
verify(userService).findById(1L);
verify(permissionService, never()).hasPermission(anyLong(), anyString());
}
@Test
void filter_UserDisabled_ShouldReturnForbidden() {
testUser.setStatus("1");
when(userService.findById(1L)).thenReturn(Mono.just(testUser));
StepVerifier.create(permissionFilter.filter(exchange, mockChain()))
.expectComplete()
.verify();
verify(userService).findById(1L);
verify(permissionService, never()).hasPermission(anyLong(), anyString());
}
@Test
void filter_UserHasPermission_ShouldAllowAccess() {
when(userService.findById(1L)).thenReturn(Mono.just(testUser));
when(permissionService.hasPermission(1L, "sys:user:list")).thenReturn(true);
StepVerifier.create(permissionFilter.filter(exchange, mockChain()))
.expectComplete()
.verify();
verify(userService).findById(1L);
verify(permissionService).hasPermission(1L, "sys:user:list");
}
@Test
void filter_UserNoPermission_ShouldReturnForbidden() {
when(userService.findById(1L)).thenReturn(Mono.just(testUser));
when(permissionService.hasPermission(1L, "sys:user:list")).thenReturn(false);
StepVerifier.create(permissionFilter.filter(exchange, mockChain()))
.expectComplete()
.verify();
verify(userService).findById(1L);
verify(permissionService).hasPermission(1L, "sys:user:list");
}
@Test
void filter_NoPermissionRuleDefined_ShouldAllowAccess() {
ServerWebExchange exchangeWithNoRule = MockServerWebExchange.from(
MockServerHttpRequest.get("/sys/unknown")
.header("X-User-Id", "1")
.header("X-Username", "admin")
);
when(userService.findById(1L)).thenReturn(Mono.just(testUser));
StepVerifier.create(permissionFilter.filter(exchangeWithNoRule, mockChain()))
.expectComplete()
.verify();
verify(userService).findById(1L);
verify(permissionService, never()).hasPermission(anyLong(), anyString());
}
@Test
void filter_InvalidUserIdFormat_ShouldReturnForbidden() {
ServerWebExchange exchangeWithInvalidUserId = MockServerWebExchange.from(
MockServerHttpRequest.get("/sys/user")
.header("X-User-Id", "invalid")
.header("X-Username", "admin")
);
StepVerifier.create(permissionFilter.filter(exchangeWithInvalidUserId, mockChain()))
.expectComplete()
.verify();
verify(userService, never()).findById(anyLong());
}
@Test
void filter_PostUser_ShouldCheckAddPermission() {
ServerWebExchange postExchange = MockServerWebExchange.from(
MockServerHttpRequest.post("/sys/user")
.header("X-User-Id", "1")
.header("X-Username", "admin")
);
when(userService.findById(1L)).thenReturn(Mono.just(testUser));
when(permissionService.hasPermission(1L, "sys:user:add")).thenReturn(true);
StepVerifier.create(permissionFilter.filter(postExchange, mockChain()))
.expectComplete()
.verify();
verify(userService).findById(1L);
verify(permissionService).hasPermission(1L, "sys:user:add");
}
@Test
void filter_DeleteUser_ShouldCheckRemovePermission() {
ServerWebExchange deleteExchange = MockServerWebExchange.from(
MockServerHttpRequest.delete("/sys/user/1")
.header("X-User-Id", "1")
.header("X-Username", "admin")
);
when(userService.findById(1L)).thenReturn(Mono.just(testUser));
when(permissionService.hasPermission(1L, "sys:user:remove")).thenReturn(true);
StepVerifier.create(permissionFilter.filter(deleteExchange, mockChain()))
.expectComplete()
.verify();
verify(userService).findById(1L);
verify(permissionService).hasPermission(1L, "sys:user:remove");
}
@Test
void filter_RoleManagement_ShouldCheckRolePermissions() {
ServerWebExchange roleExchange = MockServerWebExchange.from(
MockServerHttpRequest.get("/sys/role")
.header("X-User-Id", "1")
.header("X-Username", "admin")
);
when(userService.findById(1L)).thenReturn(Mono.just(testUser));
when(permissionService.hasPermission(1L, "sys:role:list")).thenReturn(true);
StepVerifier.create(permissionFilter.filter(roleExchange, mockChain()))
.expectComplete()
.verify();
verify(userService).findById(1L);
verify(permissionService).hasPermission(1L, "sys:role:list");
}
@Test
void filter_MenuManagement_ShouldCheckMenuPermissions() {
ServerWebExchange menuExchange = MockServerWebExchange.from(
MockServerHttpRequest.get("/sys/menu")
.header("X-User-Id", "1")
.header("X-Username", "admin")
);
when(userService.findById(1L)).thenReturn(Mono.just(testUser));
when(permissionService.hasPermission(1L, "sys:menu:list")).thenReturn(true);
StepVerifier.create(permissionFilter.filter(menuExchange, mockChain()))
.expectComplete()
.verify();
verify(userService).findById(1L);
verify(permissionService).hasPermission(1L, "sys:menu:list");
}
private org.springframework.web.server.WebFilterChain mockChain() {
return exchange -> {
exchange.getResponse().setStatusCode(org.springframework.http.HttpStatus.OK);
return exchange.getResponse().writeWith(
reactor.core.publisher.Flux.just(
exchange.getResponse().bufferFactory().wrap("OK".getBytes())));
};
}
}
@@ -0,0 +1,285 @@
package io.destiny.sys.service;
import io.destiny.sys.core.domain.SysUser;
import io.destiny.sys.core.service.ISysUserService;
import io.destiny.sys.core.service.ISysPermissionService;
import io.destiny.sys.core.service.ISysPermissionInitService;
import io.destiny.sys.core.service.impl.SysAuthServiceImpl;
import io.destiny.sys.dto.request.SysUserLoginRequest;
import io.destiny.sys.dto.response.SysUserRegisterRequest;
import io.destiny.sys.security.SysJwtTokenProvider;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.util.HashSet;
import java.util.Set;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class SysAuthServiceTest {
@Mock
private ISysUserService userService;
@Mock
private SysJwtTokenProvider jwtTokenProvider;
@Mock
private ISysPermissionService permissionService;
@Mock
private ISysPermissionInitService permissionInitService;
@InjectMocks
private SysAuthServiceImpl authService;
private final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
private SysUser testUser;
private SysUserRegisterRequest registerRequest;
private SysUserLoginRequest loginRequest;
@BeforeEach
void setUp() {
testUser = new SysUser();
testUser.setId(1L);
testUser.setUsername("testuser");
testUser.setPassword(passwordEncoder.encode("password123"));
testUser.setStatus("0");
registerRequest = new SysUserRegisterRequest();
registerRequest.setUsername("testuser");
registerRequest.setPassword("password123");
registerRequest.setEmail("test@example.com");
loginRequest = new SysUserLoginRequest();
loginRequest.setUsername("testuser");
loginRequest.setPassword("password123");
}
@Test
void register_Success() {
when(userService.findByUsername(anyString())).thenReturn(Mono.empty());
when(userService.create(any(SysUser.class))).thenReturn(Mono.just(testUser));
when(permissionInitService.initializeUserPermissions(anyLong())).thenReturn(Mono.empty());
StepVerifier.create(authService.register(registerRequest))
.expectNext(1L)
.verifyComplete();
verify(userService).findByUsername("testuser");
verify(userService).create(any(SysUser.class));
verify(permissionInitService).initializeUserPermissions(1L);
}
@Test
void register_AdminUser() {
SysUser adminUser = new SysUser();
adminUser.setId(2L);
adminUser.setUsername("admin");
adminUser.setPassword(passwordEncoder.encode("admin123"));
adminUser.setStatus("0");
SysUserRegisterRequest adminRegisterRequest = new SysUserRegisterRequest();
adminRegisterRequest.setUsername("admin");
adminRegisterRequest.setPassword("admin123");
adminRegisterRequest.setEmail("admin@example.com");
when(userService.findByUsername(anyString())).thenReturn(Mono.empty());
when(userService.create(any(SysUser.class))).thenReturn(Mono.just(adminUser));
when(permissionInitService.initializeAdminPermissions(anyLong())).thenReturn(Mono.empty());
StepVerifier.create(authService.register(adminRegisterRequest))
.expectNext(2L)
.verifyComplete();
verify(userService).findByUsername("admin");
verify(userService).create(any(SysUser.class));
verify(permissionInitService).initializeAdminPermissions(2L);
}
@Test
void register_UserExists() {
when(userService.findByUsername(anyString())).thenReturn(Mono.just(testUser));
StepVerifier.create(authService.register(registerRequest))
.expectErrorMatches(throwable -> throwable instanceof RuntimeException &&
throwable.getMessage().equals("用户名已存在"))
.verify();
verify(userService).findByUsername("testuser");
verify(userService, never()).create(any(SysUser.class));
}
@Test
void login_Success() {
Set<String> permissions = new HashSet<>();
permissions.add("user:read");
permissions.add("user:write");
when(userService.findByUsername(anyString())).thenReturn(Mono.just(testUser));
when(jwtTokenProvider.generateAccessToken(anyLong(), anyString())).thenReturn("access-token");
when(permissionService.getUserPermissions(anyLong())).thenReturn(Mono.just(permissions));
StepVerifier.create(authService.login(loginRequest))
.expectNextMatches(response -> response.getToken().equals("access-token") &&
response.getUser().getUsername().equals("testuser"))
.verifyComplete();
verify(userService).findByUsername("testuser");
verify(jwtTokenProvider).generateAccessToken(1L, "testuser");
verify(permissionService).getUserPermissions(1L);
}
@Test
void login_UserNotFound() {
when(userService.findByUsername(anyString())).thenReturn(Mono.empty());
StepVerifier.create(authService.login(loginRequest))
.expectErrorMatches(throwable -> throwable instanceof RuntimeException &&
throwable.getMessage().equals("用户名或密码错误"))
.verify();
verify(userService).findByUsername("testuser");
verify(jwtTokenProvider, never()).generateAccessToken(anyLong(), anyString());
}
@Test
void login_WrongPassword() {
testUser.setPassword(passwordEncoder.encode("correctpassword"));
when(userService.findByUsername(anyString())).thenReturn(Mono.just(testUser));
loginRequest.setPassword("wrongpassword");
StepVerifier.create(authService.login(loginRequest))
.expectErrorMatches(throwable -> throwable instanceof RuntimeException &&
throwable.getMessage().equals("用户名或密码错误"))
.verify();
verify(userService).findByUsername("testuser");
verify(jwtTokenProvider, never()).generateAccessToken(anyLong(), anyString());
}
@Test
void login_UserDisabled() {
testUser.setStatus("1");
when(userService.findByUsername(anyString())).thenReturn(Mono.just(testUser));
StepVerifier.create(authService.login(loginRequest))
.expectErrorMatches(throwable -> throwable instanceof RuntimeException &&
throwable.getMessage().equals("用户已被禁用"))
.verify();
verify(userService).findByUsername("testuser");
verify(jwtTokenProvider, never()).generateAccessToken(anyLong(), anyString());
}
@Test
void refreshToken_Success() {
Set<String> permissions = new HashSet<>();
permissions.add("user:read");
permissions.add("user:write");
String refreshToken = "valid-refresh-token";
when(jwtTokenProvider.validateToken(anyString())).thenReturn(true);
when(jwtTokenProvider.isTokenExpired(anyString())).thenReturn(false);
when(jwtTokenProvider.getTokenType(anyString())).thenReturn("refresh");
when(jwtTokenProvider.getUserIdFromToken(anyString())).thenReturn(1L);
when(userService.findById(anyLong())).thenReturn(Mono.just(testUser));
when(jwtTokenProvider.generateAccessToken(anyLong(), anyString())).thenReturn("new-access-token");
when(permissionService.getUserPermissions(anyLong())).thenReturn(Mono.just(permissions));
StepVerifier.create(authService.refreshToken(refreshToken))
.expectNextMatches(response -> response.getToken().equals("new-access-token") &&
response.getUser().getUsername().equals("testuser"))
.verifyComplete();
verify(jwtTokenProvider).validateToken(refreshToken);
verify(userService).findById(1L);
verify(jwtTokenProvider).generateAccessToken(1L, "testuser");
verify(permissionService).getUserPermissions(1L);
}
@Test
void refreshToken_InvalidToken() {
String refreshToken = "invalid-token";
when(jwtTokenProvider.validateToken(anyString())).thenReturn(false);
StepVerifier.create(authService.refreshToken(refreshToken))
.expectErrorMatches(throwable -> throwable instanceof RuntimeException &&
throwable.getMessage().equals("无效的刷新令牌"))
.verify();
verify(jwtTokenProvider).validateToken(refreshToken);
verify(userService, never()).findById(anyLong());
}
@Test
void refreshToken_ExpiredToken() {
String refreshToken = "expired-token";
when(jwtTokenProvider.validateToken(anyString())).thenReturn(true);
when(jwtTokenProvider.isTokenExpired(anyString())).thenReturn(true);
StepVerifier.create(authService.refreshToken(refreshToken))
.expectErrorMatches(throwable -> throwable instanceof RuntimeException &&
throwable.getMessage().equals("刷新令牌已过期"))
.verify();
verify(jwtTokenProvider).validateToken(refreshToken);
verify(jwtTokenProvider).isTokenExpired(refreshToken);
verify(userService, never()).findById(anyLong());
}
@Test
void refreshToken_WrongTokenType() {
String refreshToken = "access-token";
when(jwtTokenProvider.validateToken(anyString())).thenReturn(true);
when(jwtTokenProvider.isTokenExpired(anyString())).thenReturn(false);
when(jwtTokenProvider.getTokenType(anyString())).thenReturn("access");
StepVerifier.create(authService.refreshToken(refreshToken))
.expectErrorMatches(throwable -> throwable instanceof RuntimeException &&
throwable.getMessage().equals("令牌类型错误"))
.verify();
verify(jwtTokenProvider).validateToken(refreshToken);
verify(jwtTokenProvider).getTokenType(refreshToken);
verify(userService, never()).findById(anyLong());
}
@Test
void logout_Success() {
String token = "valid-token";
when(jwtTokenProvider.validateToken(anyString())).thenReturn(true);
when(jwtTokenProvider.getUserIdFromToken(anyString())).thenReturn(1L);
StepVerifier.create(authService.logout(token))
.verifyComplete();
verify(jwtTokenProvider).validateToken(token);
verify(jwtTokenProvider).getUserIdFromToken(token);
}
@Test
void logout_InvalidToken() {
String token = "invalid-token";
when(jwtTokenProvider.validateToken(anyString())).thenReturn(false);
StepVerifier.create(authService.logout(token))
.verifyComplete();
verify(jwtTokenProvider).validateToken(token);
verify(jwtTokenProvider, never()).getUserIdFromToken(anyString());
}
}
@@ -0,0 +1,22 @@
package io.destiny.sys.util;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
public class BCryptGenerator {
public static void main(String[] args) {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
String[] passwords = {
"admin123456",
"user123",
"test123"
};
for (String password : passwords) {
String encoded = encoder.encode(password);
System.out.println("Password: " + password);
System.out.println("BCrypt: " + encoded);
System.out.println();
}
}
}
@@ -0,0 +1,25 @@
package io.destiny.sys.util;
import org.junit.jupiter.api.Test;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
public class BCryptGeneratorTest {
@Test
public void generateBCryptPasswords() {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
String[] passwords = {
"admin123456",
"user123",
"test123"
};
for (String password : passwords) {
String encoded = encoder.encode(password);
System.out.println("Password: " + password);
System.out.println("BCrypt: " + encoded);
System.out.println();
}
}
}
@@ -0,0 +1,241 @@
package io.destiny.sys.util;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.util.UriBuilder;
import java.net.URI;
import java.util.function.Function;
public class SysApiTestHelper {
public static Function<UriBuilder, URI> buildUserListUri(Integer page, Integer size, String username, Integer status) {
return uriBuilder -> {
UriBuilder builder = uriBuilder.path("/sys/user/list");
if (page != null) {
builder.queryParam("page", page);
}
if (size != null) {
builder.queryParam("size", size);
}
if (username != null && !username.isEmpty()) {
builder.queryParam("username", username);
}
if (status != null) {
builder.queryParam("status", status);
}
return builder.build();
};
}
public static Function<UriBuilder, URI> buildUserDetailUri(Long userId) {
return uriBuilder -> uriBuilder.path("/sys/user/" + userId).build();
}
public static Function<UriBuilder, URI> buildRoleListUri(Integer page, Integer size, String roleName, Integer status) {
return uriBuilder -> {
UriBuilder builder = uriBuilder.path("/sys/role/list");
if (page != null) {
builder.queryParam("page", page);
}
if (size != null) {
builder.queryParam("size", size);
}
if (roleName != null && !roleName.isEmpty()) {
builder.queryParam("roleName", roleName);
}
if (status != null) {
builder.queryParam("status", status);
}
return builder.build();
};
}
public static Function<UriBuilder, URI> buildRoleDetailUri(Long roleId) {
return uriBuilder -> uriBuilder.path("/sys/role/" + roleId).build();
}
public static Function<UriBuilder, URI> buildMenuListUri(Integer status) {
return uriBuilder -> {
UriBuilder builder = uriBuilder.path("/sys/menu/list");
if (status != null) {
builder.queryParam("status", status);
}
return builder.build();
};
}
public static Function<UriBuilder, URI> buildMenuTreeUri() {
return uriBuilder -> uriBuilder.path("/sys/menu/tree").build();
}
public static Function<UriBuilder, URI> buildMenuDetailUri(Long menuId) {
return uriBuilder -> uriBuilder.path("/sys/menu/" + menuId).build();
}
public static Function<UriBuilder, URI> buildAuthRegisterUri() {
return uriBuilder -> uriBuilder.path("/sys/auth/register").build();
}
public static Function<UriBuilder, URI> buildAuthLoginUri() {
return uriBuilder -> uriBuilder.path("/sys/auth/login").build();
}
public static Function<UriBuilder, URI> buildAuthRefreshUri() {
return uriBuilder -> uriBuilder.path("/sys/auth/refresh").build();
}
public static Function<UriBuilder, URI> buildAuthLogoutUri() {
return uriBuilder -> uriBuilder.path("/sys/auth/logout").build();
}
public static WebTestClient.ResponseSpec performGetRequest(
WebTestClient webTestClient,
Function<UriBuilder, URI> uriFunction) {
return webTestClient.get()
.uri(uriFunction)
.accept(MediaType.APPLICATION_JSON)
.exchange();
}
public static WebTestClient.ResponseSpec performPostRequest(
WebTestClient webTestClient,
Function<UriBuilder, URI> uriFunction,
Object body) {
return webTestClient.post()
.uri(uriFunction)
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(body)
.accept(MediaType.APPLICATION_JSON)
.exchange();
}
public static WebTestClient.ResponseSpec performPutRequest(
WebTestClient webTestClient,
Function<UriBuilder, URI> uriFunction,
Object body) {
return webTestClient.put()
.uri(uriFunction)
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(body)
.accept(MediaType.APPLICATION_JSON)
.exchange();
}
public static WebTestClient.ResponseSpec performDeleteRequest(
WebTestClient webTestClient,
Function<UriBuilder, URI> uriFunction) {
return webTestClient.delete()
.uri(uriFunction)
.accept(MediaType.APPLICATION_JSON)
.exchange();
}
public static void assertSuccessResponse(WebTestClient.BodyContentSpec bodySpec) {
bodySpec
.jsonPath("$.code").isEqualTo(200)
.jsonPath("$.message").isEqualTo("Success");
}
public static void assertErrorResponse(WebTestClient.BodyContentSpec bodySpec, int expectedCode) {
bodySpec
.jsonPath("$.code").isEqualTo(expectedCode);
}
public static void assertDataNotEmpty(WebTestClient.BodyContentSpec bodySpec) {
bodySpec.jsonPath("$.data").isNotEmpty();
}
public static void assertDataIsArray(WebTestClient.BodyContentSpec bodySpec) {
bodySpec.jsonPath("$.data").isArray();
}
public static void assertArrayLength(WebTestClient.BodyContentSpec bodySpec, int expectedLength) {
bodySpec.jsonPath("$.data.length()").isEqualTo(expectedLength);
}
public static void assertPaginationData(WebTestClient.BodyContentSpec bodySpec) {
bodySpec
.jsonPath("$.data.records").isArray()
.jsonPath("$.data.total").exists()
.jsonPath("$.data.page").exists()
.jsonPath("$.data.size").exists();
}
public static void assertPaginationTotal(WebTestClient.BodyContentSpec bodySpec, long expectedTotal) {
bodySpec.jsonPath("$.data.total").isEqualTo(expectedTotal);
}
public static void assertPaginationPage(WebTestClient.BodyContentSpec bodySpec, int expectedPage) {
bodySpec.jsonPath("$.data.page").isEqualTo(expectedPage);
}
public static void assertPaginationSize(WebTestClient.BodyContentSpec bodySpec, int expectedSize) {
bodySpec.jsonPath("$.data.size").isEqualTo(expectedSize);
}
public static void assertUserDataFields(WebTestClient.BodyContentSpec bodySpec) {
bodySpec
.jsonPath("$.data.id").exists()
.jsonPath("$.data.username").exists()
.jsonPath("$.data.nickname").exists()
.jsonPath("$.data.email").exists()
.jsonPath("$.data.phone").exists()
.jsonPath("$.data.status").exists()
.jsonPath("$.data.createdAt").exists();
}
public static void assertRoleDataFields(WebTestClient.BodyContentSpec bodySpec) {
bodySpec
.jsonPath("$.data.id").exists()
.jsonPath("$.data.roleName").exists()
.jsonPath("$.data.roleKey").exists()
.jsonPath("$.data.roleSort").exists()
.jsonPath("$.data.status").exists()
.jsonPath("$.data.createdAt").exists();
}
public static void assertMenuDataFields(WebTestClient.BodyContentSpec bodySpec) {
bodySpec
.jsonPath("$.data.id").exists()
.jsonPath("$.data.menuName").exists()
.jsonPath("$.data.parentId").exists()
.jsonPath("$.data.menuType").exists()
.jsonPath("$.data.perms").exists()
.jsonPath("$.data.path").exists()
.jsonPath("$.data.status").exists();
}
public static void assertLoginResponseFields(WebTestClient.BodyContentSpec bodySpec) {
bodySpec
.jsonPath("$.data.accessToken").exists()
.jsonPath("$.data.refreshToken").exists()
.jsonPath("$.data.tokenType").exists()
.jsonPath("$.data.expiresIn").exists();
}
public static void assertUserId(WebTestClient.BodyContentSpec bodySpec, Long expectedUserId) {
bodySpec.jsonPath("$.data.id").isEqualTo(expectedUserId);
}
public static void assertUsername(WebTestClient.BodyContentSpec bodySpec, String expectedUsername) {
bodySpec.jsonPath("$.data.username").isEqualTo(expectedUsername);
}
public static void assertStatus(WebTestClient.BodyContentSpec bodySpec, Integer expectedStatus) {
bodySpec.jsonPath("$.data.status").isEqualTo(expectedStatus);
}
public static void assertTokenExists(WebTestClient.BodyContentSpec bodySpec) {
bodySpec.jsonPath("$.data.accessToken").isNotEmpty()
.jsonPath("$.data.refreshToken").isNotEmpty();
}
public static void assertContentType(WebTestClient.ResponseSpec responseSpec) {
responseSpec.expectHeader().contentType("application/json");
}
public static void assertStatusCode(WebTestClient.ResponseSpec responseSpec, int expectedStatus) {
responseSpec.expectStatus().isEqualTo(expectedStatus);
}
}
@@ -0,0 +1,263 @@
package io.destiny.sys.util;
import io.destiny.sys.core.domain.SysUser;
import io.destiny.sys.core.domain.SysRole;
import io.destiny.sys.core.domain.SysMenu;
import io.destiny.sys.dto.request.SysMenuRequest;
import io.destiny.sys.dto.request.SysRoleRequest;
import io.destiny.sys.dto.request.SysUserLoginRequest;
import io.destiny.sys.dto.request.SysUserRequest;
import io.destiny.sys.dto.response.SysLoginResponse;
import io.destiny.sys.dto.response.SysMenuResponse;
import io.destiny.sys.dto.response.SysRoleResponse;
import io.destiny.sys.dto.response.SysUserRegisterRequest;
import io.destiny.sys.dto.response.SysUserResponse;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
public class SysTestDataBuilder {
public static SysUser createDefaultUser() {
SysUser user = new SysUser();
user.setId(1L);
user.setUsername("testuser");
user.setPassword("$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iKTVKIUi");
user.setEmail(io.destiny.common.primitive.EmailAddress.of("test@example.com"));
user.setPhone(io.destiny.common.primitive.PhoneNumber.of("13800138000"));
user.setStatus("0");
user.setCreateBy("admin");
user.setUpdateBy("admin");
user.setCreatedAt(LocalDateTime.now());
user.setUpdatedAt(LocalDateTime.now());
return user;
}
public static SysUser createAdminUser() {
SysUser user = new SysUser();
user.setId(1L);
user.setUsername("admin");
user.setPassword("$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iKTVKIUi");
user.setEmail(io.destiny.common.primitive.EmailAddress.of("admin@example.com"));
user.setPhone(io.destiny.common.primitive.PhoneNumber.of("13800138001"));
user.setStatus("0");
user.setCreateBy("system");
user.setUpdateBy("system");
user.setCreatedAt(LocalDateTime.now());
user.setUpdatedAt(LocalDateTime.now());
return user;
}
public static SysUserResponse createDefaultUserResponse() {
return SysUserResponse.builder()
.id(1L)
.username("testuser")
.email("test@example.com")
.phone("13800138000")
.status("0")
.createBy("admin")
.updateBy("admin")
.createdAt(LocalDateTime.now())
.updatedAt(LocalDateTime.now())
.build();
}
public static SysUserRegisterRequest createDefaultUserRegisterRequest() {
SysUserRegisterRequest request = new SysUserRegisterRequest();
request.setUsername("testuser");
request.setPassword("password123");
request.setEmail("test@example.com");
request.setPhone("13800138000");
return request;
}
public static SysUserLoginRequest createDefaultUserLoginRequest() {
SysUserLoginRequest request = new SysUserLoginRequest();
request.setUsername("testuser");
request.setPassword("password123");
request.setIpAddress("127.0.0.1");
return request;
}
public static SysUserRequest createDefaultUserRequest() {
return SysUserRequest.builder()
.id(1L)
.username("testuser")
.email("test@example.com")
.phone("13800138000")
.status("0")
.build();
}
public static SysUserRequest createUpdateUserRequest() {
return SysUserRequest.builder()
.id(1L)
.username("updateduser")
.email("updated@example.com")
.phone("13900139000")
.status("0")
.build();
}
public static SysRole createDefaultRole() {
SysRole role = new SysRole();
role.setId(1L);
role.setRoleName("普通用户");
role.setRoleKey("user");
role.setRoleSort(1);
role.setStatus("0");
role.setCreateBy("admin");
role.setUpdateBy("admin");
role.setCreatedAt(LocalDateTime.now());
role.setUpdatedAt(LocalDateTime.now());
return role;
}
public static SysRole createAdminRole() {
SysRole role = new SysRole();
role.setId(1L);
role.setRoleName("管理员");
role.setRoleKey("admin");
role.setRoleSort(0);
role.setStatus("0");
role.setCreateBy("system");
role.setUpdateBy("system");
role.setCreatedAt(LocalDateTime.now());
role.setUpdatedAt(LocalDateTime.now());
return role;
}
public static SysRoleResponse createDefaultRoleResponse() {
return SysRoleResponse.builder()
.id(1L)
.name("普通用户")
.roleKey("user")
.sortOrder(1)
.status("0")
.createdAt(LocalDateTime.now())
.updatedAt(LocalDateTime.now())
.build();
}
public static SysRoleRequest createDefaultRoleRequest() {
return SysRoleRequest.builder()
.name("普通用户")
.roleKey("user")
.sortOrder(1)
.status("0")
.build();
}
public static SysRoleRequest createUpdateRoleRequest() {
return SysRoleRequest.builder()
.id(1L)
.name("更新角色")
.roleKey("updated_role")
.sortOrder(2)
.status("0")
.build();
}
public static SysMenu createDefaultMenu() {
SysMenu menu = new SysMenu();
menu.setId(1L);
menu.setMenuName("系统管理");
menu.setParentId(0L);
menu.setOrderNum(1);
menu.setMenuType("M");
menu.setPerms("");
menu.setComponent("");
menu.setStatus("0");
menu.setCreateBy("admin");
menu.setUpdateBy("admin");
menu.setCreatedAt(LocalDateTime.now());
menu.setUpdatedAt(LocalDateTime.now());
return menu;
}
public static SysMenu createSubMenu() {
SysMenu menu = new SysMenu();
menu.setId(2L);
menu.setMenuName("用户管理");
menu.setParentId(1L);
menu.setOrderNum(1);
menu.setMenuType("C");
menu.setPerms("system:user:list");
menu.setComponent("system/user/index");
menu.setStatus("0");
menu.setCreateBy("admin");
menu.setUpdateBy("admin");
menu.setCreatedAt(LocalDateTime.now());
menu.setUpdatedAt(LocalDateTime.now());
return menu;
}
public static SysMenuResponse createDefaultMenuResponse() {
return SysMenuResponse.builder()
.id(1L)
.name("系统管理")
.parentId(0L)
.sortOrder(1)
.status("0")
.createdAt(LocalDateTime.now())
.updatedAt(LocalDateTime.now())
.build();
}
public static SysMenuRequest createDefaultMenuRequest() {
return SysMenuRequest.builder()
.name("系统管理")
.parentId(0L)
.sortOrder(1)
.status("0")
.build();
}
public static SysMenuRequest createUpdateMenuRequest() {
return SysMenuRequest.builder()
.id(1L)
.name("更新菜单")
.parentId(0L)
.sortOrder(2)
.status("0")
.build();
}
public static SysLoginResponse createLoginResponse(Long userId, String username, String token) {
SysUser user = new SysUser();
user.setId(userId);
user.setUsername(username);
return SysLoginResponse.from(token, "refresh_token_" + token, user);
}
public static List<SysUser> createMultipleUsers(int count) {
return Arrays.asList(
createDefaultUser(),
createAdminUser()).subList(0, Math.min(count, 2));
}
public static List<SysRole> createMultipleRoles(int count) {
return Arrays.asList(
createAdminRole(),
createDefaultRole()).subList(0, Math.min(count, 2));
}
public static List<SysMenu> createMultipleMenus(int count) {
return Arrays.asList(
createDefaultMenu(),
createSubMenu()).subList(0, Math.min(count, 2));
}
public static List<Long> createUserIds(int count) {
return Arrays.asList(1L, 2L, 3L).subList(0, Math.min(count, 3));
}
public static List<Long> createRoleIds(int count) {
return Arrays.asList(1L, 2L, 3L).subList(0, Math.min(count, 3));
}
public static List<Long> createMenuIds(int count) {
return Arrays.asList(1L, 2L, 3L).subList(0, Math.min(count, 3));
}
}