feat: 添加管理员权限检查功能和Maven打包配置优化
主要更新: 1. 新增管理员权限检查功能 - 添加 UserRoleEnum 枚举类统一管理用户角色(USER, ADMIN, BAN) - 改进 ContextHolderUtils.isAdmin() 方法,支持不区分大小写的角色比较 - 更新 UserServiceImpl 使用枚举常量代替硬编码字符串 - 新增管理员权限使用指南文档 (docs/admin-permission-guide.md) 2. 修复Maven打包配置 - 配置根POM的spring-boot-maven-plugin默认跳过repackage - 为所有服务模块启用repackage,确保可以打包为可执行JAR - 修复公共库模块打包失败的问题 - 涉及服务:gateway, auth, user-service, question-service, file-service, blog-service, upms-biz, ai-service 3. 更新项目文档 - README.md:添加详细的打包说明、首次克隆准备工作、服务启动顺序等 - CLAUDE.md:更新项目架构说明和开发指南 4. 重构题目服务责任链结构 - 将责任链类按功能分类到 question/ 和 submit/ 子目录 - 新增 QuestionSubmitJudgeInfoEnum 和相关查询功能 - 改进题目提交服务的实现 5. 其他改进 - 添加 Feign Token 中继拦截器 - 更新 AsyncConfig 配置 - 优化 Jackson 和 Security 配置
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
package cn.meowrain.aioj.backend.framework.core.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 用户角色枚举
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum UserRoleEnum {
|
||||
|
||||
/**
|
||||
* 普通用户
|
||||
*/
|
||||
USER("user", "普通用户"),
|
||||
|
||||
/**
|
||||
* 管理员
|
||||
*/
|
||||
ADMIN("admin", "管理员"),
|
||||
|
||||
/**
|
||||
* 封禁用户
|
||||
*/
|
||||
BAN("ban", "封禁用户");
|
||||
|
||||
/**
|
||||
* 角色代码(数据库存储值)
|
||||
*/
|
||||
private final String code;
|
||||
|
||||
/**
|
||||
* 角色描述
|
||||
*/
|
||||
private final String description;
|
||||
|
||||
/**
|
||||
* 根据角色代码获取枚举
|
||||
* @param code 角色代码
|
||||
* @return 角色枚举,未找到返回 null
|
||||
*/
|
||||
public static UserRoleEnum fromCode(String code) {
|
||||
if (code == null) {
|
||||
return null;
|
||||
}
|
||||
for (UserRoleEnum role : values()) {
|
||||
if (role.code.equalsIgnoreCase(code)) {
|
||||
return role;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为管理员角色
|
||||
* @param code 角色代码
|
||||
* @return true-是管理员, false-不是管理员
|
||||
*/
|
||||
public static boolean isAdmin(String code) {
|
||||
return ADMIN.code.equalsIgnoreCase(code);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -25,7 +25,10 @@ public class JacksonConfiguration {
|
||||
return builder -> {
|
||||
// 注册 JavaTimeModule 处理时间类型
|
||||
builder.modules(new JavaTimeModule());
|
||||
|
||||
|
||||
// 输出格式化 JSON,便于调试与阅读
|
||||
builder.indentOutput(true);
|
||||
|
||||
// Long 和 long 类型序列化为 String,避免前端精度丢失
|
||||
builder.serializerByType(Long.class, ToStringSerializer.instance);
|
||||
builder.serializerByType(Long.TYPE, ToStringSerializer.instance);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package cn.meowrain.aioj.backend.framework.core.utils;
|
||||
|
||||
import cn.meowrain.aioj.backend.framework.core.enums.UserRoleEnum;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.core.Authentication;
|
||||
@@ -81,7 +82,13 @@ public class ContextHolderUtils {
|
||||
* @return true-是管理员, false-不是管理员
|
||||
*/
|
||||
public static boolean isAdmin() {
|
||||
return "ADMIN".equals(getCurrentUserRole());
|
||||
try {
|
||||
String role = getCurrentUserRole();
|
||||
return UserRoleEnum.isAdmin(role);
|
||||
}
|
||||
catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
package cn.meowrain.aioj.backend.framework.feign;
|
||||
|
||||
import cn.meowrain.aioj.backend.framework.feign.interceptor.TokenRelayRequestInterceptor;
|
||||
import feign.RequestInterceptor;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
@AutoConfiguration
|
||||
public class FeignAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public RequestInterceptor tokenRelayRequestInterceptor() {
|
||||
return new TokenRelayRequestInterceptor();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package cn.meowrain.aioj.backend.framework.feign.interceptor;
|
||||
|
||||
import cn.meowrain.aioj.backend.framework.feign.annotation.NoToken;
|
||||
import feign.RequestInterceptor;
|
||||
import feign.RequestTemplate;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Feign 调用时透传 Authorization 头
|
||||
*/
|
||||
public class TokenRelayRequestInterceptor implements RequestInterceptor {
|
||||
|
||||
private static final String HEADER_NAME = "Authorization";
|
||||
private static final String TOKEN_PREFIX = "Bearer ";
|
||||
|
||||
@Override
|
||||
public void apply(RequestTemplate template) {
|
||||
if (isNoToken(template) || template.headers().containsKey(HEADER_NAME)) {
|
||||
return;
|
||||
}
|
||||
|
||||
String authorization = resolveAuthorization();
|
||||
if (StringUtils.hasText(authorization)) {
|
||||
template.header(HEADER_NAME, authorization);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isNoToken(RequestTemplate template) {
|
||||
if (template.methodMetadata() == null) {
|
||||
return false;
|
||||
}
|
||||
Method method = template.methodMetadata().method();
|
||||
return method != null && (method.isAnnotationPresent(NoToken.class)
|
||||
|| method.getDeclaringClass().isAnnotationPresent(NoToken.class));
|
||||
}
|
||||
|
||||
private String resolveAuthorization() {
|
||||
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
if (attributes != null) {
|
||||
HttpServletRequest request = attributes.getRequest();
|
||||
return request.getHeader(HEADER_NAME);
|
||||
}
|
||||
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (authentication != null && authentication.getDetails() instanceof String token
|
||||
&& StringUtils.hasText(token)) {
|
||||
return token.startsWith(TOKEN_PREFIX) ? token : TOKEN_PREFIX + token;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -43,6 +43,9 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
if (StringUtils.hasText(token) && jwtUtil.isTokenValid(token)) {
|
||||
Claims claims = jwtUtil.parseClaims(token);
|
||||
Authentication authentication = createAuthentication(claims);
|
||||
if (authentication instanceof UsernamePasswordAuthenticationToken authToken) {
|
||||
authToken.setDetails(token);
|
||||
}
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
|
||||
log.debug("JWT Authentication successful for user: {}", claims.getSubject());
|
||||
|
||||
Reference in New Issue
Block a user