diff --git a/.claude/settings.local.json b/.claude/settings.local.json deleted file mode 100644 index 18b6da3..0000000 --- a/.claude/settings.local.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(cat:*)", - "Bash(find ./aioj-backend-common -path \"*common-log*pom.xml\" -exec cat {} ;)" - ], - "deny": [], - "ask": [] - } -} diff --git a/.idea/CoolRequestCommonStatePersistent.xml b/.idea/CoolRequestCommonStatePersistent.xml index 9253e64..591a8f6 100644 --- a/.idea/CoolRequestCommonStatePersistent.xml +++ b/.idea/CoolRequestCommonStatePersistent.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/.idea/copilot.data.migration.agent.xml b/.idea/copilot.data.migration.agent.xml new file mode 100644 index 0000000..4ea72a9 --- /dev/null +++ b/.idea/copilot.data.migration.agent.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index 3391e6b..bec41f0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,83 +4,193 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Project Overview -This is an AI-integrated OJ (Online Judge) judging system with a microservices architecture. The system allows users to submit code for programming problems, which is then judged automatically. It also includes AI features to assist with problem-solving and code evaluation. +This is a microservices-based Online Judge (OJ) system with AI integration called AIOJ (AI Online Judge). It's built with Spring Boot 3.5.7 and Spring Cloud 2025.0.0, following a modular Maven multi-module architecture. -## Architecture - -The project follows a microservices architecture pattern built with Spring Boot and Spring Cloud Alibaba: - -### Core Modules: - -1. **aioj-backend-common**: Common utilities and dependencies shared across all modules. -2. **aioj-backend-gateway**: API gateway that routes requests to the appropriate microservices. -3. **aioj-backend-judge-service**: Handles code submission, compilation, and judging processes. -4. **aioj-backend-user-service**: Manages user accounts, authentication, and authorization. -5. **aioj-backend-question-service**: Manages programming problems and test cases. -6. **aioj-backend-ai-service**: Provides AI-assisted features such as code analysis and problem-solving. -7. **aioj-backend-auth**: Manages authentication and token issuance. -8. **aioj-backend-upms**: User Management System for administrative operations. - -## Database - -The system uses SQL databases. Database scripts can be found in the `db/` directory. - -## Build System - -The project uses Maven for build and dependency management. - -### Common Commands: - -- Compile the entire project: - ```bash - mvn clean compile - ``` - -- Compile a specific module: - ```bash - mvn clean compile -pl - ``` - -- Package the entire project: - ```bash - mvn clean package - ``` - -- Package a specific module: - ```bash - mvn clean package -pl - ``` - -## Development - -### Environment Profiles - -The project supports different environments (dev, test, prod) with corresponding configuration files: - -- Development: application-dev.yml -- Test: application-test.yml -- Production: application-prod.yml - -### Running a Service - -To run a specific microservice, use the Spring Boot Maven plugin: +## Common Development Commands +### Building the Project ```bash -cd +# Build entire project +mvn clean install + +# Build with specific environment profile +mvn clean install -P dev # Development (default) +mvn clean install -P test # Testing +mvn clean install -P prod # Production + +# Format code according to Spring standards +mvn spring-javaformat:apply + +# Build Docker images using Jib +mvn clean package jib:build + +# Or use Maven wrapper +./mvnw clean install +``` + +### Running Services +Each service runs on different ports: +- Gateway: 8085 +- Other services: configured via Nacos + +Run individual services from their respective directories: +```bash +cd aioj-backend-gateway mvn spring-boot:run + +# Or with specific profile +mvn spring-boot:run -Dspring.profiles.active=dev ``` -Or run the built JAR file: - +### Database Setup +1. Create databases using the provided script: ```bash -cd /target -java -jar -.jar +mysql -u root -p < db/create_db.sql +``` +This creates three databases: `aioj_dev`, `aioj_test`, and `aioj_prod` + +## Architecture Overview + +### Microservices Architecture +The system consists of seven main services: + +1. **aioj-backend-gateway** (Port 8085) + - API Gateway using Spring Cloud Gateway + - Routes requests to appropriate services + - Built with WebFlux for reactive programming + +2. **aioj-backend-auth** + - OAuth2 authentication and authorization service + - Manages user credentials and tokens + +3. **aioj-backend-user-service** + - User management and profiles + - Handles registration, login, profile updates + - Integrates with Redis for session management + +4. **aioj-backend-question-service** + - Problem/question management + - Handles problem storage and retrieval + +5. **aioj-backend-judge-service** + - Core OJ functionality for code execution + - Supports multiple programming languages + +6. **aioj-backend-ai-service** + - AI integration for enhanced features + - Code analysis and automated feedback + +7. **aioj-backend-upms** (User Permission Management System) + - Role-based access control + - Permission management + +### Common Modules +- **aioj-backend-common**: Shared utilities with sub-modules: + - `core`: Core utilities and configurations + - `log`: Custom logging implementation + - `starter`: Auto-configuration starters + - `mybatis`: Database access layer + - `feign`: HTTP client for service communication + - `bom`: Bill of Materials for dependency management + +## Technology Stack + +### Core Technologies +- **Java 17** +- **Spring Boot 3.5.7** +- **Spring Cloud 2025.0.0** +- **Spring Cloud Alibaba 2025.0.0.0** +- **Maven** for build management + +### Database & Persistence +- **MySQL 9.4.0** as primary database +- **MyBatis-Plus 3.5.14** for ORM +- **Redis** for caching and session management + +### Cloud & Infrastructure +- **Nacos** for service discovery and configuration (server: 10.0.0.10:8848) +- **Spring Cloud Gateway** for API routing +- **Docker** with Jib plugin for containerization +- **Sentinel** for circuit breaking + +### Security +- **Spring Security 6.5.6** with OAuth2 +- JWT token-based authentication +- Role-based access control + +### API Documentation +- **Knife4j** (OpenAPI 3) integrated across services + +## Configuration Management + +### Environment-Specific Configuration +Three environments are supported: +- `dev` (development, default) +- `test` (testing) +- `prod` (production) + +Configuration files: +- `bootstrap.yml` - Nacos service discovery configuration +- `application.yml` - Main application configuration +- `application-{env}.yml` - Environment-specific settings + +### Nacos Integration +All services use Nacos for: +- Service discovery +- Configuration management +- Centralized properties management + +Default Nacos configuration: +```yaml +spring: + cloud: + nacos: + discovery: + server-addr: 10.0.0.10:8848 + username: nacos + password: nacos ``` -## Technologies Used +## Database Schema -- **Java 17**: Programming language -- **Spring Boot 3.5.7**: Framework for building microservices -- **Spring Cloud Alibaba 2025.0.0.0**: Microservices ecosystem -- **Maven**: Build tool -- **Lombok**: Java library to reduce boilerplate code \ No newline at end of file +### Environment Databases +- Development: `aioj_dev` +- Testing: `aioj_test` +- Production: `aioj_prod` + +All databases use UTF-8 character set with `utf8mb4_general_ci` collation. + +## Development Guidelines + +### Code Formatting +- Uses Spring JavaFormat plugin for consistent code style +- IDE plugin available: https://repo1.maven.org/maven2/io/spring/javaformat/spring-javaformat-intellij-idea-plugin +- Run `mvn spring-javaformat:apply` before commits + +### Docker Integration +- Jib plugin configured for container builds +- Target registry: `10.0.0.3/aioj/{service-name}:{version}` +- JVM memory configured: -Xms512m -Xmx512m + +### Service Communication +- Uses OpenFeign for inter-service communication +- Load balancing with Spring Cloud LoadBalancer +- Circuit breaking with Sentinel + +### Logging +- Custom logging implementation in `aioj-backend-common-log` +- Integrates with Spring Security for context logging +- Uses Hutool utilities for enhanced logging + +## Testing +- Spring Boot Test framework included +- Spring Security Test for authentication testing +- Test structure is evolving - check individual modules for test coverage + +## Important Notes + +1. **Service Dependencies**: Services depend on Nacos for discovery - ensure Nacos is running before starting services +2. **Database Setup**: Run the database creation script before first run +3. **Port Configuration**: Gateway runs on 8085, other services are dynamically registered +4. **Environment Profiles**: Default is `dev` - use appropriate profiles for different environments +5. **Configuration Management**: Most configuration is externalized to Nacos - check Nacos for service-specific settings \ No newline at end of file diff --git a/aioj-backend-auth/pom.xml b/aioj-backend-auth/pom.xml index 297d88d..72da676 100644 --- a/aioj-backend-auth/pom.xml +++ b/aioj-backend-auth/pom.xml @@ -19,40 +19,51 @@ + + + cn.meowrain + aioj-backend-common-core + 1.0-SNAPSHOT + + + cn.meowrain + aioj-backend-common-feign + 1.0-SNAPSHOT + - + + + cn.hutool + hutool-crypto + + + org.apache.commons + commons-lang3 + + + com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery - + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot spring-boot-starter-oauth2-client - org.springframework.boot spring-boot-starter-security - - org.springframework.boot - spring-boot-starter-test - test - - - org.springframework.boot - spring-boot-devtools - runtime - true - - - - org.springframework.boot - spring-boot-starter-web - - + io.jsonwebtoken jjwt-api @@ -70,37 +81,42 @@ 0.13.0 runtime - - - com.github.xiaoymin - knife4j-openapi3-jakarta-spring-boot-starter - - - - cn.meowrain - aioj-backend-common-starter - 1.0-SNAPSHOT - compile - - - + + org.springframework.cloud spring-cloud-starter-openfeign - 4.3.0 org.springframework.cloud spring-cloud-starter-loadbalancer - 4.3.0 - + org.springframework.boot spring-boot-starter-data-redis + + + + com.github.xiaoymin + knife4j-openapi3-jakarta-spring-boot-starter + + + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + + + org.springframework.boot + spring-boot-starter-test + test + \ No newline at end of file diff --git a/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/clients/UserClient.java b/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/clients/UserClient.java index fa7f64a..6717dc1 100644 --- a/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/clients/UserClient.java +++ b/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/clients/UserClient.java @@ -13,6 +13,6 @@ public interface UserClient { Result getUserByUserName(@RequestParam("userAccount") String userAccount); @GetMapping("/inner/get-by-userid") - public Result getUserById(@RequestParam("userId") String userid); + public Result getUserById(@RequestParam("userId") String userId); } diff --git a/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/controller/AuthController.java b/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/controller/AuthController.java index a2ce03b..d90de77 100644 --- a/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/controller/AuthController.java +++ b/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/controller/AuthController.java @@ -3,8 +3,9 @@ package cn.meowrain.aioj.backend.auth.controller; import cn.meowrain.aioj.backend.auth.dto.req.UserLoginRequestDTO; import cn.meowrain.aioj.backend.auth.dto.resp.UserLoginResponseDTO; import cn.meowrain.aioj.backend.auth.service.AuthService; -import cn.meowrain.aioj.backend.framework.web.Results; -import cn.meowrain.aioj.backend.framework.web.Result; + +import cn.meowrain.aioj.backend.framework.core.web.Result; +import cn.meowrain.aioj.backend.framework.core.web.Results; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; @@ -33,4 +34,16 @@ public class AuthController { return Results.success(userLoginResponseDTO.getAccessToken()); } + @PostMapping("/validate") + public Result validate(@RequestHeader(value = "Authorization", required = false) String authorization) { + // 从Authorization头中提取Bearer token + String token = null; + if (authorization != null && authorization.startsWith("Bearer ")) { + token = authorization.substring(7); + } + + Boolean isValid = authService.validateToken(token); + return Results.success(isValid); + } + } diff --git a/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/dto/chains/UserLoginRequestParamVerifyChain.java b/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/dto/chains/UserLoginRequestParamVerifyChain.java index e8bcdd8..578f78b 100644 --- a/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/dto/chains/UserLoginRequestParamVerifyChain.java +++ b/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/dto/chains/UserLoginRequestParamVerifyChain.java @@ -2,9 +2,10 @@ package cn.meowrain.aioj.backend.auth.dto.chains; import cn.meowrain.aioj.backend.auth.common.enums.ChainMarkEnums; import cn.meowrain.aioj.backend.auth.dto.req.UserLoginRequestDTO; -import cn.meowrain.aioj.backend.framework.designpattern.chains.AbstractChianHandler; -import cn.meowrain.aioj.backend.framework.errorcode.ErrorCode; -import cn.meowrain.aioj.backend.framework.exception.ClientException; + +import cn.meowrain.aioj.backend.framework.core.designpattern.chains.AbstractChianHandler; +import cn.meowrain.aioj.backend.framework.core.errorcode.ErrorCode; +import cn.meowrain.aioj.backend.framework.core.exception.ClientException; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; diff --git a/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/service/AuthService.java b/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/service/AuthService.java index 5f5ffcc..d0f7183 100644 --- a/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/service/AuthService.java +++ b/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/service/AuthService.java @@ -19,4 +19,11 @@ public interface AuthService { */ UserLoginResponseDTO refreshToken(String refreshToken); + /** + * 验证token的有效性 + * @param accessToken 访问令牌 + * @return token是否有效 + */ + Boolean validateToken(String accessToken); + } diff --git a/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/service/impl/AuthServiceImpl.java b/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/service/impl/AuthServiceImpl.java index 258bd69..221ba53 100644 --- a/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/service/impl/AuthServiceImpl.java +++ b/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/service/impl/AuthServiceImpl.java @@ -39,23 +39,37 @@ public class AuthServiceImpl implements AuthService { @Override public UserLoginResponseDTO userLogin(UserLoginRequestDTO requestParam) { + log.info("用户登录请求: userAccount={}", requestParam.getUserAccount()); + // 1.校验 userLoginRequestParamVerifyContext.handler(ChainMarkEnums.USER_LOGIN_REQ_PARAM_VERIFY.getMarkName(), requestParam); + // 如果调用user-service失败,那么就说明是系统内部错误 + log.info("正在调用user-service查询用户信息..."); Result userResp = userClient.getUserByUserName(requestParam.getUserAccount()); + if (userResp.isFail()) { log.error("调用user-service返回失败:{}", userResp.getMessage()); throw new ServiceException(ErrorCode.SYSTEM_ERROR); } - UserAuthRespDTO user = userResp.getData(); - if (ObjectUtil.isNull(user) || !BCrypt.checkpw(requestParam.getUserPassword(), user.getUserPassword())) { - throw new ServiceException("用户不存在或者密码错误", ErrorCode.NOT_LOGIN_ERROR); + UserAuthRespDTO user = userResp.getData(); + if (user == null) { + log.warn("用户不存在: {}", requestParam.getUserAccount()); + throw new ServiceException("用户不存在或密码错误", ErrorCode.NOT_LOGIN_ERROR); } + + if (!BCrypt.checkpw(requestParam.getUserPassword(), user.getUserPassword())) { + log.warn("密码错误: {}", requestParam.getUserAccount()); + throw new ServiceException("用户不存在或密码错误", ErrorCode.NOT_LOGIN_ERROR); + } + // 生成 JWT + log.info("正在生成JWT token..."); String accessToken = jwtUtil.generateAccessToken(user); String refreshToken = jwtUtil.generateRefreshToken(user.getId()); + UserLoginResponseDTO resp = new UserLoginResponseDTO(); resp.setId(user.getId()); resp.setUserAccount(user.getUserAccount()); @@ -66,6 +80,8 @@ public class AuthServiceImpl implements AuthService { stringRedisTemplate.opsForValue() .set(String.format(RedisKeyConstants.REFRESH_TOKEN_KEY_PREFIX, user.getId()), refreshToken, jwtPropertiesConfiguration.getRefreshExpire(), TimeUnit.MILLISECONDS); + + log.info("用户登录成功: userId={}, userAccount={}", user.getId(), user.getUserAccount()); return resp; } @@ -106,4 +122,46 @@ public class AuthServiceImpl implements AuthService { return userLoginResponseDTO; } + /** + * 验证token的有效性 + * @param accessToken 访问令牌 + * @return token是否有效 + */ + @Override + public Boolean validateToken(String accessToken) { + try { + // 1. 检查token格式 + if (accessToken == null || accessToken.trim().isEmpty()) { + log.warn("Access token is null or empty"); + return false; + } + + // 2. 验证token签名和过期时间 + if (!jwtUtil.isTokenValid(accessToken)) { + log.warn("Access token is invalid or expired"); + return false; + } + + // 3. 解析token获取用户信息 + String userId = jwtUtil.parseClaims(accessToken).getSubject(); + if (userId == null) { + log.warn("Access token does not contain valid user id"); + return false; + } + + // 4. 验证用户是否存在(可选,增加安全性) + Result userResult = userClient.getUserById(userId); + if (userResult.isFail() || userResult.getData() == null) { + log.warn("User not found for id: {}", userId); + return false; + } + + log.debug("Access token validation successful for user: {}", userId); + return true; + } catch (Exception e) { + log.error("Error validating access token", e); + return false; + } + } + } diff --git a/aioj-backend-gateway/pom.xml b/aioj-backend-gateway/pom.xml index f8efddb..1506346 100644 --- a/aioj-backend-gateway/pom.xml +++ b/aioj-backend-gateway/pom.xml @@ -18,11 +18,6 @@ 4.3.2 - - cn.meowrain - aioj-backend-common-starter - 1.0-SNAPSHOT - org.springframework.boot spring-boot-starter-webflux @@ -66,5 +61,27 @@ spring-cloud-starter-bootstrap 4.3.0 + + cn.meowrain + aioj-backend-common-core + 1.0-SNAPSHOT + + + + org.springframework.boot + spring-boot-starter-web + + + + jakarta.servlet + jakarta.servlet-api + + + + org.springframework + spring-webmvc + + + \ No newline at end of file diff --git a/aioj-backend-gateway/src/main/java/cn/meowrain/aioj/backend/gateway/config/GatewayConfiguration.java b/aioj-backend-gateway/src/main/java/cn/meowrain/aioj/backend/gateway/config/GatewayConfiguration.java new file mode 100644 index 0000000..d1e34b7 --- /dev/null +++ b/aioj-backend-gateway/src/main/java/cn/meowrain/aioj/backend/gateway/config/GatewayConfiguration.java @@ -0,0 +1,23 @@ +package cn.meowrain.aioj.backend.gateway.config; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.reactive.function.client.WebClient; + +/** + * 网关配置类 + */ +@Configuration(proxyBeanMethods = false) +@EnableConfigurationProperties(GatewayPropertiesConfiguration.class) +public class GatewayConfiguration { + + /** + * WebClient Bean,用于服务间调用 + */ + @Bean + public WebClient.Builder webClientBuilder() { + return WebClient.builder(); + } + +} diff --git a/aioj-backend-gateway/src/main/java/cn/meowrain/aioj/backend/gateway/config/GatewayPropertiesConfiguration.java b/aioj-backend-gateway/src/main/java/cn/meowrain/aioj/backend/gateway/config/GatewayPropertiesConfiguration.java index 0a7d98c..9c8be7e 100644 --- a/aioj-backend-gateway/src/main/java/cn/meowrain/aioj/backend/gateway/config/GatewayPropertiesConfiguration.java +++ b/aioj-backend-gateway/src/main/java/cn/meowrain/aioj/backend/gateway/config/GatewayPropertiesConfiguration.java @@ -2,7 +2,12 @@ package cn.meowrain.aioj.backend.gateway.config; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; +import java.util.ArrayList; +import java.util.List; + +@Component @ConfigurationProperties(prefix = GatewayPropertiesConfiguration.PREFIX) @Data public class GatewayPropertiesConfiguration { @@ -10,8 +15,9 @@ public class GatewayPropertiesConfiguration { public static final String PREFIX = "aioj-backend-gateway"; /* - * 白名单放行 + * 白名单放行路径 + * 支持Ant风格路径匹配,如 /api/v1/question/** */ - private String[] whiteList; + private List whiteList = new ArrayList<>(); } diff --git a/aioj-backend-gateway/src/main/java/cn/meowrain/aioj/backend/gateway/filter/AuthGlobalFilter.java b/aioj-backend-gateway/src/main/java/cn/meowrain/aioj/backend/gateway/filter/AuthGlobalFilter.java index d2b02ee..6f3f861 100644 --- a/aioj-backend-gateway/src/main/java/cn/meowrain/aioj/backend/gateway/filter/AuthGlobalFilter.java +++ b/aioj-backend-gateway/src/main/java/cn/meowrain/aioj/backend/gateway/filter/AuthGlobalFilter.java @@ -1,28 +1,169 @@ package cn.meowrain.aioj.backend.gateway.filter; +import cn.meowrain.aioj.backend.framework.core.errorcode.ErrorCode; +import cn.meowrain.aioj.backend.framework.core.exception.RemoteException; +import cn.meowrain.aioj.backend.framework.core.web.Result; +import cn.meowrain.aioj.backend.framework.core.web.Results; +import cn.meowrain.aioj.backend.gateway.config.GatewayPropertiesConfiguration; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; +import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.http.server.reactive.ServerHttpResponseDecorator; import org.springframework.stereotype.Component; +import org.springframework.util.AntPathMatcher; import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Objects; + +@Slf4j @Component @RequiredArgsConstructor public class AuthGlobalFilter implements GlobalFilter, Ordered { private final WebClient.Builder webClientBuilder; + @Autowired + private GatewayPropertiesConfiguration gatewayPropertiesConfiguration; + + private final AntPathMatcher antPathMatcher = new AntPathMatcher(); + + private final ObjectMapper objectMapper = new ObjectMapper(); + + /** + * 不需要认证的路径 + */ + private static final String[] DEFAULT_WHITE_LIST = { + "/api/v1/auth/login", + "/api/v1/auth/register", + "/api/v1/auth/refresh", + "/api/v1/user/info" + }; + @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - return null; + ServerHttpRequest request = exchange.getRequest(); + String path = request.getURI().getPath(); + + log.info("Auth filter processing request: {}", path); + + // 检查是否在白名单中 + if (isWhiteListPath(path)) { + log.info("Path {} is in whitelist, skip authentication", path); + return chain.filter(exchange); + } + + // 获取Authorization头 + String authHeader = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION); + if (authHeader == null || !authHeader.startsWith("Bearer ")) { + log.warn("No valid authorization header found for path: {}", path); + return handleUnauthorized(exchange); + } + + String token = authHeader.substring(7); + + // 调用auth服务验证token + return validateToken(token) + .flatMap(isValid -> { + if (isValid) { + log.info("Token validation successful for path: {}", path); + return chain.filter(exchange); + } else { + log.warn("Token validation failed for path: {}", path); + return handleUnauthorized(exchange); + } + }) + .onErrorResume(throwable -> { + log.error("Token validation error for path: {}", path, throwable); + return handleUnauthorized(exchange); + }); + } + + /** + * 检查路径是否在白名单中 + */ + private boolean isWhiteListPath(String path) { + // 先检查默认白名单 + for (String whitePath : DEFAULT_WHITE_LIST) { + if (antPathMatcher.match(whitePath, path)) { + return true; + } + } + + // 检查配置文件中的白名单 + if (gatewayPropertiesConfiguration.getWhiteList() != null && !gatewayPropertiesConfiguration.getWhiteList().isEmpty()) { + for (String whitePath : gatewayPropertiesConfiguration.getWhiteList()) { + if (antPathMatcher.match(whitePath, path)) { + return true; + } + } + } + + return false; + } + + /** + * 调用auth服务验证token + */ + private Mono validateToken(String token) { + return webClientBuilder.build() + .post() + .uri("lb://auth-service/api/v1/auth/validate") + .header(HttpHeaders.AUTHORIZATION, "Bearer " + token) + .contentType(MediaType.APPLICATION_JSON) + .retrieve() + .bodyToMono(String.class) + .map(response -> { + try { + // 解析响应,判断是否有效 + Result result = objectMapper.readValue(response, Result.class); + return Objects.equals(result.getCode(), Result.SUCCESS_CODE); + } catch (JsonProcessingException e) { + log.error("Failed to parse auth response", e); + return false; + } + }) + .defaultIfEmpty(false); + } + + /** + * 处理未授权的请求 + */ + private Mono handleUnauthorized(ServerWebExchange exchange) { + ServerHttpResponse response = exchange.getResponse(); + response.setStatusCode(HttpStatus.UNAUTHORIZED); + response.getHeaders().setContentType(MediaType.APPLICATION_JSON); + + Result result = Results.failure(new RemoteException(ErrorCode.NO_AUTH_ERROR)); + String responseBody; + try { + responseBody = objectMapper.writeValueAsString(result); + } catch (JsonProcessingException e) { + responseBody = "{\"code\":401,\"message\":\"Unauthorized\",\"data\":null}"; + } + + DataBuffer buffer = response.bufferFactory().wrap(responseBody.getBytes(StandardCharsets.UTF_8)); + return response.writeWith(Mono.just(buffer)); } @Override public int getOrder() { - return 0; + // 设置较高的优先级,确保在其他过滤器之前执行 + return -100; } } diff --git a/aioj-backend-gateway/src/main/resources/application.yml b/aioj-backend-gateway/src/main/resources/application.yml index f471913..00539f5 100644 --- a/aioj-backend-gateway/src/main/resources/application.yml +++ b/aioj-backend-gateway/src/main/resources/application.yml @@ -7,13 +7,28 @@ spring: webflux: routes: - id: auth-service - uri: lb://auth-service + uri: lb://auth-service/api predicates: - Path=/api/v1/auth/** - id: user-service - uri: lb://user-service + uri: lb://user-service/api predicates: - Path=/api/v1/user/** + +aioj-backend-gateway: + # 白名单配置 + white-list: + - /api/v1/auth/login + - /api/v1/auth/register + - /api/v1/auth/refresh + - /api/v1/user/info + - /api/v1/question/list + - /api/v1/question/detail/** + - /actuator/health + - /swagger-ui/** + - /v3/api-docs/** + - /swagger-resources/** + aioj: log: enabled: true diff --git a/aioj-backend-upms/aioj-backend-upms-api/pom.xml b/aioj-backend-upms/aioj-backend-upms-api/pom.xml index b4d93f4..6520d36 100644 --- a/aioj-backend-upms/aioj-backend-upms-api/pom.xml +++ b/aioj-backend-upms/aioj-backend-upms-api/pom.xml @@ -34,10 +34,5 @@ aioj-backend-common-feign 1.0-SNAPSHOT - - cn.meowrain - aioj-backend-upms-api - 1.0-SNAPSHOT - \ No newline at end of file diff --git a/aioj-backend-upms/aioj-backend-upms-biz/pom.xml b/aioj-backend-upms/aioj-backend-upms-biz/pom.xml index f577a29..7c198d7 100644 --- a/aioj-backend-upms/aioj-backend-upms-biz/pom.xml +++ b/aioj-backend-upms/aioj-backend-upms-biz/pom.xml @@ -28,5 +28,9 @@ aioj-backend-upms-api 1.0-SNAPSHOT + + org.springframework.boot + spring-boot-starter-web + \ No newline at end of file diff --git a/aioj-backend-upms/aioj-backend-upms-biz/src/main/resources/application-dev.yml b/aioj-backend-upms/aioj-backend-upms-biz/src/main/resources/application-dev.yml new file mode 100644 index 0000000..019d5c9 --- /dev/null +++ b/aioj-backend-upms/aioj-backend-upms-biz/src/main/resources/application-dev.yml @@ -0,0 +1,19 @@ +spring: + data: + redis: + host: 10.0.0.10 + port: 6379 + password: 123456 + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://10.0.0.10/aioj_dev + username: root + password: root + cloud: + nacos: + discovery: + enabled: true + register-enabled: true + server-addr: 10.0.0.10:8848 + username: nacos + password: nacos \ No newline at end of file diff --git a/aioj-backend-upms/aioj-backend-upms-biz/src/main/resources/application-prod.yml b/aioj-backend-upms/aioj-backend-upms-biz/src/main/resources/application-prod.yml new file mode 100644 index 0000000..ed56224 --- /dev/null +++ b/aioj-backend-upms/aioj-backend-upms-biz/src/main/resources/application-prod.yml @@ -0,0 +1,11 @@ +spring: + data: + redis: + host: 10.0.0.10 + port: 6379 + password: 123456 + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://10.0.0.10/aioj_prod + username: root + password: root \ No newline at end of file diff --git a/aioj-backend-upms/aioj-backend-upms-biz/src/main/resources/application-test.yml b/aioj-backend-upms/aioj-backend-upms-biz/src/main/resources/application-test.yml new file mode 100644 index 0000000..1e38912 --- /dev/null +++ b/aioj-backend-upms/aioj-backend-upms-biz/src/main/resources/application-test.yml @@ -0,0 +1,11 @@ +spring: + data: + redis: + host: 10.0.0.10 + port: 6379 + password: 123456 + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://10.0.0.10/aioj_test + username: root + password: 123456 \ No newline at end of file diff --git a/aioj-backend-upms/aioj-backend-upms-biz/src/main/resources/application.yml b/aioj-backend-upms/aioj-backend-upms-biz/src/main/resources/application.yml new file mode 100644 index 0000000..7f5b6b5 --- /dev/null +++ b/aioj-backend-upms/aioj-backend-upms-biz/src/main/resources/application.yml @@ -0,0 +1,35 @@ +spring: + application: + name: user-service + profiles: + active: @env@ +server: + port: 10012 + servlet: + context-path: /api +springdoc: + api-docs: + enabled: true + path: /v3/api-docs + default-flat-param-object: true + swagger-ui: + path: /swagger-ui.html + tags-sorter: alpha + operations-sorter: alpha + group-configs: + - group: 'default' + paths-to-match: '/**' + packages-to-scan: cn.meowrain.aioj.backend.userservice.controller +knife4j: + basic: + enable: true + setting: + language: zh_cn +mybatis-plus: + configuration: + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + mapper-locations: classpath*:/mapper/**/*.xml +aioj: + log: + enabled: true + max-length: 20000 \ No newline at end of file