fix: 修复网关启动找不到服务的问题,修复jwt问题,修复自动导入失败问题。

This commit is contained in:
2025-12-14 15:02:24 +08:00
parent 4912e48922
commit 63d0528af4
33 changed files with 792 additions and 421 deletions

View File

@@ -0,0 +1,9 @@
{
"permissions": {
"allow": [
"Bash(mvn clean compile:*)",
"Bash(mvn spring-javaformat:apply)",
"Bash(cat:*)"
]
}
}

View File

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

13
.idea/dataSources.xml generated Normal file
View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="jdbc:mysql://10.0.0.10/aioj_dev [DEBUG]" group="UserServiceApplication" uuid="07fb0260-0355-4ed0-9bd2-c20d69288289">
<driver-ref>mysql.8</driver-ref>
<synchronize>true</synchronize>
<imported>true</imported>
<jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
<jdbc-url>jdbc:mysql://10.0.0.10/aioj_dev</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>

6
.idea/db-forest-config.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="db-tree-configuration">
<option name="data" value="1:0:UserServiceApplication&#10;----------------------------------------&#10;2:1:07fb0260-0355-4ed0-9bd2-c20d69288289&#10;" />
</component>
</project>

251
CLAUDE.md
View File

@@ -2,195 +2,122 @@
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. 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 The `aioj-backend-common` directory contains shared components and utilities used across all service modules:
```bash
# Build entire project
mvn clean install
# Build with specific environment profile 1. **aioj-backend-common-bom**
mvn clean install -P dev # Development (default) Bill of materials for centralized dependency management. This ensures consistent versions of all external libraries across all modules.
mvn clean install -P test # Testing
mvn clean install -P prod # Production
# Format code according to Spring standards 2. **aioj-backend-common-core**
mvn spring-javaformat:apply 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 3. **aioj-backend-common-feign**
mvn clean package jib:build 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 4. **aioj-backend-common-log**
./mvnw clean install 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 5. **aioj-backend-common-mybatis**
Each service runs on different ports: MyBatis ORM framework extensions:
- Gateway: 8085 - Auto-fill functionality for `createTime` and `updateTime` fields
- Other services: configured via Nacos - Pagination interceptor implementation
- MyBatis configuration auto-configuration classes
Run individual services from their respective directories: 6. **aioj-backend-common-starter**
```bash Auto-configuration starters for easily enabling common features in service modules.
cd aioj-backend-gateway
mvn spring-boot:run
# Or with specific profile
mvn spring-boot:run -Dspring.profiles.active=dev
```
### Database Setup ### Service Modules
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`
## Architecture Overview The service modules represent the individual microservices that make up the system:
### Microservices Architecture 1. **aioj-backend-auth**
The system consists of seven main services: 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) 2. **aioj-backend-gateway**
- API Gateway using Spring Cloud Gateway API gateway for request routing and filtering:
- Routes requests to appropriate services - Request routing to appropriate service modules
- Built with WebFlux for reactive programming - Authentication token validation before forwarding requests
- Rate limiting and request filtering mechanisms
2. **aioj-backend-auth** 3. **aioj-backend-judge-service**
- OAuth2 authentication and authorization service Code judge service (under development):
- Manages user credentials and tokens - Will handle code submission, compilation, and execution
- Support for multiple programming languages
- Test case validation and result return
3. **aioj-backend-user-service** 4. **aioj-backend-user-service**
- User management and profiles User management service:
- Handles registration, login, profile updates - User registration, profile management, and information retrieval
- Integrates with Redis for session management - User role and permission assignment
- Integration with the auth service for authentication
4. **aioj-backend-question-service** 5. **aioj-backend-question-service**
- Problem/question management Question bank service (under development):
- Handles problem storage and retrieval - Will manage programming problems, test cases, and problem categories
- Support for problem difficulty levels and tags
5. **aioj-backend-judge-service** - Integration with the judge service for problem submission
- Core OJ functionality for code execution
- Supports multiple programming languages
6. **aioj-backend-ai-service** 6. **aioj-backend-ai-service**
- AI integration for enhanced features AI-related functionality service (under development):
- Code analysis and automated feedback - Will provide AI-assisted features like problem recommendation, code analysis, etc.
7. **aioj-backend-upms** (User Permission Management System) 7. **aioj-backend-upms**
- Role-based access control User, permission, and menu management service:
- Permission management - Low-level user and permission management
- Menu and resource access control
- Integration with other services for authorization
### Common Modules ## Commonly Used Commands
- **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 ### Build
- **Build the entire project**: `mvn clean compile`
### Core Technologies - **Build with tests**: `mvn clean install`
- **Java 17** - **Build a single module**: `mvn clean compile -pl aioj-backend-auth`
- **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
### Code Formatting ### Code Formatting
- Uses Spring JavaFormat plugin for consistent code style - **Format code**: `mvn spring-javaformat:apply`
- IDE plugin available: https://repo1.maven.org/maven2/io/spring/javaformat/spring-javaformat-intellij-idea-plugin - **Check code format**: `mvn spring-javaformat:check`
- Run `mvn spring-javaformat:apply` before commits
### Docker Integration ### Testing
- Jib plugin configured for container builds - **Run all tests**: `mvn test`
- Target registry: `10.0.0.3/aioj/{service-name}:{version}` - **Run tests for a single module**: `mvn test -pl aioj-backend-user-service`
- JVM memory configured: -Xms512m -Xmx512m
### Service Communication ### Running Services
- Uses OpenFeign for inter-service communication - **Run a service locally**: Use Spring Boot's `Application` class directly in IDE or use `mvn spring-boot:run -pl <module-name>`
- Load balancing with Spring Cloud LoadBalancer - **Example**: `mvn spring-boot:run -pl aioj-backend-auth`
- Circuit breaking with Sentinel
### Logging ## Architecture Highlights
- Custom logging implementation in `aioj-backend-common-log`
- Integrates with Spring Security for context logging
- Uses Hutool utilities for enhanced logging
## Testing - **Authentication**: JWT-based authentication implemented in `aioj-backend-auth` with `JwtAuthenticationFilter`
- Spring Boot Test framework included - **Logging**: Aspect-oriented logging with `SysLogAspect` in `aioj-backend-common-log`
- Spring Security Test for authentication testing - **Database**: MyBatis with auto-fill for create/update times implemented in `common-mybatis`
- Test structure is evolving - check individual modules for test coverage - **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 - **Gateway Application**: `aioj-backend-gateway/src/main/java/cn/meowrain/aioj/backend/gateway/AIOJGatewayApplication.java`
2. **Database Setup**: Run the database creation script before first run - **Auth Application**: `aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/AIOJAuthApplication.java`
3. **Port Configuration**: Gateway runs on 8085, other services are dynamically registered - **User Service Application**: `aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/UserServiceApplication.java`
4. **Environment Profiles**: Default is `dev` - use appropriate profiles for different environments
5. **Configuration Management**: Most configuration is externalized to Nacos - check Nacos for service-specific settings

