Compare commits
13 Commits
09a5674672
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| c61ee69561 | |||
|
|
6f7963a73b | ||
|
|
d89960f51c | ||
| 4304ec6e29 | |||
| 050e808ab8 | |||
| 7a3d3a06ba | |||
|
|
122a1738bd | ||
| 00c2fffad1 | |||
| aba1e36e03 | |||
| 3603d450e8 | |||
| c03876e29e | |||
| f93ec43915 | |||
| 9b28ef0a37 |
10
.claude/settings.local.json
Normal file
10
.claude/settings.local.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(cat:*)",
|
||||||
|
"Bash(find ./aioj-backend-common -path \"*common-log*pom.xml\" -exec cat {} ;)"
|
||||||
|
],
|
||||||
|
"deny": [],
|
||||||
|
"ask": []
|
||||||
|
}
|
||||||
|
}
|
||||||
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="/*" />
|
||||||
|
</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>
|
||||||
18
.idea/encodings.xml
generated
18
.idea/encodings.xml
generated
@@ -3,8 +3,17 @@
|
|||||||
<component name="Encoding">
|
<component name="Encoding">
|
||||||
<file url="file://$PROJECT_DIR$/aioj-backend-ai-service/src/main/java" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/aioj-backend-ai-service/src/main/java" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/aioj-backend-ai-service/src/main/resources" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/aioj-backend-ai-service/src/main/resources" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/aioj-backend-auth/src/main/java" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/aioj-backend-client/src/main/java" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/aioj-backend-client/src/main/java" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/aioj-backend-client/src/main/resources" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/aioj-backend-client/src/main/resources" charset="UTF-8" />
|
||||||
|
<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" />
|
||||||
|
<file url="file://$PROJECT_DIR$/aioj-backend-common/aioj-backend-common-starter/src/main/resources" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/aioj-backend-common/src/main/java" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/aioj-backend-common/src/main/java" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/aioj-backend-common/src/main/resources" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/aioj-backend-common/src/main/resources" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/aioj-backend-gateway/src/main/java" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/aioj-backend-gateway/src/main/java" charset="UTF-8" />
|
||||||
@@ -15,11 +24,16 @@
|
|||||||
<file url="file://$PROJECT_DIR$/aioj-backend-model/src/main/resources" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/aioj-backend-model/src/main/resources" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/aioj-backend-question-service/src/main/java" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/aioj-backend-question-service/src/main/java" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/aioj-backend-question-service/src/main/resources" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/aioj-backend-question-service/src/main/resources" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/aioj-backend-upms/aioj-backend-upms-api/src/main/java" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/aioj-backend-upms/aioj-backend-upms-biz/src/main/java" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/aioj-backend-upms/aioj-upms-api/src/main/java" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/aioj-backend-upms/src/main/java" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/aioj-backend-upms/src/main/resources" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/aioj-backend-user-service/src/main/java" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/aioj-backend-user-service/src/main/java" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/aioj-backend-user-service/src/main/resources" charset="UTF-8" />
|
<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/java" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/src/main/resources" 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/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/resources" charset="UTF-8" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
8
.idea/misc.xml
generated
8
.idea/misc.xml
generated
@@ -1,13 +1,19 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
<component name="KubernetesApiProvider"><![CDATA[{}]]></component>
|
|
||||||
<component name="MavenProjectsManager">
|
<component name="MavenProjectsManager">
|
||||||
<option name="originalFiles">
|
<option name="originalFiles">
|
||||||
<list>
|
<list>
|
||||||
<option value="$PROJECT_DIR$/pom.xml" />
|
<option value="$PROJECT_DIR$/pom.xml" />
|
||||||
</list>
|
</list>
|
||||||
</option>
|
</option>
|
||||||
|
<option name="ignoredFiles">
|
||||||
|
<set>
|
||||||
|
<option value="$PROJECT_DIR$/aioj-backend-client/pom.xml" />
|
||||||
|
<option value="$PROJECT_DIR$/aioj-backend-model/pom.xml" />
|
||||||
|
<option value="$PROJECT_DIR$/aioj-backend-upms/aioj-upms-api/pom.xml" />
|
||||||
|
</set>
|
||||||
|
</option>
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="zulu-17" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="zulu-17" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/out" />
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
|
|||||||
86
CLAUDE.md
Normal file
86
CLAUDE.md
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
This is an AI-integrated OJ (Online Judge) judging system with a microservices architecture. The system allows users to submit code for programming problems, which is then judged automatically. It also includes AI features to assist with problem-solving and code evaluation.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
The project follows a microservices architecture pattern built with Spring Boot and Spring Cloud Alibaba:
|
||||||
|
|
||||||
|
### Core Modules:
|
||||||
|
|
||||||
|
1. **aioj-backend-common**: Common utilities and dependencies shared across all modules.
|
||||||
|
2. **aioj-backend-gateway**: API gateway that routes requests to the appropriate microservices.
|
||||||
|
3. **aioj-backend-judge-service**: Handles code submission, compilation, and judging processes.
|
||||||
|
4. **aioj-backend-user-service**: Manages user accounts, authentication, and authorization.
|
||||||
|
5. **aioj-backend-question-service**: Manages programming problems and test cases.
|
||||||
|
6. **aioj-backend-ai-service**: Provides AI-assisted features such as code analysis and problem-solving.
|
||||||
|
7. **aioj-backend-auth**: Manages authentication and token issuance.
|
||||||
|
8. **aioj-backend-upms**: User Management System for administrative operations.
|
||||||
|
|
||||||
|
## Database
|
||||||
|
|
||||||
|
The system uses SQL databases. Database scripts can be found in the `db/` directory.
|
||||||
|
|
||||||
|
## Build System
|
||||||
|
|
||||||
|
The project uses Maven for build and dependency management.
|
||||||
|
|
||||||
|
### Common Commands:
|
||||||
|
|
||||||
|
- Compile the entire project:
|
||||||
|
```bash
|
||||||
|
mvn clean compile
|
||||||
|
```
|
||||||
|
|
||||||
|
- Compile a specific module:
|
||||||
|
```bash
|
||||||
|
mvn clean compile -pl <module-name>
|
||||||
|
```
|
||||||
|
|
||||||
|
- Package the entire project:
|
||||||
|
```bash
|
||||||
|
mvn clean package
|
||||||
|
```
|
||||||
|
|
||||||
|
- Package a specific module:
|
||||||
|
```bash
|
||||||
|
mvn clean package -pl <module-name>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
### Environment Profiles
|
||||||
|
|
||||||
|
The project supports different environments (dev, test, prod) with corresponding configuration files:
|
||||||
|
|
||||||
|
- Development: application-dev.yml
|
||||||
|
- Test: application-test.yml
|
||||||
|
- Production: application-prod.yml
|
||||||
|
|
||||||
|
### Running a Service
|
||||||
|
|
||||||
|
To run a specific microservice, use the Spring Boot Maven plugin:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd <module-name>
|
||||||
|
mvn spring-boot:run
|
||||||
|
```
|
||||||
|
|
||||||
|
Or run the built JAR file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd <module-name>/target
|
||||||
|
java -jar <module-name>-<version>.jar
|
||||||
|
```
|
||||||
|
|
||||||
|
## Technologies Used
|
||||||
|
|
||||||
|
- **Java 17**: Programming language
|
||||||
|
- **Spring Boot 3.5.7**: Framework for building microservices
|
||||||
|
- **Spring Cloud Alibaba 2025.0.0.0**: Microservices ecosystem
|
||||||
|
- **Maven**: Build tool
|
||||||
|
- **Lombok**: Java library to reduce boilerplate code
|
||||||
106
aioj-backend-auth/pom.xml
Normal file
106
aioj-backend-auth/pom.xml
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
<?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>ai-oj</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>aioj-backend-auth</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>
|
||||||
|
|
||||||
|
<!-- spring cloud发现服务-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<!-- OAuth2 Client -->
|
||||||
|
<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>
|
||||||
|
<artifactId>jjwt-api</artifactId>
|
||||||
|
<version>0.13.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
|
<artifactId>jjwt-impl</artifactId>
|
||||||
|
<version>0.13.0</version>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
|
<artifactId>jjwt-jackson</artifactId>
|
||||||
|
<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 -->
|
||||||
|
<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-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package cn.meowrain.aioj.backend.auth;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package cn.meowrain.aioj.backend.auth.clients;
|
||||||
|
|
||||||
|
import cn.meowrain.aioj.backend.auth.dto.resp.UserAuthRespDTO;
|
||||||
|
import cn.meowrain.aioj.backend.framework.core.web.Result;
|
||||||
|
import org.springframework.cloud.openfeign.FeignClient;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
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);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package cn.meowrain.aioj.backend.auth.common.constants;
|
||||||
|
|
||||||
|
public class RedisKeyConstants {
|
||||||
|
|
||||||
|
public static String REFRESH_TOKEN_KEY_PREFIX = "refresh_token:%s";
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package cn.meowrain.aioj.backend.auth.common.enums;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public enum ChainMarkEnums {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户登录请求验证
|
||||||
|
*/
|
||||||
|
USER_LOGIN_REQ_PARAM_VERIFY("USER_LOGIN_REQ_PARAM_VERIFY");
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final String markName;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return markName;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package cn.meowrain.aioj.backend.auth.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
public class SecurityConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
|
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")
|
||||||
|
.permitAll()
|
||||||
|
.anyRequest()
|
||||||
|
.authenticated());
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public PasswordEncoder passwordEncoder() {
|
||||||
|
return new BCryptPasswordEncoder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
|
||||||
|
return configuration.getAuthenticationManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package cn.meowrain.aioj.backend.auth.config;
|
||||||
|
|
||||||
|
import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
|
||||||
|
import io.swagger.v3.oas.models.OpenAPI;
|
||||||
|
import io.swagger.v3.oas.models.info.Contact;
|
||||||
|
import io.swagger.v3.oas.models.info.Info;
|
||||||
|
import io.swagger.v3.oas.models.info.License;
|
||||||
|
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.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@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微服务✨")
|
||||||
|
.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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package cn.meowrain.aioj.backend.auth.config.properties;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Data
|
||||||
|
@ConfigurationProperties(value = JwtPropertiesConfiguration.PREFIX)
|
||||||
|
public class JwtPropertiesConfiguration {
|
||||||
|
|
||||||
|
public static final String PREFIX = "jwt";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JWT 密钥(必须 32 字节以上)
|
||||||
|
*/
|
||||||
|
private String secret;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 过期时间(单位:毫秒)
|
||||||
|
*/
|
||||||
|
private long accessExpire; // access token TTL
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新令牌时间
|
||||||
|
*/
|
||||||
|
private long refreshExpire; // refresh token TTL
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
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 lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@RequestMapping("/v1/auth")
|
||||||
|
public class AuthController {
|
||||||
|
|
||||||
|
private final AuthService authService;
|
||||||
|
|
||||||
|
@PostMapping("/login")
|
||||||
|
public Result<UserLoginResponseDTO> login(@RequestBody UserLoginRequestDTO userLoginRequest) {
|
||||||
|
UserLoginResponseDTO userLoginResponse = authService.userLogin(userLoginRequest);
|
||||||
|
return Results.success(userLoginResponse);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/refresh")
|
||||||
|
public Result<UserLoginResponseDTO> refresh(@RequestParam String refreshToken) {
|
||||||
|
return Results.success(authService.refreshToken(refreshToken));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/auth")
|
||||||
|
public Result<String> auth(@RequestBody UserLoginRequestDTO userLoginRequest) {
|
||||||
|
UserLoginResponseDTO userLoginResponseDTO = authService.userLogin(userLoginRequest);
|
||||||
|
return Results.success(userLoginResponseDTO.getAccessToken());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
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 lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
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())) {
|
||||||
|
throw new ClientException("参数为空", ErrorCode.PARAMS_ERROR);
|
||||||
|
}
|
||||||
|
if (requestParam.getUserAccount().length() < 4) {
|
||||||
|
throw new ClientException("账号长度不小于4位", ErrorCode.PARAMS_ERROR);
|
||||||
|
}
|
||||||
|
if (requestParam.getUserPassword().length() < 8) {
|
||||||
|
throw new ClientException("密码长度不小于8位", ErrorCode.PARAMS_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String mark() {
|
||||||
|
return ChainMarkEnums.USER_LOGIN_REQ_PARAM_VERIFY.getMarkName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOrder() {
|
||||||
|
return 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package cn.meowrain.aioj.backend.auth.dto.chains.context;
|
||||||
|
|
||||||
|
import cn.meowrain.aioj.backend.auth.dto.req.UserLoginRequestDTO;
|
||||||
|
import cn.meowrain.aioj.backend.framework.core.designpattern.chains.CommonChainContext;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class UserLoginRequestParamVerifyContext extends CommonChainContext<UserLoginRequestDTO> {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package cn.meowrain.aioj.backend.auth.dto.req;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class UserLoginRequestDTO {
|
||||||
|
|
||||||
|
private String userAccount;
|
||||||
|
|
||||||
|
private String userPassword;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package cn.meowrain.aioj.backend.auth.dto.resp;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户认证响应体
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class UserAuthRespDTO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* id
|
||||||
|
*/
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户账号
|
||||||
|
*/
|
||||||
|
private String userAccount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户密码
|
||||||
|
*/
|
||||||
|
private String userPassword;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开放平台id
|
||||||
|
*/
|
||||||
|
private String unionId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 公众号openId
|
||||||
|
*/
|
||||||
|
private String mpOpenId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户昵称
|
||||||
|
*/
|
||||||
|
private String userName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户头像
|
||||||
|
*/
|
||||||
|
private String userAvatar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户简介
|
||||||
|
*/
|
||||||
|
private String userProfile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户角色:user/admin/ban
|
||||||
|
*/
|
||||||
|
private String userRole;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
private Date createTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
private Date updateTime;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package cn.meowrain.aioj.backend.auth.dto.resp;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class UserLoginResponseDTO implements Serializable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* id
|
||||||
|
*/
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户账号
|
||||||
|
*/
|
||||||
|
private String userAccount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开放平台id
|
||||||
|
*/
|
||||||
|
private String unionId;
|
||||||
|
|
||||||
|
private String accessToken;
|
||||||
|
|
||||||
|
private String refreshToken;
|
||||||
|
|
||||||
|
private Long expire;
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package cn.meowrain.aioj.backend.auth.filter;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JWT拦截器
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class JwtAuthenticationFilter {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package cn.meowrain.aioj.backend.auth.service;
|
||||||
|
|
||||||
|
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}
|
||||||
|
* @return {@link UserLoginResponseDTO}
|
||||||
|
*/
|
||||||
|
UserLoginResponseDTO userLogin(UserLoginRequestDTO request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新token
|
||||||
|
* @param refreshToken
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
UserLoginResponseDTO refreshToken(String refreshToken);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
package cn.meowrain.aioj.backend.auth.service.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
import cn.hutool.crypto.digest.BCrypt;
|
||||||
|
import cn.meowrain.aioj.backend.auth.clients.UserClient;
|
||||||
|
import cn.meowrain.aioj.backend.auth.common.constants.RedisKeyConstants;
|
||||||
|
import cn.meowrain.aioj.backend.auth.common.enums.ChainMarkEnums;
|
||||||
|
import cn.meowrain.aioj.backend.auth.config.properties.JwtPropertiesConfiguration;
|
||||||
|
import cn.meowrain.aioj.backend.auth.dto.chains.context.UserLoginRequestParamVerifyContext;
|
||||||
|
import cn.meowrain.aioj.backend.auth.dto.req.UserLoginRequestDTO;
|
||||||
|
import cn.meowrain.aioj.backend.auth.dto.resp.UserAuthRespDTO;
|
||||||
|
import cn.meowrain.aioj.backend.auth.dto.resp.UserLoginResponseDTO;
|
||||||
|
import cn.meowrain.aioj.backend.auth.service.AuthService;
|
||||||
|
import cn.meowrain.aioj.backend.auth.utils.JwtUtil;
|
||||||
|
import cn.meowrain.aioj.backend.framework.core.errorcode.ErrorCode;
|
||||||
|
import cn.meowrain.aioj.backend.framework.core.exception.ServiceException;
|
||||||
|
import cn.meowrain.aioj.backend.framework.core.web.Result;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@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) {
|
||||||
|
// 1.校验
|
||||||
|
userLoginRequestParamVerifyContext.handler(ChainMarkEnums.USER_LOGIN_REQ_PARAM_VERIFY.getMarkName(),
|
||||||
|
requestParam);
|
||||||
|
// 如果调用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);
|
||||||
|
}
|
||||||
|
// 生成 JWT
|
||||||
|
String accessToken = jwtUtil.generateAccessToken(user);
|
||||||
|
String refreshToken = jwtUtil.generateRefreshToken(user.getId());
|
||||||
|
UserLoginResponseDTO resp = new UserLoginResponseDTO();
|
||||||
|
resp.setId(user.getId());
|
||||||
|
resp.setUserAccount(user.getUserAccount());
|
||||||
|
resp.setAccessToken(accessToken);
|
||||||
|
resp.setRefreshToken(refreshToken);
|
||||||
|
|
||||||
|
// refresh token存入到REDIS里面
|
||||||
|
stringRedisTemplate.opsForValue()
|
||||||
|
.set(String.format(RedisKeyConstants.REFRESH_TOKEN_KEY_PREFIX, user.getId()), refreshToken,
|
||||||
|
jwtPropertiesConfiguration.getRefreshExpire(), TimeUnit.MILLISECONDS);
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新access token,使用refresh token
|
||||||
|
* @param refreshToken
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public UserLoginResponseDTO refreshToken(String refreshToken) {
|
||||||
|
UserLoginResponseDTO userLoginResponseDTO = new UserLoginResponseDTO();
|
||||||
|
if (!jwtUtil.isTokenValid(refreshToken)) {
|
||||||
|
throw new RuntimeException("Refresh Token 已过期");
|
||||||
|
}
|
||||||
|
|
||||||
|
Long userId = Long.valueOf(jwtUtil.parseClaims(refreshToken).getSubject());
|
||||||
|
|
||||||
|
String cacheKey = String.format(RedisKeyConstants.REFRESH_TOKEN_KEY_PREFIX, userId);
|
||||||
|
String cacheValue = stringRedisTemplate.opsForValue().get(cacheKey);
|
||||||
|
|
||||||
|
if (cacheValue == null || !cacheValue.equals(refreshToken)) {
|
||||||
|
throw new RuntimeException("Refresh Token 已失效");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 再次签发新的 Access Token
|
||||||
|
// 此处你需要查用户,拿 userName, role
|
||||||
|
Result<UserAuthRespDTO> userResult = userClient.getUserById(String.valueOf(userId));
|
||||||
|
if (userResult.isFail()) {
|
||||||
|
log.error("通过id查找用户失败:{}", userResult.getMessage());
|
||||||
|
throw new ServiceException(ErrorCode.SYSTEM_ERROR);
|
||||||
|
}
|
||||||
|
UserAuthRespDTO user = userResult.getData();
|
||||||
|
String newAccessToken = jwtUtil.generateAccessToken(user);
|
||||||
|
|
||||||
|
// 设置refresh token和access token
|
||||||
|
userLoginResponseDTO.setRefreshToken(refreshToken);
|
||||||
|
userLoginResponseDTO.setAccessToken(newAccessToken);
|
||||||
|
return userLoginResponseDTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package cn.meowrain.aioj.backend.auth.utils;
|
||||||
|
|
||||||
|
import cn.meowrain.aioj.backend.auth.config.properties.JwtPropertiesConfiguration;
|
||||||
|
import cn.meowrain.aioj.backend.auth.dto.resp.UserAuthRespDTO;
|
||||||
|
import io.jsonwebtoken.Claims;
|
||||||
|
import io.jsonwebtoken.Jwts;
|
||||||
|
import io.jsonwebtoken.security.Keys;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Component
|
||||||
|
public class JwtUtil {
|
||||||
|
|
||||||
|
private final JwtPropertiesConfiguration jwtConfig;
|
||||||
|
|
||||||
|
private SecretKey getSigningKey() {
|
||||||
|
return Keys.hmacShaKeyFor(jwtConfig.getSecret().getBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 生成 Access Token */
|
||||||
|
public String generateAccessToken(UserAuthRespDTO user) {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
|
||||||
|
Map<String, Object> claims = new HashMap<>();
|
||||||
|
claims.put("userId", user.getId());
|
||||||
|
claims.put("userName", user.getUserName());
|
||||||
|
claims.put("role", user.getUserRole());
|
||||||
|
|
||||||
|
return Jwts.builder()
|
||||||
|
.subject(user.getUserAccount())
|
||||||
|
.issuedAt(new Date(now))
|
||||||
|
.expiration(new Date(now + jwtConfig.getAccessExpire()))
|
||||||
|
.claims(claims)
|
||||||
|
.signWith(getSigningKey(), Jwts.SIG.HS256)
|
||||||
|
.compact();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 生成 Refresh Token(只含 userId) */
|
||||||
|
public String generateRefreshToken(Long userId) {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
|
||||||
|
return Jwts.builder()
|
||||||
|
.subject(String.valueOf(userId))
|
||||||
|
.issuedAt(new Date(now))
|
||||||
|
.expiration(new Date(now + jwtConfig.getRefreshExpire()))
|
||||||
|
.signWith(getSigningKey(), Jwts.SIG.HS256)
|
||||||
|
.compact();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 解析 Token */
|
||||||
|
public Claims parseClaims(String token) {
|
||||||
|
return Jwts.parser().verifyWith(getSigningKey()).build().parseSignedClaims(token).getPayload();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 校验 Token 是否过期 */
|
||||||
|
public boolean isTokenValid(String token) {
|
||||||
|
try {
|
||||||
|
Claims claims = parseClaims(token);
|
||||||
|
return claims.getExpiration().after(new Date());
|
||||||
|
}
|
||||||
|
catch (Exception ignored) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
16
aioj-backend-auth/src/main/resources/application-dev.yml
Normal file
16
aioj-backend-auth/src/main/resources/application-dev.yml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: auth-service
|
||||||
|
data:
|
||||||
|
redis:
|
||||||
|
host: 10.0.0.10
|
||||||
|
port: 6379
|
||||||
|
password: 123456
|
||||||
|
cloud:
|
||||||
|
nacos:
|
||||||
|
discovery:
|
||||||
|
enabled: true
|
||||||
|
register-enabled: true
|
||||||
|
server-addr: 10.0.0.10:8848
|
||||||
|
username: nacos
|
||||||
|
password: nacos
|
||||||
38
aioj-backend-auth/src/main/resources/application.yml
Normal file
38
aioj-backend-auth/src/main/resources/application.yml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: auth-service
|
||||||
|
profiles:
|
||||||
|
active: @env@
|
||||||
|
devtools:
|
||||||
|
livereload:
|
||||||
|
enabled: true
|
||||||
|
server:
|
||||||
|
port: 10011
|
||||||
|
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
|
||||||
|
jwt:
|
||||||
|
secret: "12345678901234567890123456789012" # 至少32字节!!
|
||||||
|
access-expire: 900000 # 24小时
|
||||||
|
refresh-expire: 604800000 # 7天
|
||||||
97
aioj-backend-common/aioj-backend-common-bom/pom.xml
Normal file
97
aioj-backend-common/aioj-backend-common-bom/pom.xml
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
<?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>
|
||||||
|
<artifactId>aioj-backend-common-bom</artifactId>
|
||||||
|
<groupId>cn.meowrain.aioj</groupId>
|
||||||
|
<packaging>pom</packaging>
|
||||||
|
<version>${revision}</version>
|
||||||
|
|
||||||
|
<name>aioj-common-bom</name>
|
||||||
|
<description>依赖管理</description>
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>17</maven.compiler.source>
|
||||||
|
<maven.compiler.target>17</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<revision>1.0.0</revision>
|
||||||
|
<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.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>
|
||||||
|
<artifactId>hutool-bom</artifactId>
|
||||||
|
<version>5.8.41</version>
|
||||||
|
<type>pom</type>
|
||||||
|
<scope>import</scope>
|
||||||
|
</dependency>
|
||||||
|
<!--orm 相关 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.baomidou</groupId>
|
||||||
|
<artifactId>mybatis-plus-bom</artifactId>
|
||||||
|
<version>${mybatis-plus.version}</version>
|
||||||
|
<type>pom</type>
|
||||||
|
<scope>import</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.mysql</groupId>
|
||||||
|
<artifactId>mysql-connector-j</artifactId>
|
||||||
|
<version>${mysql.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- https://github.com/alibaba/easyexcel -->
|
||||||
|
<!-- https://mvnrepository.com/artifact/com.alibaba/easyexcel -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba</groupId>
|
||||||
|
<artifactId>easyexcel</artifactId>
|
||||||
|
<version>4.0.3</version>
|
||||||
|
</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>
|
||||||
|
<version>4.5.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- OAuth2 Client -->
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-oauth2-client -->
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-oauth2-client -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-oauth2-client</artifactId>
|
||||||
|
<version>3.5.7</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-test -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.security</groupId>
|
||||||
|
<artifactId>spring-security-test</artifactId>
|
||||||
|
<version>6.5.6</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Spring Security -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-security</artifactId>
|
||||||
|
<version>3.5.7</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
</dependencyManagement>
|
||||||
|
</project>
|
||||||
67
aioj-backend-common/aioj-backend-common-core/pom.xml
Normal file
67
aioj-backend-common/aioj-backend-common-core/pom.xml
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
<?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-core</artifactId>
|
||||||
|
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<description>aioj 公共工具类核心包</description>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
<!--hutool-->
|
||||||
|
<dependency>
|
||||||
|
<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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package cn.meowrain.aioj.backend.framework.core.banner;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||||
|
import org.springframework.context.ApplicationListener;
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.lang.management.ManagementFactory;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Deprecated
|
||||||
|
public class EnvironmentBanner implements ApplicationListener<ApplicationReadyEvent> {
|
||||||
|
|
||||||
|
@Value("${spring.application.name:unknown}")
|
||||||
|
private String appName;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onApplicationEvent(ApplicationReadyEvent event) {
|
||||||
|
|
||||||
|
Environment env = event.getApplicationContext().getEnvironment();
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +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";
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
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();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package cn.meowrain.aioj.backend.framework.core.designpattern.chains;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.BeansException;
|
||||||
|
import org.springframework.boot.CommandLineRunner;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.context.ApplicationContextAware;
|
||||||
|
import org.springframework.core.Ordered;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 公共责任链容器
|
||||||
|
*
|
||||||
|
* @param <T>
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class CommonChainContext<T> implements ApplicationContextAware, CommandLineRunner {
|
||||||
|
|
||||||
|
private ApplicationContext applicationContext;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||||
|
this.applicationContext = applicationContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Map<String, List<AbstractChianHandler<T>>> abstractChainHandlerMap = new HashMap<>();
|
||||||
|
|
||||||
|
public void handler(String mark, T requestParam) {
|
||||||
|
List<AbstractChianHandler<T>> merchantAdminAbstractChainHandlers = abstractChainHandlerMap.get(mark);
|
||||||
|
if (merchantAdminAbstractChainHandlers == null || merchantAdminAbstractChainHandlers.isEmpty()) {
|
||||||
|
throw new RuntimeException(String.format("[%s] Chain of Responsibility ID is undefined.", mark));
|
||||||
|
}
|
||||||
|
merchantAdminAbstractChainHandlers.forEach(h -> {
|
||||||
|
h.handle(requestParam);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run(String... args) throws Exception {
|
||||||
|
log.info("【责任链路初始化】开始加载并分组所有处理器...");
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 步骤 2: 对每个链路中的处理器进行排序 (Sort 阶段)
|
||||||
|
abstractChainHandlerMap.forEach((mark, handlers) -> {
|
||||||
|
handlers.sort(Comparator.comparing(Ordered::getOrder));
|
||||||
|
|
||||||
|
// 打印排序后的 Bean 列表
|
||||||
|
String sortedList = handlers.stream()
|
||||||
|
.map(h -> String.format("%s (Order:%d)", h.getClass().getSimpleName(), h.getOrder()))
|
||||||
|
.collect(Collectors.joining(" -> "));
|
||||||
|
|
||||||
|
log.info(" ✅ 链路 {} 排序完成:{}", mark, sortedList);
|
||||||
|
});
|
||||||
|
|
||||||
|
log.info("【责任链路初始化】所有处理器链已完全就绪。");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package cn.meowrain.aioj.backend.framework.core.enums;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除枚举
|
||||||
|
*/
|
||||||
|
public enum DelStatusEnum {
|
||||||
|
|
||||||
|
STATUS_NORMAL("0"), STATUS_DELETE("1");
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
|
||||||
|
DelStatusEnum(String code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String code() {
|
||||||
|
return this.code;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
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", "接口调用失败");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态码
|
||||||
|
*/
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 信息
|
||||||
|
*/
|
||||||
|
private final String message;
|
||||||
|
|
||||||
|
ErrorCode(String code, String message) {
|
||||||
|
this.code = code;
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String code() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String message() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package cn.meowrain.aioj.backend.framework.core.errorcode;
|
||||||
|
|
||||||
|
public interface IErrorCode {
|
||||||
|
|
||||||
|
String code();
|
||||||
|
|
||||||
|
String message();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package cn.meowrain.aioj.backend.framework.core.exception;
|
||||||
|
|
||||||
|
import cn.meowrain.aioj.backend.framework.core.errorcode.IErrorCode;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 抽象错误处理Exception,基于这个抽象类,我们能创建很多其它类型的exception,定义错误类型
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
|
||||||
|
public class AbstractException extends RuntimeException {
|
||||||
|
|
||||||
|
public final String errorCode;
|
||||||
|
|
||||||
|
public final String errorMessage;
|
||||||
|
|
||||||
|
public AbstractException(String message, Throwable throwable, IErrorCode errorCode) {
|
||||||
|
super(message);
|
||||||
|
this.errorCode = errorCode.code();
|
||||||
|
this.errorMessage = Optional.ofNullable(StringUtils.hasLength(message) ? message : null)
|
||||||
|
.orElse(errorCode.message());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package cn.meowrain.aioj.backend.framework.core.exception;
|
||||||
|
|
||||||
|
import cn.meowrain.aioj.backend.framework.core.errorcode.ErrorCode;
|
||||||
|
import cn.meowrain.aioj.backend.framework.core.errorcode.IErrorCode;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户端异常
|
||||||
|
*/
|
||||||
|
@ToString
|
||||||
|
public class ClientException extends AbstractException {
|
||||||
|
|
||||||
|
public ClientException(String message, Throwable throwable, IErrorCode errorCode) {
|
||||||
|
super(message, throwable, errorCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientException(IErrorCode errorCode) {
|
||||||
|
this(null, null, errorCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientException(String message, IErrorCode errorCode) {
|
||||||
|
this(message, null, errorCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientException(String message) {
|
||||||
|
this(message, null, ErrorCode.PARAMS_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package cn.meowrain.aioj.backend.framework.core.exception;
|
||||||
|
|
||||||
|
import cn.meowrain.aioj.backend.framework.core.errorcode.ErrorCode;
|
||||||
|
import cn.meowrain.aioj.backend.framework.core.errorcode.IErrorCode;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调用第三方服务异常
|
||||||
|
*/
|
||||||
|
@ToString
|
||||||
|
public class RemoteException extends AbstractException {
|
||||||
|
|
||||||
|
public RemoteException(IErrorCode errorCode) {
|
||||||
|
this(null, null, errorCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RemoteException(String message, IErrorCode errorCode) {
|
||||||
|
this(message, null, errorCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RemoteException(String message, Throwable throwable, IErrorCode errorCode) {
|
||||||
|
super(message, throwable, errorCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RemoteException(String message) {
|
||||||
|
this(message, null, ErrorCode.API_REQUEST_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package cn.meowrain.aioj.backend.framework.core.exception;
|
||||||
|
|
||||||
|
import cn.meowrain.aioj.backend.framework.core.errorcode.ErrorCode;
|
||||||
|
import cn.meowrain.aioj.backend.framework.core.errorcode.IErrorCode;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统执行异常
|
||||||
|
*/
|
||||||
|
@ToString
|
||||||
|
public class ServiceException extends AbstractException {
|
||||||
|
|
||||||
|
public ServiceException(String message, IErrorCode errorCode) {
|
||||||
|
this(message, null, errorCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServiceException(String message) {
|
||||||
|
this(message, null, ErrorCode.SYSTEM_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServiceException(IErrorCode errorCode) {
|
||||||
|
this(null, null, errorCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServiceException(String message, Throwable throwable, IErrorCode errorCode) {
|
||||||
|
super(message, throwable, errorCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package cn.meowrain.aioj.backend.framework.core.exception.handler;
|
||||||
|
|
||||||
|
import cn.meowrain.aioj.backend.framework.core.exception.AbstractException;
|
||||||
|
import cn.meowrain.aioj.backend.framework.core.web.Result;
|
||||||
|
import cn.meowrain.aioj.backend.framework.core.web.Results;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.validation.BindingResult;
|
||||||
|
import org.springframework.validation.FieldError;
|
||||||
|
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全局错误捕获器
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RestControllerAdvice
|
||||||
|
public class GlobalExceptionHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 捕获所有参数错误,然后统一捕获并且抛出
|
||||||
|
* @param request {@link HttpServletRequest}
|
||||||
|
* @param ex {@link org.springframework.validation.method.MethodValidationException}
|
||||||
|
* @return {@link Result<Void>}
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(value = MethodArgumentNotValidException.class)
|
||||||
|
public Result<Void> validExceptionHandler(HttpServletRequest request, MethodArgumentNotValidException ex) {
|
||||||
|
BindingResult bindingResult = ex.getBindingResult();
|
||||||
|
// 收集所有错误字段
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 抽象异常捕获其
|
||||||
|
* @param request {@link HttpServletRequest}
|
||||||
|
* @param ex {@link AbstractException}
|
||||||
|
* @return {@link Result<Void>}
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(value = { AbstractException.class })
|
||||||
|
public Result<Void> abstractExceptionHandler(HttpServletRequest request, AbstractException ex) {
|
||||||
|
if (ex.getCause() != null) {
|
||||||
|
log.error("[{}] {} [ex] {}", request.getMethod(), request.getRequestURL().toString(), ex, ex.getCause());
|
||||||
|
return Results.failure(ex);
|
||||||
|
}
|
||||||
|
StringBuilder stackTraceBuilder = new StringBuilder();
|
||||||
|
stackTraceBuilder.append(ex.getClass().getName()).append(": ").append(ex.getErrorMessage()).append("\n");
|
||||||
|
StackTraceElement[] stackTrace = ex.getStackTrace();
|
||||||
|
for (int i = 0; i < Math.min(5, stackTrace.length); i++) {
|
||||||
|
stackTraceBuilder.append("\tat ").append(stackTrace[i]).append("\n");
|
||||||
|
}
|
||||||
|
log.error("[{}] {} [ex] {} \n\n{}", request.getMethod(), request.getRequestURL().toString(), ex,
|
||||||
|
stackTraceBuilder);
|
||||||
|
return Results.failure(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拦截未捕获异常
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(value = Throwable.class)
|
||||||
|
public Result<Void> defaultErrorHandler(HttpServletRequest request, Throwable throwable) {
|
||||||
|
log.error("[{}] {} ", request.getMethod(), getUrl(request), throwable);
|
||||||
|
return Results.failure();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取请求URL
|
||||||
|
* @param request {@link HttpServletRequest}
|
||||||
|
* @return String
|
||||||
|
*/
|
||||||
|
private String getUrl(HttpServletRequest request) {
|
||||||
|
if (!StringUtils.hasText(request.getQueryString())) {
|
||||||
|
return request.getRequestURI();
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package cn.meowrain.aioj.backend.framework.core.web;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应信息
|
||||||
|
*/
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回是否是正确响应
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public boolean isSuccess() {
|
||||||
|
return SUCCESS_CODE.equals(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回是否是错误响应
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public boolean isFail() {
|
||||||
|
return !isSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建响应的工具类
|
||||||
|
*/
|
||||||
|
public final class Results {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 成功响应,不返回任何信息
|
||||||
|
* @return {@link Result<Void>}
|
||||||
|
*/
|
||||||
|
public static Result<Void> success() {
|
||||||
|
return new Result<Void>().setCode(Result.SUCCESS_CODE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 成功响应 返回数据
|
||||||
|
* @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 {@link Result<Void>}
|
||||||
|
*/
|
||||||
|
public static Result<Void> paramsValidFailure() {
|
||||||
|
return failure(ErrorCode.PARAMS_ERROR.code(), ErrorCode.PARAMS_ERROR.message());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 服务端错误默认响应 -- <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());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 服务端错误响应 - 接收一个AbstractException,里面定义了错误码和错误信息
|
||||||
|
* @param exception {@link AbstractException}
|
||||||
|
* @return {@link Result<Void>}
|
||||||
|
*/
|
||||||
|
public static Result<Void> failure(AbstractException exception) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 服务端错误响应,自定义错误码和错误信息
|
||||||
|
* @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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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 {
|
||||||
|
|
||||||
|
}
|
||||||
50
aioj-backend-common/aioj-backend-common-log/pom.xml
Normal file
50
aioj-backend-common/aioj-backend-common-log/pom.xml
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<?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-log</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>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>
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
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;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.scheduling.annotation.EnableAsync;
|
||||||
|
|
||||||
|
@EnableAsync
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
@EnableConfigurationProperties(AIOJLogPropertiesConfiguration.class)
|
||||||
|
@ConditionalOnProperty(value = "aioj.log.enabled", matchIfMissing = true)
|
||||||
|
public class LogAutoConfiguration {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建并返回SysLogListener的Bean实例
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnBean(RemoteLogService.class)
|
||||||
|
public SysLogListener sysLogListener(AIOJLogPropertiesConfiguration logProperties,
|
||||||
|
RemoteLogService remoteLogService) {
|
||||||
|
return new SysLogListener(remoteLogService, logProperties);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回切面类实例
|
||||||
|
* @return {@link SysLogAspect}
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public SysLogAspect sysLogAspect() {
|
||||||
|
return new SysLogAspect();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package cn.meowrain.aioj.backend.framework.log.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统日志注解
|
||||||
|
*/
|
||||||
|
@Target(ElementType.METHOD)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Documented
|
||||||
|
public @interface SysLog {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 描述
|
||||||
|
* @return {@link String}
|
||||||
|
*/
|
||||||
|
String value() default "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spel表达式
|
||||||
|
* @return 日志描述
|
||||||
|
*/
|
||||||
|
String expression() default "";
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
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;
|
||||||
|
import org.aspectj.lang.annotation.Aspect;
|
||||||
|
import org.aspectj.lang.reflect.MethodSignature;
|
||||||
|
import org.springframework.expression.EvaluationContext;
|
||||||
|
|
||||||
|
@Aspect
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SysLogAspect {
|
||||||
|
/**
|
||||||
|
* 环绕通知方法,用于处理系统日志记录
|
||||||
|
* @param point 连接点对象
|
||||||
|
* @param sysLog 系统日志注解
|
||||||
|
* @return 目标方法执行结果
|
||||||
|
* @throws Throwable 目标方法执行可能抛出的异常
|
||||||
|
*/
|
||||||
|
@Around("@annotation(sysLog)")
|
||||||
|
@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) 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
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";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开启日志记录
|
||||||
|
*/
|
||||||
|
private boolean enabled = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求报文最大存储长度
|
||||||
|
*/
|
||||||
|
private Integer maxLength = 20000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 放行字段,password,mobile,idcard,phone
|
||||||
|
*/
|
||||||
|
@Value("${log.exclude-fields:password,mobile,idcard,phone}")
|
||||||
|
private List<String> excludeFields;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package cn.meowrain.aioj.backend.framework.log.enums;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日志类型枚举
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public enum LogTypeEnum {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 正常日志类型
|
||||||
|
*/
|
||||||
|
NORMAL("0", "正常日志"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 错误日志类型
|
||||||
|
*/
|
||||||
|
ERROR("9", "错误日志");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 类型
|
||||||
|
*/
|
||||||
|
private final String type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 描述
|
||||||
|
*/
|
||||||
|
private final String description;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package cn.meowrain.aioj.backend.framework.log.event;
|
||||||
|
|
||||||
|
|
||||||
|
import cn.meowrain.aioj.backend.upms.api.entity.SysLog;
|
||||||
|
import org.springframework.context.ApplicationEvent;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统日志事件类
|
||||||
|
*/
|
||||||
|
public class SysLogEvent extends ApplicationEvent {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造方法,根据源SysLog对象创建SysLogEvent
|
||||||
|
*/
|
||||||
|
public SysLogEvent(SysLog source) {
|
||||||
|
super(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
public class SysLogEventSource extends SysLog {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 参数重写成object
|
||||||
|
*/
|
||||||
|
private Object body;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
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.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;
|
||||||
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
|
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;
|
||||||
|
|
||||||
|
private final AIOJLogPropertiesConfiguration logProperties;
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
@Async
|
||||||
|
@Order
|
||||||
|
@EventListener(SysLogEvent.class)
|
||||||
|
public void saveLog(SysLogEvent sysLogEvent) {
|
||||||
|
SysLogEventSource source = (SysLogEventSource) sysLogEvent.getSource();
|
||||||
|
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 {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package cn.meowrain.aioj.backend.framework.log.init;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.env.EnvironmentPostProcessor;
|
||||||
|
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) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOrder() {
|
||||||
|
return Ordered.LOWEST_PRECEDENCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
cn.meowrain.aioj.backend.framework.log.LogAutoConfiguration
|
||||||
65
aioj-backend-common/aioj-backend-common-mybatis/pom.xml
Normal file
65
aioj-backend-common/aioj-backend-common-mybatis/pom.xml
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<?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-mybatis</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<description>aioj mybatis 封装</description>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
<!--hutool-->
|
||||||
|
<dependency>
|
||||||
|
<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>
|
||||||
|
<artifactId>mybatis-plus-spring</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.baomidou</groupId>
|
||||||
|
<artifactId>mybatis-plus-jsqlparser</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.mysql</groupId>
|
||||||
|
<artifactId>mysql-connector-j</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>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.security</groupId>
|
||||||
|
<artifactId>spring-security-core</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.meowrain</groupId>
|
||||||
|
<artifactId>aioj-backend-common-core</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
@AutoConfiguration
|
||||||
|
public class MybatisPlusAutoConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public MybatisPlusInterceptor mybatisPlusInterceptor() {
|
||||||
|
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
|
||||||
|
interceptor.addInnerInterceptor(new PaginationInterceptor());
|
||||||
|
return interceptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建并返回MybatisPlusMetaObjectHandler实例,用于审计字段自动填充
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public MybatisPlusMetaObjectHandler mybatisPlusMetaObjectHandler() {
|
||||||
|
return new MybatisPlusMetaObjectHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package cn.meowrain.backend.common.mybaits.base;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基础实体抽象类,包含通用实体字段
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class BaseEntity implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建者
|
||||||
|
*/
|
||||||
|
@Schema(description = "创建人")
|
||||||
|
@TableField(fill = FieldFill.INSERT)
|
||||||
|
private String createBy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
@Schema(description = "创建时间")
|
||||||
|
@TableField(fill = FieldFill.INSERT)
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新者
|
||||||
|
*/
|
||||||
|
@Schema(description = "更新人")
|
||||||
|
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||||
|
private String updateBy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
@Schema(description = "更新时间")
|
||||||
|
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package cn.meowrain.backend.common.mybaits.config;
|
||||||
|
|
||||||
|
import cn.meowrain.aioj.backend.framework.core.enums.DelStatusEnum;
|
||||||
|
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.ibatis.reflection.MetaObject;
|
||||||
|
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.util.ClassUtils;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MybatisPlus 自动填充处理器,用于实体类字段的自动填充
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class MybatisPlusMetaObjectHandler implements MetaObjectHandler {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void insertFill(MetaObject metaObject) {
|
||||||
|
log.debug("mybatis plus start insert fill ....");
|
||||||
|
LocalDateTime now = LocalDateTime.now();
|
||||||
|
|
||||||
|
fillValIfNullByName("createTime", now, metaObject, true);
|
||||||
|
fillValIfNullByName("updateTime", now, metaObject, true);
|
||||||
|
fillValIfNullByName("createBy", getUserName(), metaObject, true);
|
||||||
|
fillValIfNullByName("updateBy", getUserName(), metaObject, true);
|
||||||
|
|
||||||
|
// 删除标记自动填充
|
||||||
|
fillValIfNullByName("delFlag", DelStatusEnum.STATUS_NORMAL.code(), metaObject, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateFill(MetaObject metaObject) {
|
||||||
|
log.debug("mybatis plus start update fill ....");
|
||||||
|
fillValIfNullByName("updateTime", LocalDateTime.now(), metaObject, true);
|
||||||
|
fillValIfNullByName("updateBy", getUserName(), metaObject, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fillValIfNullByName(String fieldName, Object fieldVal, MetaObject metaObject, boolean isCover) {
|
||||||
|
// 如果填充值为空
|
||||||
|
if (fieldVal == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 没有 set 方法
|
||||||
|
if (!metaObject.hasSetter(fieldName)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// field 类型相同时设置
|
||||||
|
Class<?> getterType = metaObject.getGetterType(fieldName);
|
||||||
|
if (ClassUtils.isAssignableValue(getterType, fieldVal)) {
|
||||||
|
metaObject.setValue(fieldName, fieldVal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object getUserName() {
|
||||||
|
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
// 匿名接口直接返回
|
||||||
|
if (authentication instanceof AnonymousAuthenticationToken) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Optional.ofNullable(authentication).isPresent()) {
|
||||||
|
return authentication.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
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;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.dialects.IDialect;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.apache.ibatis.executor.Executor;
|
||||||
|
import org.apache.ibatis.mapping.BoundSql;
|
||||||
|
import org.apache.ibatis.mapping.MappedStatement;
|
||||||
|
import org.apache.ibatis.session.ResultHandler;
|
||||||
|
import org.apache.ibatis.session.RowBounds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* * 分页拦截器实现类,用于处理分页查询逻辑 *
|
||||||
|
* <p>
|
||||||
|
* * 当分页大小小于0时自动设置为0,防止全表查询
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
public class PaginationInterceptor extends PaginationInnerInterceptor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库类型
|
||||||
|
* <p>
|
||||||
|
* 查看 {@link #findIDialect(Executor)} 逻辑
|
||||||
|
*/
|
||||||
|
private DbType dbType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 方言实现类
|
||||||
|
* <p>
|
||||||
|
* 查看 {@link #findIDialect(Executor)} 逻辑
|
||||||
|
*/
|
||||||
|
private IDialect dialect;
|
||||||
|
|
||||||
|
public PaginationInterceptor(DbType dbType) {
|
||||||
|
this.dbType = dbType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PaginationInterceptor(IDialect dialect) {
|
||||||
|
this.dialect = dialect;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在执行查询前处理分页参数
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
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) {
|
||||||
|
page.setSize(0);
|
||||||
|
}
|
||||||
|
super.beforeQuery(executor, ms, page, rowBounds, resultHandler, boundSql);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
cn.meowrain.backend.common.mybaits.MybatisPlusAutoConfiguration
|
||||||
33
aioj-backend-common/aioj-backend-common-starter/pom.xml
Normal file
33
aioj-backend-common/aioj-backend-common-starter/pom.xml
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?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>
|
||||||
|
<packaging>pom</packaging>
|
||||||
|
|
||||||
|
<artifactId>aioj-backend-common-starter</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>cn.meowrain</groupId>
|
||||||
|
<artifactId>aioj-backend-common-log</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
||||||
@@ -10,10 +10,31 @@
|
|||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>aioj-backend-common</artifactId>
|
<artifactId>aioj-backend-common</artifactId>
|
||||||
|
<packaging>pom</packaging>
|
||||||
|
<modules>
|
||||||
|
<module>aioj-backend-common-log</module>
|
||||||
|
<module>aioj-backend-common-core</module>
|
||||||
|
<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>
|
<properties>
|
||||||
<maven.compiler.source>17</maven.compiler.source>
|
<maven.compiler.source>17</maven.compiler.source>
|
||||||
<maven.compiler.target>17</maven.compiler.target>
|
<maven.compiler.target>17</maven.compiler.target>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
|
<dependencyManagement>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.meowrain.aioj</groupId>
|
||||||
|
<artifactId>aioj-backend-common-bom</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
<type>pom</type>
|
||||||
|
<scope>import</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</dependencyManagement>
|
||||||
</project>
|
</project>
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
package cn.meowrain.aioj.backend.framework.annotation;
|
|
||||||
|
|
||||||
import java.lang.annotation.ElementType;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
|
|
||||||
@Target(ElementType.METHOD)
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
public @interface AuthCheck {
|
|
||||||
String mustRole() default "";
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
package cn.meowrain.aioj.backend.framework.banner;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
|
||||||
import org.springframework.context.ApplicationListener;
|
|
||||||
import org.springframework.core.env.Environment;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import java.lang.management.ManagementFactory;
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.time.format.DateTimeFormatter;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
@Component
|
|
||||||
public class EnvironmentBanner implements ApplicationListener<ApplicationReadyEvent> {
|
|
||||||
|
|
||||||
@Value("${spring.application.name:unknown}")
|
|
||||||
private String appName;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onApplicationEvent(ApplicationReadyEvent event) {
|
|
||||||
|
|
||||||
Environment env = event.getApplicationContext().getEnvironment();
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
package cn.meowrain.aioj.backend.framework.config;
|
|
||||||
|
|
||||||
import cn.meowrain.aioj.backend.framework.exception.handler.GlobalExceptionHandler;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 注册为bean,全局异常拦截器
|
|
||||||
*/
|
|
||||||
public class WebAutoConfiguration {
|
|
||||||
@Bean
|
|
||||||
public GlobalExceptionHandler globalExceptionHandler() {
|
|
||||||
return new GlobalExceptionHandler();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
package cn.meowrain.aioj.backend.framework.designpattern.chains;
|
|
||||||
|
|
||||||
import org.springframework.core.Ordered;
|
|
||||||
|
|
||||||
public interface AbstractChianHandler<T> extends Ordered {
|
|
||||||
/**
|
|
||||||
* 执行责任链逻辑
|
|
||||||
*
|
|
||||||
* @param requestParam 责任链执行入参
|
|
||||||
*/
|
|
||||||
void handle(T requestParam);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 责任链组件标识
|
|
||||||
*
|
|
||||||
* @return String
|
|
||||||
*/
|
|
||||||
String mark();
|
|
||||||
}
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
package cn.meowrain.aioj.backend.framework.designpattern.chains;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.beans.BeansException;
|
|
||||||
import org.springframework.boot.CommandLineRunner;
|
|
||||||
import org.springframework.context.ApplicationContext;
|
|
||||||
import org.springframework.context.ApplicationContextAware;
|
|
||||||
import org.springframework.core.Ordered;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 公共责任链容器
|
|
||||||
*
|
|
||||||
* @param <T>
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
@Slf4j
|
|
||||||
public class CommonChainContext<T> implements ApplicationContextAware, CommandLineRunner {
|
|
||||||
private ApplicationContext applicationContext;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
|
||||||
this.applicationContext = applicationContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Map<String, List<AbstractChianHandler<T>>> abstractChainHandlerMap = new HashMap<>();
|
|
||||||
|
|
||||||
public void handler(String mark, T requestParam) {
|
|
||||||
List<AbstractChianHandler<T>> merchantAdminAbstractChainHandlers = abstractChainHandlerMap.get(mark);
|
|
||||||
if (merchantAdminAbstractChainHandlers == null || merchantAdminAbstractChainHandlers.isEmpty()) {
|
|
||||||
throw new RuntimeException(String.format("[%s] Chain of Responsibility ID is undefined.", mark));
|
|
||||||
}
|
|
||||||
merchantAdminAbstractChainHandlers.forEach(h -> {
|
|
||||||
h.handle(requestParam);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run(String... args) throws Exception {
|
|
||||||
log.info("【责任链路初始化】开始加载并分组所有处理器...");
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 步骤 2: 对每个链路中的处理器进行排序 (Sort 阶段)
|
|
||||||
abstractChainHandlerMap.forEach((mark, handlers) -> {
|
|
||||||
handlers.sort(Comparator.comparing(Ordered::getOrder));
|
|
||||||
|
|
||||||
// 打印排序后的 Bean 列表
|
|
||||||
String sortedList = handlers.stream()
|
|
||||||
.map(h -> String.format("%s (Order:%d)", h.getClass().getSimpleName(), h.getOrder()))
|
|
||||||
.collect(Collectors.joining(" -> "));
|
|
||||||
|
|
||||||
log.info(" ✅ 链路 {} 排序完成:{}", mark, sortedList);
|
|
||||||
});
|
|
||||||
|
|
||||||
log.info("【责任链路初始化】所有处理器链已完全就绪。");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
package cn.meowrain.aioj.backend.framework.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", "接口调用失败");
|
|
||||||
/**
|
|
||||||
* 状态码
|
|
||||||
*/
|
|
||||||
|
|
||||||
private final String code;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 信息
|
|
||||||
*/
|
|
||||||
private final String message;
|
|
||||||
|
|
||||||
ErrorCode(String code, String message) {
|
|
||||||
this.code = code;
|
|
||||||
this.message = message;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String code() {
|
|
||||||
return code;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String message() {
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
package cn.meowrain.aioj.backend.framework.errorcode;
|
|
||||||
|
|
||||||
public interface IErrorCode {
|
|
||||||
String code();
|
|
||||||
String message();
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
package cn.meowrain.aioj.backend.framework.exception;
|
|
||||||
|
|
||||||
import cn.meowrain.aioj.backend.framework.errorcode.IErrorCode;
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 抽象错误处理Exception,基于这个抽象类,我们能创建很多其它类型的exception,定义错误类型
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
|
|
||||||
public class AbstractException extends RuntimeException {
|
|
||||||
public final String errorCode;
|
|
||||||
public final String errorMessage;
|
|
||||||
|
|
||||||
public AbstractException(String message, Throwable throwable, IErrorCode errorCode) {
|
|
||||||
super(message);
|
|
||||||
this.errorCode = errorCode.code();
|
|
||||||
this.errorMessage = Optional.ofNullable(StringUtils.hasLength(message) ? message : null)
|
|
||||||
.orElse(errorCode.message());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
package cn.meowrain.aioj.backend.framework.exception;
|
|
||||||
|
|
||||||
import cn.meowrain.aioj.backend.framework.errorcode.ErrorCode;
|
|
||||||
import cn.meowrain.aioj.backend.framework.errorcode.IErrorCode;
|
|
||||||
import lombok.ToString;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 客户端异常
|
|
||||||
*/
|
|
||||||
@ToString
|
|
||||||
public class ClientException extends AbstractException{
|
|
||||||
public ClientException(String message, Throwable throwable, IErrorCode errorCode) {
|
|
||||||
super(message, throwable, errorCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientException(IErrorCode errorCode) {
|
|
||||||
this(null, null, errorCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientException(String message, IErrorCode errorCode) {
|
|
||||||
this(message, null, errorCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientException(String message) {
|
|
||||||
this(message, null, ErrorCode.PARAMS_ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
package cn.meowrain.aioj.backend.framework.exception;
|
|
||||||
|
|
||||||
import cn.meowrain.aioj.backend.framework.errorcode.ErrorCode;
|
|
||||||
import cn.meowrain.aioj.backend.framework.errorcode.IErrorCode;
|
|
||||||
import lombok.ToString;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 调用第三方服务异常
|
|
||||||
*/
|
|
||||||
@ToString
|
|
||||||
public class RemoteException extends AbstractException{
|
|
||||||
public RemoteException(IErrorCode errorCode) {
|
|
||||||
this(null, null, errorCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
public RemoteException(String message, IErrorCode errorCode) {
|
|
||||||
this(message, null, errorCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
public RemoteException(String message, Throwable throwable, IErrorCode errorCode) {
|
|
||||||
super(message, throwable, errorCode);
|
|
||||||
}
|
|
||||||
public RemoteException(String message) {
|
|
||||||
this(message, null, ErrorCode.API_REQUEST_ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
package cn.meowrain.aioj.backend.framework.exception;
|
|
||||||
|
|
||||||
import cn.meowrain.aioj.backend.framework.errorcode.ErrorCode;
|
|
||||||
import cn.meowrain.aioj.backend.framework.errorcode.IErrorCode;
|
|
||||||
import lombok.ToString;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 系统执行异常
|
|
||||||
*/
|
|
||||||
@ToString
|
|
||||||
public class ServiceException extends AbstractException {
|
|
||||||
public ServiceException(String message, IErrorCode errorCode) {
|
|
||||||
this(message, null, errorCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServiceException(String message) {
|
|
||||||
this(message, null, ErrorCode.SYSTEM_ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServiceException(IErrorCode errorCode) {
|
|
||||||
this(null, null, errorCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServiceException(String message, Throwable throwable, IErrorCode errorCode) {
|
|
||||||
super(message, throwable, errorCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
package cn.meowrain.aioj.backend.framework.exception.handler;
|
|
||||||
|
|
||||||
import cn.meowrain.aioj.backend.framework.exception.AbstractException;
|
|
||||||
import cn.meowrain.aioj.backend.framework.web.Result;
|
|
||||||
import cn.meowrain.aioj.backend.framework.web.Results;
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import lombok.experimental.StandardException;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
import org.springframework.validation.BindingResult;
|
|
||||||
import org.springframework.validation.FieldError;
|
|
||||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
|
||||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
|
||||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 全局错误捕获器
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
@RestControllerAdvice
|
|
||||||
public class GlobalExceptionHandler {
|
|
||||||
/**
|
|
||||||
* 捕获所有参数错误,然后统一捕获并且抛出
|
|
||||||
* @param request {@link HttpServletRequest}
|
|
||||||
* @param ex {@link org.springframework.validation.method.MethodValidationException}
|
|
||||||
* @return {@link Result<Void>}
|
|
||||||
*/
|
|
||||||
@ExceptionHandler(value = MethodArgumentNotValidException.class)
|
|
||||||
public Result<Void> validExceptionHandler(HttpServletRequest request, MethodArgumentNotValidException ex) {
|
|
||||||
BindingResult bindingResult = ex.getBindingResult();
|
|
||||||
// 收集所有错误字段
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 抽象异常捕获其
|
|
||||||
* @param request {@link HttpServletRequest}
|
|
||||||
* @param ex {@link AbstractException}
|
|
||||||
* @return {@link Result<Void>}
|
|
||||||
*/
|
|
||||||
@ExceptionHandler(value = {AbstractException.class})
|
|
||||||
public Result<Void> abstractExceptionHandler(HttpServletRequest request,AbstractException ex ) {
|
|
||||||
if (ex.getCause() != null) {
|
|
||||||
log.error("[{}] {} [ex] {}", request.getMethod(), request.getRequestURL().toString(), ex, ex.getCause());
|
|
||||||
return Results.failure(ex);
|
|
||||||
}
|
|
||||||
StringBuilder stackTraceBuilder = new StringBuilder();
|
|
||||||
stackTraceBuilder.append(ex.getClass().getName()).append(": ").append(ex.getErrorMessage()).append("\n");
|
|
||||||
StackTraceElement[] stackTrace = ex.getStackTrace();
|
|
||||||
for (int i = 0; i < Math.min(5, stackTrace.length); i++) {
|
|
||||||
stackTraceBuilder.append("\tat ").append(stackTrace[i]).append("\n");
|
|
||||||
}
|
|
||||||
log.error("[{}] {} [ex] {} \n\n{}", request.getMethod(), request.getRequestURL().toString(), ex,
|
|
||||||
stackTraceBuilder);
|
|
||||||
return Results.failure(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 拦截未捕获异常
|
|
||||||
*/
|
|
||||||
@ExceptionHandler(value = Throwable.class)
|
|
||||||
public Result<Void> defaultErrorHandler(HttpServletRequest request, Throwable throwable) {
|
|
||||||
log.error("[{}] {} ", request.getMethod(), getUrl(request), throwable);
|
|
||||||
return Results.failure();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取请求URL
|
|
||||||
* @param request {@link HttpServletRequest}
|
|
||||||
* @return String
|
|
||||||
*/
|
|
||||||
private String getUrl(HttpServletRequest request) {
|
|
||||||
if (!StringUtils.hasText(request.getQueryString())) {
|
|
||||||
return request.getRequestURI();
|
|
||||||
}
|
|
||||||
return request.getRequestURL() + "?" + request.getQueryString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
package cn.meowrain.aioj.backend.framework.web;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.experimental.Accessors;
|
|
||||||
|
|
||||||
import java.io.Serial;
|
|
||||||
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;
|
|
||||||
/**
|
|
||||||
* 响应信息
|
|
||||||
*/
|
|
||||||
private String message;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 返回是否是正确响应
|
|
||||||
*
|
|
||||||
* @return boolean
|
|
||||||
*/
|
|
||||||
public boolean isSuccess() {
|
|
||||||
return SUCCESS_CODE.equals(code);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 返回是否是错误响应
|
|
||||||
* @return boolean
|
|
||||||
*/
|
|
||||||
public boolean isFail() {
|
|
||||||
return !isSuccess();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
package cn.meowrain.aioj.backend.framework.web;
|
|
||||||
|
|
||||||
import cn.meowrain.aioj.backend.framework.errorcode.ErrorCode;
|
|
||||||
import cn.meowrain.aioj.backend.framework.exception.AbstractException;
|
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 构建响应的工具类
|
|
||||||
*/
|
|
||||||
public final class Results {
|
|
||||||
/**
|
|
||||||
* 成功响应,不返回任何信息
|
|
||||||
*
|
|
||||||
* @return {@link Result<Void>}
|
|
||||||
*/
|
|
||||||
public static Result<Void> success() {
|
|
||||||
return new Result<Void>().setCode(Result.SUCCESS_CODE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 成功响应 返回数据
|
|
||||||
*
|
|
||||||
* @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 {@link Result<Void>}
|
|
||||||
*/
|
|
||||||
public static Result<Void> paramsValidFailure() {
|
|
||||||
return failure(ErrorCode.PARAMS_ERROR.code(), ErrorCode.PARAMS_ERROR.message());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 服务端错误默认响应 -- <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());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 服务端错误响应 - 接收一个AbstractException,里面定义了错误码和错误信息
|
|
||||||
*
|
|
||||||
* @param exception {@link AbstractException}
|
|
||||||
* @return {@link Result<Void>}
|
|
||||||
*/
|
|
||||||
public static Result<Void> failure(AbstractException exception) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 服务端错误响应,自定义错误码和错误信息
|
|
||||||
*
|
|
||||||
* @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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
cn.meowrain.aioj.backend.framework.config.WebAutoConfiguration
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
█████████ █████ ███████ █████
|
|
||||||
███░░░░░███ ░░███ ███░░░░░███ ░░███
|
|
||||||
░███ ░███ ░███ ███ ░░███ ░███
|
|
||||||
░███████████ ░███ ░███ ░███ ░███
|
|
||||||
░███░░░░░███ ░███ ░███ ░███ ░███
|
|
||||||
░███ ░███ ░███ ░░███ ███ ███ ░███
|
|
||||||
█████ █████ █████ ░░░███████░ ░░████████
|
|
||||||
░░░░░ ░░░░░ ░░░░░ ░░░░░░░ ░░░░░░░░
|
|
||||||
|
|
||||||
|
|
||||||
🚀 AIOJ - ${spring.application.name}
|
|
||||||
🌍 Environment : ${spring.profiles.active}
|
|
||||||
☕ Java Version : ${java.version}
|
|
||||||
🔥 Started At : ${application.startup.time}
|
|
||||||
❤️ Service Running: http://${server.address:127.0.0.1}:${server.port}${server.servlet.context-path}
|
|
||||||
✨API Document: http://127.0.0.1:${server.port}/doc.html
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -15,11 +15,56 @@
|
|||||||
<maven.compiler.source>17</maven.compiler.source>
|
<maven.compiler.source>17</maven.compiler.source>
|
||||||
<maven.compiler.target>17</maven.compiler.target>
|
<maven.compiler.target>17</maven.compiler.target>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<spring-cloud-gateway.version>4.3.2</spring-cloud-gateway.version>
|
||||||
</properties>
|
</properties>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.meowrain</groupId>
|
||||||
|
<artifactId>aioj-backend-common-starter</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- Spring Cloud Gateway -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-gateway-server-webflux</artifactId>
|
||||||
|
<version>4.3.2</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- Nacos Discovery (parent 应已引入 alibaba 版本管理) -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
|
||||||
|
<version>4.3.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.csp</groupId>
|
||||||
|
<artifactId>sentinel-datasource-extension</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>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-bootstrap</artifactId>
|
||||||
|
<version>4.3.0</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package cn.meowrain.aioj.backend.gateway;
|
||||||
|
|
||||||
|
import cn.meowrain.aioj.backend.gateway.config.GatewayPropertiesConfiguration;
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
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)
|
* 全局 CORS 配置(WebFlux 环境使用 CorsWebFilter)
|
||||||
*
|
*
|
||||||
* WebFlux 不使用 Spring MVC 的 CorsFilter,
|
* WebFlux 不使用 Spring MVC 的 CorsFilter, 而是使用专门的 CorsWebFilter 处理跨域。
|
||||||
* 而是使用专门的 CorsWebFilter 处理跨域。
|
|
||||||
*
|
*
|
||||||
* 此配置实现了:
|
* 此配置实现了: - 允许任意域名访问(AllowedOriginPatterns = "*") - 允许所有请求方法(GET、POST、PUT...) - 允许所有请求头 -
|
||||||
* - 允许任意域名访问(AllowedOriginPatterns = "*")
|
* 允许跨域携带 Cookie(AllowCredentials) - 对所有路径生效(/**)
|
||||||
* - 允许所有请求方法(GET、POST、PUT...)
|
|
||||||
* - 允许所有请求头
|
|
||||||
* - 允许跨域携带 Cookie(AllowCredentials)
|
|
||||||
* - 对所有路径生效(/**)
|
|
||||||
*/
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
public class CorsConfig {
|
public class CorsConfig {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 注册全局 CORS 过滤器
|
* 注册全局 CORS 过滤器
|
||||||
*
|
|
||||||
* @return CorsWebFilter 跨域过滤器
|
* @return CorsWebFilter 跨域过滤器
|
||||||
*/
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
@@ -43,9 +37,8 @@ public class CorsConfig {
|
|||||||
corsConfiguration.setAllowCredentials(true);
|
corsConfiguration.setAllowCredentials(true);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 允许跨域的来源域名
|
* 允许跨域的来源域名 使用 setAllowedOriginPatterns("*") 是 WebFlux 推荐方式, 因为
|
||||||
* 使用 setAllowedOriginPatterns("*") 是 WebFlux 推荐方式,
|
* setAllowedOrigins("*") 在 allowCredentials=true 时会被拦截。
|
||||||
* 因为 setAllowedOrigins("*") 在 allowCredentials=true 时会被拦截。
|
|
||||||
*/
|
*/
|
||||||
corsConfiguration.setAllowedOriginPatterns(List.of("*"));
|
corsConfiguration.setAllowedOriginPatterns(List.of("*"));
|
||||||
|
|
||||||
@@ -53,11 +46,9 @@ public class CorsConfig {
|
|||||||
corsConfiguration.addAllowedHeader("*");
|
corsConfiguration.addAllowedHeader("*");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 基于 URL 的跨域配置源,
|
* 基于 URL 的跨域配置源, PathPatternParser 用于解析路径模式(更高性能)
|
||||||
* PathPatternParser 用于解析路径模式(更高性能)
|
|
||||||
*/
|
*/
|
||||||
UrlBasedCorsConfigurationSource source =
|
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
|
||||||
new UrlBasedCorsConfigurationSource(new PathPatternParser());
|
|
||||||
|
|
||||||
// 对所有路径应用跨域设置
|
// 对所有路径应用跨域设置
|
||||||
source.registerCorsConfiguration("/**", corsConfiguration);
|
source.registerCorsConfiguration("/**", corsConfiguration);
|
||||||
@@ -65,4 +56,5 @@ public class CorsConfig {
|
|||||||
// 创建并返回 WebFlux 专用的 CORS 过滤器
|
// 创建并返回 WebFlux 专用的 CORS 过滤器
|
||||||
return new CorsWebFilter(source);
|
return new CorsWebFilter(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package cn.meowrain.aioj.backend.gateway.config;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
|
||||||
|
@ConfigurationProperties(prefix = GatewayPropertiesConfiguration.PREFIX)
|
||||||
|
@Data
|
||||||
|
public class GatewayPropertiesConfiguration {
|
||||||
|
|
||||||
|
public static final String PREFIX = "aioj-backend-gateway";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 白名单放行
|
||||||
|
*/
|
||||||
|
private String[] whiteList;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package cn.meowrain.aioj.backend.gateway.filter;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||||
|
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
||||||
|
import org.springframework.core.Ordered;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.reactive.function.client.WebClient;
|
||||||
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class AuthGlobalFilter implements GlobalFilter, Ordered {
|
||||||
|
|
||||||
|
private final WebClient.Builder webClientBuilder;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOrder() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
18
aioj-backend-gateway/src/main/resources/application-dev.yml
Normal file
18
aioj-backend-gateway/src/main/resources/application-dev.yml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
spring:
|
||||||
|
data:
|
||||||
|
redis:
|
||||||
|
host: 10.0.0.10
|
||||||
|
port: 6379
|
||||||
|
password: 123456
|
||||||
|
cloud:
|
||||||
|
nacos:
|
||||||
|
discovery:
|
||||||
|
enabled: true
|
||||||
|
register-enabled: true
|
||||||
|
server-addr: 10.0.0.10:8848
|
||||||
|
username: nacos
|
||||||
|
password: nacos
|
||||||
|
|
||||||
|
logging:
|
||||||
|
level:
|
||||||
|
root: INFO
|
||||||
18
aioj-backend-gateway/src/main/resources/application-prod.yml
Normal file
18
aioj-backend-gateway/src/main/resources/application-prod.yml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
spring:
|
||||||
|
data:
|
||||||
|
redis:
|
||||||
|
host: 10.0.0.10
|
||||||
|
port: 6379
|
||||||
|
password: 123456
|
||||||
|
cloud:
|
||||||
|
nacos:
|
||||||
|
discovery:
|
||||||
|
enabled: true
|
||||||
|
register-enabled: true
|
||||||
|
server-addr: 10.0.0.10:8848
|
||||||
|
username: nacos
|
||||||
|
password: nacos
|
||||||
|
|
||||||
|
logging:
|
||||||
|
level:
|
||||||
|
root: INFO
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user