diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..31d5b2b --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,9 @@ +{ + "permissions": { + "allow": [ + "Bash(mvn clean compile:*)", + "Bash(mvn spring-javaformat:apply)", + "Bash(cat:*)" + ] + } +} diff --git a/.idea/CoolRequestCommonStatePersistent.xml b/.idea/CoolRequestCommonStatePersistent.xml index 591a8f6..6435996 100644 --- a/.idea/CoolRequestCommonStatePersistent.xml +++ b/.idea/CoolRequestCommonStatePersistent.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml new file mode 100644 index 0000000..d3dcd6e --- /dev/null +++ b/.idea/dataSources.xml @@ -0,0 +1,13 @@ + + + + + mysql.8 + true + true + com.mysql.cj.jdbc.Driver + jdbc:mysql://10.0.0.10/aioj_dev + $ProjectFileDir$ + + + \ No newline at end of file diff --git a/.idea/db-forest-config.xml b/.idea/db-forest-config.xml new file mode 100644 index 0000000..d0a62c0 --- /dev/null +++ b/.idea/db-forest-config.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index bec41f0..2fe2580 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,195 +2,122 @@ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. -## Project Overview +## Codebase Overview -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. +This is a microservices architecture for an Online Judge (OJ) system, built on **Spring Boot 3.5.7**. The project uses Maven as the build tool and follows a modular monorepo structure, with clearly separated core modules and service modules. -## Common Development Commands +### Core Modules -### Building the Project -```bash -# Build entire project -mvn clean install +The `aioj-backend-common` directory contains shared components and utilities used across all service modules: -# Build with specific environment profile -mvn clean install -P dev # Development (default) -mvn clean install -P test # Testing -mvn clean install -P prod # Production +1. **aioj-backend-common-bom** + Bill of materials for centralized dependency management. This ensures consistent versions of all external libraries across all modules. -# Format code according to Spring standards -mvn spring-javaformat:apply +2. **aioj-backend-common-core** + Core utilities and Spring framework extensions: + - `BannerApplicationRunner`: Custom application banner display during startup + - `SpringContextHolder`: Spring context accessor for non-Spring-managed classes + - `JavaTimeModule`: Jackson module for Java 8+ time API support + - Common constants and enumerations + - Application-level configurations and auto-configuration classes -# Build Docker images using Jib -mvn clean package jib:build +3. **aioj-backend-common-feign** + Feign client configurations for seamless inter-service communication: + - Auto-configuration for Feign clients with default settings + - `@EnableAIOJFeignClients` annotation for enabling Feign clients with predefined base packages + - Feign interceptors and error handling mechanisms -# Or use Maven wrapper -./mvnw clean install -``` +4. **aioj-backend-common-log** + Aspect-oriented programming (AOP) based logging framework: + - `SysLogAspect`: Aspect for logging system operations (controller methods, service calls) + - `SysLogEvent` and `SysLogListener`: Event-driven logging mechanism + - `SysLogUtils`: Utility class for creating and managing log entries + - Configuration properties for logging behavior -### Running Services -Each service runs on different ports: -- Gateway: 8085 -- Other services: configured via Nacos +5. **aioj-backend-common-mybatis** + MyBatis ORM framework extensions: + - Auto-fill functionality for `createTime` and `updateTime` fields + - Pagination interceptor implementation + - MyBatis configuration auto-configuration classes -Run individual services from their respective directories: -```bash -cd aioj-backend-gateway -mvn spring-boot:run +6. **aioj-backend-common-starter** + Auto-configuration starters for easily enabling common features in service modules. -# Or with specific profile -mvn spring-boot:run -Dspring.profiles.active=dev -``` -### Database Setup -1. Create databases using the provided script: -```bash -mysql -u root -p < db/create_db.sql -``` -This creates three databases: `aioj_dev`, `aioj_test`, and `aioj_prod` +### Service Modules -## Architecture Overview +The service modules represent the individual microservices that make up the system: -### Microservices Architecture -The system consists of seven main services: +1. **aioj-backend-auth** + Authentication and authorization service: + - JWT-based authentication with `JwtAuthenticationFilter` + - Security configuration with Spring Security + - User login, token generation, and validation + - Permission verification and access control -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-gateway** + API gateway for request routing and filtering: + - Request routing to appropriate service modules + - Authentication token validation before forwarding requests + - Rate limiting and request filtering mechanisms -2. **aioj-backend-auth** - - OAuth2 authentication and authorization service - - Manages user credentials and tokens +3. **aioj-backend-judge-service** + Code judge service (under development): + - Will handle code submission, compilation, and execution + - Support for multiple programming languages + - Test case validation and result return -3. **aioj-backend-user-service** - - User management and profiles - - Handles registration, login, profile updates - - Integrates with Redis for session management +4. **aioj-backend-user-service** + User management service: + - User registration, profile management, and information retrieval + - User role and permission assignment + - Integration with the auth service for authentication -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 +5. **aioj-backend-question-service** + Question bank service (under development): + - Will manage programming problems, test cases, and problem categories + - Support for problem difficulty levels and tags + - Integration with the judge service for problem submission 6. **aioj-backend-ai-service** - - AI integration for enhanced features - - Code analysis and automated feedback + AI-related functionality service (under development): + - Will provide AI-assisted features like problem recommendation, code analysis, etc. -7. **aioj-backend-upms** (User Permission Management System) - - Role-based access control - - Permission management +7. **aioj-backend-upms** + User, permission, and menu management service: + - Low-level user and permission management + - Menu and resource access control + - Integration with other services for authorization -### 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 +## Commonly Used Commands -## 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 -``` - -## Database Schema - -### 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 +### Build +- **Build the entire project**: `mvn clean compile` +- **Build with tests**: `mvn clean install` +- **Build a single module**: `mvn clean compile -pl aioj-backend-auth` ### 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 +- **Format code**: `mvn spring-javaformat:apply` +- **Check code format**: `mvn spring-javaformat:check` -### Docker Integration -- Jib plugin configured for container builds -- Target registry: `10.0.0.3/aioj/{service-name}:{version}` -- JVM memory configured: -Xms512m -Xmx512m +### Testing +- **Run all tests**: `mvn test` +- **Run tests for a single module**: `mvn test -pl aioj-backend-user-service` -### Service Communication -- Uses OpenFeign for inter-service communication -- Load balancing with Spring Cloud LoadBalancer -- Circuit breaking with Sentinel +### Running Services +- **Run a service locally**: Use Spring Boot's `Application` class directly in IDE or use `mvn spring-boot:run -pl ` +- **Example**: `mvn spring-boot:run -pl aioj-backend-auth` -### Logging -- Custom logging implementation in `aioj-backend-common-log` -- Integrates with Spring Security for context logging -- Uses Hutool utilities for enhanced logging +## Architecture Highlights -## Testing -- Spring Boot Test framework included -- Spring Security Test for authentication testing -- Test structure is evolving - check individual modules for test coverage +- **Authentication**: JWT-based authentication implemented in `aioj-backend-auth` with `JwtAuthenticationFilter` +- **Logging**: Aspect-oriented logging with `SysLogAspect` in `aioj-backend-common-log` +- **Database**: MyBatis with auto-fill for create/update times implemented in `common-mybatis` +- **Inter-service communication**: Feign clients with auto-configuration from `common-feign` +- **Banner**: Custom application banner system in `common-core` -## Important Notes +## Key Entry Points -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 +- **Gateway Application**: `aioj-backend-gateway/src/main/java/cn/meowrain/aioj/backend/gateway/AIOJGatewayApplication.java` +- **Auth Application**: `aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/AIOJAuthApplication.java` +- **User Service Application**: `aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/UserServiceApplication.java` \ No newline at end of file diff --git a/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/config/SecurityConfiguration.java b/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/config/SecurityConfiguration.java index f80b8ff..33aa51c 100644 --- a/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/config/SecurityConfiguration.java +++ b/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/config/SecurityConfiguration.java @@ -1,5 +1,7 @@ package cn.meowrain.aioj.backend.auth.config; +import cn.meowrain.aioj.backend.auth.filter.JwtAuthenticationFilter; +import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; @@ -10,11 +12,15 @@ import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration @EnableWebSecurity +@RequiredArgsConstructor public class SecurityConfiguration { + private final JwtAuthenticationFilter jwtAuthenticationFilter; + @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.csrf(csrf -> csrf.disable()) @@ -24,7 +30,8 @@ public class SecurityConfiguration { "/v3/api-docs/**", "/favicon.ico") .permitAll() .anyRequest() - .authenticated()); + .authenticated()) + .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } diff --git a/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/filter/JwtAuthenticationFilter.java b/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/filter/JwtAuthenticationFilter.java index b2aa727..8cb6ca0 100644 --- a/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/filter/JwtAuthenticationFilter.java +++ b/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/filter/JwtAuthenticationFilter.java @@ -1,11 +1,106 @@ package cn.meowrain.aioj.backend.auth.filter; +import cn.meowrain.aioj.backend.auth.service.AuthService; +import cn.meowrain.aioj.backend.auth.utils.JwtUtil; +import io.jsonwebtoken.Claims; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; /** - * JWT拦截器 + * JWT认证过滤器 + * 拦截所有请求,验证JWT Token */ @Component -public class JwtAuthenticationFilter { +@RequiredArgsConstructor +@Slf4j +public class JwtAuthenticationFilter extends OncePerRequestFilter { + private final JwtUtil jwtUtil; + private final AuthService authService; + + private static final String TOKEN_PREFIX = "Bearer "; + private static final String HEADER_NAME = "Authorization"; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + + try { + String token = extractTokenFromRequest(request); + + if (StringUtils.hasText(token) && jwtUtil.isTokenValid(token)) { + Claims claims = jwtUtil.parseClaims(token); + Authentication authentication = createAuthentication(claims); + SecurityContextHolder.getContext().setAuthentication(authentication); + + log.debug("JWT Authentication successful for user: {}", claims.getSubject()); + } else { + log.debug("No valid JWT token found in request"); + } + } catch (Exception e) { + log.error("JWT Authentication failed", e); + SecurityContextHolder.clearContext(); + } + + filterChain.doFilter(request, response); + } + + /** + * 从请求中提取JWT Token + */ + private String extractTokenFromRequest(HttpServletRequest request) { + String bearerToken = request.getHeader(HEADER_NAME); + if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(TOKEN_PREFIX)) { + return bearerToken.substring(TOKEN_PREFIX.length()); + } + return null; + } + + /** + * 根据JWT Claims创建Authentication对象 + */ + private Authentication createAuthentication(Claims claims) { + String userId = claims.getSubject(); + String userName = claims.get("userName", String.class); + String role = claims.get("role", String.class); + + // 创建权限列表 + List authorities = Collections.singletonList( + new SimpleGrantedAuthority("ROLE_" + (role != null ? role : "USER")) + ); + + // 创建认证对象 + UsernamePasswordAuthenticationToken authentication = + new UsernamePasswordAuthenticationToken(userId, null, authorities); + + return authentication; + } + + @Override + protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException { + String path = request.getRequestURI(); + // 跳过不需要JWT验证的路径 + return path.startsWith("/v1/auth/") || + path.startsWith("/doc.html") || + path.startsWith("/swagger-ui/") || + path.startsWith("/swagger-resources/") || + path.startsWith("/webjars/") || + path.startsWith("/v3/api-docs/") || + path.equals("/favicon.ico"); + } } diff --git a/aioj-backend-common/aioj-backend-common-core/pom.xml b/aioj-backend-common/aioj-backend-common-core/pom.xml index e3d07b1..b9055e7 100644 --- a/aioj-backend-common/aioj-backend-common-core/pom.xml +++ b/aioj-backend-common/aioj-backend-common-core/pom.xml @@ -1,7 +1,7 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 cn.meowrain @@ -63,5 +63,9 @@ org.springframework.boot spring-boot-starter-validation + + org.springframework.boot + spring-boot-autoconfigure + \ No newline at end of file diff --git a/aioj-backend-common/aioj-backend-common-core/src/main/java/cn/meowrain/aioj/backend/framework/core/banner/BannerApplicationRunner.java b/aioj-backend-common/aioj-backend-common-core/src/main/java/cn/meowrain/aioj/backend/framework/core/banner/BannerApplicationRunner.java index 5444d01..5e01ca6 100644 --- a/aioj-backend-common/aioj-backend-common-core/src/main/java/cn/meowrain/aioj/backend/framework/core/banner/BannerApplicationRunner.java +++ b/aioj-backend-common/aioj-backend-common-core/src/main/java/cn/meowrain/aioj/backend/framework/core/banner/BannerApplicationRunner.java @@ -14,43 +14,47 @@ import java.time.format.DateTimeFormatter; @Slf4j @RequiredArgsConstructor public class BannerApplicationRunner implements ApplicationRunner { - private final Environment env; - @Value("${spring.application.name:unknown}") - private String appName; - @Override - public void run(ApplicationArguments args) throws Exception { - // Active profiles - String profiles = String.join(",", env.getActiveProfiles()); - if (profiles.isEmpty()) { - profiles = "default"; - } + private final Environment env; - // Port - String port = env.getProperty("server.port", "unknown"); + @Value("${spring.application.name:unknown}") + private String appName; - // JVM info - String jvm = System.getProperty("java.version") + " (" + System.getProperty("java.vendor") + ")"; + @Override + public void run(ApplicationArguments args) throws Exception { + // Active profiles + String profiles = String.join(",", env.getActiveProfiles()); + if (profiles.isEmpty()) { + profiles = "default"; + } - // PID - String pid = ManagementFactory.getRuntimeMXBean().getPid() + ""; + // Port + String port = env.getProperty("server.port", "unknown"); - // Time - String time = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + // JVM info + String jvm = System.getProperty("java.version") + " (" + System.getProperty("java.vendor") + ")"; - // Git commit id (如果没有 git.properties,不会报错) - String gitCommit = env.getProperty("git.commit.id.abbrev", "N/A"); + // PID + String pid = ManagementFactory.getRuntimeMXBean().getPid() + ""; - printBanner(appName, profiles, port, jvm, pid, time, gitCommit); - } - private void printBanner(String appName, String profiles, String port, String jvm, String pid, String time, - String gitCommit) { + // Time + String time = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + + // Git commit id (如果没有 git.properties,不会报错) + String gitCommit = env.getProperty("git.commit.id.abbrev", "N/A"); + + printBanner(appName, profiles, port, jvm, pid, time, gitCommit); + } + + private void printBanner(String appName, String profiles, String port, String jvm, String pid, String time, + String gitCommit) { + + String banner = "\n" + "------------------------------------------------------------\n" + + " ✨AI Online Judge✨ - " + appName + "\n" + " Environment : " + profiles + "\n" + " Port : " + + port + "\n" + " Git Commit : " + gitCommit + "\n" + " JVM : " + jvm + "\n" + + " PID : " + pid + "\n" + " Started At : " + time + "\n" + + "------------------------------------------------------------\n"; + System.out.println(banner); + } - String banner = "\n" + "------------------------------------------------------------\n" - + " ✨AI Online Judge✨ - " + appName + "\n" + " Environment : " + profiles + "\n" + " Port : " - + port + "\n" + " Git Commit : " + gitCommit + "\n" + " JVM : " + jvm + "\n" - + " PID : " + pid + "\n" + " Started At : " + time + "\n" - + "------------------------------------------------------------\n"; - System.out.println(banner); - } } diff --git a/aioj-backend-common/aioj-backend-common-core/src/main/java/cn/meowrain/aioj/backend/framework/core/banner/config/AIOJBannerAutoConfiguration.java b/aioj-backend-common/aioj-backend-common-core/src/main/java/cn/meowrain/aioj/backend/framework/core/banner/config/AIOJBannerAutoConfiguration.java index 7f1bc17..13b01ad 100644 --- a/aioj-backend-common/aioj-backend-common-core/src/main/java/cn/meowrain/aioj/backend/framework/core/banner/config/AIOJBannerAutoConfiguration.java +++ b/aioj-backend-common/aioj-backend-common-core/src/main/java/cn/meowrain/aioj/backend/framework/core/banner/config/AIOJBannerAutoConfiguration.java @@ -7,8 +7,10 @@ import org.springframework.core.env.Environment; @AutoConfiguration public class AIOJBannerAutoConfiguration { - @Bean - public BannerApplicationRunner bannerApplicationRunner(Environment env) { - return new BannerApplicationRunner(env); - } + + @Bean + public BannerApplicationRunner bannerApplicationRunner(Environment env) { + return new BannerApplicationRunner(env); + } + } diff --git a/aioj-backend-common/aioj-backend-common-core/src/main/java/cn/meowrain/aioj/backend/framework/core/constants/ServiceNameConstants.java b/aioj-backend-common/aioj-backend-common-core/src/main/java/cn/meowrain/aioj/backend/framework/core/constants/ServiceNameConstants.java index d0b440f..a4e7ca1 100644 --- a/aioj-backend-common/aioj-backend-common-core/src/main/java/cn/meowrain/aioj/backend/framework/core/constants/ServiceNameConstants.java +++ b/aioj-backend-common/aioj-backend-common-core/src/main/java/cn/meowrain/aioj/backend/framework/core/constants/ServiceNameConstants.java @@ -4,18 +4,20 @@ package cn.meowrain.aioj.backend.framework.core.constants; * 全局服务名称 */ public class ServiceNameConstants { - /** - * 用户服务 SERVICE NAME - */ - public static final String USER_SERVICE = "user-service"; - /** - * 认证服务 SERVICE NAME - */ - public static final String AUTH_SERVICE = "auth-service"; + /** + * 用户服务 SERVICE NAME + */ + public static final String USER_SERVICE = "user-service"; + + /** + * 认证服务 SERVICE NAME + */ + public static final String AUTH_SERVICE = "auth-service"; + + /** + * UPMS模块 + */ + public static final String UPMS_SERVICE = "upms-service"; - /** - * UPMS模块 - */ - public static final String UPMS_SERVICE = "upms-service"; } diff --git a/aioj-backend-common/aioj-backend-common-core/src/main/java/cn/meowrain/aioj/backend/framework/core/exception/handler/GlobalExceptionHandler.java b/aioj-backend-common/aioj-backend-common-core/src/main/java/cn/meowrain/aioj/backend/framework/core/exception/handler/GlobalExceptionHandler.java index f4b6232..b6e01f5 100644 --- a/aioj-backend-common/aioj-backend-common-core/src/main/java/cn/meowrain/aioj/backend/framework/core/exception/handler/GlobalExceptionHandler.java +++ b/aioj-backend-common/aioj-backend-common-core/src/main/java/cn/meowrain/aioj/backend/framework/core/exception/handler/GlobalExceptionHandler.java @@ -5,6 +5,8 @@ import cn.meowrain.aioj.backend.framework.core.web.Result; import cn.meowrain.aioj.backend.framework.core.web.Results; import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; import org.springframework.util.StringUtils; import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; @@ -12,6 +14,8 @@ import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.stereotype.Component; + import java.util.List; /** @@ -19,12 +23,18 @@ import java.util.List; */ @Slf4j @RestControllerAdvice +@Order(Ordered.HIGHEST_PRECEDENCE) public class GlobalExceptionHandler { + // 加这个构造器,启动看日志 + public GlobalExceptionHandler() { + System.out.println("===== 自定义异常处理器已加载 ====="); + } /** * 捕获所有参数错误,然后统一捕获并且抛出 + * * @param request {@link HttpServletRequest} - * @param ex {@link org.springframework.validation.method.MethodValidationException} + * @param ex {@link org.springframework.validation.method.MethodValidationException} * @return {@link Result} */ @ExceptionHandler(value = MethodArgumentNotValidException.class) @@ -32,9 +42,9 @@ public class GlobalExceptionHandler { BindingResult bindingResult = ex.getBindingResult(); // 收集所有错误字段 List errorMessages = bindingResult.getFieldErrors() - .stream() - .map(FieldError::getDefaultMessage) - .toList(); + .stream() + .map(FieldError::getDefaultMessage) + .toList(); String exceptionMessage = String.join(",", errorMessages); log.error("[{}] {} [ex] {}", request.getMethod(), getUrl(request), exceptionMessage); return Results.paramsValidFailure(); @@ -42,8 +52,9 @@ public class GlobalExceptionHandler { /** * 抽象异常捕获其 + * * @param request {@link HttpServletRequest} - * @param ex {@link AbstractException} + * @param ex {@link AbstractException} * @return {@link Result} */ @ExceptionHandler(value = { AbstractException.class }) @@ -74,6 +85,7 @@ public class GlobalExceptionHandler { /** * 获取请求URL + * * @param request {@link HttpServletRequest} * @return String */ diff --git a/aioj-backend-common/aioj-backend-common-core/src/main/java/cn/meowrain/aioj/backend/framework/core/jackson/JavaTimeModule.java b/aioj-backend-common/aioj-backend-common-core/src/main/java/cn/meowrain/aioj/backend/framework/core/jackson/JavaTimeModule.java index 92ca9b0..69b1f4b 100644 --- a/aioj-backend-common/aioj-backend-common-core/src/main/java/cn/meowrain/aioj/backend/framework/core/jackson/JavaTimeModule.java +++ b/aioj-backend-common/aioj-backend-common-core/src/main/java/cn/meowrain/aioj/backend/framework/core/jackson/JavaTimeModule.java @@ -10,7 +10,6 @@ import java.io.Serial; import java.time.*; import java.time.format.DateTimeFormatter; - public class JavaTimeModule extends SimpleModule { @Serial @@ -19,7 +18,7 @@ public class JavaTimeModule extends SimpleModule { /** * JavaTimeModule构造函数,用于初始化时间序列化和反序列化规则 */ - public JavaTimeModule() { + public JavaTimeModule() { super(PackageVersion.VERSION); // ======================= 时间序列化规则 =============================== diff --git a/aioj-backend-common/aioj-backend-common-core/src/main/java/cn/meowrain/aioj/backend/framework/core/utils/SpringContextHolder.java b/aioj-backend-common/aioj-backend-common-core/src/main/java/cn/meowrain/aioj/backend/framework/core/utils/SpringContextHolder.java index 126b038..587d180 100644 --- a/aioj-backend-common/aioj-backend-common-core/src/main/java/cn/meowrain/aioj/backend/framework/core/utils/SpringContextHolder.java +++ b/aioj-backend-common/aioj-backend-common-core/src/main/java/cn/meowrain/aioj/backend/framework/core/utils/SpringContextHolder.java @@ -13,65 +13,69 @@ import org.springframework.stereotype.Service; @Slf4j @Service -public class SpringContextHolder implements ApplicationContextAware, EnvironmentAware, DisposableBean { - private static ApplicationContext applicationContext = null; +public class SpringContextHolder implements ApplicationContextAware, EnvironmentAware, DisposableBean { - private static Environment environment = null; - /** - * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型. - */ - public static T getBean(String name) { - return (T) applicationContext.getBean(name); - } + private static ApplicationContext applicationContext = null; - /** - * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型. - */ - public static T getBean(Class requiredType) { - return applicationContext.getBean(requiredType); - } + private static Environment environment = null; - @Override - @SneakyThrows - public void destroy() throws Exception { - clearHolder(); - } + /** + * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型. + */ + public static T getBean(String name) { + return (T) applicationContext.getBean(name); + } - @Override - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - SpringContextHolder.applicationContext = applicationContext; - } + /** + * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型. + */ + public static T getBean(Class requiredType) { + return applicationContext.getBean(requiredType); + } - @Override - public void setEnvironment(Environment environment) { - SpringContextHolder.environment = environment; - } + @Override + @SneakyThrows + public void destroy() throws Exception { + clearHolder(); + } - /** - * 清除SpringContextHolder中的ApplicationContext为Null. - */ - public static void clearHolder() { - if (log.isDebugEnabled()) { - log.debug("清除SpringContextHolder中的ApplicationContext:" + applicationContext); - } - applicationContext = null; - } - /** - * 发布事件 - * @param event - */ - public static void publishEvent(ApplicationEvent event) { - if (applicationContext == null) { - return; - } - applicationContext.publishEvent(event); - } - /** - * 是否是微服务 - * @return boolean - */ - public static boolean isMicro() { - return environment.getProperty("spring.cloud.nacos.discovery.enabled", Boolean.class, true); - } + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + SpringContextHolder.applicationContext = applicationContext; + } + + @Override + public void setEnvironment(Environment environment) { + SpringContextHolder.environment = environment; + } + + /** + * 清除SpringContextHolder中的ApplicationContext为Null. + */ + public static void clearHolder() { + if (log.isDebugEnabled()) { + log.debug("清除SpringContextHolder中的ApplicationContext:" + applicationContext); + } + applicationContext = null; + } + + /** + * 发布事件 + * @param event + */ + public static void publishEvent(ApplicationEvent event) { + if (applicationContext == null) { + return; + } + applicationContext.publishEvent(event); + } + + /** + * 是否是微服务 + * @return boolean + */ + public static boolean isMicro() { + return environment.getProperty("spring.cloud.nacos.discovery.enabled", Boolean.class, true); + } } diff --git a/aioj-backend-common/aioj-backend-common-feign/src/main/java/cn/meowrain/aioj/backend/framework/feign/FeignAutoConfiguration.java b/aioj-backend-common/aioj-backend-common-feign/src/main/java/cn/meowrain/aioj/backend/framework/feign/FeignAutoConfiguration.java index 7f34be2..25f160f 100644 --- a/aioj-backend-common/aioj-backend-common-feign/src/main/java/cn/meowrain/aioj/backend/framework/feign/FeignAutoConfiguration.java +++ b/aioj-backend-common/aioj-backend-common-feign/src/main/java/cn/meowrain/aioj/backend/framework/feign/FeignAutoConfiguration.java @@ -4,4 +4,5 @@ import org.springframework.boot.autoconfigure.AutoConfiguration; @AutoConfiguration public class FeignAutoConfiguration { + } diff --git a/aioj-backend-common/aioj-backend-common-feign/src/main/java/cn/meowrain/aioj/backend/framework/feign/annotation/EnableAIOJFeignClients.java b/aioj-backend-common/aioj-backend-common-feign/src/main/java/cn/meowrain/aioj/backend/framework/feign/annotation/EnableAIOJFeignClients.java index 86290f6..10336f7 100644 --- a/aioj-backend-common/aioj-backend-common-feign/src/main/java/cn/meowrain/aioj/backend/framework/feign/annotation/EnableAIOJFeignClients.java +++ b/aioj-backend-common/aioj-backend-common-feign/src/main/java/cn/meowrain/aioj/backend/framework/feign/annotation/EnableAIOJFeignClients.java @@ -9,5 +9,7 @@ import java.lang.annotation.*; @Documented @EnableFeignClients public @interface EnableAIOJFeignClients { - String[] basePackages() default {}; + + String[] basePackages() default {}; + } diff --git a/aioj-backend-common/aioj-backend-common-log/src/main/java/cn/meowrain/aioj/backend/framework/log/aspect/SysLogAspect.java b/aioj-backend-common/aioj-backend-common-log/src/main/java/cn/meowrain/aioj/backend/framework/log/aspect/SysLogAspect.java index 99b6917..cf18321 100644 --- a/aioj-backend-common/aioj-backend-common-log/src/main/java/cn/meowrain/aioj/backend/framework/log/aspect/SysLogAspect.java +++ b/aioj-backend-common/aioj-backend-common-log/src/main/java/cn/meowrain/aioj/backend/framework/log/aspect/SysLogAspect.java @@ -20,6 +20,7 @@ import org.springframework.expression.EvaluationContext; @Slf4j @RequiredArgsConstructor public class SysLogAspect { + /** * 环绕通知方法,用于处理系统日志记录 * @param point 连接点对象 @@ -29,7 +30,7 @@ public class SysLogAspect { */ @Around("@annotation(sysLog)") @SneakyThrows - public Object around(ProceedingJoinPoint point,SysLog sysLog) { + public Object around(ProceedingJoinPoint point, SysLog sysLog) { String strClassName = point.getTarget().getClass().getName(); String strMethodName = point.getSignature().getName(); log.debug("[类名]:{},[方法]:{}", strClassName, strMethodName); @@ -76,4 +77,5 @@ public class SysLogAspect { return obj; } + } diff --git a/aioj-backend-common/aioj-backend-common-log/src/main/java/cn/meowrain/aioj/backend/framework/log/config/AIOJLogPropertiesConfiguration.java b/aioj-backend-common/aioj-backend-common-log/src/main/java/cn/meowrain/aioj/backend/framework/log/config/AIOJLogPropertiesConfiguration.java index dd90fd4..940e5f5 100644 --- a/aioj-backend-common/aioj-backend-common-log/src/main/java/cn/meowrain/aioj/backend/framework/log/config/AIOJLogPropertiesConfiguration.java +++ b/aioj-backend-common/aioj-backend-common-log/src/main/java/cn/meowrain/aioj/backend/framework/log/config/AIOJLogPropertiesConfiguration.java @@ -24,11 +24,10 @@ public class AIOJLogPropertiesConfiguration { */ private Integer maxLength = 20000; - /** - * 放行字段,password,mobile,idcard,phone - */ - @Value("${log.exclude-fields:password,mobile,idcard,phone}") - private List excludeFields; - + /** + * 放行字段,password,mobile,idcard,phone + */ + @Value("${log.exclude-fields:password,mobile,idcard,phone}") + private List excludeFields; } diff --git a/aioj-backend-common/aioj-backend-common-log/src/main/java/cn/meowrain/aioj/backend/framework/log/event/SysLogEvent.java b/aioj-backend-common/aioj-backend-common-log/src/main/java/cn/meowrain/aioj/backend/framework/log/event/SysLogEvent.java index 0934408..7770a36 100644 --- a/aioj-backend-common/aioj-backend-common-log/src/main/java/cn/meowrain/aioj/backend/framework/log/event/SysLogEvent.java +++ b/aioj-backend-common/aioj-backend-common-log/src/main/java/cn/meowrain/aioj/backend/framework/log/event/SysLogEvent.java @@ -1,6 +1,5 @@ package cn.meowrain.aioj.backend.framework.log.event; - import cn.meowrain.aioj.backend.upms.api.entity.SysLog; import org.springframework.context.ApplicationEvent; diff --git a/aioj-backend-common/aioj-backend-common-log/src/main/java/cn/meowrain/aioj/backend/framework/log/event/SysLogListener.java b/aioj-backend-common/aioj-backend-common-log/src/main/java/cn/meowrain/aioj/backend/framework/log/event/SysLogListener.java index d0c4618..0d2a879 100644 --- a/aioj-backend-common/aioj-backend-common-log/src/main/java/cn/meowrain/aioj/backend/framework/log/event/SysLogListener.java +++ b/aioj-backend-common/aioj-backend-common-log/src/main/java/cn/meowrain/aioj/backend/framework/log/event/SysLogListener.java @@ -38,38 +38,40 @@ public class SysLogListener implements InitializingBean { SysLog sysLog = new SysLog(); BeanUtils.copyProperties(source, sysLog); - // json 格式刷参数放在异步中处理,提升性能 - if (Objects.nonNull(source.getBody())) { - String params = objectMapper.writeValueAsString(source.getBody()); - sysLog.setParams(StrUtil.subPre(params, logProperties.getMaxLength())); - } + // json 格式刷参数放在异步中处理,提升性能 + if (Objects.nonNull(source.getBody())) { + String params = objectMapper.writeValueAsString(source.getBody()); + sysLog.setParams(StrUtil.subPre(params, logProperties.getMaxLength())); + } - remoteLogService.saveLog(sysLog); + remoteLogService.saveLog(sysLog); } - /** - * 在 Bean 初始化后执行,用于初始化 ObjectMapper - * @throws Exception - */ + /** + * 在 Bean 初始化后执行,用于初始化 ObjectMapper + * @throws Exception + */ @Override public void afterPropertiesSet() throws Exception { - //给 ObjectMapper 添加 MixIn(用于过滤字段) - objectMapper.addMixIn(Object.class, PropertyFilterMixIn.class); - String[] ignorableFieldNames = logProperties.getExcludeFields().toArray(new String[0]); + // 给 ObjectMapper 添加 MixIn(用于过滤字段) + objectMapper.addMixIn(Object.class, PropertyFilterMixIn.class); + String[] ignorableFieldNames = logProperties.getExcludeFields().toArray(new String[0]); + + FilterProvider filters = new SimpleFilterProvider().addFilter("filter properties by name", + SimpleBeanPropertyFilter.serializeAllExcept(ignorableFieldNames)); + objectMapper.setFilterProvider(filters); + objectMapper.registerModule(new JavaTimeModule()); + } + + /** + * 属性过滤混合类:用于通过名称过滤属性 + * + * @author lengleng + * @date 2025/05/31 + */ + @JsonFilter("filter properties by name") + class PropertyFilterMixIn { - FilterProvider filters = new SimpleFilterProvider().addFilter("filter properties by name", - SimpleBeanPropertyFilter.serializeAllExcept(ignorableFieldNames)); - objectMapper.setFilterProvider(filters); - objectMapper.registerModule(new JavaTimeModule()); } - /** - * 属性过滤混合类:用于通过名称过滤属性 - * - * @author lengleng - * @date 2025/05/31 - */ - @JsonFilter("filter properties by name") - class PropertyFilterMixIn { - } } diff --git a/aioj-backend-common/aioj-backend-common-log/src/main/java/cn/meowrain/aioj/backend/framework/log/utils/SysLogUtils.java b/aioj-backend-common/aioj-backend-common-log/src/main/java/cn/meowrain/aioj/backend/framework/log/utils/SysLogUtils.java index 1e146f0..1754f37 100644 --- a/aioj-backend-common/aioj-backend-common-log/src/main/java/cn/meowrain/aioj/backend/framework/log/utils/SysLogUtils.java +++ b/aioj-backend-common/aioj-backend-common-log/src/main/java/cn/meowrain/aioj/backend/framework/log/utils/SysLogUtils.java @@ -29,14 +29,13 @@ import java.util.Objects; public final class SysLogUtils { - /** * 获取系统日志事件源 * @return 系统日志事件源对象 */ - public static SysLogEventSource getSysLog() { + public static SysLogEventSource getSysLog() { HttpServletRequest request = ((ServletRequestAttributes) Objects - .requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest(); + .requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest(); SysLogEventSource sysLog = new SysLogEventSource(); sysLog.setLogType(LogTypeEnum.NORMAL.getType()); sysLog.setRequestUri(URLUtil.getPath(request.getRequestURI())); @@ -47,7 +46,8 @@ public final class SysLogUtils { sysLog.setServiceId(SpringUtil.getProperty("spring.application.name")); // get 参数脱敏 - AIOJLogPropertiesConfiguration logProperties = SpringContextHolder.getBean(AIOJLogPropertiesConfiguration.class); + AIOJLogPropertiesConfiguration logProperties = SpringContextHolder + .getBean(AIOJLogPropertiesConfiguration.class); Map paramsMap = MapUtil.removeAny(new HashMap<>(request.getParameterMap()), ArrayUtil.toArray(logProperties.getExcludeFields(), String.class)); sysLog.setParams(HttpUtil.toParams(paramsMap)); @@ -74,7 +74,7 @@ public final class SysLogUtils { * @param 返回泛型 * @return 参数值 */ - public static T getValue(EvaluationContext context, String key, Class clazz) { + public static T getValue(EvaluationContext context, String key, Class clazz) { SpelExpressionParser spelExpressionParser = new SpelExpressionParser(); Expression expression = spelExpressionParser.parseExpression(key); return expression.getValue(context, clazz); diff --git a/aioj-backend-gateway/pom.xml b/aioj-backend-gateway/pom.xml index 1506346..657e0a8 100644 --- a/aioj-backend-gateway/pom.xml +++ b/aioj-backend-gateway/pom.xml @@ -1,7 +1,7 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 cn.meowrain @@ -50,17 +50,14 @@ com.alibaba.csp sentinel-datasource-extension - + com.github.xiaoymin knife4j-openapi3-jakarta-spring-boot-starter + 4.5.0 - - org.springframework.cloud - spring-cloud-starter-bootstrap - 4.3.0 - cn.meowrain aioj-backend-common-core 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 6f3f861..6de7761 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 @@ -19,7 +19,6 @@ 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; @@ -27,7 +26,6 @@ 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 @@ -44,15 +42,6 @@ public class AuthGlobalFilter implements GlobalFilter, Ordered { 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) { @@ -60,6 +49,7 @@ public class AuthGlobalFilter implements GlobalFilter, Ordered { String path = request.getURI().getPath(); log.info("Auth filter processing request: {}", path); + log.info("Loaded white list from config: {}", gatewayPropertiesConfiguration.getWhiteList()); // 检查是否在白名单中 if (isWhiteListPath(path)) { @@ -97,13 +87,6 @@ public class AuthGlobalFilter implements GlobalFilter, Ordered { * 检查路径是否在白名单中 */ 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()) { @@ -122,7 +105,7 @@ public class AuthGlobalFilter implements GlobalFilter, Ordered { private Mono validateToken(String token) { return webClientBuilder.build() .post() - .uri("lb://auth-service/api/v1/auth/validate") + .uri("lb://auth-service/v1/auth/validate") .header(HttpHeaders.AUTHORIZATION, "Bearer " + token) .contentType(MediaType.APPLICATION_JSON) .retrieve() diff --git a/aioj-backend-gateway/src/main/resources/application-dev.yml b/aioj-backend-gateway/src/main/resources/application-dev.yml index f3af779..f6453a4 100644 --- a/aioj-backend-gateway/src/main/resources/application-dev.yml +++ b/aioj-backend-gateway/src/main/resources/application-dev.yml @@ -8,11 +8,59 @@ spring: nacos: discovery: enabled: true - register-enabled: true + register-enabled: false server-addr: 10.0.0.10:8848 username: nacos password: nacos + config: + enabled: false + import-check: + enabled: false + gateway: + # Gateway 发现定位器配置 + server: + webflux: + discovery: + locator: + enabled: true + lower-case-service-id: true + loadbalancer: + nacos: + enabled: true + retry: + enabled: true + max-retries-on-same-service-instance: 1 + max-retries-on-next-service-instance: 2 + cache: + enabled: true + ttl: 35s + health-check: + initial-delay: 0ms + interval: 30s + + +aioj-backend-gateway: + # 白名单配置 + white-list: + - /api/v1/auth/login + - /api/v1/auth/register + - /api/v1/auth/refresh + - /api/v1/user/register + - /api/v1/user/info + - /api/v1/question/list + - /api/v1/question/detail/** + - /actuator/health + - /swagger-ui/** logging: level: - root: INFO \ No newline at end of file + root: INFO + # Nacos 相关日志 + com.alibaba.nacos: INFO + com.alibaba.cloud.nacos: DEBUG + # LoadBalancer 日志 + org.springframework.cloud.loadbalancer: DEBUG + # Gateway 日志 + org.springframework.cloud.gateway: DEBUG + # 自定义过滤器日志 + cn.meowrain.aioj.backend.gateway: DEBUG diff --git a/aioj-backend-gateway/src/main/resources/application.yml b/aioj-backend-gateway/src/main/resources/application.yml index 00539f5..76a2fd5 100644 --- a/aioj-backend-gateway/src/main/resources/application.yml +++ b/aioj-backend-gateway/src/main/resources/application.yml @@ -1,29 +1,50 @@ server: port: 8085 + error: + include-stacktrace: never + spring: + profiles: + active: @env@ cloud: gateway: server: webflux: routes: - id: auth-service - uri: lb://auth-service/api + uri: lb://auth-service predicates: - Path=/api/v1/auth/** + filters: + - name: Retry + args: + retries: 3 + statuses: BAD_GATEWAY,SERVICE_UNAVAILABLE + backoff: + firstBackoff: 50ms + maxBackoff: 500ms - id: user-service - uri: lb://user-service/api + uri: lb://user-service predicates: - Path=/api/v1/user/** + filters: + - name: Retry + args: + retries: 3 + statuses: BAD_GATEWAY,SERVICE_UNAVAILABLE + backoff: + firstBackoff: 50ms + maxBackoff: 500ms + # 设置应用启动后的就绪探针 + lifecycle: + timeout-per-shutdown-phase: 30s aioj-backend-gateway: # 白名单配置 white-list: - /api/v1/auth/login - - /api/v1/auth/register + - /api/v1/user/register - /api/v1/auth/refresh - - /api/v1/user/info - - /api/v1/question/list - - /api/v1/question/detail/** - /actuator/health - /swagger-ui/** - /v3/api-docs/** diff --git a/aioj-backend-gateway/src/main/resources/bootstrap.yml b/aioj-backend-gateway/src/main/resources/bootstrap.yml deleted file mode 100644 index 80e4c6f..0000000 --- a/aioj-backend-gateway/src/main/resources/bootstrap.yml +++ /dev/null @@ -1,15 +0,0 @@ -spring: - application: - name: aioj-gateway - cloud: - nacos: - discovery: - server-addr: 10.0.0.10:8848 - username: nacos - password: nacos - config: - server-addr: 10.0.0.10:8848 - username: nacos - password: nacos - file-extension: yaml - group: DEFAULT_GROUP \ No newline at end of file diff --git a/aioj-backend-upms/aioj-backend-upms-api/src/main/java/cn/meowrain/aioj/backend/upms/api/entity/SysLog.java b/aioj-backend-upms/aioj-backend-upms-api/src/main/java/cn/meowrain/aioj/backend/upms/api/entity/SysLog.java index 2a1ca44..d4c7cc3 100644 --- a/aioj-backend-upms/aioj-backend-upms-api/src/main/java/cn/meowrain/aioj/backend/upms/api/entity/SysLog.java +++ b/aioj-backend-upms/aioj-backend-upms-api/src/main/java/cn/meowrain/aioj/backend/upms/api/entity/SysLog.java @@ -19,7 +19,6 @@ package cn.meowrain.aioj.backend.upms.api.entity; - import com.baomidou.mybatisplus.annotation.*; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; @@ -41,14 +40,14 @@ import java.time.LocalDateTime; @Schema(description = "日志") public class SysLog implements Serializable { - @Serial + @Serial private static final long serialVersionUID = 1L; /** * 编号 */ @TableId(type = IdType.ASSIGN_ID) -// @ExcelProperty("日志编号") + // @ExcelProperty("日志编号") @Schema(description = "日志编号") private Long id; @@ -56,7 +55,7 @@ public class SysLog implements Serializable { * 日志类型 */ @NotBlank(message = "日志类型不能为空") -// @ExcelProperty("日志类型(0-正常 9-错误)") + // @ExcelProperty("日志类型(0-正常 9-错误)") @Schema(description = "日志类型") private String logType; @@ -64,14 +63,14 @@ public class SysLog implements Serializable { * 日志标题 */ @NotBlank(message = "日志标题不能为空") -// @ExcelProperty("日志标题") + // @ExcelProperty("日志标题") @Schema(description = "日志标题") private String title; /** * 创建者 */ -// @ExcelProperty("创建人") + // @ExcelProperty("创建人") @TableField(fill = FieldFill.INSERT) @Schema(description = "创建人") private String createBy; @@ -79,7 +78,7 @@ public class SysLog implements Serializable { /** * 创建时间 */ -// @ExcelProperty("创建时间") + // @ExcelProperty("创建时间") @TableField(fill = FieldFill.INSERT) @Schema(description = "创建时间") private LocalDateTime createTime; @@ -87,7 +86,7 @@ public class SysLog implements Serializable { /** * 更新时间 */ -// @ExcelIgnore + // @ExcelIgnore @TableField(fill = FieldFill.UPDATE) @Schema(description = "更新时间") private LocalDateTime updateTime; @@ -95,7 +94,7 @@ public class SysLog implements Serializable { /** * 操作IP地址 */ -// @ExcelProperty("操作ip地址") + // @ExcelProperty("操作ip地址") @Schema(description = "操作ip地址") private String remoteAddr; @@ -108,42 +107,42 @@ public class SysLog implements Serializable { /** * 请求URI */ -// @ExcelProperty("浏览器") + // @ExcelProperty("浏览器") @Schema(description = "请求uri") private String requestUri; /** * 操作方式 */ -// @ExcelProperty("操作方式") + // @ExcelProperty("操作方式") @Schema(description = "操作方式") private String method; /** * 操作提交的数据 */ -// @ExcelProperty("提交数据") + // @ExcelProperty("提交数据") @Schema(description = "提交数据") private String params; /** * 执行时间 */ -// @ExcelProperty("执行时间") + // @ExcelProperty("执行时间") @Schema(description = "方法执行时间") private Long time; /** * 异常信息 */ -// @ExcelProperty("异常信息") + // @ExcelProperty("异常信息") @Schema(description = "异常信息") private String exception; /** * 服务ID */ -// @ExcelProperty("应用标识") + // @ExcelProperty("应用标识") @Schema(description = "应用标识") private String serviceId; @@ -151,7 +150,7 @@ public class SysLog implements Serializable { * 删除标记 */ @TableLogic -// @ExcelIgnore + // @ExcelIgnore @TableField(fill = FieldFill.INSERT) @Schema(description = "删除标记,1:已删除,0:正常") private String delFlag; diff --git a/aioj-backend-upms/aioj-backend-upms-api/src/main/java/cn/meowrain/aioj/backend/upms/api/feign/RemoteLogService.java b/aioj-backend-upms/aioj-backend-upms-api/src/main/java/cn/meowrain/aioj/backend/upms/api/feign/RemoteLogService.java index 41c5784..160bba7 100644 --- a/aioj-backend-upms/aioj-backend-upms-api/src/main/java/cn/meowrain/aioj/backend/upms/api/feign/RemoteLogService.java +++ b/aioj-backend-upms/aioj-backend-upms-api/src/main/java/cn/meowrain/aioj/backend/upms/api/feign/RemoteLogService.java @@ -10,12 +10,14 @@ import org.springframework.web.bind.annotation.RequestBody; @FeignClient(contextId = "remoteLogService", value = ServiceNameConstants.UPMS_SERVICE) public interface RemoteLogService { - /** - * 保存日志 (异步多线程调用,无token) - * @param sysLog 日志实体 - * @return succes、false - */ - @NoToken - @PostMapping("/log/save") - Result saveLog(@RequestBody SysLog sysLog); + + /** + * 保存日志 (异步多线程调用,无token) + * @param sysLog 日志实体 + * @return succes、false + */ + @NoToken + @PostMapping("/log/save") + Result saveLog(@RequestBody SysLog sysLog); + } diff --git a/aioj-backend-user-service/pom.xml b/aioj-backend-user-service/pom.xml index 936c3a0..cae9988 100644 --- a/aioj-backend-user-service/pom.xml +++ b/aioj-backend-user-service/pom.xml @@ -1,7 +1,7 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 cn.meowrain @@ -20,7 +20,12 @@ cn.meowrain - aioj-backend-common-starter + aioj-backend-common-core + 1.0-SNAPSHOT + + + cn.meowrain + aioj-backend-common-log 1.0-SNAPSHOT @@ -60,21 +65,15 @@ spring-boot-starter-test test - - org.springframework.boot - spring-boot-devtools - runtime - true - + + + + + + com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery - - - cn.meowrain - aioj-backend-common-core - 1.0-SNAPSHOT - \ No newline at end of file diff --git a/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/config/FrameworkConfiguration.java b/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/config/FrameworkConfiguration.java index a65791b..a00a3e5 100644 --- a/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/config/FrameworkConfiguration.java +++ b/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/config/FrameworkConfiguration.java @@ -5,7 +5,8 @@ import org.springframework.context.annotation.ComponentScans; import org.springframework.context.annotation.Configuration; @Configuration -@ComponentScans({ @ComponentScan("cn.meowrain.aioj.backend.framework.core.banner") }) +// @ComponentScans({ +// @ComponentScan("cn.meowrain.aioj.backend.framework.core.banner") }) public class FrameworkConfiguration { } diff --git a/aioj-backend-user-service/src/main/resources/application.yml b/aioj-backend-user-service/src/main/resources/application.yml index 068e481..4b017f9 100644 --- a/aioj-backend-user-service/src/main/resources/application.yml +++ b/aioj-backend-user-service/src/main/resources/application.yml @@ -7,6 +7,8 @@ server: port: 10010 servlet: context-path: /api + error: + include-stacktrace: never springdoc: api-docs: enabled: true diff --git a/docs/auth-api.md b/docs/auth-api.md new file mode 100644 index 0000000..ff72f52 --- /dev/null +++ b/docs/auth-api.md @@ -0,0 +1,243 @@ +# AIOJ 认证服务 API 文档 + +## 概述 + +AIOJ认证服务提供JWT(JSON Web Token)为基础的用户认证和授权功能。该服务负责用户登录、令牌生成、令牌刷新和令牌验证。 + +**基础信息:** +- 服务名称: auth-service +- 基础路径: `/api` +- API版本: v1 +- 认证方式: JWT Bearer Token +- 数据格式: JSON + +## JWT认证机制 + +### 访问令牌 (Access Token) +- 有效期: 15分钟 +- 用途: 访问受保护的API接口 +- 格式: Bearer Token + +### 刷新令牌 (Refresh Token) +- 有效期: 7天 +- 用途: 获取新的Access Token +- 存储位置: Redis + +### 请求头格式 +``` +Authorization: Bearer {access_token} +``` + +## API接口 + +### 1. 用户登录 +用户通过用户名和密码进行登录认证。 + +**接口信息:** +- URL: `POST /api/v1/auth/login` +- 描述: 用户登录获取访问令牌 +- 认证要求: 无需认证 + +**请求参数:** +```json +{ + "userAccount": "string", // 用户账号 + "userPassword": "string" // 用户密码 +} +``` + +**请求示例:** +```bash +curl -X POST http://localhost:10011/api/v1/auth/login \ + -H "Content-Type: application/json" \ + -d '{ + "userAccount": "admin", + "userPassword": "password123" + }' +``` + +**响应示例:** +```json +{ + "success": true, + "message": "操作成功", + "data": { + "id": 1, + "userAccount": "admin", + "unionId": null, + "accessToken": "eyJhbGciOiJIUzI1NiJ9...", + "refreshToken": "eyJhbGciOiJIUzI1NiJ9...", + "expire": null + } +} +``` + +### 2. 令牌刷新 +使用刷新令牌获取新的访问令牌。 + +**接口信息:** +- URL: `POST /api/v1/auth/refresh` +- 描述: 刷新访问令牌 +- 认证要求: 无需认证 + +**请求参数:** +``` +refreshToken=string // 刷新令牌 +``` + +**请求示例:** +```bash +curl -X POST "http://localhost:10011/api/v1/auth/refresh?refreshToken=eyJhbGciOiJIUzI1NiJ9..." +``` + +**响应示例:** +```json +{ + "success": true, + "message": "操作成功", + "data": { + "id": null, + "userAccount": null, + "unionId": null, + "accessToken": "eyJhbGciOiJIUzI1NiJ9...", + "refreshToken": "eyJhbGciOiJIUzI1NiJ9...", + "expire": null + } +} +``` + +### 3. 获取访问令牌 +简化版的登录接口,仅返回访问令牌。 + +**接口信息:** +- URL: `POST /api/v1/auth/auth` +- 描述: 获取访问令牌(简化版登录) +- 认证要求: 无需认证 + +**请求参数:** +```json +{ + "userAccount": "string", // 用户账号 + "userPassword": "string" // 用户密码 +} +``` + +**请求示例:** +```bash +curl -X POST http://localhost:10011/api/v1/auth/auth \ + -H "Content-Type: application/json" \ + -d '{ + "userAccount": "admin", + "userPassword": "password123" + }' +``` + +**响应示例:** +```json +{ + "success": true, + "message": "操作成功", + "data": "eyJhbGciOiJIUzI1NiJ9..." +} +``` + +### 4. 令牌验证 +验证访问令牌的有效性。 + +**接口信息:** +- URL: `POST /api/v1/auth/validate` +- 描述: 验证访问令牌 +- 认证要求: 可选的Authorization头 + +**请求头:** +``` +Authorization: Bearer {access_token} // 可选 +``` + +**请求示例:** +```bash +curl -X POST http://localhost:10011/api/v1/auth/validate \ + -H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9..." +``` + +**响应示例:** +```json +{ + "success": true, + "message": "操作成功", + "data": true +} +``` + +## 状态码说明 + +| 状态码 | 说明 | 描述 | +|--------|------|------| +| 200 | OK | 请求成功 | +| 400 | Bad Request | 请求参数错误 | +| 401 | Unauthorized | 未授权访问或令牌无效 | +| 403 | Forbidden | 禁止访问 | +| 500 | Internal Server Error | 服务器内部错误 | + +## 错误响应格式 + +```json +{ + "success": false, + "message": "错误描述", + "data": null +} +``` + +## 常见错误码 + +| 错误信息 | 可能原因 | 解决方案 | +|----------|----------|----------| +| "用户不存在或密码错误" | 用户账号或密码不正确 | 检查用户名和密码 | +| "Refresh Token 已过期" | 刷新令牌已过期 | 重新登录获取新令牌 | +| "Refresh Token 已失效" | 刷新令牌在Redis中不存在或已失效 | 重新登录获取新令牌 | +| "系统错误" | 服务间调用失败或系统异常 | 检查服务状态和网络连接 | + +## 配置信息 + +### JWT配置 +```yaml +jwt: + secret: "12345678901234567890123456789012" # 32字节密钥 + access-expire: 900000 # 15分钟(毫秒) + refresh-expire: 604800000 # 7天(毫秒) +``` + +### 服务配置 +```yaml +spring: + application: + name: auth-service +server: + port: 10011 + servlet: + context-path: /api +``` + +## 安全注意事项 + +1. **令牌存储**: Access Token应存储在内存或HttpOnly Cookie中 +2. **HTTPS传输**: 生产环境必须使用HTTPS协议 +3. **密钥管理**: JWT密钥应定期轮换,使用强密钥 +4. **令牌过期**: 合理设置令牌过期时间,平衡安全性和用户体验 +5. **错误处理**: 不要在错误信息中泄露敏感信息 + +## 使用流程 + +1. **用户登录**: 调用`/login`接口获取Access Token和Refresh Token +2. **API访问**: 在请求头中携带Access Token访问受保护接口 +3. **令牌刷新**: Access Token过期时使用Refresh Token获取新的Access Token +4. **令牌验证**: 可使用`/validate`接口验证Token有效性 + +## 联系信息 + +如有问题或建议,请联系开发团队。 + +--- +*文档版本: v1.0* +*更新时间: 2025-12-14* \ No newline at end of file diff --git a/pom.xml b/pom.xml index 984ec16..6a26b97 100644 --- a/pom.xml +++ b/pom.xml @@ -1,13 +1,13 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 org.springframework.boot spring-boot-starter-parent 3.5.7 - + cn.meowrain ai-oj @@ -66,7 +66,8 @@ pom import - + org.springframework.boot spring-boot-dependencies @@ -75,7 +76,8 @@ import - + org.springframework.cloud spring-cloud-dependencies @@ -96,10 +98,6 @@ src/main/resources - - application*.yml - application*.properties - true @@ -120,7 +118,8 @@ ${spring-boot.version} @@ -135,7 +134,8 @@ - + io.github.git-commit-id git-commit-id-maven-plugin @@ -168,13 +168,14 @@ true - registry.cn-shanghai.aliyuncs.com/all_lib/eclipse-temurin:17.0.10_7-jdk-jammy + + registry.cn-shanghai.aliyuncs.com/all_lib/eclipse-temurin:17.0.10_7-jdk-jammy - + 10.0.0.3/aioj/${project.artifactId}:${project.version} - + ${project.version} @@ -183,7 +184,8 @@ - ${project.build.directory}/${project.artifactId}-${project.version}.tar + + ${project.build.directory}/${project.artifactId}-${project.version}.tar