新增答辩用后台管理系统
This commit is contained in:
+181
-101
@@ -9,24 +9,17 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.core.io.buffer.DataBufferUtils;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.reactive.function.server.HandlerStrategies;
|
||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
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.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@Component
|
||||
@Order(Ordered.LOWEST_PRECEDENCE)
|
||||
@@ -37,19 +30,76 @@ public class OperationLogWebFilter implements WebFilter {
|
||||
private final IOperationLogService operationLogService;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
private static final Map<String, OperationInfo> OPERATION_MAPPING = new ConcurrentHashMap<>();
|
||||
/** 精确匹配的操作映射(method:path → module, operation) */
|
||||
private static final Map<String, OperationInfo> PRECISE_MAPPING = new LinkedHashMap<>();
|
||||
|
||||
/** 前缀匹配的操作映射(按声明顺序) */
|
||||
private static final Map<String, OperationInfo> PREFIX_MAPPING = new LinkedHashMap<>();
|
||||
|
||||
/** URL模块名称映射 */
|
||||
private static final Map<String, String> MODULE_NAMES = new LinkedHashMap<>();
|
||||
|
||||
static {
|
||||
OPERATION_MAPPING.put("POST:/api/roles", new OperationInfo("角色管理", "创建角色"));
|
||||
OPERATION_MAPPING.put("PUT:/api/roles/", new OperationInfo("角色管理", "更新角色"));
|
||||
OPERATION_MAPPING.put("DELETE:/api/roles/", new OperationInfo("角色管理", "删除角色"));
|
||||
OPERATION_MAPPING.put("POST:/api/users", new OperationInfo("用户管理", "创建用户"));
|
||||
OPERATION_MAPPING.put("PUT:/api/users/", new OperationInfo("用户管理", "更新用户"));
|
||||
OPERATION_MAPPING.put("DELETE:/api/users/", new OperationInfo("用户管理", "删除用户"));
|
||||
OPERATION_MAPPING.put("POST:/api/users/", new OperationInfo("用户管理", "用户操作"));
|
||||
OPERATION_MAPPING.put("POST:/api/menus", new OperationInfo("菜单管理", "创建菜单"));
|
||||
OPERATION_MAPPING.put("PUT:/api/menus/", new OperationInfo("菜单管理", "更新菜单"));
|
||||
OPERATION_MAPPING.put("DELETE:/api/menus/", new OperationInfo("菜单管理", "删除菜单"));
|
||||
// ===== 精确路径匹配 =====
|
||||
PRECISE_MAPPING.put("POST:/api/roles", new OperationInfo("角色管理", "创建角色"));
|
||||
PRECISE_MAPPING.put("POST:/api/users", new OperationInfo("用户管理", "创建用户"));
|
||||
PRECISE_MAPPING.put("POST:/api/menus", new OperationInfo("菜单管理", "创建菜单"));
|
||||
PRECISE_MAPPING.put("POST:/api/auth/login", new OperationInfo("认证", "用户登录"));
|
||||
PRECISE_MAPPING.put("GET:/api/groupCourse/types/categories", new OperationInfo("团课类型", "查询分类"));
|
||||
PRECISE_MAPPING.put("POST:/api/groupCourse/types", new OperationInfo("团课类型", "创建类型"));
|
||||
PRECISE_MAPPING.put("POST:/api/groupCourse", new OperationInfo("团课管理", "创建团课"));
|
||||
PRECISE_MAPPING.put("POST:/api/member", new OperationInfo("会员管理", "创建会员"));
|
||||
PRECISE_MAPPING.put("POST:/api/member-cards", new OperationInfo("会员卡管理", "创建会员卡"));
|
||||
PRECISE_MAPPING.put("POST:/api/groupCourse/recommend", new OperationInfo("推荐管理", "创建推荐"));
|
||||
PRECISE_MAPPING.put("POST:/api/checkIn", new OperationInfo("签到管理", "签到"));
|
||||
PRECISE_MAPPING.put("POST:/api/payment/create", new OperationInfo("支付管理", "创建支付"));
|
||||
PRECISE_MAPPING.put("POST:/api/upload/image", new OperationInfo("文件管理", "上传图片"));
|
||||
|
||||
// ===== 前缀匹配 =====
|
||||
PREFIX_MAPPING.put("PUT:/api/roles/", new OperationInfo("角色管理", "更新角色"));
|
||||
PREFIX_MAPPING.put("DELETE:/api/roles/", new OperationInfo("角色管理", "删除角色"));
|
||||
PREFIX_MAPPING.put("PUT:/api/users/", new OperationInfo("用户管理", "更新用户"));
|
||||
PREFIX_MAPPING.put("DELETE:/api/users/", new OperationInfo("用户管理", "删除用户"));
|
||||
PREFIX_MAPPING.put("PUT:/api/menus/", new OperationInfo("菜单管理", "更新菜单"));
|
||||
PREFIX_MAPPING.put("DELETE:/api/menus/", new OperationInfo("菜单管理", "删除菜单"));
|
||||
PREFIX_MAPPING.put("PUT:/api/groupCourse/types/", new OperationInfo("团课类型", "更新类型"));
|
||||
PREFIX_MAPPING.put("DELETE:/api/groupCourse/types/", new OperationInfo("团课类型", "删除类型"));
|
||||
PREFIX_MAPPING.put("PUT:/api/groupCourse/", new OperationInfo("团课管理", "更新团课"));
|
||||
PREFIX_MAPPING.put("DELETE:/api/groupCourse/", new OperationInfo("团课管理", "删除团课"));
|
||||
PREFIX_MAPPING.put("POST:/api/groupCourse/", new OperationInfo("团课管理", "操作团课"));
|
||||
PREFIX_MAPPING.put("PUT:/api/member/", new OperationInfo("会员管理", "编辑会员"));
|
||||
PREFIX_MAPPING.put("DELETE:/api/member/", new OperationInfo("会员管理", "删除会员"));
|
||||
PREFIX_MAPPING.put("PUT:/api/member-cards/", new OperationInfo("会员卡管理", "编辑会员卡"));
|
||||
PREFIX_MAPPING.put("DELETE:/api/member-cards/", new OperationInfo("会员卡管理", "删除会员卡"));
|
||||
PREFIX_MAPPING.put("PUT:/api/groupCourse/recommend/", new OperationInfo("推荐管理", "更新推荐"));
|
||||
PREFIX_MAPPING.put("DELETE:/api/groupCourse/recommend/", new OperationInfo("推荐管理", "删除推荐"));
|
||||
PREFIX_MAPPING.put("POST:/api/groupCourse/recommend/", new OperationInfo("推荐管理", "操作推荐"));
|
||||
PREFIX_MAPPING.put("POST:/api/member-card-transactions/", new OperationInfo("会员卡", "交易操作"));
|
||||
PREFIX_MAPPING.put("PUT:/api/payment/", new OperationInfo("支付管理", "更新支付"));
|
||||
PREFIX_MAPPING.put("POST:/api/payment/", new OperationInfo("支付管理", "支付操作"));
|
||||
PREFIX_MAPPING.put("PUT:/api/admin/member/", new OperationInfo("会员管理", "管理员编辑会员"));
|
||||
PREFIX_MAPPING.put("DELETE:/api/admin/member/", new OperationInfo("会员管理", "管理员删除会员"));
|
||||
PREFIX_MAPPING.put("DELETE:/api/groupCourse/labels/", new OperationInfo("标签管理", "删除标签"));
|
||||
|
||||
// ===== URL模块名映射(用于未匹配写操作自动生成) =====
|
||||
MODULE_NAMES.put("roles", "角色管理");
|
||||
MODULE_NAMES.put("users", "用户管理");
|
||||
MODULE_NAMES.put("menus", "菜单管理");
|
||||
MODULE_NAMES.put("auth", "认证");
|
||||
MODULE_NAMES.put("groupCourse", "团课管理");
|
||||
MODULE_NAMES.put("member", "会员管理");
|
||||
MODULE_NAMES.put("member-cards", "会员卡管理");
|
||||
MODULE_NAMES.put("member-card-records", "会员卡记录");
|
||||
MODULE_NAMES.put("member-card-transactions", "会员卡交易");
|
||||
MODULE_NAMES.put("checkIn", "签到管理");
|
||||
MODULE_NAMES.put("payment", "支付管理");
|
||||
MODULE_NAMES.put("dictionaries", "字典管理");
|
||||
MODULE_NAMES.put("config", "系统配置");
|
||||
MODULE_NAMES.put("upload", "文件管理");
|
||||
MODULE_NAMES.put("logs", "日志管理");
|
||||
MODULE_NAMES.put("datacount", "数据统计");
|
||||
MODULE_NAMES.put("diagnostic", "诊断");
|
||||
MODULE_NAMES.put("stats", "统计");
|
||||
}
|
||||
|
||||
public OperationLogWebFilter(IOperationLogService operationLogService, ObjectMapper objectMapper) {
|
||||
@@ -61,10 +111,8 @@ public class OperationLogWebFilter implements WebFilter {
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
logger.info("=== OperationLogWebFilter 初始化 ===");
|
||||
logger.info("操作日志映射配置数量: {}", OPERATION_MAPPING.size());
|
||||
OPERATION_MAPPING.forEach((key, value) -> {
|
||||
logger.info(" {} -> {}:{}", key, value.module, value.operation);
|
||||
});
|
||||
logger.info("精确匹配配置数量: {}, 前缀匹配配置数量: {}, 模块映射数量: {}",
|
||||
PRECISE_MAPPING.size(), PREFIX_MAPPING.size(), MODULE_NAMES.size());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -72,103 +120,135 @@ public class OperationLogWebFilter implements WebFilter {
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
String method = request.getMethod().name();
|
||||
String path = request.getPath().value();
|
||||
String key = method + ":" + path;
|
||||
|
||||
logger.info("WebFilter 拦截请求: {} {}", method, path);
|
||||
// 先尝试精确匹配
|
||||
OperationInfo operationInfo = PRECISE_MAPPING.get(key);
|
||||
|
||||
OperationInfo operationInfo = findOperationInfo(method, path);
|
||||
if (operationInfo == null) {
|
||||
// 尝试前缀匹配
|
||||
operationInfo = findPrefixMatch(key);
|
||||
}
|
||||
|
||||
if (operationInfo == null) {
|
||||
// 未匹配:如果是写操作(POST/PUT/DELETE),自动生成记录
|
||||
if (isWriteOperation(method)) {
|
||||
operationInfo = buildAutoOperationInfo(method, path);
|
||||
logger.info("自动生成操作日志: {} {} -> {}:{}", method, path, operationInfo.module, operationInfo.operation);
|
||||
}
|
||||
} else {
|
||||
logger.info("匹配到操作日志配置: {} {} -> {}:{}", method, path, operationInfo.module, operationInfo.operation);
|
||||
}
|
||||
|
||||
if (operationInfo == null) {
|
||||
logger.info("未匹配到操作日志配置,跳过: {} {}", method, path);
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
|
||||
logger.info("匹配到操作日志配置: {} {} -> {}:{}", method, path, operationInfo.module, operationInfo.operation);
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
String ip = IpUtils.getClientIp(request);
|
||||
final OperationInfo finalInfo = operationInfo;
|
||||
|
||||
return Mono.deferContextual(contextView -> {
|
||||
return chain.filter(exchange)
|
||||
.then(Mono.defer(() -> {
|
||||
long duration = System.currentTimeMillis() - startTime;
|
||||
logger.info("请求处理完成,准备保存操作日志: {} {}, 耗时: {}ms", method, path, duration);
|
||||
|
||||
return ReactiveSecurityContextHolder.getContext()
|
||||
.flatMap(securityContext -> {
|
||||
Object principal = securityContext.getAuthentication().getPrincipal();
|
||||
String username = principal instanceof String ? (String) principal : "system";
|
||||
logger.info("获取到用户名: {}", username);
|
||||
return Mono.just(username);
|
||||
})
|
||||
.defaultIfEmpty("system")
|
||||
.flatMap(username -> {
|
||||
logger.info("开始保存操作日志: 用户={}, 操作={}", username,
|
||||
operationInfo.module + " - " + operationInfo.operation);
|
||||
|
||||
OperationLog log = new OperationLog();
|
||||
log.setUsername(username);
|
||||
log.setOperation(operationInfo.module + " - " + operationInfo.operation);
|
||||
log.setMethod(method + " " + path);
|
||||
log.setParams(null);
|
||||
log.setIp(ip);
|
||||
log.setDuration(duration);
|
||||
log.setStatus("0");
|
||||
|
||||
return operationLogService.save(log)
|
||||
.doOnSuccess(saved -> logger.info("操作日志保存成功: {} - {}",
|
||||
operationInfo.module, operationInfo.operation))
|
||||
.doOnError(e -> logger.error("操作日志保存失败: {}", e.getMessage(), e))
|
||||
.onErrorResume(e -> Mono.empty());
|
||||
})
|
||||
.then();
|
||||
}))
|
||||
.onErrorResume(error -> {
|
||||
long duration = System.currentTimeMillis() - startTime;
|
||||
logger.error("请求处理失败: {} {}, 错误: {}", method, path, error.getMessage());
|
||||
|
||||
return ReactiveSecurityContextHolder.getContext()
|
||||
.flatMap(securityContext -> {
|
||||
Object principal = securityContext.getAuthentication().getPrincipal();
|
||||
String username = principal instanceof String ? (String) principal : "system";
|
||||
return Mono.just(username);
|
||||
})
|
||||
.defaultIfEmpty("system")
|
||||
.flatMap(username -> {
|
||||
OperationLog log = new OperationLog();
|
||||
log.setUsername(username);
|
||||
log.setOperation(operationInfo.module + " - " + operationInfo.operation);
|
||||
log.setMethod(method + " " + path);
|
||||
log.setParams(null);
|
||||
log.setIp(ip);
|
||||
log.setDuration(duration);
|
||||
log.setStatus("1");
|
||||
log.setErrorMsg(error.getMessage());
|
||||
|
||||
return operationLogService.save(log)
|
||||
.doOnError(e -> logger.error("错误日志保存失败: {}", e.getMessage()))
|
||||
.onErrorResume(e -> Mono.empty());
|
||||
})
|
||||
.then(Mono.error(error));
|
||||
});
|
||||
});
|
||||
return chain.filter(exchange)
|
||||
.then(Mono.defer(() -> {
|
||||
long duration = System.currentTimeMillis() - startTime;
|
||||
return getCurrentUsername()
|
||||
.flatMap(username -> saveOperationLog(username, method, path, ip, duration, "0", null, finalInfo));
|
||||
}))
|
||||
.onErrorResume(error -> {
|
||||
long duration = System.currentTimeMillis() - startTime;
|
||||
logger.error("请求处理失败: {} {}, 错误: {}", method, path, error.getMessage());
|
||||
return getCurrentUsername()
|
||||
.flatMap(username -> saveOperationLog(username, method, path, ip, duration, "1",
|
||||
error.getMessage().substring(0, Math.min(error.getMessage().length(), 500)), finalInfo))
|
||||
.then(Mono.error(error));
|
||||
});
|
||||
}
|
||||
|
||||
private OperationInfo findOperationInfo(String method, String path) {
|
||||
String key = method + ":" + path;
|
||||
if (OPERATION_MAPPING.containsKey(key)) {
|
||||
return OPERATION_MAPPING.get(key);
|
||||
}
|
||||
private boolean isWriteOperation(String method) {
|
||||
return HttpMethod.POST.name().equals(method) ||
|
||||
HttpMethod.PUT.name().equals(method) ||
|
||||
HttpMethod.DELETE.name().equals(method) ||
|
||||
HttpMethod.PATCH.name().equals(method);
|
||||
}
|
||||
|
||||
for (Map.Entry<String, OperationInfo> entry : OPERATION_MAPPING.entrySet()) {
|
||||
String mappingKey = entry.getKey();
|
||||
if (key.startsWith(mappingKey)) {
|
||||
private OperationInfo findPrefixMatch(String key) {
|
||||
for (Map.Entry<String, OperationInfo> entry : PREFIX_MAPPING.entrySet()) {
|
||||
if (key.startsWith(entry.getKey())) {
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据URL路径自动生成操作信息
|
||||
* 例如: DELETE:/api/groupCourse/types/5 → 团课管理 - 删除操作
|
||||
*/
|
||||
private OperationInfo buildAutoOperationInfo(String method, String path) {
|
||||
String module = extractModuleFromPath(path);
|
||||
String operation = methodToOperationName(method);
|
||||
return new OperationInfo(module, operation);
|
||||
}
|
||||
|
||||
private String extractModuleFromPath(String path) {
|
||||
// 去掉 /api/ 前缀,取第一段作为模块名
|
||||
if (path.startsWith("/api/")) {
|
||||
String subPath = path.substring(5); // remove "/api/"
|
||||
int slashIdx = subPath.indexOf('/');
|
||||
String moduleKey = slashIdx > 0 ? subPath.substring(0, slashIdx) : subPath;
|
||||
|
||||
// 尝试复合模块名 (如 member-cards)
|
||||
if (slashIdx > 0) {
|
||||
String rest = subPath.substring(slashIdx + 1);
|
||||
int nextSlash = rest.indexOf('/');
|
||||
String secondPart = nextSlash > 0 ? rest.substring(0, nextSlash) : rest;
|
||||
String compositeKey = moduleKey + "/" + secondPart;
|
||||
if (MODULE_NAMES.containsKey(compositeKey)) {
|
||||
return MODULE_NAMES.get(compositeKey);
|
||||
}
|
||||
}
|
||||
|
||||
return MODULE_NAMES.getOrDefault(moduleKey, moduleKey);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
private String methodToOperationName(String method) {
|
||||
switch (method.toUpperCase()) {
|
||||
case "POST": return "创建/新增操作";
|
||||
case "PUT": return "编辑/更新操作";
|
||||
case "DELETE": return "删除操作";
|
||||
case "PATCH": return "修改操作";
|
||||
default: return "操作";
|
||||
}
|
||||
}
|
||||
|
||||
private Mono<String> getCurrentUsername() {
|
||||
return ReactiveSecurityContextHolder.getContext()
|
||||
.map(ctx -> ctx.getAuthentication().getPrincipal())
|
||||
.map(principal -> principal instanceof String ? (String) principal : "system")
|
||||
.defaultIfEmpty("system")
|
||||
.onErrorReturn("system");
|
||||
}
|
||||
|
||||
private Mono<Void> saveOperationLog(String username, String method, String path, String ip,
|
||||
long duration, String status, String errorMsg, OperationInfo info) {
|
||||
OperationLog log = new OperationLog();
|
||||
log.setUsername(username);
|
||||
log.setOperation(info.module + " - " + info.operation);
|
||||
log.setMethod(method + " " + path);
|
||||
log.setIp(ip);
|
||||
log.setDuration(duration);
|
||||
log.setStatus(status);
|
||||
log.setErrorMsg(errorMsg);
|
||||
|
||||
return operationLogService.save(log)
|
||||
.doOnSuccess(saved -> logger.debug("操作日志保存成功: {} - {}", info.module, info.operation))
|
||||
.doOnError(e -> logger.error("操作日志保存失败: {}", e.getMessage(), e))
|
||||
.onErrorResume(e -> Mono.empty())
|
||||
.then();
|
||||
}
|
||||
|
||||
private static class OperationInfo {
|
||||
final String module;
|
||||
final String operation;
|
||||
|
||||
+20
@@ -11,6 +11,11 @@ import org.springframework.security.config.annotation.web.reactive.EnableWebFlux
|
||||
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.reactive.CorsConfigurationSource;
|
||||
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@Configuration
|
||||
@EnableWebFluxSecurity
|
||||
@@ -29,6 +34,20 @@ public class SecurityConfig {
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CorsConfigurationSource corsConfigurationSource() {
|
||||
CorsConfiguration configuration = new CorsConfiguration();
|
||||
configuration.setAllowedOriginPatterns(Arrays.asList("*"));
|
||||
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"));
|
||||
configuration.setAllowedHeaders(Arrays.asList("*"));
|
||||
configuration.setAllowCredentials(true);
|
||||
configuration.setMaxAge(3600L);
|
||||
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", configuration);
|
||||
return source;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
|
||||
String[] activeProfiles = environment.getActiveProfiles();
|
||||
@@ -41,6 +60,7 @@ public class SecurityConfig {
|
||||
activeProfiles.length > 0 ? String.join(",", activeProfiles) : "default", isDevOrTest);
|
||||
|
||||
http
|
||||
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
|
||||
.csrf(ServerHttpSecurity.CsrfSpec::disable)
|
||||
.httpBasic(ServerHttpSecurity.HttpBasicSpec::disable)
|
||||
.formLogin(ServerHttpSecurity.FormLoginSpec::disable)
|
||||
|
||||
+2
@@ -53,6 +53,8 @@ public interface ISysUserService {
|
||||
|
||||
Mono<SysUser> changePassword(Long userId, String oldPassword, String newPassword);
|
||||
|
||||
Mono<Boolean> verifyPassword(Long userId, String password);
|
||||
|
||||
Mono<Void> updateRoleIdToNullByRoleId(Long roleId);
|
||||
|
||||
Mono<Void> assignRolesToUser(Long userId, java.util.List<Long> roleIds);
|
||||
|
||||
+3
@@ -120,6 +120,9 @@ public class SysPermissionService implements ISysPermissionService {
|
||||
|
||||
@Override
|
||||
public Flux<SysPermission> findByRoleIds(List<Long> roleIds) {
|
||||
if (roleIds == null || roleIds.isEmpty()) {
|
||||
return Flux.empty();
|
||||
}
|
||||
return permissionRepository.findByRoleIds(roleIds);
|
||||
}
|
||||
|
||||
|
||||
-1
@@ -83,7 +83,6 @@ public class SysRoleService implements ISysRoleService {
|
||||
@Override
|
||||
public Mono<SysRole> createRole(CreateRoleCommand command) {
|
||||
SysRole role = new SysRole();
|
||||
role.generateId();
|
||||
role.setRoleName(command.roleName());
|
||||
role.setRoleKey(command.roleKey());
|
||||
role.setRoleSort(command.roleSort());
|
||||
|
||||
+7
-2
@@ -97,7 +97,6 @@ public class SysUserService implements ISysUserService {
|
||||
logger.info("SysUserService.createUser - 用户名: {}, 密码前缀: {}",
|
||||
user.getUsername(),
|
||||
user.getPassword() != null ? user.getPassword().substring(0, 7) : "null");
|
||||
user.generateId();
|
||||
if (user.getPassword() != null && !user.getPassword().startsWith("$2a$")
|
||||
&& !user.getPassword().startsWith("$2b$")) {
|
||||
logger.info("密码不以$2a$或$2b$开头,重新编码");
|
||||
@@ -117,7 +116,6 @@ public class SysUserService implements ISysUserService {
|
||||
@Override
|
||||
public Mono<SysUser> createUser(CreateUserCommand command) {
|
||||
SysUser user = new SysUser();
|
||||
user.generateId();
|
||||
user.setUsername(command.username().getValue());
|
||||
user.setPassword(passwordEncoder.encode(command.password().getValue()));
|
||||
user.setEmail(command.email().getValue());
|
||||
@@ -204,6 +202,13 @@ public class SysUserService implements ISysUserService {
|
||||
return userRepository.updateRoleIdToNullByRoleId(roleId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> verifyPassword(Long userId, String password) {
|
||||
return userRepository.findById(userId)
|
||||
.map(user -> passwordEncoder.matches(password, user.getPassword()))
|
||||
.defaultIfEmpty(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<SysUser> changePassword(Long userId, String oldPassword, String newPassword) {
|
||||
return userRepository.findById(userId)
|
||||
|
||||
+27
-1
@@ -2,6 +2,8 @@ package cn.novalon.gym.manage.sys.dto.response;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 认证响应DTO
|
||||
*
|
||||
@@ -20,13 +22,21 @@ public class AuthResponse {
|
||||
@Schema(description = "用户名", example = "admin")
|
||||
private String username;
|
||||
|
||||
@Schema(description = "角色标识列表", example = "[\"admin\"]")
|
||||
private List<String> roles;
|
||||
|
||||
@Schema(description = "权限码列表", example = "[\"system:user:view\", \"system:user:create\"]")
|
||||
private List<String> permissions;
|
||||
|
||||
public AuthResponse() {
|
||||
}
|
||||
|
||||
public AuthResponse(String token, Long userId, String username) {
|
||||
public AuthResponse(String token, Long userId, String username, List<String> roles, List<String> permissions) {
|
||||
this.token = token;
|
||||
this.userId = userId;
|
||||
this.username = username;
|
||||
this.roles = roles;
|
||||
this.permissions = permissions;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
@@ -52,4 +62,20 @@ public class AuthResponse {
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public List<String> getRoles() {
|
||||
return roles;
|
||||
}
|
||||
|
||||
public void setRoles(List<String> roles) {
|
||||
this.roles = roles;
|
||||
}
|
||||
|
||||
public List<String> getPermissions() {
|
||||
return permissions;
|
||||
}
|
||||
|
||||
public void setPermissions(List<String> permissions) {
|
||||
this.permissions = permissions;
|
||||
}
|
||||
}
|
||||
|
||||
+88
-19
@@ -8,6 +8,7 @@ import cn.novalon.gym.manage.sys.core.domain.SysUser;
|
||||
import cn.novalon.gym.manage.sys.core.domain.SysLoginLog;
|
||||
import cn.novalon.gym.manage.sys.core.service.ISysUserService;
|
||||
import cn.novalon.gym.manage.sys.core.service.ISysLoginLogService;
|
||||
import cn.novalon.gym.manage.sys.core.service.ISysPermissionService;
|
||||
import cn.novalon.gym.manage.sys.util.UserAgentParser;
|
||||
import cn.novalon.gym.manage.sys.util.IpLocationParser;
|
||||
import cn.novalon.gym.manage.common.util.StatusConstants;
|
||||
@@ -28,6 +29,7 @@ import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -50,6 +52,7 @@ public class SysAuthHandler {
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
private final JwtTokenProvider jwtTokenProvider;
|
||||
private final ISysLoginLogService loginLogService;
|
||||
private final ISysPermissionService permissionService;
|
||||
private final UserAgentParser userAgentParser;
|
||||
private final IpLocationParser ipLocationParser;
|
||||
|
||||
@@ -60,11 +63,13 @@ public class SysAuthHandler {
|
||||
public SysAuthHandler(ISysUserService userService,
|
||||
@Qualifier("passwordEncoder") PasswordEncoder passwordEncoder,
|
||||
JwtTokenProvider jwtTokenProvider, ISysLoginLogService loginLogService,
|
||||
ISysPermissionService permissionService,
|
||||
UserAgentParser userAgentParser, IpLocationParser ipLocationParser) {
|
||||
this.userService = userService;
|
||||
this.passwordEncoder = passwordEncoder;
|
||||
this.jwtTokenProvider = jwtTokenProvider;
|
||||
this.loginLogService = loginLogService;
|
||||
this.permissionService = permissionService;
|
||||
this.userAgentParser = userAgentParser;
|
||||
this.ipLocationParser = ipLocationParser;
|
||||
|
||||
@@ -126,29 +131,49 @@ public class SysAuthHandler {
|
||||
}
|
||||
|
||||
return userService.getUserRoles(user.getId())
|
||||
.map(role -> role.getRoleKey())
|
||||
.collectList()
|
||||
.flatMap(roleKeys -> {
|
||||
String token = jwtTokenProvider
|
||||
.generateToken(
|
||||
.flatMap(roles -> {
|
||||
List<String> roleKeys = roles.stream()
|
||||
.map(r -> r.getRoleKey())
|
||||
.collect(Collectors.toList());
|
||||
List<Long> roleIds = roles.stream()
|
||||
.map(r -> r.getId())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Mono<List<String>> permCodesMono;
|
||||
if (roleIds.isEmpty()) {
|
||||
permCodesMono = Mono.just(java.util.Collections.<String>emptyList());
|
||||
} else {
|
||||
permCodesMono = permissionService.findByRoleIds(roleIds)
|
||||
.map(p -> p.getPermissionCode())
|
||||
.collectList();
|
||||
}
|
||||
|
||||
return permCodesMono
|
||||
.flatMap(permCodes -> {
|
||||
String token = jwtTokenProvider
|
||||
.generateToken(
|
||||
user.getUsername(),
|
||||
user.getId(),
|
||||
roleKeys);
|
||||
logger.info("用户登录成功: username={}, userId={}, roles={}",
|
||||
user.getUsername(),
|
||||
user.getId(),
|
||||
roleKeys);
|
||||
recordLoginLog(loginRequest
|
||||
.getUsername(),
|
||||
clientIp,
|
||||
"0", "登录成功",
|
||||
userAgent);
|
||||
AuthResponse response = new AuthResponse(
|
||||
token,
|
||||
user.getId(),
|
||||
user.getUsername());
|
||||
return ServerResponse.ok()
|
||||
.bodyValue(response);
|
||||
logger.info("用户登录成功: username={}, userId={}, roles={}, permissions={}",
|
||||
user.getUsername(),
|
||||
user.getId(),
|
||||
roleKeys,
|
||||
permCodes.size());
|
||||
recordLoginLog(loginRequest.getUsername(),
|
||||
clientIp,
|
||||
"0", "登录成功",
|
||||
userAgent);
|
||||
AuthResponse response = new AuthResponse(
|
||||
token,
|
||||
user.getId(),
|
||||
user.getUsername(),
|
||||
roleKeys,
|
||||
permCodes);
|
||||
return ServerResponse.ok()
|
||||
.bodyValue(response);
|
||||
});
|
||||
});
|
||||
})
|
||||
.switchIfEmpty(Mono.defer(() -> {
|
||||
@@ -190,6 +215,50 @@ public class SysAuthHandler {
|
||||
});
|
||||
}
|
||||
|
||||
@Operation(summary = "获取当前用户信息", description = "根据Token获取当前登录用户的详细信息和权限")
|
||||
public Mono<ServerResponse> me(ServerRequest request) {
|
||||
String authHeader = request.headers().firstHeader("Authorization");
|
||||
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
|
||||
return ServerResponse.status(HttpStatus.UNAUTHORIZED).build();
|
||||
}
|
||||
String token = authHeader.substring(7);
|
||||
if (!jwtTokenProvider.validateToken(token)) {
|
||||
return ServerResponse.status(HttpStatus.UNAUTHORIZED).build();
|
||||
}
|
||||
Long userId = jwtTokenProvider.getUserIdFromToken(token);
|
||||
return userService.findById(userId)
|
||||
.flatMap(user -> userService.getUserRoles(user.getId())
|
||||
.collectList()
|
||||
.flatMap(roles -> {
|
||||
List<String> roleKeys = roles.stream()
|
||||
.map(r -> r.getRoleKey())
|
||||
.collect(Collectors.toList());
|
||||
List<Long> roleIds = roles.stream()
|
||||
.map(r -> r.getId())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Mono<List<String>> permCodesMono;
|
||||
if (roleIds.isEmpty()) {
|
||||
permCodesMono = Mono.just(java.util.Collections.<String>emptyList());
|
||||
} else {
|
||||
permCodesMono = permissionService.findByRoleIds(roleIds)
|
||||
.map(p -> p.getPermissionCode())
|
||||
.collectList();
|
||||
}
|
||||
|
||||
return permCodesMono
|
||||
.flatMap(permCodes -> {
|
||||
AuthResponse response = new AuthResponse(
|
||||
token,
|
||||
user.getId(),
|
||||
user.getUsername(),
|
||||
roleKeys,
|
||||
permCodes);
|
||||
return ServerResponse.ok().bodyValue(response);
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
private void recordLoginLog(String username, String ip, String status, String message, String userAgent) {
|
||||
try {
|
||||
SysLoginLog loginLog = new SysLoginLog();
|
||||
|
||||
+34
-6
@@ -1,7 +1,11 @@
|
||||
package cn.novalon.gym.manage.sys.handler.permission;
|
||||
|
||||
import cn.novalon.gym.manage.sys.core.domain.SysPermission;
|
||||
import cn.novalon.gym.manage.sys.core.domain.SysRole;
|
||||
import cn.novalon.gym.manage.sys.core.service.ISysPermissionService;
|
||||
import cn.novalon.gym.manage.sys.core.service.ISysRoleService;
|
||||
import cn.novalon.gym.manage.sys.core.service.ISysUserService;
|
||||
import cn.novalon.gym.manage.sys.util.AuthUtil;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.http.HttpStatus;
|
||||
@@ -22,10 +26,19 @@ import java.util.List;
|
||||
@Tag(name = "权限管理", description = "权限相关操作")
|
||||
public class SysPermissionHandler {
|
||||
|
||||
private final ISysPermissionService permissionService;
|
||||
private static final Long BUILTIN_ROLE_ID = 1L;
|
||||
|
||||
public SysPermissionHandler(ISysPermissionService permissionService) {
|
||||
private final ISysPermissionService permissionService;
|
||||
private final ISysRoleService roleService;
|
||||
private final ISysUserService userService;
|
||||
private final AuthUtil authUtil;
|
||||
|
||||
public SysPermissionHandler(ISysPermissionService permissionService, ISysRoleService roleService,
|
||||
ISysUserService userService, AuthUtil authUtil) {
|
||||
this.permissionService = permissionService;
|
||||
this.roleService = roleService;
|
||||
this.userService = userService;
|
||||
this.authUtil = authUtil;
|
||||
}
|
||||
|
||||
@Operation(summary = "获取所有权限", description = "获取系统中所有权限列表")
|
||||
@@ -97,12 +110,27 @@ public class SysPermissionHandler {
|
||||
.body(permissionService.getPermissionsByRoleId(roleId), SysPermission.class);
|
||||
}
|
||||
|
||||
@Operation(summary = "为角色分配权限", description = "为指定角色分配权限列表")
|
||||
@Operation(summary = "为角色分配权限", description = "为指定角色分配权限列表,需验证管理员密码,超级管理员角色不可被分配")
|
||||
public Mono<ServerResponse> assignPermissionsToRole(ServerRequest request) {
|
||||
Long roleId = Long.valueOf(request.pathVariable("id"));
|
||||
return request.bodyToMono(AssignPermissionsRequest.class)
|
||||
.flatMap(req -> permissionService.assignPermissionsToRole(roleId, req.permissionIds()))
|
||||
.then(ServerResponse.ok().build());
|
||||
|
||||
return verifyAdminPassword(request)
|
||||
.flatMap(valid -> {
|
||||
if (!valid) return ServerResponse.badRequest().bodyValue("管理员密码不能为空或错误");
|
||||
if (BUILTIN_ROLE_ID.equals(roleId)) return ServerResponse.badRequest().bodyValue("超级管理员角色权限不可被修改");
|
||||
return request.bodyToMono(AssignPermissionsRequest.class)
|
||||
.flatMap(req -> permissionService.assignPermissionsToRole(roleId, req.permissionIds()))
|
||||
.then(ServerResponse.ok().build());
|
||||
});
|
||||
}
|
||||
|
||||
private Mono<Boolean> verifyAdminPassword(ServerRequest request) {
|
||||
String adminPassword = request.queryParam("adminPassword").orElse(null);
|
||||
if (adminPassword == null || adminPassword.isBlank()) {
|
||||
return Mono.just(false);
|
||||
}
|
||||
Long adminId = authUtil.getMemberIdOrThrow(request);
|
||||
return userService.verifyPassword(adminId, adminPassword);
|
||||
}
|
||||
|
||||
private record AssignPermissionsRequest(List<Long> permissionIds) {}
|
||||
|
||||
+43
-17
@@ -2,6 +2,8 @@ package cn.novalon.gym.manage.sys.handler.role;
|
||||
|
||||
import cn.novalon.gym.manage.sys.core.domain.SysRole;
|
||||
import cn.novalon.gym.manage.sys.core.service.ISysRoleService;
|
||||
import cn.novalon.gym.manage.sys.core.service.ISysUserService;
|
||||
import cn.novalon.gym.manage.sys.util.AuthUtil;
|
||||
import cn.novalon.gym.manage.common.dto.PageRequest;
|
||||
import cn.novalon.gym.manage.sys.dto.request.RoleCreateRequest;
|
||||
import cn.novalon.gym.manage.sys.dto.request.RoleUpdateRequest;
|
||||
@@ -30,12 +32,18 @@ import java.util.Map;
|
||||
@Tag(name = "角色管理", description = "角色相关操作")
|
||||
public class SysRoleHandler {
|
||||
|
||||
private static final Long BUILTIN_ROLE_ID = 1L;
|
||||
|
||||
private final ISysRoleService roleService;
|
||||
private final Validator validator;
|
||||
private final AuthUtil authUtil;
|
||||
private final ISysUserService userService;
|
||||
|
||||
public SysRoleHandler(ISysRoleService roleService, Validator validator) {
|
||||
public SysRoleHandler(ISysRoleService roleService, Validator validator, AuthUtil authUtil, ISysUserService userService) {
|
||||
this.roleService = roleService;
|
||||
this.validator = validator;
|
||||
this.authUtil = authUtil;
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
@Operation(summary = "获取所有角色", description = "获取系统中所有角色列表")
|
||||
@@ -115,30 +123,39 @@ public class SysRoleHandler {
|
||||
});
|
||||
}
|
||||
|
||||
@Operation(summary = "更新角色", description = "更新角色信息")
|
||||
@Operation(summary = "更新角色", description = "更新角色信息,需验证管理员密码,超级管理员角色不可被编辑")
|
||||
@OperationLog(operation = "更新角色", module = "角色管理")
|
||||
public Mono<ServerResponse> updateRole(ServerRequest request) {
|
||||
Long id = Long.valueOf(request.pathVariable("id"));
|
||||
return request.bodyToMono(RoleUpdateRequest.class)
|
||||
.map(req -> UpdateRoleCommand.of(
|
||||
id,
|
||||
req.getRoleName(),
|
||||
req.getRoleKey(),
|
||||
req.getRoleSort(),
|
||||
req.getStatus()
|
||||
))
|
||||
.flatMap(roleService::updateRole)
|
||||
.flatMap(updatedRole -> ServerResponse.ok().bodyValue(updatedRole))
|
||||
.switchIfEmpty(ServerResponse.notFound().build());
|
||||
|
||||
return verifyAdminPassword(request)
|
||||
.flatMap(valid -> {
|
||||
if (!valid) return ServerResponse.badRequest().bodyValue("管理员密码不能为空或错误");
|
||||
if (BUILTIN_ROLE_ID.equals(id)) return ServerResponse.badRequest().bodyValue("超级管理员角色不可编辑");
|
||||
return request.bodyToMono(RoleUpdateRequest.class)
|
||||
.map(req -> UpdateRoleCommand.of(
|
||||
id, req.getRoleName(), req.getRoleKey(),
|
||||
req.getRoleSort(), req.getStatus()
|
||||
))
|
||||
.flatMap(roleService::updateRole)
|
||||
.flatMap(updatedRole -> ServerResponse.ok().bodyValue(updatedRole))
|
||||
.switchIfEmpty(ServerResponse.notFound().build());
|
||||
});
|
||||
}
|
||||
|
||||
@Operation(summary = "删除角色", description = "逻辑删除角色")
|
||||
@Operation(summary = "删除角色", description = "逻辑删除角色,需验证管理员密码,超级管理员角色不可被删除")
|
||||
@OperationLog(operation = "删除角色", module = "角色管理")
|
||||
public Mono<ServerResponse> deleteRole(ServerRequest request) {
|
||||
Long id = Long.valueOf(request.pathVariable("id"));
|
||||
return roleService.logicalDeleteRole(id)
|
||||
.flatMap(role -> ServerResponse.ok().bodyValue(role))
|
||||
.switchIfEmpty(ServerResponse.notFound().build());
|
||||
|
||||
return verifyAdminPassword(request)
|
||||
.flatMap(valid -> {
|
||||
if (!valid) return ServerResponse.badRequest().bodyValue("管理员密码不能为空或错误");
|
||||
if (BUILTIN_ROLE_ID.equals(id)) return ServerResponse.badRequest().bodyValue("超级管理员角色不可删除");
|
||||
return roleService.logicalDeleteRole(id)
|
||||
.flatMap(role -> ServerResponse.ok().bodyValue(role))
|
||||
.switchIfEmpty(ServerResponse.notFound().build());
|
||||
});
|
||||
}
|
||||
|
||||
@Operation(summary = "恢复角色", description = "恢复被逻辑删除的角色")
|
||||
@@ -148,4 +165,13 @@ public class SysRoleHandler {
|
||||
.flatMap(role -> ServerResponse.ok().bodyValue(role))
|
||||
.switchIfEmpty(ServerResponse.notFound().build());
|
||||
}
|
||||
|
||||
private Mono<Boolean> verifyAdminPassword(ServerRequest request) {
|
||||
String adminPassword = request.queryParam("adminPassword").orElse(null);
|
||||
if (adminPassword == null || adminPassword.isBlank()) {
|
||||
return Mono.just(false);
|
||||
}
|
||||
Long adminId = authUtil.getMemberIdOrThrow(request);
|
||||
return userService.verifyPassword(adminId, adminPassword);
|
||||
}
|
||||
}
|
||||
|
||||
+92
-32
@@ -2,6 +2,7 @@ package cn.novalon.gym.manage.sys.handler.user;
|
||||
|
||||
import cn.novalon.gym.manage.sys.core.domain.SysUser;
|
||||
import cn.novalon.gym.manage.sys.core.service.ISysUserService;
|
||||
import cn.novalon.gym.manage.sys.util.AuthUtil;
|
||||
import cn.novalon.gym.manage.common.dto.PageRequest;
|
||||
import cn.novalon.gym.manage.sys.dto.request.AssignRolesRequest;
|
||||
import cn.novalon.gym.manage.sys.dto.request.PasswordChangeRequest;
|
||||
@@ -42,10 +43,12 @@ public class SysUserHandler {
|
||||
private static final Logger logger = LoggerFactory.getLogger(SysUserHandler.class);
|
||||
private final ISysUserService userService;
|
||||
private final Validator validator;
|
||||
private final AuthUtil authUtil;
|
||||
|
||||
public SysUserHandler(ISysUserService userService, Validator validator) {
|
||||
public SysUserHandler(ISysUserService userService, Validator validator, AuthUtil authUtil) {
|
||||
this.userService = userService;
|
||||
this.validator = validator;
|
||||
this.authUtil = authUtil;
|
||||
}
|
||||
|
||||
@Operation(summary = "获取所有用户", description = "获取系统中所有用户列表")
|
||||
@@ -152,39 +155,63 @@ public class SysUserHandler {
|
||||
});
|
||||
}
|
||||
|
||||
@Operation(summary = "更新用户", description = "更新用户信息")
|
||||
@Operation(summary = "更新用户", description = "更新用户信息,需验证管理员密码,超级管理员不可编辑")
|
||||
@OperationLog(operation = "更新用户", module = "用户管理")
|
||||
public Mono<ServerResponse> updateUser(ServerRequest request) {
|
||||
Long id = Long.valueOf(request.pathVariable("id"));
|
||||
return request.bodyToMono(UserUpdateRequest.class)
|
||||
.map(req -> {
|
||||
boolean clearRole = Boolean.TRUE.equals(req.getClearRole()) ||
|
||||
(req.getRoleId() == null && req.getClearRole() != null);
|
||||
return UpdateUserCommand.of(
|
||||
id,
|
||||
null,
|
||||
null,
|
||||
req.getEmail(),
|
||||
req.getRoleId(),
|
||||
req.getStatus(),
|
||||
clearRole
|
||||
);
|
||||
})
|
||||
.flatMap(userService::updateUser)
|
||||
.flatMap(user -> ServerResponse.ok().bodyValue(user))
|
||||
.switchIfEmpty(ServerResponse.notFound().build());
|
||||
|
||||
return verifyAdminPassword(request)
|
||||
.flatMap(valid -> {
|
||||
if (!valid) {
|
||||
return ServerResponse.badRequest().bodyValue("管理员密码不能为空或错误");
|
||||
}
|
||||
return userService.findById(id)
|
||||
.flatMap(user -> {
|
||||
if ("admin".equals(user.getUsername())) {
|
||||
return Mono.<ServerResponse>error(new RuntimeException("超级管理员不可编辑"));
|
||||
}
|
||||
return request.bodyToMono(UserUpdateRequest.class)
|
||||
.map(req -> {
|
||||
boolean clearRole = Boolean.TRUE.equals(req.getClearRole()) ||
|
||||
(req.getRoleId() == null && req.getClearRole() != null);
|
||||
return UpdateUserCommand.of(
|
||||
id, null, null, req.getEmail(),
|
||||
req.getRoleId(), req.getStatus(), clearRole
|
||||
);
|
||||
})
|
||||
.flatMap(userService::updateUser)
|
||||
.flatMap(updated -> ServerResponse.ok().bodyValue(updated));
|
||||
})
|
||||
.switchIfEmpty(ServerResponse.notFound().build());
|
||||
});
|
||||
}
|
||||
|
||||
@Operation(summary = "删除用户", description = "物理删除用户")
|
||||
@Operation(summary = "删除用户", description = "物理删除用户,需验证管理员密码,超级管理员不可删除")
|
||||
@OperationLog(operation = "删除用户", module = "用户管理")
|
||||
public Mono<ServerResponse> deleteUser(ServerRequest request) {
|
||||
Long id = Long.valueOf(request.pathVariable("id"));
|
||||
return userService.findById(id)
|
||||
.flatMap(user -> userService.deleteUser(id)
|
||||
.then(ServerResponse.noContent().build()))
|
||||
.switchIfEmpty(Mono.error(new RuntimeException("User not found")))
|
||||
|
||||
return verifyAdminPassword(request)
|
||||
.flatMap(valid -> {
|
||||
if (!valid) {
|
||||
return ServerResponse.badRequest().bodyValue("管理员密码不能为空或错误");
|
||||
}
|
||||
return userService.findById(id)
|
||||
.flatMap(user -> {
|
||||
if ("admin".equals(user.getUsername())) {
|
||||
return Mono.<ServerResponse>error(new RuntimeException("超级管理员不可删除"));
|
||||
}
|
||||
return userService.deleteUser(id)
|
||||
.then(ServerResponse.noContent().build());
|
||||
})
|
||||
.switchIfEmpty(Mono.error(new RuntimeException("User not found")));
|
||||
})
|
||||
.onErrorResume(RuntimeException.class, ex -> {
|
||||
if (ex.getMessage().contains("not found")) {
|
||||
String msg = ex.getMessage();
|
||||
if ("超级管理员不可删除".equals(msg) || "超级管理员不可编辑".equals(msg)) {
|
||||
return ServerResponse.badRequest().bodyValue(msg);
|
||||
}
|
||||
if ("User not found".equals(msg)) {
|
||||
return ServerResponse.notFound().build();
|
||||
}
|
||||
return Mono.error(ex);
|
||||
@@ -258,16 +285,37 @@ public class SysUserHandler {
|
||||
.flatMap(exists -> ServerResponse.ok().bodyValue(exists));
|
||||
}
|
||||
|
||||
@Operation(summary = "为用户分配角色", description = "为指定用户分配角色列表")
|
||||
@Operation(summary = "为用户分配角色", description = "为指定用户分配角色列表,需验证管理员密码,超级管理员不可分配")
|
||||
@OperationLog(operation = "分配角色", module = "用户管理")
|
||||
public Mono<ServerResponse> assignRoles(ServerRequest request) {
|
||||
Long id = Long.valueOf(request.pathVariable("id"));
|
||||
return request.bodyToMono(AssignRolesRequest.class)
|
||||
.flatMap(req -> userService.assignRolesToUser(id, req.getRoleIdsAsLong()))
|
||||
.then(ServerResponse.ok().build())
|
||||
.onErrorResume(error -> {
|
||||
logger.error("分配角色失败", error);
|
||||
return ServerResponse.status(500).bodyValue("分配角色失败: " + error.getMessage());
|
||||
|
||||
return verifyAdminPassword(request)
|
||||
.flatMap(valid -> {
|
||||
if (!valid) {
|
||||
return ServerResponse.badRequest().bodyValue("管理员密码不能为空或错误");
|
||||
}
|
||||
return userService.findById(id)
|
||||
.flatMap(user -> {
|
||||
if ("admin".equals(user.getUsername())) {
|
||||
return Mono.<ServerResponse>error(new RuntimeException("超级管理员不可被分配角色"));
|
||||
}
|
||||
return request.bodyToMono(AssignRolesRequest.class)
|
||||
.flatMap(req -> userService.assignRolesToUser(id, req.getRoleIdsAsLong()))
|
||||
.then(ServerResponse.ok().build());
|
||||
})
|
||||
.switchIfEmpty(Mono.error(new RuntimeException("User not found")));
|
||||
})
|
||||
.onErrorResume(RuntimeException.class, ex -> {
|
||||
String msg = ex.getMessage();
|
||||
if ("超级管理员不可被分配角色".equals(msg)) {
|
||||
return ServerResponse.badRequest().bodyValue(msg);
|
||||
}
|
||||
if ("User not found".equals(msg)) {
|
||||
return ServerResponse.notFound().build();
|
||||
}
|
||||
logger.error("分配角色失败", ex);
|
||||
return ServerResponse.status(500).bodyValue("分配角色失败: " + msg);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -277,4 +325,16 @@ public class SysUserHandler {
|
||||
return ServerResponse.ok()
|
||||
.body(userService.getUserRoles(id), cn.novalon.gym.manage.sys.core.domain.SysRole.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证管理员密码
|
||||
*/
|
||||
private Mono<Boolean> verifyAdminPassword(ServerRequest request) {
|
||||
String adminPassword = request.queryParam("adminPassword").orElse(null);
|
||||
if (adminPassword == null || adminPassword.isBlank()) {
|
||||
return Mono.just(false);
|
||||
}
|
||||
Long adminId = authUtil.getMemberIdOrThrow(request);
|
||||
return userService.verifyPassword(adminId, adminPassword);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user