fix: 确保项目可以启动
This commit is contained in:
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(cat:*)",
|
||||
"Bash(find ./aioj-backend-common -path \"*common-log*pom.xml\" -exec cat {} ;)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
}
|
||||
}
|
||||
2
.idea/CoolRequestCommonStatePersistent.xml
generated
2
.idea/CoolRequestCommonStatePersistent.xml
generated
@@ -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
6
.idea/copilot.data.migration.agent.xml
generated
Normal 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
250
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 <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
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -19,4 +19,11 @@ public interface AuthService {
|
||||
*/
|
||||
UserLoginResponseDTO refreshToken(String refreshToken);
|
||||
|
||||
/**
|
||||
* 验证token的有效性
|
||||
* @param accessToken 访问令牌
|
||||
* @return token是否有效
|
||||
*/
|
||||
Boolean validateToken(String accessToken);
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<>();
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user