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