fix: 确保项目可以启动

This commit is contained in:
2025-12-12 23:50:55 +08:00
parent c61ee69561
commit 4912e48922
21 changed files with 623 additions and 145 deletions

View File

@@ -1,10 +0,0 @@
{
"permissions": {
"allow": [
"Bash(cat:*)",
"Bash(find ./aioj-backend-common -path \"*common-log*pom.xml\" -exec cat {} ;)"
],
"deny": [],
"ask": []
}
}

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CoolRequestCommonStatePersistent">
<option name="searchCache" value="/*" />
<option name="searchCache" value="AIOJAdminA" />
</component>
</project>

6
.idea/copilot.data.migration.agent.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AgentMigrationStateService">
<option name="migrationStatus" value="COMPLETED" />
</component>
</project>

250
CLAUDE.md
View File

@@ -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 <module-name>
```
- Package the entire project:
```bash
mvn clean package
```
- Package a specific module:
```bash
mvn clean package -pl <module-name>
```
## 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 <module-name>
# 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 <module-name>/target
java -jar <module-name>-<version>.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
### 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

View File

@@ -19,40 +19,51 @@
</properties>
<dependencies>
<!-- 核心模块 -->
<dependency>
<groupId>cn.meowrain</groupId>
<artifactId>aioj-backend-common-core</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>cn.meowrain</groupId>
<artifactId>aioj-backend-common-feign</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- spring cloud发现服务-->
<!-- 工具类 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-crypto</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<!-- Spring Cloud服务发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- OAuth2 Client -->
<!-- Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- OAuth2 & Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--JWT-->
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
@@ -70,37 +81,42 @@
<version>0.13.0</version>
<scope>runtime</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/com.github.xiaoymin/knife4j-openapi3-jakarta-spring-boot-starter -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
</dependency>
<!--
引用通用模块
-->
<dependency>
<groupId>cn.meowrain</groupId>
<artifactId>aioj-backend-common-starter</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<!--引入openfeign-->
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-openfeign -->
<!-- Feign客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>4.3.0</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
<version>4.3.0</version>
</dependency>
<!-- 引入redis存储refreshToken-->
<!-- Redis用于存储refreshToken -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- API文档 -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
</dependency>
<!-- 开发工具 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- 测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -13,6 +13,6 @@ public interface UserClient {
Result<UserAuthRespDTO> getUserByUserName(@RequestParam("userAccount") String userAccount);
@GetMapping("/inner/get-by-userid")
public Result<UserAuthRespDTO> getUserById(@RequestParam("userId") String userid);
public Result<UserAuthRespDTO> getUserById(@RequestParam("userId") String userId);
}

View File

@@ -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<Boolean> 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);
}
}

View File

@@ -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;

View File

@@ -19,4 +19,11 @@ public interface AuthService {
*/
UserLoginResponseDTO refreshToken(String refreshToken);
/**
* 验证token的有效性
* @param accessToken 访问令牌
* @return token是否有效
*/
Boolean validateToken(String accessToken);
}

View File

@@ -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<UserAuthRespDTO> 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<UserAuthRespDTO> 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;
}
}
}

View File

@@ -18,11 +18,6 @@
<spring-cloud-gateway.version>4.3.2</spring-cloud-gateway.version>
</properties>
<dependencies>
<dependency>
<groupId>cn.meowrain</groupId>
<artifactId>aioj-backend-common-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
@@ -66,5 +61,27 @@
<artifactId>spring-cloud-starter-bootstrap</artifactId>
<version>4.3.0</version>
</dependency>
<dependency>
<groupId>cn.meowrain</groupId>
<artifactId>aioj-backend-common-core</artifactId>
<version>1.0-SNAPSHOT</version>
<exclusions>
<!-- 🚫 必须排除Spring MVC 核心 -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</exclusion>
<!-- 🚫 必须排除Servlet API (Gateway用不到) -->
<exclusion>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
</exclusion>
<!-- 🚫 必须排除Spring WebMVC -->
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>

View File

@@ -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();
}
}

View File

@@ -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<String> whiteList = new ArrayList<>();
}

View File

@@ -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<Void> 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<Boolean> 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<Void> handleUnauthorized(ServerWebExchange exchange) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
Result<Void> 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;
}
}

View File

@@ -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

View File

@@ -34,10 +34,5 @@
<artifactId>aioj-backend-common-feign</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>cn.meowrain</groupId>
<artifactId>aioj-backend-upms-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>

View File

@@ -28,5 +28,9 @@
<artifactId>aioj-backend-upms-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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