Compare commits
4 Commits
4304ec6e29
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 4912e48922 | |||
| c61ee69561 | |||
|
|
6f7963a73b | ||
|
|
d89960f51c |
BIN
.idea/.cache/.easy-yapi/.api.cache.v1.1.db
generated
Normal file
BIN
.idea/.cache/.easy-yapi/.api.cache.v1.1.db
generated
Normal file
Binary file not shown.
0
.idea/.cache/.easy-yapi/.cookies.v1.0.json
generated
Normal file
0
.idea/.cache/.easy-yapi/.cookies.v1.0.json
generated
Normal file
0
.idea/.cache/.easy-yapi/.http_content_cache
generated
Normal file
0
.idea/.cache/.easy-yapi/.http_content_cache
generated
Normal file
6
.idea/CoolRequestCommonStatePersistent.xml
generated
Normal file
6
.idea/CoolRequestCommonStatePersistent.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CoolRequestCommonStatePersistent">
|
||||
<option name="searchCache" value="AIOJAdminA" />
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/CoolRequestSetting.xml
generated
Normal file
6
.idea/CoolRequestSetting.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CoolRequestSetting">
|
||||
<option name="projectCachePath" value="project-e82c3cb9-7dfc-4fa6-b498-45789b6b3803" />
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/copilot.data.migration.agent.xml
generated
Normal file
6
.idea/copilot.data.migration.agent.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="AgentMigrationStateService">
|
||||
<option name="migrationStatus" value="COMPLETED" />
|
||||
</component>
|
||||
</project>
|
||||
5
.idea/encodings.xml
generated
5
.idea/encodings.xml
generated
@@ -9,6 +9,7 @@
|
||||
<file url="file://$PROJECT_DIR$/aioj-backend-common/aioj-backend-common-bom/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/aioj-backend-common/aioj-backend-common-bom/src/main/resources" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/aioj-backend-common/aioj-backend-common-core/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/aioj-backend-common/aioj-backend-common-feign/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/aioj-backend-common/aioj-backend-common-log/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/aioj-backend-common/aioj-backend-common-mybatis/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/aioj-backend-common/aioj-backend-common-starter/src/main/java" charset="UTF-8" />
|
||||
@@ -32,7 +33,7 @@
|
||||
<file url="file://$PROJECT_DIR$/aioj-backend-user-service/src/main/resources" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/../../../../../Windows/System32/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/../../../../../Windows/System32/src/main/resources" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/../../../../Windows/System32/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/../../../../Windows/System32/src/main/resources" charset="UTF-8" />
|
||||
</component>
|
||||
</project>
|
||||
196
CLAUDE.md
Normal file
196
CLAUDE.md
Normal file
@@ -0,0 +1,196 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project 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.
|
||||
|
||||
## Common Development Commands
|
||||
|
||||
### Building the Project
|
||||
```bash
|
||||
# Build entire project
|
||||
mvn clean install
|
||||
|
||||
# Build with specific environment profile
|
||||
mvn clean install -P dev # Development (default)
|
||||
mvn clean install -P test # Testing
|
||||
mvn clean install -P prod # Production
|
||||
|
||||
# Format code according to Spring standards
|
||||
mvn spring-javaformat:apply
|
||||
|
||||
# Build Docker images using Jib
|
||||
mvn clean package jib:build
|
||||
|
||||
# Or use Maven wrapper
|
||||
./mvnw clean install
|
||||
```
|
||||
|
||||
### Running Services
|
||||
Each service runs on different ports:
|
||||
- Gateway: 8085
|
||||
- Other services: configured via Nacos
|
||||
|
||||
Run individual services from their respective directories:
|
||||
```bash
|
||||
cd aioj-backend-gateway
|
||||
mvn spring-boot:run
|
||||
|
||||
# Or with specific profile
|
||||
mvn spring-boot:run -Dspring.profiles.active=dev
|
||||
```
|
||||
|
||||
### 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`
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
### Microservices Architecture
|
||||
The system consists of seven main services:
|
||||
|
||||
1. **aioj-backend-gateway** (Port 8085)
|
||||
- API Gateway using Spring Cloud Gateway
|
||||
- Routes requests to appropriate services
|
||||
- Built with WebFlux for reactive programming
|
||||
|
||||
2. **aioj-backend-auth**
|
||||
- OAuth2 authentication and authorization service
|
||||
- Manages user credentials and tokens
|
||||
|
||||
3. **aioj-backend-user-service**
|
||||
- User management and profiles
|
||||
- Handles registration, login, profile updates
|
||||
- Integrates with Redis for session management
|
||||
|
||||
4. **aioj-backend-question-service**
|
||||
- Problem/question management
|
||||
- Handles problem storage and retrieval
|
||||
|
||||
5. **aioj-backend-judge-service**
|
||||
- Core OJ functionality for code execution
|
||||
- Supports multiple programming languages
|
||||
|
||||
6. **aioj-backend-ai-service**
|
||||
- AI integration for enhanced features
|
||||
- Code analysis and automated feedback
|
||||
|
||||
7. **aioj-backend-upms** (User Permission Management System)
|
||||
- Role-based access control
|
||||
- Permission management
|
||||
|
||||
### Common Modules
|
||||
- **aioj-backend-common**: Shared utilities with sub-modules:
|
||||
- `core`: Core utilities and configurations
|
||||
- `log`: Custom logging implementation
|
||||
- `starter`: Auto-configuration starters
|
||||
- `mybatis`: Database access layer
|
||||
- `feign`: HTTP client for service communication
|
||||
- `bom`: Bill of Materials for dependency management
|
||||
|
||||
## Technology Stack
|
||||
|
||||
### Core Technologies
|
||||
- **Java 17**
|
||||
- **Spring Boot 3.5.7**
|
||||
- **Spring Cloud 2025.0.0**
|
||||
- **Spring Cloud Alibaba 2025.0.0.0**
|
||||
- **Maven** for build management
|
||||
|
||||
### Database & Persistence
|
||||
- **MySQL 9.4.0** as primary database
|
||||
- **MyBatis-Plus 3.5.14** for ORM
|
||||
- **Redis** for caching and session management
|
||||
|
||||
### Cloud & Infrastructure
|
||||
- **Nacos** for service discovery and configuration (server: 10.0.0.10:8848)
|
||||
- **Spring Cloud Gateway** for API routing
|
||||
- **Docker** with Jib plugin for containerization
|
||||
- **Sentinel** for circuit breaking
|
||||
|
||||
### Security
|
||||
- **Spring Security 6.5.6** with OAuth2
|
||||
- JWT token-based authentication
|
||||
- Role-based access control
|
||||
|
||||
### API Documentation
|
||||
- **Knife4j** (OpenAPI 3) integrated across services
|
||||
|
||||
## Configuration Management
|
||||
|
||||
### Environment-Specific Configuration
|
||||
Three environments are supported:
|
||||
- `dev` (development, default)
|
||||
- `test` (testing)
|
||||
- `prod` (production)
|
||||
|
||||
Configuration files:
|
||||
- `bootstrap.yml` - Nacos service discovery configuration
|
||||
- `application.yml` - Main application configuration
|
||||
- `application-{env}.yml` - Environment-specific settings
|
||||
|
||||
### Nacos Integration
|
||||
All services use Nacos for:
|
||||
- Service discovery
|
||||
- Configuration management
|
||||
- Centralized properties management
|
||||
|
||||
Default Nacos configuration:
|
||||
```yaml
|
||||
spring:
|
||||
cloud:
|
||||
nacos:
|
||||
discovery:
|
||||
server-addr: 10.0.0.10:8848
|
||||
username: nacos
|
||||
password: nacos
|
||||
```
|
||||
|
||||
## 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
|
||||
- Uses Spring JavaFormat plugin for consistent code style
|
||||
- IDE plugin available: https://repo1.maven.org/maven2/io/spring/javaformat/spring-javaformat-intellij-idea-plugin
|
||||
- Run `mvn spring-javaformat:apply` before commits
|
||||
|
||||
### Docker Integration
|
||||
- Jib plugin configured for container builds
|
||||
- Target registry: `10.0.0.3/aioj/{service-name}:{version}`
|
||||
- JVM memory configured: -Xms512m -Xmx512m
|
||||
|
||||
### Service Communication
|
||||
- Uses OpenFeign for inter-service communication
|
||||
- Load balancing with Spring Cloud LoadBalancer
|
||||
- Circuit breaking with Sentinel
|
||||
|
||||
### Logging
|
||||
- Custom logging implementation in `aioj-backend-common-log`
|
||||
- Integrates with Spring Security for context logging
|
||||
- Uses Hutool utilities for enhanced logging
|
||||
|
||||
## Testing
|
||||
- Spring Boot Test framework included
|
||||
- Spring Security Test for authentication testing
|
||||
- Test structure is evolving - check individual modules for test coverage
|
||||
|
||||
## Important Notes
|
||||
|
||||
1. **Service Dependencies**: Services depend on Nacos for discovery - ensure Nacos is running before starting services
|
||||
2. **Database Setup**: Run the database creation script before first run
|
||||
3. **Port Configuration**: Gateway runs on 8085, other services are dynamically registered
|
||||
4. **Environment Profiles**: Default is `dev` - use appropriate profiles for different environments
|
||||
5. **Configuration Management**: Most configuration is externalized to Nacos - check Nacos for service-specific settings
|
||||
@@ -19,39 +19,50 @@
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- 核心模块 -->
|
||||
<dependency>
|
||||
<groupId>cn.meowrain</groupId>
|
||||
<artifactId>aioj-backend-common-core</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.meowrain</groupId>
|
||||
<artifactId>aioj-backend-common-feign</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<!-- spring cloud发现服务-->
|
||||
<!-- 工具类 -->
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-crypto</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Cloud服务发现 -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||
</dependency>
|
||||
<!-- OAuth2 Client -->
|
||||
|
||||
<!-- Web -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- OAuth2 & Spring Security -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-oauth2-client</artifactId>
|
||||
</dependency>
|
||||
<!-- Spring Security -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-devtools</artifactId>
|
||||
<scope>runtime</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<!-- JWT -->
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
@@ -70,37 +81,42 @@
|
||||
<version>0.13.0</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/com.github.xiaoymin/knife4j-openapi3-jakarta-spring-boot-starter -->
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<!--
|
||||
引用通用模块
|
||||
-->
|
||||
<dependency>
|
||||
<groupId>cn.meowrain</groupId>
|
||||
<artifactId>aioj-backend-common-starter</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<!--引入openfeign-->
|
||||
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-openfeign -->
|
||||
|
||||
<!-- Feign客户端 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||||
<version>4.3.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
|
||||
<version>4.3.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 引入redis,存储refreshToken-->
|
||||
<!-- Redis用于存储refreshToken -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- API文档 -->
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 开发工具 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-devtools</artifactId>
|
||||
<scope>runtime</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- 测试 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -7,7 +7,9 @@ import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||
@EnableFeignClients(basePackages = "cn.meowrain.aioj.backend.auth.clients")
|
||||
@SpringBootApplication
|
||||
public class AIOJAuthApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(AIOJAuthApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,9 +8,11 @@ import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
@FeignClient(name = "user-service", path = "/api/v1/user")
|
||||
public interface UserClient {
|
||||
|
||||
@GetMapping("/inner/get-by-username")
|
||||
Result<UserAuthRespDTO> getUserByUserName(@RequestParam("userAccount") String userAccount);
|
||||
|
||||
@GetMapping("/inner/get-by-userid")
|
||||
public Result<UserAuthRespDTO> getUserById(@RequestParam("userId") String userid);
|
||||
public Result<UserAuthRespDTO> getUserById(@RequestParam("userId") String userId);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package cn.meowrain.aioj.backend.auth.common.constants;
|
||||
|
||||
public class RedisKeyConstants {
|
||||
|
||||
public static String REFRESH_TOKEN_KEY_PREFIX = "refresh_token:%s";
|
||||
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import lombok.RequiredArgsConstructor;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public enum ChainMarkEnums {
|
||||
|
||||
/**
|
||||
* 用户登录请求验证
|
||||
*/
|
||||
@@ -17,4 +18,5 @@ public enum ChainMarkEnums {
|
||||
public String toString() {
|
||||
return markName;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,21 +17,14 @@ public class SecurityConfiguration {
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.csrf(csrf -> csrf.disable())
|
||||
http.csrf(csrf -> csrf.disable())
|
||||
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
.requestMatchers(
|
||||
"/v1/auth/**",
|
||||
"/doc.html",
|
||||
"/swagger-ui/**",
|
||||
"/swagger-resources/**",
|
||||
"/webjars/**",
|
||||
"/v3/api-docs/**",
|
||||
"/favicon.ico"
|
||||
)
|
||||
.requestMatchers("/v1/auth/**", "/doc.html", "/swagger-ui/**", "/swagger-resources/**", "/webjars/**",
|
||||
"/v3/api-docs/**", "/favicon.ico")
|
||||
.permitAll()
|
||||
.anyRequest().authenticated());
|
||||
.anyRequest()
|
||||
.authenticated());
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@@ -44,4 +37,5 @@ public class SecurityConfiguration {
|
||||
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
|
||||
return configuration.getAuthenticationManager();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,23 +16,25 @@ import org.springframework.context.annotation.Configuration;
|
||||
@Configuration
|
||||
@EnableKnife4j
|
||||
public class SwaggerConfiguration implements ApplicationRunner {
|
||||
|
||||
@Value("${server.port:8080}")
|
||||
private String serverPort;
|
||||
|
||||
@Value("${server.servlet.context-path:}")
|
||||
private String contextPath;
|
||||
|
||||
@Bean
|
||||
public OpenAPI customerOpenAPI() {
|
||||
return new OpenAPI()
|
||||
.info(new Info()
|
||||
.title("AIOJ-renz微服务✨")
|
||||
return new OpenAPI().info(new Info().title("AIOJ-renz微服务✨")
|
||||
.description("用户认证功能")
|
||||
.version("v1.0.0")
|
||||
.contact(new Contact().name("meowrain").email("meowrain@126.com"))
|
||||
.license(new License().name("MeowRain").url("https://meowrain.cn")));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(ApplicationArguments args) throws Exception {
|
||||
log.info("✨API Document: http://127.0.0.1:{}{}/doc.html", serverPort, contextPath);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,7 +8,9 @@ import org.springframework.stereotype.Component;
|
||||
@Data
|
||||
@ConfigurationProperties(value = JwtPropertiesConfiguration.PREFIX)
|
||||
public class JwtPropertiesConfiguration {
|
||||
|
||||
public static final String PREFIX = "jwt";
|
||||
|
||||
/**
|
||||
* JWT 密钥(必须 32 字节以上)
|
||||
*/
|
||||
@@ -18,8 +20,10 @@ public class JwtPropertiesConfiguration {
|
||||
* 过期时间(单位:毫秒)
|
||||
*/
|
||||
private long accessExpire; // access token TTL
|
||||
|
||||
/**
|
||||
* 刷新令牌时间
|
||||
*/
|
||||
private long refreshExpire; // refresh token TTL
|
||||
|
||||
}
|
||||
|
||||
@@ -3,8 +3,9 @@ package cn.meowrain.aioj.backend.auth.controller;
|
||||
import cn.meowrain.aioj.backend.auth.dto.req.UserLoginRequestDTO;
|
||||
import cn.meowrain.aioj.backend.auth.dto.resp.UserLoginResponseDTO;
|
||||
import cn.meowrain.aioj.backend.auth.service.AuthService;
|
||||
import cn.meowrain.aioj.backend.framework.web.Results;
|
||||
import cn.meowrain.aioj.backend.framework.web.Result;
|
||||
|
||||
import cn.meowrain.aioj.backend.framework.core.web.Result;
|
||||
import cn.meowrain.aioj.backend.framework.core.web.Results;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@@ -33,5 +34,16 @@ public class AuthController {
|
||||
return Results.success(userLoginResponseDTO.getAccessToken());
|
||||
}
|
||||
|
||||
@PostMapping("/validate")
|
||||
public Result<Boolean> validate(@RequestHeader(value = "Authorization", required = false) String authorization) {
|
||||
// 从Authorization头中提取Bearer token
|
||||
String token = null;
|
||||
if (authorization != null && authorization.startsWith("Bearer ")) {
|
||||
token = authorization.substring(7);
|
||||
}
|
||||
|
||||
Boolean isValid = authService.validateToken(token);
|
||||
return Results.success(isValid);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,9 +2,10 @@ package cn.meowrain.aioj.backend.auth.dto.chains;
|
||||
|
||||
import cn.meowrain.aioj.backend.auth.common.enums.ChainMarkEnums;
|
||||
import cn.meowrain.aioj.backend.auth.dto.req.UserLoginRequestDTO;
|
||||
import cn.meowrain.aioj.backend.framework.designpattern.chains.AbstractChianHandler;
|
||||
import cn.meowrain.aioj.backend.framework.errorcode.ErrorCode;
|
||||
import cn.meowrain.aioj.backend.framework.exception.ClientException;
|
||||
|
||||
import cn.meowrain.aioj.backend.framework.core.designpattern.chains.AbstractChianHandler;
|
||||
import cn.meowrain.aioj.backend.framework.core.errorcode.ErrorCode;
|
||||
import cn.meowrain.aioj.backend.framework.core.exception.ClientException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -12,6 +13,7 @@ import org.springframework.stereotype.Component;
|
||||
@Component
|
||||
@Slf4j
|
||||
public class UserLoginRequestParamVerifyChain implements AbstractChianHandler<UserLoginRequestDTO> {
|
||||
|
||||
@Override
|
||||
public void handle(UserLoginRequestDTO requestParam) {
|
||||
if (StringUtils.isAnyBlank(requestParam.getUserAccount(), requestParam.getUserPassword())) {
|
||||
@@ -34,4 +36,5 @@ public class UserLoginRequestParamVerifyChain implements AbstractChianHandler<Us
|
||||
public int getOrder() {
|
||||
return 10;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,4 +6,5 @@ import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class UserLoginRequestParamVerifyContext extends CommonChainContext<UserLoginRequestDTO> {
|
||||
|
||||
}
|
||||
|
||||
@@ -4,6 +4,9 @@ import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class UserLoginRequestDTO {
|
||||
|
||||
private String userAccount;
|
||||
|
||||
private String userPassword;
|
||||
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ public class UserAuthRespDTO {
|
||||
* 用户账号
|
||||
*/
|
||||
private String userAccount;
|
||||
|
||||
/**
|
||||
* 用户密码
|
||||
*/
|
||||
@@ -64,5 +65,4 @@ public class UserAuthRespDTO {
|
||||
*/
|
||||
private Date updateTime;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import java.util.Date;
|
||||
|
||||
@Data
|
||||
public class UserLoginResponseDTO implements Serializable {
|
||||
|
||||
/**
|
||||
* id
|
||||
*/
|
||||
@@ -23,8 +24,11 @@ public class UserLoginResponseDTO implements Serializable {
|
||||
private String unionId;
|
||||
|
||||
private String accessToken;
|
||||
|
||||
private String refreshToken;
|
||||
|
||||
private Long expire;
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
}
|
||||
|
||||
@@ -7,4 +7,5 @@ import org.springframework.stereotype.Component;
|
||||
*/
|
||||
@Component
|
||||
public class JwtAuthenticationFilter {
|
||||
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import cn.meowrain.aioj.backend.auth.dto.req.UserLoginRequestDTO;
|
||||
import cn.meowrain.aioj.backend.auth.dto.resp.UserLoginResponseDTO;
|
||||
|
||||
public interface AuthService {
|
||||
|
||||
/**
|
||||
* 用户登录
|
||||
* @param request {@link UserLoginRequestDTO}
|
||||
@@ -17,4 +18,12 @@ public interface AuthService {
|
||||
* @return
|
||||
*/
|
||||
UserLoginResponseDTO refreshToken(String refreshToken);
|
||||
|
||||
/**
|
||||
* 验证token的有效性
|
||||
* @param accessToken 访问令牌
|
||||
* @return token是否有效
|
||||
*/
|
||||
Boolean validateToken(String accessToken);
|
||||
|
||||
}
|
||||
|
||||
@@ -26,32 +26,50 @@ import java.util.concurrent.TimeUnit;
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class AuthServiceImpl implements AuthService {
|
||||
|
||||
private final JwtUtil jwtUtil;
|
||||
|
||||
private final UserLoginRequestParamVerifyContext userLoginRequestParamVerifyContext;
|
||||
|
||||
private final UserClient userClient;
|
||||
|
||||
private final StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
private final JwtPropertiesConfiguration jwtPropertiesConfiguration;
|
||||
|
||||
@Override
|
||||
public UserLoginResponseDTO userLogin(UserLoginRequestDTO requestParam) {
|
||||
log.info("用户登录请求: userAccount={}", requestParam.getUserAccount());
|
||||
|
||||
// 1.校验
|
||||
userLoginRequestParamVerifyContext.handler(ChainMarkEnums.USER_LOGIN_REQ_PARAM_VERIFY.getMarkName(),
|
||||
requestParam);
|
||||
|
||||
// 如果调用user-service失败,那么就说明是系统内部错误
|
||||
log.info("正在调用user-service查询用户信息...");
|
||||
Result<UserAuthRespDTO> userResp = userClient.getUserByUserName(requestParam.getUserAccount());
|
||||
|
||||
if (userResp.isFail()) {
|
||||
log.error("调用user-service返回失败:{}", userResp.getMessage());
|
||||
throw new ServiceException(ErrorCode.SYSTEM_ERROR);
|
||||
}
|
||||
UserAuthRespDTO user = userResp.getData();
|
||||
|
||||
if (ObjectUtil.isNull(user)
|
||||
|| !BCrypt.checkpw(requestParam.getUserPassword(), user.getUserPassword())) {
|
||||
throw new ServiceException("用户不存在或者密码错误", ErrorCode.NOT_LOGIN_ERROR);
|
||||
UserAuthRespDTO user = userResp.getData();
|
||||
if (user == null) {
|
||||
log.warn("用户不存在: {}", requestParam.getUserAccount());
|
||||
throw new ServiceException("用户不存在或密码错误", ErrorCode.NOT_LOGIN_ERROR);
|
||||
}
|
||||
|
||||
if (!BCrypt.checkpw(requestParam.getUserPassword(), user.getUserPassword())) {
|
||||
log.warn("密码错误: {}", requestParam.getUserAccount());
|
||||
throw new ServiceException("用户不存在或密码错误", ErrorCode.NOT_LOGIN_ERROR);
|
||||
}
|
||||
|
||||
// 生成 JWT
|
||||
log.info("正在生成JWT token...");
|
||||
String accessToken = jwtUtil.generateAccessToken(user);
|
||||
String refreshToken = jwtUtil.generateRefreshToken(user.getId());
|
||||
|
||||
UserLoginResponseDTO resp = new UserLoginResponseDTO();
|
||||
resp.setId(user.getId());
|
||||
resp.setUserAccount(user.getUserAccount());
|
||||
@@ -59,11 +77,11 @@ public class AuthServiceImpl implements AuthService {
|
||||
resp.setRefreshToken(refreshToken);
|
||||
|
||||
// refresh token存入到REDIS里面
|
||||
stringRedisTemplate.opsForValue().set(
|
||||
String.format(RedisKeyConstants.REFRESH_TOKEN_KEY_PREFIX, user.getId()),
|
||||
refreshToken,
|
||||
jwtPropertiesConfiguration.getRefreshExpire(),
|
||||
TimeUnit.MILLISECONDS);
|
||||
stringRedisTemplate.opsForValue()
|
||||
.set(String.format(RedisKeyConstants.REFRESH_TOKEN_KEY_PREFIX, user.getId()), refreshToken,
|
||||
jwtPropertiesConfiguration.getRefreshExpire(), TimeUnit.MILLISECONDS);
|
||||
|
||||
log.info("用户登录成功: userId={}, userAccount={}", user.getId(), user.getUserAccount());
|
||||
return resp;
|
||||
}
|
||||
|
||||
@@ -103,4 +121,47 @@ public class AuthServiceImpl implements AuthService {
|
||||
userLoginResponseDTO.setAccessToken(newAccessToken);
|
||||
return userLoginResponseDTO;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证token的有效性
|
||||
* @param accessToken 访问令牌
|
||||
* @return token是否有效
|
||||
*/
|
||||
@Override
|
||||
public Boolean validateToken(String accessToken) {
|
||||
try {
|
||||
// 1. 检查token格式
|
||||
if (accessToken == null || accessToken.trim().isEmpty()) {
|
||||
log.warn("Access token is null or empty");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 验证token签名和过期时间
|
||||
if (!jwtUtil.isTokenValid(accessToken)) {
|
||||
log.warn("Access token is invalid or expired");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 解析token获取用户信息
|
||||
String userId = jwtUtil.parseClaims(accessToken).getSubject();
|
||||
if (userId == null) {
|
||||
log.warn("Access token does not contain valid user id");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. 验证用户是否存在(可选,增加安全性)
|
||||
Result<UserAuthRespDTO> userResult = userClient.getUserById(userId);
|
||||
if (userResult.isFail() || userResult.getData() == null) {
|
||||
log.warn("User not found for id: {}", userId);
|
||||
return false;
|
||||
}
|
||||
|
||||
log.debug("Access token validation successful for user: {}", userId);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("Error validating access token", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -55,11 +55,7 @@ public class JwtUtil {
|
||||
|
||||
/** 解析 Token */
|
||||
public Claims parseClaims(String token) {
|
||||
return Jwts.parser()
|
||||
.verifyWith(getSigningKey())
|
||||
.build()
|
||||
.parseSignedClaims(token)
|
||||
.getPayload();
|
||||
return Jwts.parser().verifyWith(getSigningKey()).build().parseSignedClaims(token).getPayload();
|
||||
}
|
||||
|
||||
/** 校验 Token 是否过期 */
|
||||
@@ -67,8 +63,10 @@ public class JwtUtil {
|
||||
try {
|
||||
Claims claims = parseClaims(token);
|
||||
return claims.getExpiration().after(new Date());
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
catch (Exception ignored) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,10 +18,19 @@
|
||||
<mybatis-plus.version>3.5.14</mybatis-plus.version>
|
||||
<spring-boot.version>3.5.7</spring-boot.version>
|
||||
<spring-cloud-alibaba.version>2025.0.0.0</spring-cloud-alibaba.version>
|
||||
<mysql.version>9.5.0</mysql.version>
|
||||
<mysql.version>9.4.0</mysql.version>
|
||||
<jackson.bom>3.0.2</jackson.bom>
|
||||
</properties>
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<!-- https://mvnrepository.com/artifact/tools.jackson/jackson-bom -->
|
||||
<dependency>
|
||||
<groupId>tools.jackson</groupId>
|
||||
<artifactId>jackson-bom</artifactId>
|
||||
<version>${jackson.bom}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/cn.hutool/hutool-bom -->
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
@@ -72,7 +81,7 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-test</artifactId>
|
||||
<version>6.5.7</version>
|
||||
<version>6.5.6</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -82,6 +91,7 @@
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
<version>3.5.7</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
</project>
|
||||
@@ -26,5 +26,42 @@
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-core</artifactId>
|
||||
</dependency>
|
||||
<!--server-api-->
|
||||
<dependency>
|
||||
<groupId>jakarta.servlet</groupId>
|
||||
<artifactId>jakarta.servlet-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-commons</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-webmvc</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-extra</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-http</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<!--json模块-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-json</artifactId>
|
||||
</dependency>
|
||||
<!--hibernate-validator-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,56 @@
|
||||
package cn.meowrain.aioj.backend.framework.core.banner;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.ApplicationArguments;
|
||||
import org.springframework.boot.ApplicationRunner;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.time.LocalDateTime;
|
||||
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";
|
||||
}
|
||||
|
||||
// Port
|
||||
String port = env.getProperty("server.port", "unknown");
|
||||
|
||||
// JVM info
|
||||
String jvm = System.getProperty("java.version") + " (" + System.getProperty("java.vendor") + ")";
|
||||
|
||||
// PID
|
||||
String pid = ManagementFactory.getRuntimeMXBean().getPid() + "";
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@Deprecated
|
||||
public class EnvironmentBanner implements ApplicationListener<ApplicationReadyEvent> {
|
||||
|
||||
@Value("${spring.application.name:unknown}")
|
||||
@@ -47,24 +47,15 @@ public class EnvironmentBanner implements ApplicationListener<ApplicationReadyEv
|
||||
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 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";
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package cn.meowrain.aioj.backend.framework.core.banner.config;
|
||||
|
||||
import cn.meowrain.aioj.backend.framework.core.banner.BannerApplicationRunner;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
@AutoConfiguration
|
||||
public class AIOJBannerAutoConfiguration {
|
||||
@Bean
|
||||
public BannerApplicationRunner bannerApplicationRunner(Environment env) {
|
||||
return new BannerApplicationRunner(env);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,18 @@
|
||||
package cn.meowrain.aioj.backend.framework.core.config;
|
||||
|
||||
|
||||
import cn.meowrain.aioj.backend.framework.core.exception.handler.GlobalExceptionHandler;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
/**
|
||||
* 注册为bean,全局异常拦截器
|
||||
*/
|
||||
@AutoConfiguration
|
||||
public class WebAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public GlobalExceptionHandler globalExceptionHandler() {
|
||||
return new GlobalExceptionHandler();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
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";
|
||||
|
||||
/**
|
||||
* UPMS模块
|
||||
*/
|
||||
public static final String UPMS_SERVICE = "upms-service";
|
||||
}
|
||||
@@ -3,17 +3,17 @@ package cn.meowrain.aioj.backend.framework.core.designpattern.chains;
|
||||
import org.springframework.core.Ordered;
|
||||
|
||||
public interface AbstractChianHandler<T> extends Ordered {
|
||||
|
||||
/**
|
||||
* 执行责任链逻辑
|
||||
*
|
||||
* @param requestParam 责任链执行入参
|
||||
*/
|
||||
void handle(T requestParam);
|
||||
|
||||
/**
|
||||
* 责任链组件标识
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String mark();
|
||||
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import java.util.stream.Collectors;
|
||||
@Component
|
||||
@Slf4j
|
||||
public class CommonChainContext<T> implements ApplicationContextAware, CommandLineRunner {
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
@Override
|
||||
@@ -41,16 +42,12 @@ public class CommonChainContext<T> implements ApplicationContextAware, CommandLi
|
||||
@Override
|
||||
public void run(String... args) throws Exception {
|
||||
log.info("【责任链路初始化】开始加载并分组所有处理器...");
|
||||
applicationContext.getBeansOfType(AbstractChianHandler.class)
|
||||
.values()
|
||||
.forEach(handler -> {
|
||||
applicationContext.getBeansOfType(AbstractChianHandler.class).values().forEach(handler -> {
|
||||
// 打印当前处理器的类名和它所属的 ChainMark
|
||||
String handlerName = handler.getClass().getSimpleName();
|
||||
String mark = handler.mark();
|
||||
log.info(" -> 发现处理器:{},归属链路:{}", handlerName, mark);
|
||||
abstractChainHandlerMap
|
||||
.computeIfAbsent(handler.mark(), k -> new ArrayList<>())
|
||||
.add(handler);
|
||||
abstractChainHandlerMap.computeIfAbsent(handler.mark(), k -> new ArrayList<>()).add(handler);
|
||||
});
|
||||
|
||||
// 步骤 2: 对每个链路中的处理器进行排序 (Sort 阶段)
|
||||
|
||||
@@ -4,14 +4,17 @@ package cn.meowrain.aioj.backend.framework.core.enums;
|
||||
* 删除枚举
|
||||
*/
|
||||
public enum DelStatusEnum {
|
||||
STATUS_NORMAL("0"),
|
||||
STATUS_DELETE("1");
|
||||
|
||||
STATUS_NORMAL("0"), STATUS_DELETE("1");
|
||||
|
||||
private final String code;
|
||||
|
||||
DelStatusEnum(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String code() {
|
||||
return this.code;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
package cn.meowrain.aioj.backend.framework.core.errorcode;
|
||||
|
||||
public enum ErrorCode implements IErrorCode {
|
||||
SUCCESS("0", "ok"),
|
||||
PARAMS_ERROR("40000", "请求参数错误"),
|
||||
NOT_LOGIN_ERROR("40100", "未登录"),
|
||||
NO_AUTH_ERROR("40101", "无权限"),
|
||||
NOT_FOUND_ERROR("40400", "请求数据不存在"),
|
||||
FORBIDDEN_ERROR("40300", "禁止访问"),
|
||||
SYSTEM_ERROR("50000", "系统内部异常"),
|
||||
OPERATION_ERROR("50001", "操作失败"),
|
||||
API_REQUEST_ERROR("50010", "接口调用失败");
|
||||
|
||||
SUCCESS("0", "ok"), PARAMS_ERROR("40000", "请求参数错误"), NOT_LOGIN_ERROR("40100", "未登录"), NO_AUTH_ERROR("40101", "无权限"),
|
||||
NOT_FOUND_ERROR("40400", "请求数据不存在"), FORBIDDEN_ERROR("40300", "禁止访问"), SYSTEM_ERROR("50000", "系统内部异常"),
|
||||
OPERATION_ERROR("50001", "操作失败"), API_REQUEST_ERROR("50010", "接口调用失败");
|
||||
|
||||
/**
|
||||
* 状态码
|
||||
*/
|
||||
@@ -35,4 +31,5 @@ public enum ErrorCode implements IErrorCode {
|
||||
public String message() {
|
||||
return message;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package cn.meowrain.aioj.backend.framework.core.errorcode;
|
||||
|
||||
public interface IErrorCode {
|
||||
|
||||
String code();
|
||||
|
||||
String message();
|
||||
|
||||
}
|
||||
|
||||
@@ -12,7 +12,9 @@ import java.util.Optional;
|
||||
@Getter
|
||||
|
||||
public class AbstractException extends RuntimeException {
|
||||
|
||||
public final String errorCode;
|
||||
|
||||
public final String errorMessage;
|
||||
|
||||
public AbstractException(String message, Throwable throwable, IErrorCode errorCode) {
|
||||
@@ -21,4 +23,5 @@ public class AbstractException extends RuntimeException {
|
||||
this.errorMessage = Optional.ofNullable(StringUtils.hasLength(message) ? message : null)
|
||||
.orElse(errorCode.message());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import lombok.ToString;
|
||||
*/
|
||||
@ToString
|
||||
public class ClientException extends AbstractException {
|
||||
|
||||
public ClientException(String message, Throwable throwable, IErrorCode errorCode) {
|
||||
super(message, throwable, errorCode);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import lombok.ToString;
|
||||
*/
|
||||
@ToString
|
||||
public class RemoteException extends AbstractException {
|
||||
|
||||
public RemoteException(IErrorCode errorCode) {
|
||||
this(null, null, errorCode);
|
||||
}
|
||||
@@ -20,6 +21,7 @@ public class RemoteException extends AbstractException{
|
||||
public RemoteException(String message, Throwable throwable, IErrorCode errorCode) {
|
||||
super(message, throwable, errorCode);
|
||||
}
|
||||
|
||||
public RemoteException(String message) {
|
||||
this(message, null, ErrorCode.API_REQUEST_ERROR);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import lombok.ToString;
|
||||
*/
|
||||
@ToString
|
||||
public class ServiceException extends AbstractException {
|
||||
|
||||
public ServiceException(String message, IErrorCode errorCode) {
|
||||
this(message, null, errorCode);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import java.util.List;
|
||||
@Slf4j
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
/**
|
||||
* 捕获所有参数错误,然后统一捕获并且抛出
|
||||
* @param request {@link HttpServletRequest}
|
||||
@@ -30,8 +31,10 @@ public class GlobalExceptionHandler {
|
||||
public Result<Void> validExceptionHandler(HttpServletRequest request, MethodArgumentNotValidException ex) {
|
||||
BindingResult bindingResult = ex.getBindingResult();
|
||||
// 收集所有错误字段
|
||||
List<String> errorMessages = bindingResult.getFieldErrors().stream()
|
||||
.map(FieldError::getDefaultMessage).toList();
|
||||
List<String> errorMessages = bindingResult.getFieldErrors()
|
||||
.stream()
|
||||
.map(FieldError::getDefaultMessage)
|
||||
.toList();
|
||||
String exceptionMessage = String.join(",", errorMessages);
|
||||
log.error("[{}] {} [ex] {}", request.getMethod(), getUrl(request), exceptionMessage);
|
||||
return Results.paramsValidFailure();
|
||||
@@ -80,4 +83,5 @@ public class GlobalExceptionHandler {
|
||||
}
|
||||
return request.getRequestURL() + "?" + request.getQueryString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package cn.meowrain.aioj.backend.framework.core.jackson;
|
||||
|
||||
import cn.hutool.core.date.DatePattern;
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
import com.fasterxml.jackson.datatype.jsr310.PackageVersion;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.*;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.*;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.time.*;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
|
||||
public class JavaTimeModule extends SimpleModule {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* JavaTimeModule构造函数,用于初始化时间序列化和反序列化规则
|
||||
*/
|
||||
public JavaTimeModule() {
|
||||
super(PackageVersion.VERSION);
|
||||
|
||||
// ======================= 时间序列化规则 ===============================
|
||||
// yyyy-MM-dd HH:mm:ss
|
||||
this.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DatePattern.NORM_DATETIME_FORMATTER));
|
||||
// yyyy-MM-dd
|
||||
this.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ISO_LOCAL_DATE));
|
||||
// HH:mm:ss
|
||||
this.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ISO_LOCAL_TIME));
|
||||
// Instant 类型序列化
|
||||
this.addSerializer(Instant.class, InstantSerializer.INSTANCE);
|
||||
// Duration 类型序列化
|
||||
this.addSerializer(Duration.class, DurationSerializer.INSTANCE);
|
||||
|
||||
// ======================= 时间反序列化规则 ==============================
|
||||
// yyyy-MM-dd HH:mm:ss
|
||||
this.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DatePattern.NORM_DATETIME_FORMATTER));
|
||||
// yyyy-MM-dd
|
||||
this.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ISO_LOCAL_DATE));
|
||||
// HH:mm:ss
|
||||
this.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ISO_LOCAL_TIME));
|
||||
// Instant 反序列化
|
||||
this.addDeserializer(Instant.class, InstantDeserializer.INSTANT);
|
||||
// Duration 反序列化
|
||||
this.addDeserializer(Duration.class, DurationDeserializer.INSTANCE);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package cn.meowrain.aioj.backend.framework.core.utils;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.EnvironmentAware;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class SpringContextHolder implements ApplicationContextAware, EnvironmentAware, DisposableBean {
|
||||
private static ApplicationContext applicationContext = null;
|
||||
|
||||
private static Environment environment = null;
|
||||
/**
|
||||
* 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
|
||||
*/
|
||||
public static <T> T getBean(String name) {
|
||||
return (T) applicationContext.getBean(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
|
||||
*/
|
||||
public static <T> T getBean(Class<T> requiredType) {
|
||||
return applicationContext.getBean(requiredType);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void destroy() throws Exception {
|
||||
clearHolder();
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -12,20 +12,25 @@ import java.io.Serializable;
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class Result<T> implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 正确返回码
|
||||
* */
|
||||
*/
|
||||
public static final String SUCCESS_CODE = "0";
|
||||
|
||||
/**
|
||||
* 响应码
|
||||
*/
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 响应数据
|
||||
*/
|
||||
private T data;
|
||||
|
||||
/**
|
||||
* 响应信息
|
||||
*/
|
||||
@@ -33,7 +38,6 @@ public class Result<T> implements Serializable {
|
||||
|
||||
/**
|
||||
* 返回是否是正确响应
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public boolean isSuccess() {
|
||||
@@ -47,4 +51,5 @@ public class Result<T> implements Serializable {
|
||||
public boolean isFail() {
|
||||
return !isSuccess();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package cn.meowrain.aioj.backend.framework.core.web;
|
||||
|
||||
|
||||
import cn.meowrain.aioj.backend.framework.core.errorcode.ErrorCode;
|
||||
import cn.meowrain.aioj.backend.framework.core.exception.AbstractException;
|
||||
|
||||
@@ -10,9 +9,9 @@ import java.util.Optional;
|
||||
* 构建响应的工具类
|
||||
*/
|
||||
public final class Results {
|
||||
|
||||
/**
|
||||
* 成功响应,不返回任何信息
|
||||
*
|
||||
* @return {@link Result<Void>}
|
||||
*/
|
||||
public static Result<Void> success() {
|
||||
@@ -21,14 +20,12 @@ public final class Results {
|
||||
|
||||
/**
|
||||
* 成功响应 返回数据
|
||||
*
|
||||
* @param data 返回的响应体信息
|
||||
* @param <T> 泛型
|
||||
* @return {@link Result<T>}
|
||||
*/
|
||||
public static <T> Result<T> success(T data) {
|
||||
return new Result<T>().setCode(Result.SUCCESS_CODE)
|
||||
.setData(data);
|
||||
return new Result<T>().setCode(Result.SUCCESS_CODE).setData(data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -41,17 +38,14 @@ public final class Results {
|
||||
|
||||
/**
|
||||
* 服务端错误默认响应 -- <b>内部错误</b>
|
||||
*
|
||||
* @return {@link Result<Void>}
|
||||
*/
|
||||
public static Result<Void> failure() {
|
||||
return new Result<Void>().setCode(ErrorCode.SYSTEM_ERROR.code())
|
||||
.setMessage(ErrorCode.SYSTEM_ERROR.message());
|
||||
return new Result<Void>().setCode(ErrorCode.SYSTEM_ERROR.code()).setMessage(ErrorCode.SYSTEM_ERROR.message());
|
||||
}
|
||||
|
||||
/**
|
||||
* 服务端错误响应 - 接收一个AbstractException,里面定义了错误码和错误信息
|
||||
*
|
||||
* @param exception {@link AbstractException}
|
||||
* @return {@link Result<Void>}
|
||||
*/
|
||||
@@ -59,21 +53,17 @@ public final class Results {
|
||||
String errorCode = Optional.ofNullable(exception.getErrorCode()).orElse(ErrorCode.SYSTEM_ERROR.code());
|
||||
String errorMessage = Optional.ofNullable(exception.getMessage()).orElse(ErrorCode.SYSTEM_ERROR.message());
|
||||
|
||||
return new Result<Void>()
|
||||
.setCode(errorCode)
|
||||
.setMessage(errorMessage);
|
||||
return new Result<Void>().setCode(errorCode).setMessage(errorMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 服务端错误响应,自定义错误码和错误信息
|
||||
*
|
||||
* @param errorCode {@link String}
|
||||
* @param errorMessage {@link String}
|
||||
* @return {@link Result<Void>}
|
||||
*/
|
||||
public static Result<Void> failure(String errorCode, String errorMessage) {
|
||||
return new Result<Void>()
|
||||
.setCode(errorCode)
|
||||
.setMessage(errorMessage);
|
||||
return new Result<Void>().setCode(errorCode).setMessage(errorMessage);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
cn.meowrain.aioj.backend.framework.core.banner.config.AIOJBannerAutoConfiguration
|
||||
cn.meowrain.aioj.backend.framework.core.config.WebAutoConfiguration
|
||||
60
aioj-backend-common/aioj-backend-common-feign/pom.xml
Normal file
60
aioj-backend-common/aioj-backend-common-feign/pom.xml
Normal file
@@ -0,0 +1,60 @@
|
||||
<?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">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>cn.meowrain</groupId>
|
||||
<artifactId>aioj-backend-common</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>aioj-backend-common-feign</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.meowrain</groupId>
|
||||
<artifactId>aioj-backend-common-core</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
|
||||
</dependency>
|
||||
<!--feign 依赖-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||||
</dependency>
|
||||
<!-- okhttp 扩展 -->
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-okhttp</artifactId>
|
||||
</dependency>
|
||||
<!-- LB 扩展 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
|
||||
</dependency>
|
||||
<!--caffeine 替换LB 默认缓存实现-->
|
||||
<dependency>
|
||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||
<artifactId>caffeine</artifactId>
|
||||
</dependency>
|
||||
<!--oauth server 依赖-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-core</artifactId>
|
||||
</dependency>
|
||||
<!-- 异常枚举 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-webmvc</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,7 @@
|
||||
package cn.meowrain.aioj.backend.framework.feign;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
|
||||
@AutoConfiguration
|
||||
public class FeignAutoConfiguration {
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package cn.meowrain.aioj.backend.framework.feign.annotation;
|
||||
|
||||
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@EnableFeignClients
|
||||
public @interface EnableAIOJFeignClients {
|
||||
String[] basePackages() default {};
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package cn.meowrain.aioj.backend.framework.feign.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 服务无token调用声明注解
|
||||
* <p>
|
||||
* 只有发起方没有 token 时候才需要添加此注解, @NoToken + @Inner
|
||||
* <p>
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface NoToken {
|
||||
|
||||
}
|
||||
@@ -17,4 +17,34 @@
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!--安全依赖获取上下文信息-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-oauth2-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-extra</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-http</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.meowrain</groupId>
|
||||
<artifactId>aioj-backend-common-core</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.meowrain</groupId>
|
||||
<artifactId>aioj-backend-upms-api</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -3,6 +3,8 @@ package cn.meowrain.aioj.backend.framework.log;
|
||||
import cn.meowrain.aioj.backend.framework.log.aspect.SysLogAspect;
|
||||
import cn.meowrain.aioj.backend.framework.log.config.AIOJLogPropertiesConfiguration;
|
||||
import cn.meowrain.aioj.backend.framework.log.event.SysLogListener;
|
||||
import cn.meowrain.aioj.backend.upms.api.feign.RemoteLogService;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -14,13 +16,17 @@ import org.springframework.scheduling.annotation.EnableAsync;
|
||||
@EnableConfigurationProperties(AIOJLogPropertiesConfiguration.class)
|
||||
@ConditionalOnProperty(value = "aioj.log.enabled", matchIfMissing = true)
|
||||
public class LogAutoConfiguration {
|
||||
|
||||
/**
|
||||
* 创建并返回SysLogListener的Bean实例
|
||||
*/
|
||||
@Bean
|
||||
public SysLogListener sysLogListener(AIOJLogPropertiesConfiguration logProperties, RemoteLogService remoteLogService) {
|
||||
@ConditionalOnBean(RemoteLogService.class)
|
||||
public SysLogListener sysLogListener(AIOJLogPropertiesConfiguration logProperties,
|
||||
RemoteLogService remoteLogService) {
|
||||
return new SysLogListener(remoteLogService, logProperties);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回切面类实例
|
||||
* @return {@link SysLogAspect}
|
||||
@@ -29,4 +35,5 @@ public class LogAutoConfiguration {
|
||||
public SysLogAspect sysLogAspect() {
|
||||
return new SysLogAspect();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import java.lang.annotation.*;
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface SysLog {
|
||||
|
||||
/**
|
||||
* 描述
|
||||
* @return {@link String}
|
||||
@@ -20,4 +21,5 @@ public @interface SysLog {
|
||||
* @return 日志描述
|
||||
*/
|
||||
String expression() default "";
|
||||
|
||||
}
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
package cn.meowrain.aioj.backend.framework.log.aspect;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.meowrain.aioj.backend.framework.core.utils.SpringContextHolder;
|
||||
import cn.meowrain.aioj.backend.framework.log.annotation.SysLog;
|
||||
import cn.meowrain.aioj.backend.framework.log.enums.LogTypeEnum;
|
||||
import cn.meowrain.aioj.backend.framework.log.event.SysLogEvent;
|
||||
import cn.meowrain.aioj.backend.framework.log.event.SysLogEventSource;
|
||||
import cn.meowrain.aioj.backend.framework.log.utils.SysLogUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
@@ -14,18 +20,60 @@ import org.springframework.expression.EvaluationContext;
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class SysLogAspect {
|
||||
|
||||
/**
|
||||
* 环绕通知方法,用于处理系统日志记录
|
||||
* @param point 连接点对象
|
||||
* @param sysLog 系统日志注解
|
||||
* @return 目标方法执行结果
|
||||
* @throws Throwable 目标方法执行可能抛出的异常
|
||||
*/
|
||||
@Around("@annotation(sysLog)")
|
||||
public Object around(ProceedingJoinPoint joinPoint,SysLog sysLog) throws Throwable {
|
||||
String strClassName = joinPoint.getTarget().getClass().getName();
|
||||
String strMethodName = joinPoint.getSignature().getName();
|
||||
@SneakyThrows
|
||||
public Object around(ProceedingJoinPoint point,SysLog sysLog) {
|
||||
String strClassName = point.getTarget().getClass().getName();
|
||||
String strMethodName = point.getSignature().getName();
|
||||
log.debug("[类名]:{},[方法]:{}", strClassName, strMethodName);
|
||||
|
||||
String value = sysLog.value();
|
||||
String expression = sysLog.expression();
|
||||
// 当前表达式存在 SPEL,会覆盖 value 的值
|
||||
if (StrUtil.isNotBlank(expression)) {
|
||||
// 解析SPEL
|
||||
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
|
||||
MethodSignature signature = (MethodSignature) point.getSignature();
|
||||
EvaluationContext context = SysLogUtils.getContext(point.getArgs(), signature.getMethod());
|
||||
try {
|
||||
value = SysLogUtils.getValue(context, expression, String.class);
|
||||
}
|
||||
catch (Exception e) {
|
||||
// SPEL 表达式异常,获取 value 的值
|
||||
log.error("@SysLog 解析SPEL {} 异常", expression);
|
||||
}
|
||||
}
|
||||
|
||||
SysLogEventSource logVo = SysLogUtils.getSysLog();
|
||||
logVo.setTitle(value);
|
||||
// 获取请求body参数
|
||||
if (StrUtil.isBlank(logVo.getParams())) {
|
||||
logVo.setBody(point.getArgs());
|
||||
}
|
||||
// 发送异步日志事件
|
||||
Long startTime = System.currentTimeMillis();
|
||||
Object obj;
|
||||
|
||||
try {
|
||||
obj = point.proceed();
|
||||
}
|
||||
catch (Exception e) {
|
||||
logVo.setLogType(LogTypeEnum.ERROR.getType());
|
||||
logVo.setException(e.getMessage());
|
||||
throw e;
|
||||
}
|
||||
finally {
|
||||
Long endTime = System.currentTimeMillis();
|
||||
logVo.setTime(endTime - startTime);
|
||||
SpringContextHolder.publishEvent(new SysLogEvent(logVo));
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,18 @@ package cn.meowrain.aioj.backend.framework.log.config;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@ConfigurationProperties(AIOJLogPropertiesConfiguration.PREFIX)
|
||||
public class AIOJLogPropertiesConfiguration {
|
||||
|
||||
public static final String PREFIX = "aioj.log";
|
||||
|
||||
/**
|
||||
* 开启日志记录
|
||||
*/
|
||||
@@ -18,4 +23,12 @@ public class AIOJLogPropertiesConfiguration {
|
||||
* 请求报文最大存储长度
|
||||
*/
|
||||
private Integer maxLength = 20000;
|
||||
|
||||
/**
|
||||
* 放行字段,password,mobile,idcard,phone
|
||||
*/
|
||||
@Value("${log.exclude-fields:password,mobile,idcard,phone}")
|
||||
private List<String> excludeFields;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
package cn.meowrain.aioj.backend.framework.log.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 日志对象
|
||||
*/
|
||||
@Data
|
||||
public class SysLog implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 编号
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 日志类型
|
||||
*/
|
||||
private String logType;
|
||||
|
||||
/**
|
||||
* 日志标题
|
||||
*/
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
private String createBy;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* 操作IP地址
|
||||
*/
|
||||
private String remoteAddr;
|
||||
|
||||
/**
|
||||
* 用户代理
|
||||
*/
|
||||
private String userAgent;
|
||||
|
||||
/**
|
||||
* 请求URI
|
||||
*/
|
||||
private String requestUri;
|
||||
|
||||
/**
|
||||
* 操作方式
|
||||
*/
|
||||
private String method;
|
||||
|
||||
/**
|
||||
* 操作提交的数据
|
||||
*/
|
||||
private String params;
|
||||
|
||||
/**
|
||||
* 执行时间
|
||||
*/
|
||||
private Long time;
|
||||
|
||||
/**
|
||||
* 异常信息
|
||||
*/
|
||||
private String exception;
|
||||
|
||||
/**
|
||||
* 服务ID
|
||||
*/
|
||||
private String serviceId;
|
||||
|
||||
/**
|
||||
* 删除标记
|
||||
*/
|
||||
private String delFlag;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package cn.meowrain.aioj.backend.framework.log.event;
|
||||
|
||||
import cn.meowrain.aioj.backend.framework.log.annotation.SysLog;
|
||||
|
||||
import cn.meowrain.aioj.backend.upms.api.entity.SysLog;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
import java.io.Serial;
|
||||
@@ -9,12 +10,15 @@ import java.io.Serial;
|
||||
* 系统日志事件类
|
||||
*/
|
||||
public class SysLogEvent extends ApplicationEvent {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 构造方法,根据源SysLog对象创建SysLogEvent
|
||||
*/
|
||||
public SysLogEvent(SysLog source) {
|
||||
super(source);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
package cn.meowrain.aioj.backend.framework.log.event;
|
||||
|
||||
import cn.meowrain.aioj.backend.upms.api.entity.SysLog;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 系统
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public class SysLogEventSource extends SysLogEvent implements Serializable {
|
||||
public class SysLogEventSource extends SysLog {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
package cn.meowrain.aioj.backend.framework.log.event;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.meowrain.aioj.backend.framework.core.jackson.JavaTimeModule;
|
||||
import cn.meowrain.aioj.backend.framework.log.config.AIOJLogPropertiesConfiguration;
|
||||
import cn.meowrain.aioj.backend.framework.log.entity.SysLog;
|
||||
import cn.meowrain.aioj.backend.upms.api.entity.SysLog;
|
||||
import cn.meowrain.aioj.backend.upms.api.feign.RemoteLogService;
|
||||
import com.fasterxml.jackson.annotation.JsonFilter;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.ser.FilterProvider;
|
||||
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
|
||||
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
@@ -11,8 +18,11 @@ import org.springframework.context.event.EventListener;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class SysLogListener implements InitializingBean {
|
||||
|
||||
private final static ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
private final RemoteLogService remoteLogService;
|
||||
@@ -28,9 +38,38 @@ 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()));
|
||||
}
|
||||
|
||||
remoteLogService.saveLog(sysLog);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 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]);
|
||||
|
||||
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 {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import org.springframework.core.Ordered;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
|
||||
public class ApplicationLoggerInitializer implements EnvironmentPostProcessor, Ordered {
|
||||
|
||||
@Override
|
||||
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
|
||||
|
||||
@@ -15,4 +16,5 @@ public class ApplicationLoggerInitializer implements EnvironmentPostProcessor, O
|
||||
public int getOrder() {
|
||||
return Ordered.LOWEST_PRECEDENCE;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,9 +1,101 @@
|
||||
package cn.meowrain.aioj.backend.framework.log.utils;
|
||||
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
import cn.hutool.extra.servlet.JakartaServletUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import cn.meowrain.aioj.backend.framework.core.utils.SpringContextHolder;
|
||||
import cn.meowrain.aioj.backend.framework.log.config.AIOJLogPropertiesConfiguration;
|
||||
import cn.meowrain.aioj.backend.framework.log.enums.LogTypeEnum;
|
||||
import cn.meowrain.aioj.backend.framework.log.event.SysLogEventSource;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.core.StandardReflectionParameterNameDiscoverer;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
public final class SysLogUtils {
|
||||
|
||||
|
||||
/**
|
||||
* 获取系统日志事件源
|
||||
* @return 系统日志事件源对象
|
||||
*/
|
||||
public static SysLogEventSource getSysLog() {
|
||||
HttpServletRequest request = ((ServletRequestAttributes) Objects
|
||||
.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
|
||||
SysLogEventSource sysLog = new SysLogEventSource();
|
||||
sysLog.setLogType(LogTypeEnum.NORMAL.getType());
|
||||
sysLog.setRequestUri(URLUtil.getPath(request.getRequestURI()));
|
||||
sysLog.setMethod(request.getMethod());
|
||||
sysLog.setRemoteAddr(JakartaServletUtil.getClientIP(request));
|
||||
sysLog.setUserAgent(request.getHeader(HttpHeaders.USER_AGENT));
|
||||
sysLog.setCreateBy(getUsername());
|
||||
sysLog.setServiceId(SpringUtil.getProperty("spring.application.name"));
|
||||
|
||||
// get 参数脱敏
|
||||
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));
|
||||
return sysLog;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户名称
|
||||
* @return username
|
||||
*/
|
||||
private static String getUsername() {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (authentication == null) {
|
||||
return null;
|
||||
}
|
||||
return authentication.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取spel 定义的参数值
|
||||
* @param context 参数容器
|
||||
* @param key key
|
||||
* @param clazz 需要返回的类型
|
||||
* @param <T> 返回泛型
|
||||
* @return 参数值
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取参数容器
|
||||
* @param arguments 方法的参数列表
|
||||
* @param signatureMethod 被执行的方法体
|
||||
* @return 装载参数的容器
|
||||
*/
|
||||
public static EvaluationContext getContext(Object[] arguments, Method signatureMethod) {
|
||||
String[] parameterNames = new StandardReflectionParameterNameDiscoverer().getParameterNames(signatureMethod);
|
||||
EvaluationContext context = new StandardEvaluationContext();
|
||||
if (parameterNames == null) {
|
||||
return context;
|
||||
}
|
||||
for (int i = 0; i < arguments.length; i++) {
|
||||
context.setVariable(parameterNames[i], arguments[i]);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,11 @@
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-core</artifactId>
|
||||
</dependency>
|
||||
<!-- orm 模块-->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
|
||||
</dependency>
|
||||
<!--mybatis-->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
|
||||
@@ -3,10 +3,10 @@ package cn.meowrain.backend.common.mybaits;
|
||||
import cn.meowrain.backend.common.mybaits.config.MybatisPlusMetaObjectHandler;
|
||||
import cn.meowrain.backend.common.mybaits.plugins.PaginationInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@AutoConfiguration
|
||||
public class MybatisPlusAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -16,8 +16,10 @@ import java.time.LocalDateTime;
|
||||
@Getter
|
||||
@Setter
|
||||
public class BaseEntity implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
@@ -45,4 +47,5 @@ public class BaseEntity implements Serializable {
|
||||
@Schema(description = "更新时间")
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import java.util.Optional;
|
||||
*/
|
||||
@Slf4j
|
||||
public class MybatisPlusMetaObjectHandler implements MetaObjectHandler {
|
||||
|
||||
@Override
|
||||
public void insertFill(MetaObject metaObject) {
|
||||
log.debug("mybatis plus start insert fill ....");
|
||||
@@ -67,4 +68,5 @@ public class MybatisPlusMetaObjectHandler implements MetaObjectHandler {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package cn.meowrain.backend.common.mybaits.plugins;
|
||||
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.DbType;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.toolkit.ParameterUtils;
|
||||
@@ -16,14 +15,15 @@ import org.apache.ibatis.session.ResultHandler;
|
||||
import org.apache.ibatis.session.RowBounds;
|
||||
|
||||
/**
|
||||
* * 分页拦截器实现类,用于处理分页查询逻辑
|
||||
* * <p>
|
||||
* * 分页拦截器实现类,用于处理分页查询逻辑 *
|
||||
* <p>
|
||||
* * 当分页大小小于0时自动设置为0,防止全表查询
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public class PaginationInterceptor extends PaginationInnerInterceptor {
|
||||
|
||||
/**
|
||||
* 数据库类型
|
||||
* <p>
|
||||
@@ -38,7 +38,6 @@ public class PaginationInterceptor extends PaginationInnerInterceptor {
|
||||
*/
|
||||
private IDialect dialect;
|
||||
|
||||
|
||||
public PaginationInterceptor(DbType dbType) {
|
||||
this.dbType = dbType;
|
||||
}
|
||||
@@ -51,7 +50,8 @@ public class PaginationInterceptor extends PaginationInnerInterceptor {
|
||||
* 在执行查询前处理分页参数
|
||||
*/
|
||||
@Override
|
||||
public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
|
||||
public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds,
|
||||
ResultHandler resultHandler, BoundSql boundSql) {
|
||||
IPage<?> page = ParameterUtils.findPage(parameter).orElse(null);
|
||||
// size 小于 0 直接设置为 0 , 即不查询任何数据
|
||||
if (null != page && page.getSize() < 0) {
|
||||
@@ -59,4 +59,5 @@ public class PaginationInterceptor extends PaginationInnerInterceptor {
|
||||
}
|
||||
super.beforeQuery(executor, ms, page, rowBounds, resultHandler, boundSql);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
<module>aioj-backend-common-starter</module>
|
||||
<module>aioj-backend-common-mybatis</module>
|
||||
<module>aioj-backend-common-bom</module>
|
||||
<module>aioj-backend-common-feign</module>
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
|
||||
@@ -18,11 +18,6 @@
|
||||
<spring-cloud-gateway.version>4.3.2</spring-cloud-gateway.version>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.meowrain</groupId>
|
||||
<artifactId>aioj-backend-common-starter</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
@@ -66,5 +61,27 @@
|
||||
<artifactId>spring-cloud-starter-bootstrap</artifactId>
|
||||
<version>4.3.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.meowrain</groupId>
|
||||
<artifactId>aioj-backend-common-core</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<exclusions>
|
||||
<!-- 🚫 必须排除:Spring MVC 核心 -->
|
||||
<exclusion>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</exclusion>
|
||||
<!-- 🚫 必须排除:Servlet API (Gateway用不到) -->
|
||||
<exclusion>
|
||||
<groupId>jakarta.servlet</groupId>
|
||||
<artifactId>jakarta.servlet-api</artifactId>
|
||||
</exclusion>
|
||||
<!-- 🚫 必须排除:Spring WebMVC -->
|
||||
<exclusion>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-webmvc</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -8,7 +8,9 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
|
||||
@EnableConfigurationProperties(value = { GatewayPropertiesConfiguration.class })
|
||||
@SpringBootApplication
|
||||
public class AIOJGatewayApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(AIOJGatewayApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,22 +12,16 @@ import java.util.List;
|
||||
/**
|
||||
* 全局 CORS 配置(WebFlux 环境使用 CorsWebFilter)
|
||||
*
|
||||
* WebFlux 不使用 Spring MVC 的 CorsFilter,
|
||||
* 而是使用专门的 CorsWebFilter 处理跨域。
|
||||
* WebFlux 不使用 Spring MVC 的 CorsFilter, 而是使用专门的 CorsWebFilter 处理跨域。
|
||||
*
|
||||
* 此配置实现了:
|
||||
* - 允许任意域名访问(AllowedOriginPatterns = "*")
|
||||
* - 允许所有请求方法(GET、POST、PUT...)
|
||||
* - 允许所有请求头
|
||||
* - 允许跨域携带 Cookie(AllowCredentials)
|
||||
* - 对所有路径生效(/**)
|
||||
* 此配置实现了: - 允许任意域名访问(AllowedOriginPatterns = "*") - 允许所有请求方法(GET、POST、PUT...) - 允许所有请求头 -
|
||||
* 允许跨域携带 Cookie(AllowCredentials) - 对所有路径生效(/**)
|
||||
*/
|
||||
@Configuration
|
||||
public class CorsConfig {
|
||||
|
||||
/**
|
||||
* 注册全局 CORS 过滤器
|
||||
*
|
||||
* @return CorsWebFilter 跨域过滤器
|
||||
*/
|
||||
@Bean
|
||||
@@ -43,9 +37,8 @@ public class CorsConfig {
|
||||
corsConfiguration.setAllowCredentials(true);
|
||||
|
||||
/**
|
||||
* 允许跨域的来源域名
|
||||
* 使用 setAllowedOriginPatterns("*") 是 WebFlux 推荐方式,
|
||||
* 因为 setAllowedOrigins("*") 在 allowCredentials=true 时会被拦截。
|
||||
* 允许跨域的来源域名 使用 setAllowedOriginPatterns("*") 是 WebFlux 推荐方式, 因为
|
||||
* setAllowedOrigins("*") 在 allowCredentials=true 时会被拦截。
|
||||
*/
|
||||
corsConfiguration.setAllowedOriginPatterns(List.of("*"));
|
||||
|
||||
@@ -53,11 +46,9 @@ public class CorsConfig {
|
||||
corsConfiguration.addAllowedHeader("*");
|
||||
|
||||
/**
|
||||
* 基于 URL 的跨域配置源,
|
||||
* PathPatternParser 用于解析路径模式(更高性能)
|
||||
* 基于 URL 的跨域配置源, PathPatternParser 用于解析路径模式(更高性能)
|
||||
*/
|
||||
UrlBasedCorsConfigurationSource source =
|
||||
new UrlBasedCorsConfigurationSource(new PathPatternParser());
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
|
||||
|
||||
// 对所有路径应用跨域设置
|
||||
source.registerCorsConfiguration("/**", corsConfiguration);
|
||||
@@ -65,4 +56,5 @@ public class CorsConfig {
|
||||
// 创建并返回 WebFlux 专用的 CORS 过滤器
|
||||
return new CorsWebFilter(source);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package cn.meowrain.aioj.backend.gateway.config;
|
||||
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
/**
|
||||
* 网关配置类
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableConfigurationProperties(GatewayPropertiesConfiguration.class)
|
||||
public class GatewayConfiguration {
|
||||
|
||||
/**
|
||||
* WebClient Bean,用于服务间调用
|
||||
*/
|
||||
@Bean
|
||||
public WebClient.Builder webClientBuilder() {
|
||||
return WebClient.builder();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,13 +2,22 @@ package cn.meowrain.aioj.backend.gateway.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = GatewayPropertiesConfiguration.PREFIX)
|
||||
@Data
|
||||
public class GatewayPropertiesConfiguration {
|
||||
|
||||
public static final String PREFIX = "aioj-backend-gateway";
|
||||
|
||||
/*
|
||||
* 白名单放行
|
||||
* */
|
||||
private String[] whiteList;
|
||||
* 白名单放行路径
|
||||
* 支持Ant风格路径匹配,如 /api/v1/question/**
|
||||
*/
|
||||
private List<String> whiteList = new ArrayList<>();
|
||||
|
||||
}
|
||||
|
||||
@@ -1,27 +1,169 @@
|
||||
package cn.meowrain.aioj.backend.gateway.filter;
|
||||
|
||||
import cn.meowrain.aioj.backend.framework.core.errorcode.ErrorCode;
|
||||
import cn.meowrain.aioj.backend.framework.core.exception.RemoteException;
|
||||
import cn.meowrain.aioj.backend.framework.core.web.Result;
|
||||
import cn.meowrain.aioj.backend.framework.core.web.Results;
|
||||
import cn.meowrain.aioj.backend.gateway.config.GatewayPropertiesConfiguration;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class AuthGlobalFilter implements GlobalFilter, Ordered {
|
||||
|
||||
private final WebClient.Builder webClientBuilder;
|
||||
|
||||
@Autowired
|
||||
private GatewayPropertiesConfiguration gatewayPropertiesConfiguration;
|
||||
|
||||
private final AntPathMatcher antPathMatcher = new AntPathMatcher();
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
/**
|
||||
* 不需要认证的路径
|
||||
*/
|
||||
private static final String[] DEFAULT_WHITE_LIST = {
|
||||
"/api/v1/auth/login",
|
||||
"/api/v1/auth/register",
|
||||
"/api/v1/auth/refresh",
|
||||
"/api/v1/user/info"
|
||||
};
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||
return null;
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
String path = request.getURI().getPath();
|
||||
|
||||
log.info("Auth filter processing request: {}", path);
|
||||
|
||||
// 检查是否在白名单中
|
||||
if (isWhiteListPath(path)) {
|
||||
log.info("Path {} is in whitelist, skip authentication", path);
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
|
||||
// 获取Authorization头
|
||||
String authHeader = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
|
||||
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
|
||||
log.warn("No valid authorization header found for path: {}", path);
|
||||
return handleUnauthorized(exchange);
|
||||
}
|
||||
|
||||
String token = authHeader.substring(7);
|
||||
|
||||
// 调用auth服务验证token
|
||||
return validateToken(token)
|
||||
.flatMap(isValid -> {
|
||||
if (isValid) {
|
||||
log.info("Token validation successful for path: {}", path);
|
||||
return chain.filter(exchange);
|
||||
} else {
|
||||
log.warn("Token validation failed for path: {}", path);
|
||||
return handleUnauthorized(exchange);
|
||||
}
|
||||
})
|
||||
.onErrorResume(throwable -> {
|
||||
log.error("Token validation error for path: {}", path, throwable);
|
||||
return handleUnauthorized(exchange);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查路径是否在白名单中
|
||||
*/
|
||||
private boolean isWhiteListPath(String path) {
|
||||
// 先检查默认白名单
|
||||
for (String whitePath : DEFAULT_WHITE_LIST) {
|
||||
if (antPathMatcher.match(whitePath, path)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查配置文件中的白名单
|
||||
if (gatewayPropertiesConfiguration.getWhiteList() != null && !gatewayPropertiesConfiguration.getWhiteList().isEmpty()) {
|
||||
for (String whitePath : gatewayPropertiesConfiguration.getWhiteList()) {
|
||||
if (antPathMatcher.match(whitePath, path)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用auth服务验证token
|
||||
*/
|
||||
private Mono<Boolean> validateToken(String token) {
|
||||
return webClientBuilder.build()
|
||||
.post()
|
||||
.uri("lb://auth-service/api/v1/auth/validate")
|
||||
.header(HttpHeaders.AUTHORIZATION, "Bearer " + token)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.retrieve()
|
||||
.bodyToMono(String.class)
|
||||
.map(response -> {
|
||||
try {
|
||||
// 解析响应,判断是否有效
|
||||
Result result = objectMapper.readValue(response, Result.class);
|
||||
return Objects.equals(result.getCode(), Result.SUCCESS_CODE);
|
||||
} catch (JsonProcessingException e) {
|
||||
log.error("Failed to parse auth response", e);
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.defaultIfEmpty(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理未授权的请求
|
||||
*/
|
||||
private Mono<Void> handleUnauthorized(ServerWebExchange exchange) {
|
||||
ServerHttpResponse response = exchange.getResponse();
|
||||
response.setStatusCode(HttpStatus.UNAUTHORIZED);
|
||||
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
|
||||
|
||||
Result<Void> result = Results.failure(new RemoteException(ErrorCode.NO_AUTH_ERROR));
|
||||
String responseBody;
|
||||
try {
|
||||
responseBody = objectMapper.writeValueAsString(result);
|
||||
} catch (JsonProcessingException e) {
|
||||
responseBody = "{\"code\":401,\"message\":\"Unauthorized\",\"data\":null}";
|
||||
}
|
||||
|
||||
DataBuffer buffer = response.bufferFactory().wrap(responseBody.getBytes(StandardCharsets.UTF_8));
|
||||
return response.writeWith(Mono.just(buffer));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 0;
|
||||
// 设置较高的优先级,确保在其他过滤器之前执行
|
||||
return -100;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,13 +7,28 @@ spring:
|
||||
webflux:
|
||||
routes:
|
||||
- id: auth-service
|
||||
uri: lb://auth-service
|
||||
uri: lb://auth-service/api
|
||||
predicates:
|
||||
- Path=/api/v1/auth/**
|
||||
- id: user-service
|
||||
uri: lb://user-service
|
||||
uri: lb://user-service/api
|
||||
predicates:
|
||||
- Path=/api/v1/user/**
|
||||
|
||||
aioj-backend-gateway:
|
||||
# 白名单配置
|
||||
white-list:
|
||||
- /api/v1/auth/login
|
||||
- /api/v1/auth/register
|
||||
- /api/v1/auth/refresh
|
||||
- /api/v1/user/info
|
||||
- /api/v1/question/list
|
||||
- /api/v1/question/detail/**
|
||||
- /actuator/health
|
||||
- /swagger-ui/**
|
||||
- /v3/api-docs/**
|
||||
- /swagger-resources/**
|
||||
|
||||
aioj:
|
||||
log:
|
||||
enabled: true
|
||||
|
||||
@@ -17,4 +17,22 @@
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.meowrain</groupId>
|
||||
<artifactId>aioj-backend-common-core</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.meowrain</groupId>
|
||||
<artifactId>aioj-backend-common-mybatis</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<!--feign 注解依赖-->
|
||||
<dependency>
|
||||
<groupId>cn.meowrain</groupId>
|
||||
<artifactId>aioj-backend-common-feign</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -1,5 +1,7 @@
|
||||
package cn.meowrain.aioj.backend.upms.api.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*
|
||||
*/
|
||||
|
||||
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;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 日志表
|
||||
* </p>
|
||||
*
|
||||
* @author lengleng
|
||||
* @since 2017-11-20
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "日志")
|
||||
public class SysLog implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 编号
|
||||
*/
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
// @ExcelProperty("日志编号")
|
||||
@Schema(description = "日志编号")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 日志类型
|
||||
*/
|
||||
@NotBlank(message = "日志类型不能为空")
|
||||
// @ExcelProperty("日志类型(0-正常 9-错误)")
|
||||
@Schema(description = "日志类型")
|
||||
private String logType;
|
||||
|
||||
/**
|
||||
* 日志标题
|
||||
*/
|
||||
@NotBlank(message = "日志标题不能为空")
|
||||
// @ExcelProperty("日志标题")
|
||||
@Schema(description = "日志标题")
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
// @ExcelProperty("创建人")
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
@Schema(description = "创建人")
|
||||
private String createBy;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
// @ExcelProperty("创建时间")
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
// @ExcelIgnore
|
||||
@TableField(fill = FieldFill.UPDATE)
|
||||
@Schema(description = "更新时间")
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* 操作IP地址
|
||||
*/
|
||||
// @ExcelProperty("操作ip地址")
|
||||
@Schema(description = "操作ip地址")
|
||||
private String remoteAddr;
|
||||
|
||||
/**
|
||||
* 用户代理
|
||||
*/
|
||||
@Schema(description = "用户代理")
|
||||
private String userAgent;
|
||||
|
||||
/**
|
||||
* 请求URI
|
||||
*/
|
||||
// @ExcelProperty("浏览器")
|
||||
@Schema(description = "请求uri")
|
||||
private String requestUri;
|
||||
|
||||
/**
|
||||
* 操作方式
|
||||
*/
|
||||
// @ExcelProperty("操作方式")
|
||||
@Schema(description = "操作方式")
|
||||
private String method;
|
||||
|
||||
/**
|
||||
* 操作提交的数据
|
||||
*/
|
||||
// @ExcelProperty("提交数据")
|
||||
@Schema(description = "提交数据")
|
||||
private String params;
|
||||
|
||||
/**
|
||||
* 执行时间
|
||||
*/
|
||||
// @ExcelProperty("执行时间")
|
||||
@Schema(description = "方法执行时间")
|
||||
private Long time;
|
||||
|
||||
/**
|
||||
* 异常信息
|
||||
*/
|
||||
// @ExcelProperty("异常信息")
|
||||
@Schema(description = "异常信息")
|
||||
private String exception;
|
||||
|
||||
/**
|
||||
* 服务ID
|
||||
*/
|
||||
// @ExcelProperty("应用标识")
|
||||
@Schema(description = "应用标识")
|
||||
private String serviceId;
|
||||
|
||||
/**
|
||||
* 删除标记
|
||||
*/
|
||||
@TableLogic
|
||||
// @ExcelIgnore
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
@Schema(description = "删除标记,1:已删除,0:正常")
|
||||
private String delFlag;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package cn.meowrain.aioj.backend.upms.api.feign;
|
||||
|
||||
import cn.meowrain.aioj.backend.framework.core.constants.ServiceNameConstants;
|
||||
import cn.meowrain.aioj.backend.framework.core.web.Result;
|
||||
import cn.meowrain.aioj.backend.framework.feign.annotation.NoToken;
|
||||
import cn.meowrain.aioj.backend.upms.api.entity.SysLog;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
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);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
cn.meowrain.aioj.backend.upms.api.feign.RemoteLogService
|
||||
@@ -17,4 +17,20 @@
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.meowrain</groupId>
|
||||
<artifactId>aioj-backend-common-mybatis</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.meowrain</groupId>
|
||||
<artifactId>aioj-backend-upms-api</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,11 @@
|
||||
package cn.meowrain.aioj.backend.upms.biz;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class AIOJAdminApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(AIOJAdminApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package cn.meowrain.aioj.backend.upms.biz.controller;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.meowrain.aioj.backend.framework.core.web.Result;
|
||||
import cn.meowrain.aioj.backend.framework.core.web.Results;
|
||||
import cn.meowrain.aioj.backend.upms.api.dto.SysLogDTO;
|
||||
import cn.meowrain.aioj.backend.upms.api.entity.SysLog;
|
||||
import cn.meowrain.aioj.backend.upms.biz.service.SysLogService;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springdoc.core.annotations.ParameterObject;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/log")
|
||||
@Tag(description = "log", name = "日志管理模块")
|
||||
@RequiredArgsConstructor
|
||||
public class SysLogController {
|
||||
|
||||
private final SysLogService sysLogService;
|
||||
|
||||
|
||||
/**
|
||||
* 分页查询系统日志
|
||||
*
|
||||
* @param page 分页参数对象
|
||||
* @param sysLog 系统日志查询条件
|
||||
* @return 包含分页结果的响应对象
|
||||
*/
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "分页查询系统日志", description = "分页查询系统日志")
|
||||
public Result<Page> getLogPage(@ParameterObject Page page, @ParameterObject SysLogDTO sysLog) {
|
||||
return Results.success(sysLogService.getLogPage(page, sysLog));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除日志
|
||||
*
|
||||
* @param ids 要删除的日志ID数组
|
||||
* @return 操作结果,成功返回success,失败返回false
|
||||
*/
|
||||
@DeleteMapping
|
||||
// @HasPermission("sys_log_del")
|
||||
@Operation(summary = "批量删除日志", description = "批量删除日志")
|
||||
public Result<Boolean> removeByIds(@RequestBody Long[] ids) {
|
||||
return Results.success(sysLogService.removeBatchByIds(CollUtil.toList(ids)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存日志
|
||||
*
|
||||
* @param sysLog 日志实体
|
||||
* @return 操作结果,成功返回success,失败返回false
|
||||
*/
|
||||
|
||||
@PostMapping("/save")
|
||||
@Operation(summary = "保存日志", description = "保存日志")
|
||||
public Result<Boolean> saveLog(@Valid @RequestBody SysLog sysLog) {
|
||||
return Results.success(sysLogService.saveLog(sysLog));
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出系统日志到Excel表格
|
||||
* @param sysLog 系统日志查询条件DTO
|
||||
* @return 符合查询条件的系统日志列表
|
||||
*/
|
||||
// @ResponseExcel
|
||||
// @GetMapping("/export")
|
||||
// @HasPermission("sys_log_export")
|
||||
// @Operation(summary = "导出系统日志到Excel表格", description = "导出系统日志到Excel表格")
|
||||
// public List<SysLog> exportLogs(SysLogDTO sysLog) {
|
||||
// return sysLogService.listLogs(sysLog);
|
||||
// }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package cn.meowrain.aioj.backend.upms.biz.mapper;
|
||||
|
||||
import cn.meowrain.aioj.backend.upms.api.entity.SysLog;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 系统日志表 Mapper 接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface SysLogMapper extends BaseMapper<SysLog> {
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package cn.meowrain.aioj.backend.upms.biz.service;
|
||||
|
||||
import cn.meowrain.aioj.backend.upms.api.dto.SysLogDTO;
|
||||
import cn.meowrain.aioj.backend.upms.api.entity.SysLog;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface SysLogService extends IService<SysLog> {
|
||||
/**
|
||||
* 分页查询系统日志
|
||||
*
|
||||
* @param page 分页对象
|
||||
* @param sysLog 系统日志
|
||||
* @return 系统日志分页数据
|
||||
*/
|
||||
Page getLogPage(Page page, SysLogDTO sysLog);
|
||||
|
||||
/**
|
||||
* 保存日志
|
||||
*
|
||||
* @param sysLog 日志实体
|
||||
* @return Boolean
|
||||
*/
|
||||
Boolean saveLog(SysLog sysLog);
|
||||
|
||||
/**
|
||||
* 查询日志列表
|
||||
*
|
||||
* @param sysLog 查询条件
|
||||
* @return 日志列表
|
||||
*/
|
||||
List<SysLog> listLogs(SysLogDTO sysLog);
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package cn.meowrain.aioj.backend.upms.biz.service.impl;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.meowrain.aioj.backend.upms.api.dto.SysLogDTO;
|
||||
import cn.meowrain.aioj.backend.upms.api.entity.SysLog;
|
||||
import cn.meowrain.aioj.backend.upms.biz.mapper.SysLogMapper;
|
||||
import cn.meowrain.aioj.backend.upms.biz.service.SysLogService;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class SysLogServiceImpl extends ServiceImpl<SysLogMapper, SysLog> implements SysLogService {
|
||||
/**
|
||||
* 分页查询系统日志
|
||||
*
|
||||
* @param page 分页参数
|
||||
* @param sysLog 日志查询条件
|
||||
* @return 分页结果
|
||||
*/
|
||||
@Override
|
||||
public Page getLogPage(Page page, SysLogDTO sysLog) {
|
||||
return baseMapper.selectPage(page, buildQuery(sysLog));
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存日志
|
||||
*
|
||||
* @param sysLog 日志对象
|
||||
* @return 保存成功返回true
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Boolean saveLog(SysLog sysLog) {
|
||||
baseMapper.insert(sysLog);
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询日志列表
|
||||
*
|
||||
* @param sysLog 查询条件DTO对象
|
||||
* @return 日志列表
|
||||
*/
|
||||
@Override
|
||||
public List<SysLog> listLogs(SysLogDTO sysLog) {
|
||||
return baseMapper.selectList(buildQuery(sysLog));
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建查询条件
|
||||
*
|
||||
* @param sysLog 前端查询条件DTO
|
||||
* @return 构建好的LambdaQueryWrapper对象
|
||||
*/
|
||||
private LambdaQueryWrapper buildQuery(SysLogDTO sysLog) {
|
||||
LambdaQueryWrapper<SysLog> wrapper = Wrappers.lambdaQuery();
|
||||
if (StrUtil.isNotBlank(sysLog.getLogType())) {
|
||||
wrapper.eq(SysLog::getLogType, sysLog.getLogType());
|
||||
}
|
||||
|
||||
if (ArrayUtil.isNotEmpty(sysLog.getCreateTime())) {
|
||||
wrapper.ge(SysLog::getCreateTime, sysLog.getCreateTime()[0])
|
||||
.le(SysLog::getCreateTime, sysLog.getCreateTime()[1]);
|
||||
}
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
spring:
|
||||
data:
|
||||
redis:
|
||||
host: 10.0.0.10
|
||||
port: 6379
|
||||
password: 123456
|
||||
datasource:
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
url: jdbc:mysql://10.0.0.10/aioj_dev
|
||||
username: root
|
||||
password: root
|
||||
cloud:
|
||||
nacos:
|
||||
discovery:
|
||||
enabled: true
|
||||
register-enabled: true
|
||||
server-addr: 10.0.0.10:8848
|
||||
username: nacos
|
||||
password: nacos
|
||||
@@ -0,0 +1,11 @@
|
||||
spring:
|
||||
data:
|
||||
redis:
|
||||
host: 10.0.0.10
|
||||
port: 6379
|
||||
password: 123456
|
||||
datasource:
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
url: jdbc:mysql://10.0.0.10/aioj_prod
|
||||
username: root
|
||||
password: root
|
||||
@@ -0,0 +1,11 @@
|
||||
spring:
|
||||
data:
|
||||
redis:
|
||||
host: 10.0.0.10
|
||||
port: 6379
|
||||
password: 123456
|
||||
datasource:
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
url: jdbc:mysql://10.0.0.10/aioj_test
|
||||
username: root
|
||||
password: 123456
|
||||
@@ -0,0 +1,35 @@
|
||||
spring:
|
||||
application:
|
||||
name: user-service
|
||||
profiles:
|
||||
active: @env@
|
||||
server:
|
||||
port: 10012
|
||||
servlet:
|
||||
context-path: /api
|
||||
springdoc:
|
||||
api-docs:
|
||||
enabled: true
|
||||
path: /v3/api-docs
|
||||
default-flat-param-object: true
|
||||
swagger-ui:
|
||||
path: /swagger-ui.html
|
||||
tags-sorter: alpha
|
||||
operations-sorter: alpha
|
||||
group-configs:
|
||||
- group: 'default'
|
||||
paths-to-match: '/**'
|
||||
packages-to-scan: cn.meowrain.aioj.backend.userservice.controller
|
||||
knife4j:
|
||||
basic:
|
||||
enable: true
|
||||
setting:
|
||||
language: zh_cn
|
||||
mybatis-plus:
|
||||
configuration:
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
mapper-locations: classpath*:/mapper/**/*.xml
|
||||
aioj:
|
||||
log:
|
||||
enabled: true
|
||||
max-length: 20000
|
||||
@@ -23,15 +23,20 @@
|
||||
<artifactId>aioj-backend-common-starter</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.meowrain</groupId>
|
||||
<artifactId>aioj-backend-common-mybatis</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/com.github.xiaoymin/knife4j-openapi3-jakarta-spring-boot-starter -->
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-crypto</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
@@ -65,5 +70,11 @@
|
||||
<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>
|
||||
@@ -7,7 +7,9 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
@SpringBootApplication
|
||||
@MapperScan("cn.meowrain.aioj.backend.userservice.dao.mapper")
|
||||
public class UserServiceApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(UserServiceApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import lombok.RequiredArgsConstructor;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public enum ChainMarkEnums {
|
||||
|
||||
/**
|
||||
* 用户注册请求验证
|
||||
*/
|
||||
@@ -21,4 +22,5 @@ public enum ChainMarkEnums {
|
||||
public String toString() {
|
||||
return markName;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,10 +5,7 @@ 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 {
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -16,23 +16,25 @@ import org.springframework.context.annotation.Configuration;
|
||||
@Configuration
|
||||
@EnableKnife4j
|
||||
public class SwaggerConfiguration implements ApplicationRunner {
|
||||
|
||||
@Value("${server.port:8080}")
|
||||
private String serverPort;
|
||||
|
||||
@Value("${server.servlet.context-path:}")
|
||||
private String contextPath;
|
||||
|
||||
@Bean
|
||||
public OpenAPI customerOpenAPI() {
|
||||
return new OpenAPI()
|
||||
.info(new Info()
|
||||
.title("AIOJ-用户微服务✨")
|
||||
return new OpenAPI().info(new Info().title("AIOJ-用户微服务✨")
|
||||
.description("用户注册,用户登录等功能")
|
||||
.version("v1.0.0")
|
||||
.contact(new Contact().name("meowrain").email("meowrain@126.com"))
|
||||
.license(new License().name("MeowRain").url("https://meowrain.cn")));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(ApplicationArguments args) throws Exception {
|
||||
log.info("✨API Document: http://127.0.0.1:{}{}/doc.html", serverPort, contextPath);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ public class UserController {
|
||||
|
||||
private final UserService userService;
|
||||
|
||||
|
||||
@PostMapping("/register")
|
||||
public Result<Long> register(@RequestBody UserRegisterRequestDTO userRegisterRequest) {
|
||||
Long l = userService.userRegister(userRegisterRequest);
|
||||
|
||||
@@ -77,4 +77,5 @@ public class User implements Serializable {
|
||||
|
||||
@TableField(exist = false)
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
}
|
||||
|
||||
@@ -4,4 +4,5 @@ import cn.meowrain.aioj.backend.userservice.dao.entity.User;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
|
||||
public interface UserMapper extends BaseMapper<User> {
|
||||
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package cn.meowrain.aioj.backend.userservice.dto.chains;
|
||||
|
||||
import cn.meowrain.aioj.backend.framework.designpattern.chains.AbstractChianHandler;
|
||||
import cn.meowrain.aioj.backend.framework.errorcode.ErrorCode;
|
||||
import cn.meowrain.aioj.backend.framework.exception.ClientException;
|
||||
import cn.meowrain.aioj.backend.framework.core.designpattern.chains.AbstractChianHandler;
|
||||
import cn.meowrain.aioj.backend.framework.core.errorcode.ErrorCode;
|
||||
import cn.meowrain.aioj.backend.framework.core.exception.ClientException;
|
||||
import cn.meowrain.aioj.backend.userservice.common.enums.ChainMarkEnums;
|
||||
import cn.meowrain.aioj.backend.userservice.dto.req.UserRegisterRequestDTO;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -12,6 +12,7 @@ import org.springframework.stereotype.Component;
|
||||
@Component
|
||||
@Slf4j
|
||||
public class UserRegisterRequestParamVerifyChain implements AbstractChianHandler<UserRegisterRequestDTO> {
|
||||
|
||||
@Override
|
||||
public void handle(UserRegisterRequestDTO requestParam) {
|
||||
// 校验参数里面用户名和密码是不是空的
|
||||
@@ -40,4 +41,5 @@ public class UserRegisterRequestParamVerifyChain implements AbstractChianHandler
|
||||
public int getOrder() {
|
||||
return 10;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user