View File

@@ -1,5 +1,7 @@
package cn.meowrain.aioj.backend.auth.config; 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.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager; 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.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfiguration { public class SecurityConfiguration {
private final JwtAuthenticationFilter jwtAuthenticationFilter;
@Bean @Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf(csrf -> csrf.disable()) http.csrf(csrf -> csrf.disable())
@@ -24,7 +30,8 @@ public class SecurityConfiguration {
"/v3/api-docs/**", "/favicon.ico") "/v3/api-docs/**", "/favicon.ico")
.permitAll() .permitAll()
.anyRequest() .anyRequest()
.authenticated()); .authenticated())
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build(); return http.build();
} }

View File

@@ -1,11 +1,106 @@
package cn.meowrain.aioj.backend.auth.filter; 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.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 @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<SimpleGrantedAuthority> 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");
}
} }

View File

@@ -63,5 +63,9 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId> <artifactId>spring-boot-starter-validation</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -14,7 +14,9 @@ import java.time.format.DateTimeFormatter;
@Slf4j @Slf4j
@RequiredArgsConstructor @RequiredArgsConstructor
public class BannerApplicationRunner implements ApplicationRunner { public class BannerApplicationRunner implements ApplicationRunner {
private final Environment env; private final Environment env;
@Value("${spring.application.name:unknown}") @Value("${spring.application.name:unknown}")
private String appName; private String appName;
@@ -43,6 +45,7 @@ public class BannerApplicationRunner implements ApplicationRunner {
printBanner(appName, profiles, port, jvm, pid, time, gitCommit); printBanner(appName, profiles, port, jvm, pid, time, gitCommit);
} }
private void printBanner(String appName, String profiles, String port, String jvm, String pid, String time, private void printBanner(String appName, String profiles, String port, String jvm, String pid, String time,
String gitCommit) { String gitCommit) {
@@ -53,4 +56,5 @@ public class BannerApplicationRunner implements ApplicationRunner {
+ "------------------------------------------------------------\n"; + "------------------------------------------------------------\n";
System.out.println(banner); System.out.println(banner);
} }
} }

