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"?>
<project version="4">
<component name="CoolRequestCommonStatePersistent">
<option name="searchCache" value="AIOJAdminA" />
<option name="searchCache" value="G" />
</component>
</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.
## 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 <module-name>`
- **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
- **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`

View File

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

View File

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

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
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">
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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.meowrain</groupId>
@@ -63,5 +63,9 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
</dependencies>
</project>

View File

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

View File

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

View File

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

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 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<Void>}
*/
@ExceptionHandler(value = MethodArgumentNotValidException.class)
@@ -32,9 +42,9 @@ public class GlobalExceptionHandler {
BindingResult bindingResult = ex.getBindingResult();
// 收集所有错误字段
List<String> 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<Void>}
*/
@ExceptionHandler(value = { AbstractException.class })
@@ -74,6 +85,7 @@ public class GlobalExceptionHandler {
/**
* 获取请求URL
*
* @param request {@link HttpServletRequest}
* @return String
*/

View File

@@ -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);
// ======================= 时间序列化规则 ===============================

View File

@@ -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> T getBean(String name) {
return (T) applicationContext.getBean(name);
}
private static ApplicationContext applicationContext = null;
/**
* 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
*/
public static <T> T getBean(Class<T> requiredType) {
return applicationContext.getBean(requiredType);
}
private static Environment environment = null;
@Override
@SneakyThrows
public void destroy() throws Exception {
clearHolder();
}
/**
* 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
*/
public static <T> 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> T getBean(Class<T> 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);
}
}

View File

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

View File

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

View File

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

View File

@@ -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<String> excludeFields;
/**
* 放行字段password,mobile,idcard,phone
*/
@Value("${log.exclude-fields:password,mobile,idcard,phone}")
private List<String> excludeFields;
}

View File

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

View File

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

View File

@@ -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<String, String[]> 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 <T> 返回泛型
* @return 参数值
*/
public static <T> T getValue(EvaluationContext context, String key, Class<T> clazz) {
public static <T> T getValue(EvaluationContext context, String key, Class<T> clazz) {
SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
Expression expression = spelExpressionParser.parseExpression(key);
return expression.getValue(context, clazz);

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
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">
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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.meowrain</groupId>
@@ -50,17 +50,14 @@
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-extension</artifactId>
</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>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
<version>4.5.0</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
<version>4.3.0</version>
</dependency>
<dependency>
<groupId>cn.meowrain</groupId>
<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.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<Void> 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<Boolean> 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()

View File

@@ -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
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:
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/**

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

View File

@@ -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<Boolean> saveLog(@RequestBody SysLog sysLog);
/**
* 保存日志 (异步多线程调用无token)
* @param sysLog 日志实体
* @return succes、false
*/
@NoToken
@PostMapping("/log/save")
Result<Boolean> saveLog(@RequestBody SysLog sysLog);
}

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
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">
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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.meowrain</groupId>
@@ -20,7 +20,12 @@
<dependencies>
<dependency>
<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>
</dependency>
<dependency>
@@ -60,21 +65,15 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-devtools</artifactId>-->
<!-- <scope>runtime</scope>-->
<!-- <optional>true</optional>-->
<!-- </dependency>-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>cn.meowrain</groupId>
<artifactId>aioj-backend-common-core</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>

View File

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

View File

@@ -7,6 +7,8 @@ server:
port: 10010
servlet:
context-path: /api
error:
include-stacktrace: never
springdoc:
api-docs:
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*

32
pom.xml
View File

@@ -1,13 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
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">
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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.7</version>
<relativePath/>
<relativePath />
</parent>
<groupId>cn.meowrain</groupId>
<artifactId>ai-oj</artifactId>
@@ -66,7 +66,8 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<!--Spring Boot依赖-->
<!--Spring
Boot依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
@@ -75,7 +76,8 @@
<scope>import</scope>
</dependency>
<!-- Spring Cloud依赖-->
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-dependencies -->
<!--
https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-dependencies -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
@@ -96,10 +98,6 @@
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>application*.yml</include>
<include>application*.properties</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
@@ -120,7 +118,8 @@
<version>${spring-boot.version}</version>
</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
-->
<plugin>
@@ -135,7 +134,8 @@
</executions>
</plugin>
<!--打包jar 与git commit 关联插件-->
<!--打包jar
与git commit 关联插件-->
<plugin>
<groupId>io.github.git-commit-id</groupId>
<artifactId>git-commit-id-maven-plugin</artifactId>
@@ -168,13 +168,14 @@
<allowInsecureRegistries>true</allowInsecureRegistries>
<from>
<!--使用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>
<to>
<!-- 前缀名/命名空间/项目名:项目版本-->
<!-- 前缀名/命名空间/项目名:项目版本-->
<image>10.0.0.3/aioj/${project.artifactId}:${project.version}</image>
<tags>
<!--版本号-->
<!--版本号-->
<tag>${project.version}</tag>
</tags>
<auth>
@@ -183,7 +184,8 @@
</auth>
</to>
<outputPaths>
<tar>${project.build.directory}/${project.artifactId}-${project.version}.tar</tar>
<tar>
${project.build.directory}/${project.artifactId}-${project.version}.tar</tar>
</outputPaths>
<!--容器相关的属性-->
<container>