View File

@@ -7,8 +7,10 @@ import org.springframework.core.env.Environment;
@AutoConfiguration @AutoConfiguration
public class AIOJBannerAutoConfiguration { public class AIOJBannerAutoConfiguration {
@Bean @Bean
public BannerApplicationRunner bannerApplicationRunner(Environment env) { public BannerApplicationRunner bannerApplicationRunner(Environment env) {
return new BannerApplicationRunner(env); return new BannerApplicationRunner(env);
} }
} }

View File

@@ -4,6 +4,7 @@ package cn.meowrain.aioj.backend.framework.core.constants;
* 全局服务名称 * 全局服务名称
*/ */
public class ServiceNameConstants { public class ServiceNameConstants {
/** /**
* 用户服务 SERVICE NAME * 用户服务 SERVICE NAME
*/ */
@@ -18,4 +19,5 @@ public class ServiceNameConstants {
* UPMS模块 * UPMS模块
*/ */
public static final String UPMS_SERVICE = "upms-service"; public static final String UPMS_SERVICE = "upms-service";
} }

View File

@@ -5,6 +5,8 @@ import cn.meowrain.aioj.backend.framework.core.web.Result;
import cn.meowrain.aioj.backend.framework.core.web.Results; import cn.meowrain.aioj.backend.framework.core.web.Results;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult; import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError; 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.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.stereotype.Component;
import java.util.List; import java.util.List;
/** /**
@@ -19,10 +23,16 @@ import java.util.List;
*/ */
@Slf4j @Slf4j
@RestControllerAdvice @RestControllerAdvice
@Order(Ordered.HIGHEST_PRECEDENCE)
public class GlobalExceptionHandler { public class GlobalExceptionHandler {
// 加这个构造器,启动看日志
public GlobalExceptionHandler() {
System.out.println("===== 自定义异常处理器已加载 =====");
}
/** /**
* 捕获所有参数错误,然后统一捕获并且抛出 * 捕获所有参数错误,然后统一捕获并且抛出
*
* @param request {@link HttpServletRequest} * @param request {@link HttpServletRequest}
* @param ex {@link org.springframework.validation.method.MethodValidationException} * @param ex {@link org.springframework.validation.method.MethodValidationException}
* @return {@link Result<Void>} * @return {@link Result<Void>}
@@ -42,6 +52,7 @@ public class GlobalExceptionHandler {
/** /**
* 抽象异常捕获其 * 抽象异常捕获其
*
* @param request {@link HttpServletRequest} * @param request {@link HttpServletRequest}
* @param ex {@link AbstractException} * @param ex {@link AbstractException}
* @return {@link Result<Void>} * @return {@link Result<Void>}
@@ -74,6 +85,7 @@ public class GlobalExceptionHandler {
/** /**
* 获取请求URL * 获取请求URL
*
* @param request {@link HttpServletRequest} * @param request {@link HttpServletRequest}
* @return String * @return String
*/ */

View File

@@ -10,7 +10,6 @@ import java.io.Serial;
import java.time.*; import java.time.*;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
public class JavaTimeModule extends SimpleModule { public class JavaTimeModule extends SimpleModule {
@Serial @Serial

View File

@@ -14,9 +14,11 @@ import org.springframework.stereotype.Service;
@Slf4j @Slf4j
@Service @Service
public class SpringContextHolder implements ApplicationContextAware, EnvironmentAware, DisposableBean { public class SpringContextHolder implements ApplicationContextAware, EnvironmentAware, DisposableBean {
private static ApplicationContext applicationContext = null; private static ApplicationContext applicationContext = null;
private static Environment environment = null; private static Environment environment = null;
/** /**
* 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型. * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
*/ */
@@ -56,6 +58,7 @@ public class SpringContextHolder implements ApplicationContextAware, Environmen
} }
applicationContext = null; applicationContext = null;
} }
/** /**
* 发布事件 * 发布事件
* @param event * @param event
@@ -66,6 +69,7 @@ public class SpringContextHolder implements ApplicationContextAware, Environmen
} }
applicationContext.publishEvent(event); applicationContext.publishEvent(event);
} }
/** /**
* 是否是微服务 * 是否是微服务
* @return boolean * @return boolean

View File

@@ -4,4 +4,5 @@ import org.springframework.boot.autoconfigure.AutoConfiguration;
@AutoConfiguration @AutoConfiguration
public class FeignAutoConfiguration { public class FeignAutoConfiguration {
} }

View File

@@ -9,5 +9,7 @@ import java.lang.annotation.*;
@Documented @Documented
@EnableFeignClients @EnableFeignClients
public @interface EnableAIOJFeignClients { public @interface EnableAIOJFeignClients {
String[] basePackages() default {}; String[] basePackages() default {};
} }

View File

@@ -20,6 +20,7 @@ import org.springframework.expression.EvaluationContext;
@Slf4j @Slf4j
@RequiredArgsConstructor @RequiredArgsConstructor
public class SysLogAspect { public class SysLogAspect {
/** /**
* 环绕通知方法,用于处理系统日志记录 * 环绕通知方法,用于处理系统日志记录
* @param point 连接点对象 * @param point 连接点对象
@@ -29,7 +30,7 @@ public class SysLogAspect {
*/ */
@Around("@annotation(sysLog)") @Around("@annotation(sysLog)")
@SneakyThrows @SneakyThrows
public Object around(ProceedingJoinPoint point,SysLog sysLog) { public Object around(ProceedingJoinPoint point, SysLog sysLog) {
String strClassName = point.getTarget().getClass().getName(); String strClassName = point.getTarget().getClass().getName();
String strMethodName = point.getSignature().getName(); String strMethodName = point.getSignature().getName();
log.debug("[类名]:{},[方法]:{}", strClassName, strMethodName); log.debug("[类名]:{},[方法]:{}", strClassName, strMethodName);
@@ -76,4 +77,5 @@ public class SysLogAspect {
return obj; return obj;
} }
} }

View File

@@ -30,5 +30,4 @@ public class AIOJLogPropertiesConfiguration {
@Value("${log.exclude-fields:password,mobile,idcard,phone}") @Value("${log.exclude-fields:password,mobile,idcard,phone}")
private List<String> excludeFields; private List<String> excludeFields;
} }

View File

@@ -1,6 +1,5 @@
package cn.meowrain.aioj.backend.framework.log.event; package cn.meowrain.aioj.backend.framework.log.event;
import cn.meowrain.aioj.backend.upms.api.entity.SysLog; import cn.meowrain.aioj.backend.upms.api.entity.SysLog;
import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationEvent;

View File

@@ -53,7 +53,7 @@ public class SysLogListener implements InitializingBean {
*/ */
@Override @Override
public void afterPropertiesSet() throws Exception { public void afterPropertiesSet() throws Exception {
//给 ObjectMapper 添加 MixIn用于过滤字段 // 给 ObjectMapper 添加 MixIn用于过滤字段
objectMapper.addMixIn(Object.class, PropertyFilterMixIn.class); objectMapper.addMixIn(Object.class, PropertyFilterMixIn.class);
String[] ignorableFieldNames = logProperties.getExcludeFields().toArray(new String[0]); String[] ignorableFieldNames = logProperties.getExcludeFields().toArray(new String[0]);
@@ -62,6 +62,7 @@ public class SysLogListener implements InitializingBean {
objectMapper.setFilterProvider(filters); objectMapper.setFilterProvider(filters);
objectMapper.registerModule(new JavaTimeModule()); objectMapper.registerModule(new JavaTimeModule());
} }
/** /**
* 属性过滤混合类:用于通过名称过滤属性 * 属性过滤混合类:用于通过名称过滤属性
* *
@@ -72,4 +73,5 @@ public class SysLogListener implements InitializingBean {
class PropertyFilterMixIn { class PropertyFilterMixIn {
} }
} }

View File

@@ -29,7 +29,6 @@ import java.util.Objects;
public final class SysLogUtils { public final class SysLogUtils {
/** /**
* 获取系统日志事件源 * 获取系统日志事件源
* @return 系统日志事件源对象 * @return 系统日志事件源对象
@@ -47,7 +46,8 @@ public final class SysLogUtils {
sysLog.setServiceId(SpringUtil.getProperty("spring.application.name")); sysLog.setServiceId(SpringUtil.getProperty("spring.application.name"));
// get 参数脱敏 // get 参数脱敏
AIOJLogPropertiesConfiguration logProperties = SpringContextHolder.getBean(AIOJLogPropertiesConfiguration.class); AIOJLogPropertiesConfiguration logProperties = SpringContextHolder
.getBean(AIOJLogPropertiesConfiguration.class);
Map<String, String[]> paramsMap = MapUtil.removeAny(new HashMap<>(request.getParameterMap()), Map<String, String[]> paramsMap = MapUtil.removeAny(new HashMap<>(request.getParameterMap()),
ArrayUtil.toArray(logProperties.getExcludeFields(), String.class)); ArrayUtil.toArray(logProperties.getExcludeFields(), String.class));
sysLog.setParams(HttpUtil.toParams(paramsMap)); sysLog.setParams(HttpUtil.toParams(paramsMap));

View File

@@ -50,17 +50,14 @@
<groupId>com.alibaba.csp</groupId> <groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-extension</artifactId> <artifactId>sentinel-datasource-extension</artifactId>
</dependency> </dependency>
<!-- https://mvnrepository.com/artifact/com.github.xiaoymin/knife4j-openapi3-jakarta-spring-boot-starter --> <!--
https://mvnrepository.com/artifact/com.github.xiaoymin/knife4j-openapi3-jakarta-spring-boot-starter -->
<dependency> <dependency>
<groupId>com.github.xiaoymin</groupId> <groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId> <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
<version>4.5.0</version>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
<version>4.3.0</version>
</dependency>
<dependency> <dependency>
<groupId>cn.meowrain</groupId> <groupId>cn.meowrain</groupId>
<artifactId>aioj-backend-common-core</artifactId> <artifactId>aioj-backend-common-core</artifactId>

View File

@@ -19,7 +19,6 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher; import org.springframework.util.AntPathMatcher;
import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.client.WebClient;
@@ -27,7 +26,6 @@ import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Objects; import java.util.Objects;
@Slf4j @Slf4j
@@ -44,15 +42,6 @@ public class AuthGlobalFilter implements GlobalFilter, Ordered {
private final ObjectMapper objectMapper = new ObjectMapper(); 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 @Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
@@ -60,6 +49,7 @@ public class AuthGlobalFilter implements GlobalFilter, Ordered {
String path = request.getURI().getPath(); String path = request.getURI().getPath();
log.info("Auth filter processing request: {}", path); log.info("Auth filter processing request: {}", path);
log.info("Loaded white list from config: {}", gatewayPropertiesConfiguration.getWhiteList());
// 检查是否在白名单中 // 检查是否在白名单中
if (isWhiteListPath(path)) { if (isWhiteListPath(path)) {
@@ -97,13 +87,6 @@ public class AuthGlobalFilter implements GlobalFilter, Ordered {
* 检查路径是否在白名单中 * 检查路径是否在白名单中
*/ */
private boolean isWhiteListPath(String path) { 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()) { if (gatewayPropertiesConfiguration.getWhiteList() != null && !gatewayPropertiesConfiguration.getWhiteList().isEmpty()) {
for (String whitePath : gatewayPropertiesConfiguration.getWhiteList()) { for (String whitePath : gatewayPropertiesConfiguration.getWhiteList()) {
@@ -122,7 +105,7 @@ public class AuthGlobalFilter implements GlobalFilter, Ordered {
private Mono<Boolean> validateToken(String token) { private Mono<Boolean> validateToken(String token) {
return webClientBuilder.build() return webClientBuilder.build()
.post() .post()
.uri("lb://auth-service/api/v1/auth/validate") .uri("lb://auth-service/v1/auth/validate")
.header(HttpHeaders.AUTHORIZATION, "Bearer " + token) .header(HttpHeaders.AUTHORIZATION, "Bearer " + token)
.contentType(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON)
.retrieve() .retrieve()

View File

@@ -8,11 +8,59 @@ spring:
nacos: nacos:
discovery: discovery:
enabled: true enabled: true
register-enabled: true register-enabled: false
server-addr: 10.0.0.10:8848 server-addr: 10.0.0.10:8848
username: nacos username: nacos
password: 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: logging:
level: level:
root: INFO 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

View File

@@ -1,29 +1,50 @@
server: server:
port: 8085 port: 8085
error:
include-stacktrace: never
spring: spring:
profiles:
active: @env@
cloud: cloud:
gateway: gateway:
server: server:
webflux: webflux:
routes: routes:
- id: auth-service - id: auth-service
uri: lb://auth-service/api uri: lb://auth-service
predicates: predicates:
- Path=/api/v1/auth/** - Path=/api/v1/auth/**
filters:
- name: Retry
args:
retries: 3
statuses: BAD_GATEWAY,SERVICE_UNAVAILABLE
backoff:
firstBackoff: 50ms
maxBackoff: 500ms
- id: user-service - id: user-service
uri: lb://user-service/api uri: lb://user-service
predicates: predicates:
- Path=/api/v1/user/** - 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: aioj-backend-gateway:
# 白名单配置 # 白名单配置
white-list: white-list:
- /api/v1/auth/login - /api/v1/auth/login
- /api/v1/auth/register - /api/v1/user/register
- /api/v1/auth/refresh - /api/v1/auth/refresh
- /api/v1/user/info
- /api/v1/question/list
- /api/v1/question/detail/**
- /actuator/health - /actuator/health
- /swagger-ui/** - /swagger-ui/**
- /v3/api-docs/** - /v3/api-docs/**

View File

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

View File

@@ -19,7 +19,6 @@
package cn.meowrain.aioj.backend.upms.api.entity; package cn.meowrain.aioj.backend.upms.api.entity;
import com.baomidou.mybatisplus.annotation.*; import com.baomidou.mybatisplus.annotation.*;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
@@ -48,7 +47,7 @@ public class SysLog implements Serializable {
* 编号 * 编号
*/ */
@TableId(type = IdType.ASSIGN_ID) @TableId(type = IdType.ASSIGN_ID)
// @ExcelProperty("日志编号") // @ExcelProperty("日志编号")
@Schema(description = "日志编号") @Schema(description = "日志编号")
private Long id; private Long id;
@@ -56,7 +55,7 @@ public class SysLog implements Serializable {
* 日志类型 * 日志类型
*/ */
@NotBlank(message = "日志类型不能为空") @NotBlank(message = "日志类型不能为空")
// @ExcelProperty("日志类型0-正常 9-错误)") // @ExcelProperty("日志类型0-正常 9-错误)")
@Schema(description = "日志类型") @Schema(description = "日志类型")
private String logType; private String logType;
@@ -64,14 +63,14 @@ public class SysLog implements Serializable {
* 日志标题 * 日志标题
*/ */
@NotBlank(message = "日志标题不能为空") @NotBlank(message = "日志标题不能为空")
// @ExcelProperty("日志标题") // @ExcelProperty("日志标题")
@Schema(description = "日志标题") @Schema(description = "日志标题")
private String title; private String title;
/** /**
* 创建者 * 创建者
*/ */
// @ExcelProperty("创建人") // @ExcelProperty("创建人")
@TableField(fill = FieldFill.INSERT) @TableField(fill = FieldFill.INSERT)
@Schema(description = "创建人") @Schema(description = "创建人")
private String createBy; private String createBy;
@@ -79,7 +78,7 @@ public class SysLog implements Serializable {
/** /**
* 创建时间 * 创建时间
*/ */
// @ExcelProperty("创建时间") // @ExcelProperty("创建时间")
@TableField(fill = FieldFill.INSERT) @TableField(fill = FieldFill.INSERT)
@Schema(description = "创建时间") @Schema(description = "创建时间")
private LocalDateTime createTime; private LocalDateTime createTime;
@@ -87,7 +86,7 @@ public class SysLog implements Serializable {
/** /**
* 更新时间 * 更新时间
*/ */
// @ExcelIgnore // @ExcelIgnore
@TableField(fill = FieldFill.UPDATE) @TableField(fill = FieldFill.UPDATE)
@Schema(description = "更新时间") @Schema(description = "更新时间")
private LocalDateTime updateTime; private LocalDateTime updateTime;
@@ -95,7 +94,7 @@ public class SysLog implements Serializable {
/** /**
* 操作IP地址 * 操作IP地址
*/ */
// @ExcelProperty("操作ip地址") // @ExcelProperty("操作ip地址")
@Schema(description = "操作ip地址") @Schema(description = "操作ip地址")
private String remoteAddr; private String remoteAddr;
@@ -108,42 +107,42 @@ public class SysLog implements Serializable {
/** /**
* 请求URI * 请求URI
*/ */
// @ExcelProperty("浏览器") // @ExcelProperty("浏览器")
@Schema(description = "请求uri") @Schema(description = "请求uri")
private String requestUri; private String requestUri;
/** /**
* 操作方式 * 操作方式
*/ */
// @ExcelProperty("操作方式") // @ExcelProperty("操作方式")
@Schema(description = "操作方式") @Schema(description = "操作方式")
private String method; private String method;
/** /**
* 操作提交的数据 * 操作提交的数据
*/ */
// @ExcelProperty("提交数据") // @ExcelProperty("提交数据")
@Schema(description = "提交数据") @Schema(description = "提交数据")
private String params; private String params;
/** /**
* 执行时间 * 执行时间
*/ */
// @ExcelProperty("执行时间") // @ExcelProperty("执行时间")
@Schema(description = "方法执行时间") @Schema(description = "方法执行时间")
private Long time; private Long time;
/** /**
* 异常信息 * 异常信息
*/ */
// @ExcelProperty("异常信息") // @ExcelProperty("异常信息")
@Schema(description = "异常信息") @Schema(description = "异常信息")
private String exception; private String exception;
/** /**
* 服务ID * 服务ID
*/ */
// @ExcelProperty("应用标识") // @ExcelProperty("应用标识")
@Schema(description = "应用标识") @Schema(description = "应用标识")
private String serviceId; private String serviceId;
@@ -151,7 +150,7 @@ public class SysLog implements Serializable {
* 删除标记 * 删除标记
*/ */
@TableLogic @TableLogic
// @ExcelIgnore // @ExcelIgnore
@TableField(fill = FieldFill.INSERT) @TableField(fill = FieldFill.INSERT)
@Schema(description = "删除标记,1:已删除,0:正常") @Schema(description = "删除标记,1:已删除,0:正常")
private String delFlag; private String delFlag;

View File

@@ -10,6 +10,7 @@ import org.springframework.web.bind.annotation.RequestBody;
@FeignClient(contextId = "remoteLogService", value = ServiceNameConstants.UPMS_SERVICE) @FeignClient(contextId = "remoteLogService", value = ServiceNameConstants.UPMS_SERVICE)
public interface RemoteLogService { public interface RemoteLogService {
/** /**
* 保存日志 (异步多线程调用无token) * 保存日志 (异步多线程调用无token)
* @param sysLog 日志实体 * @param sysLog 日志实体
@@ -18,4 +19,5 @@ public interface RemoteLogService {
@NoToken @NoToken
@PostMapping("/log/save") @PostMapping("/log/save")
Result<Boolean> saveLog(@RequestBody SysLog sysLog); Result<Boolean> saveLog(@RequestBody SysLog sysLog);
} }

View File

@@ -20,7 +20,12 @@
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>cn.meowrain</groupId> <groupId>cn.meowrain</groupId>
<artifactId>aioj-backend-common-starter</artifactId> <artifactId>aioj-backend-common-core</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>cn.meowrain</groupId>
<artifactId>aioj-backend-common-log</artifactId>
<version>1.0-SNAPSHOT</version> <version>1.0-SNAPSHOT</version>
</dependency> </dependency>
<dependency> <dependency>
@@ -60,21 +65,15 @@
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <!-- <dependency>-->
<groupId>org.springframework.boot</groupId> <!-- <groupId>org.springframework.boot</groupId>-->
<artifactId>spring-boot-devtools</artifactId> <!-- <artifactId>spring-boot-devtools</artifactId>-->
<scope>runtime</scope> <!-- <scope>runtime</scope>-->
<optional>true</optional> <!-- <optional>true</optional>-->
</dependency> <!-- </dependency>-->
<dependency> <dependency>
<groupId>com.alibaba.cloud</groupId> <groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency> </dependency>
<dependency>
<groupId>cn.meowrain</groupId>
<artifactId>aioj-backend-common-core</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -5,7 +5,8 @@ import org.springframework.context.annotation.ComponentScans;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@Configuration @Configuration
@ComponentScans({ @ComponentScan("cn.meowrain.aioj.backend.framework.core.banner") }) // @ComponentScans({
// @ComponentScan("cn.meowrain.aioj.backend.framework.core.banner") })
public class FrameworkConfiguration { public class FrameworkConfiguration {
} }

View File

@@ -7,6 +7,8 @@ server:
port: 10010 port: 10010
servlet: servlet:
context-path: /api context-path: /api
error:
include-stacktrace: never
springdoc: springdoc:
api-docs: api-docs:
enabled: true enabled: true

243
docs/auth-api.md Normal file
View File

@@ -0,0 +1,243 @@
# AIOJ 认证服务 API 文档
## 概述
AIOJ认证服务提供JWTJSON 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*

26
pom.xml
View File

@@ -7,7 +7,7 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId> <artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.7</version> <version>3.5.7</version>
<relativePath/> <relativePath />
</parent> </parent>
<groupId>cn.meowrain</groupId> <groupId>cn.meowrain</groupId>
<artifactId>ai-oj</artifactId> <artifactId>ai-oj</artifactId>
@@ -66,7 +66,8 @@
<type>pom</type> <type>pom</type>
<scope>import</scope> <scope>import</scope>
</dependency> </dependency>
<!--Spring Boot依赖--> <!--Spring
Boot依赖-->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId> <artifactId>spring-boot-dependencies</artifactId>
@@ -75,7 +76,8 @@
<scope>import</scope> <scope>import</scope>
</dependency> </dependency>
<!-- Spring Cloud依赖--> <!-- Spring Cloud依赖-->
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-dependencies --> <!--
https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-dependencies -->
<dependency> <dependency>
<groupId>org.springframework.cloud</groupId> <groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId> <artifactId>spring-cloud-dependencies</artifactId>
@@ -96,10 +98,6 @@
<resources> <resources>
<resource> <resource>
<directory>src/main/resources</directory> <directory>src/main/resources</directory>
<includes>
<include>application*.yml</include>
<include>application*.properties</include>
</includes>
<filtering>true</filtering> <filtering>true</filtering>
</resource> </resource>
</resources> </resources>
@@ -120,7 +118,8 @@
<version>${spring-boot.version}</version> <version>${spring-boot.version}</version>
</plugin> </plugin>
<!-- <!--
代码格式插件默认使用spring 规则,可运行命令进行项目格式化:./mvnw spring-javaformat:apply 或 mvn spring-javaformat:apply可在IDEA中安装插件以下插件进行自动格式化 代码格式插件默认使用spring 规则,可运行命令进行项目格式化:./mvnw spring-javaformat:apply 或 mvn
spring-javaformat:apply可在IDEA中安装插件以下插件进行自动格式化
https://repo1.maven.org/maven2/io/spring/javaformat/spring-javaformat-intellij-idea-plugin https://repo1.maven.org/maven2/io/spring/javaformat/spring-javaformat-intellij-idea-plugin
--> -->
<plugin> <plugin>
@@ -135,7 +134,8 @@
</executions> </executions>
</plugin> </plugin>
<!--打包jar 与git commit 关联插件--> <!--打包jar
与git commit 关联插件-->
<plugin> <plugin>
<groupId>io.github.git-commit-id</groupId> <groupId>io.github.git-commit-id</groupId>
<artifactId>git-commit-id-maven-plugin</artifactId> <artifactId>git-commit-id-maven-plugin</artifactId>
@@ -168,10 +168,11 @@
<allowInsecureRegistries>true</allowInsecureRegistries> <allowInsecureRegistries>true</allowInsecureRegistries>
<from> <from>
<!--使用openjdk官方镜像tag是8-jdk-stretch表示镜像的操作系统是debian9,装好了jdk8--> <!--使用openjdk官方镜像tag是8-jdk-stretch表示镜像的操作系统是debian9,装好了jdk8-->
<image>registry.cn-shanghai.aliyuncs.com/all_lib/eclipse-temurin:17.0.10_7-jdk-jammy</image> <image>
registry.cn-shanghai.aliyuncs.com/all_lib/eclipse-temurin:17.0.10_7-jdk-jammy</image>
</from> </from>
<to> <to>
<!-- 前缀名/命名空间/项目名:项目版本--> <!-- 前缀名/命名空间/项目名:项目版本-->
<image>10.0.0.3/aioj/${project.artifactId}:${project.version}</image> <image>10.0.0.3/aioj/${project.artifactId}:${project.version}</image>
<tags> <tags>
<!--版本号--> <!--版本号-->
@@ -183,7 +184,8 @@
</auth> </auth>
</to> </to>
<outputPaths> <outputPaths>
<tar>${project.build.directory}/${project.artifactId}-${project.version}.tar</tar> <tar>
${project.build.directory}/${project.artifactId}-${project.version}.tar</tar>
</outputPaths> </outputPaths>
<!--容器相关的属性--> <!--容器相关的属性-->
<container> <container>