refactor(backend): 重命名后端项目为 gym-manage-api,修改包名为 cn.novalon.gym.manage
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright 2007-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import java.net.*;
|
||||
import java.io.*;
|
||||
import java.nio.channels.*;
|
||||
import java.util.Properties;
|
||||
|
||||
public class MavenWrapperDownloader {
|
||||
|
||||
private static final String WRAPPER_VERSION = "3.1.0";
|
||||
/**
|
||||
* Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
|
||||
*/
|
||||
private static final String DEFAULT_DOWNLOAD_URL =
|
||||
"https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/" + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
|
||||
|
||||
/**
|
||||
* Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
|
||||
* use instead of the default one.
|
||||
*/
|
||||
private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
|
||||
".mvn/wrapper/maven-wrapper.properties";
|
||||
|
||||
/**
|
||||
* Path where the maven-wrapper.jar will be saved to.
|
||||
*/
|
||||
private static final String MAVEN_WRAPPER_JAR_PATH =
|
||||
".mvn/wrapper/maven-wrapper.jar";
|
||||
|
||||
/**
|
||||
* Name of the property which should be used to override the default download url for the wrapper.
|
||||
*/
|
||||
private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
|
||||
|
||||
public static void main(String args[]) {
|
||||
System.out.println("- Downloader started");
|
||||
File baseDirectory = new File(args[0]);
|
||||
System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
|
||||
|
||||
// If the maven-wrapper.properties exists, read it and check if it contains a custom
|
||||
// wrapperUrl parameter.
|
||||
File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
|
||||
String url = DEFAULT_DOWNLOAD_URL;
|
||||
if(mavenWrapperPropertyFile.exists()) {
|
||||
FileInputStream mavenWrapperPropertyFileInputStream = null;
|
||||
try {
|
||||
mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
|
||||
Properties mavenWrapperProperties = new Properties();
|
||||
mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
|
||||
url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
|
||||
} catch (IOException e) {
|
||||
System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
|
||||
} finally {
|
||||
try {
|
||||
if(mavenWrapperPropertyFileInputStream != null) {
|
||||
mavenWrapperPropertyFileInputStream.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
System.out.println("- Downloading from: " + url);
|
||||
|
||||
File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
|
||||
if(!outputFile.getParentFile().exists()) {
|
||||
if(!outputFile.getParentFile().mkdirs()) {
|
||||
System.out.println(
|
||||
"- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
|
||||
}
|
||||
}
|
||||
System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
|
||||
try {
|
||||
downloadFileFromURL(url, outputFile);
|
||||
System.out.println("Done");
|
||||
System.exit(0);
|
||||
} catch (Throwable e) {
|
||||
System.out.println("- Error downloading");
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
private static void downloadFileFromURL(String urlString, File destination) throws Exception {
|
||||
if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
|
||||
String username = System.getenv("MVNW_USERNAME");
|
||||
char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
|
||||
Authenticator.setDefault(new Authenticator() {
|
||||
@Override
|
||||
protected PasswordAuthentication getPasswordAuthentication() {
|
||||
return new PasswordAuthentication(username, password);
|
||||
}
|
||||
});
|
||||
}
|
||||
URL website = new URL(urlString);
|
||||
ReadableByteChannel rbc;
|
||||
rbc = Channels.newChannel(website.openStream());
|
||||
FileOutputStream fos = new FileOutputStream(destination);
|
||||
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
|
||||
fos.close();
|
||||
rbc.close();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip
|
||||
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar
|
||||
@@ -0,0 +1,49 @@
|
||||
# 多阶段构建优化Dockerfile
|
||||
FROM maven:3.9-eclipse-temurin-21 AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# 复制Maven配置文件和源码
|
||||
COPY pom.xml .
|
||||
COPY mvnw .
|
||||
COPY mvnw.cmd .
|
||||
COPY .mvn .mvn
|
||||
|
||||
# 下载依赖(利用Docker缓存层)
|
||||
RUN ./mvnw dependency:go-offline -B
|
||||
|
||||
# 复制源码并构建
|
||||
COPY src ./src
|
||||
RUN ./mvnw clean package -DskipTests
|
||||
|
||||
# 运行时镜像
|
||||
FROM eclipse-temurin:21-jre-jammy
|
||||
|
||||
# 设置时区和语言环境
|
||||
RUN apt-get update && apt-get install -y \
|
||||
curl \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# 创建非root用户运行应用
|
||||
RUN groupadd -r novalon && useradd -r -g novalon novalon
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# 复制构建产物
|
||||
COPY --from=builder --chown=novalon:novalon /app/target/*.jar app.jar
|
||||
|
||||
# 设置JVM参数优化
|
||||
ENV JAVA_OPTS="-Xmx512m -Xms256m -XX:+UseG1GC -XX:+UnlockExperimentalVMOptions -XX:+UseContainerSupport -Djava.security.egd=file:/dev/./urandom"
|
||||
|
||||
# 暴露端口
|
||||
EXPOSE 8084
|
||||
|
||||
# 切换用户
|
||||
USER novalon
|
||||
|
||||
# 健康检查
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
|
||||
CMD curl -f http://localhost:8084/actuator/health || exit 1
|
||||
|
||||
# 启动命令
|
||||
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
|
||||
@@ -0,0 +1 @@
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; public class Test { public static void main(String[] args) { BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); String hash = "$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C"; System.out.println("Match Test@123: " + encoder.matches("Test@123", hash)); } }
|
||||
@@ -0,0 +1,14 @@
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
|
||||
public class TestBCrypt {
|
||||
public static void main(String[] args) {
|
||||
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(12);
|
||||
String password = "admin123";
|
||||
String hash = "$2b$12$SFefXlGRFMA0fvxIufpWPuIAl0OPLgRDoCZPThCvjpiJGPYS8yNYy";
|
||||
|
||||
System.out.println("测试密码验证:");
|
||||
System.out.println("密码: " + password);
|
||||
System.out.println("哈希: " + hash);
|
||||
System.out.println("验证结果: " + encoder.matches(password, hash));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,884 @@
|
||||
# 模块架构重构执行计划
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** 重构项目模块架构,实现清晰的职责划分和依赖倒置
|
||||
|
||||
**Architecture:**
|
||||
- app模块:只包含启动类、应用级配置和flyway脚本
|
||||
- sys模块:包含所有业务代码(domain、service、handler等)和业务级配置
|
||||
- gateway模块:包含路由和限流配置
|
||||
- db模块:依赖sys模块,实现repository接口
|
||||
- common模块:提供通用工具类和基础配置
|
||||
|
||||
**Tech Stack:** Maven, Spring Boot, Spring WebFlux, Spring Security, R2DBC
|
||||
|
||||
---
|
||||
|
||||
## 重构目标
|
||||
|
||||
### 模块职责划分
|
||||
|
||||
| 模块 | 职责 | 内容 |
|
||||
|-------|--------|------|
|
||||
| manage-app | 应用启动和配置 | ManageApplication.java、application.yml、flyway脚本、应用级配置(WebFluxConfig、MultipartConfig、OpenApiConfig) |
|
||||
| manage-sys | 业务逻辑 | domain、repository接口、service接口和实现、handler、业务级配置(SecurityConfig、WebSocketConfig) |
|
||||
| manage-gateway | 网关路由和限流 | GatewayApplication.java、路由配置(SystemRouter)、限流配置(RateLimitConfig) |
|
||||
| manage-db | 数据访问实现 | entity、dao、repository实现、converter |
|
||||
| manage-common | 通用工具和配置 | 工具类、通用DTO、基础配置、全局异常处理(GlobalExceptionHandler) |
|
||||
|
||||
### 依赖关系
|
||||
|
||||
```
|
||||
manage-gateway → 无依赖(独立模块)
|
||||
manage-app → manage-sys + manage-db
|
||||
manage-sys → manage-common
|
||||
manage-db → manage-sys
|
||||
manage-common → 无依赖
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 1: 将RateLimitConfig从app模块移到gateway模块
|
||||
|
||||
**Files:**
|
||||
- Create: `manage-gateway/src/main/java/cn/novalon/manage/gateway/config/RateLimitConfig.java`
|
||||
- Delete: `manage-app/src/main/java/cn/novalon/manage/app/config/RateLimitConfig.java`
|
||||
|
||||
**Step 1: 创建gateway模块的config目录**
|
||||
|
||||
```bash
|
||||
mkdir -p manage-gateway/src/main/java/cn/novalon/manage/gateway/config
|
||||
```
|
||||
|
||||
**Step 2: 移动RateLimitConfig.java**
|
||||
|
||||
```bash
|
||||
mv manage-app/src/main/java/cn/novalon/manage/app/config/RateLimitConfig.java \
|
||||
manage-gateway/src/main/java/cn/novalon/manage/gateway/config/
|
||||
```
|
||||
|
||||
**Step 3: 更新RateLimitConfig.java的包声明**
|
||||
|
||||
```java
|
||||
// 将
|
||||
package cn.novalon.manage.sys.config;
|
||||
// 改为
|
||||
package cn.novalon.manage.gateway.config;
|
||||
```
|
||||
|
||||
**Step 4: 更新gateway模块的pom.xml,添加Resilience4j依赖**
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>io.github.resilience4j</groupId>
|
||||
<artifactId>resilience4j-spring-boot3</artifactId>
|
||||
<version>2.2.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.resilience4j</groupId>
|
||||
<artifactId>resilience4j-reactor</artifactId>
|
||||
<version>2.2.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
**Step 5: 更新gateway模块的application.yml,添加限流配置**
|
||||
|
||||
```yaml
|
||||
rate:
|
||||
limit:
|
||||
limit-for-period: 100
|
||||
limit-refresh-period: 1s
|
||||
timeout-duration: 0
|
||||
```
|
||||
|
||||
**Step 6: 提交更改**
|
||||
|
||||
```bash
|
||||
git add manage-gateway/src/main/java/cn/novalon/manage/gateway/config/RateLimitConfig.java
|
||||
git add manage-gateway/pom.xml
|
||||
git add manage-gateway/src/main/resources/application.yml
|
||||
git rm manage-app/src/main/java/cn/novalon/manage/app/config/RateLimitConfig.java
|
||||
git commit -m "refactor: move RateLimitConfig to gateway module"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 2: 将SystemRouter从app模块移到gateway模块
|
||||
|
||||
**Files:**
|
||||
- Create: `manage-gateway/src/main/java/cn/novalon/manage/gateway/config/SystemRouter.java`
|
||||
- Delete: `manage-app/src/main/java/cn/novalon/manage/app/config/SystemRouter.java`
|
||||
|
||||
**Step 1: 移动SystemRouter.java**
|
||||
|
||||
```bash
|
||||
mv manage-app/src/main/java/cn/novalon/manage/app/config/SystemRouter.java \
|
||||
manage-gateway/src/main/java/cn/novalon/manage/gateway/config/
|
||||
```
|
||||
|
||||
**Step 2: 更新SystemRouter.java的包声明**
|
||||
|
||||
```java
|
||||
// 将
|
||||
package cn.novalon.manage.sys.config;
|
||||
// 改为
|
||||
package cn.novalon.manage.gateway.config;
|
||||
```
|
||||
|
||||
**Step 3: 更新GatewayApplication.java,集成SystemRouter**
|
||||
|
||||
```java
|
||||
package cn.novalon.manage.gateway;
|
||||
|
||||
import cn.novalon.manage.gateway.config.SystemRouter;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cloud.gateway.route.RouteLocator;
|
||||
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
@SpringBootApplication
|
||||
public class GatewayApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(GatewayApplication.class, args);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RouteLocator customRouteLocator(RouteLocatorBuilder builder, SystemRouter systemRouter) {
|
||||
return systemRouter.buildRoutes(builder);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 4: 更新SystemRouter.java,使用RouteLocatorBuilder**
|
||||
|
||||
```java
|
||||
package cn.novalon.manage.gateway.config;
|
||||
|
||||
import org.springframework.cloud.gateway.route.RouteLocator;
|
||||
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 系统路由配置
|
||||
*
|
||||
* 文件定义:配置Spring Cloud Gateway的路由规则
|
||||
* 涉及业务:API路由、负载均衡、服务发现
|
||||
* 算法:使用Spring Cloud Gateway的路由匹配和转发
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-13
|
||||
*/
|
||||
@Component
|
||||
public class SystemRouter {
|
||||
|
||||
public RouteLocator buildRoutes(RouteLocatorBuilder builder) {
|
||||
return builder.routes()
|
||||
.route("manage-app", r -> r
|
||||
.path("/api/**")
|
||||
.uri("http://manage-app:8081"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 5: 提交更改**
|
||||
|
||||
```bash
|
||||
git add manage-gateway/src/main/java/cn/novalon/manage/gateway/config/SystemRouter.java
|
||||
git add manage-gateway/src/main/java/cn/novalon/manage/gateway/GatewayApplication.java
|
||||
git rm manage-app/src/main/java/cn/novalon/manage/app/config/SystemRouter.java
|
||||
git commit -m "refactor: move SystemRouter to gateway module"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: 将SecurityConfig从app模块移到sys模块
|
||||
|
||||
**Files:**
|
||||
- Create: `manage-sys/src/main/java/cn/novalon/manage/sys/config/SecurityConfig.java`
|
||||
- Delete: `manage-app/src/main/java/cn/novalon/manage/app/config/SecurityConfig.java`
|
||||
|
||||
**Step 1: 创建sys模块的config目录**
|
||||
|
||||
```bash
|
||||
mkdir -p manage-sys/src/main/java/cn/novalon/manage/sys/config
|
||||
```
|
||||
|
||||
**Step 2: 移动SecurityConfig.java**
|
||||
|
||||
```bash
|
||||
mv manage-app/src/main/java/cn/novalon/manage/app/config/SecurityConfig.java \
|
||||
manage-sys/src/main/java/cn/novalon/manage/sys/config/
|
||||
```
|
||||
|
||||
**Step 3: 更新SecurityConfig.java的包声明**
|
||||
|
||||
```java
|
||||
// 将
|
||||
package cn.novalon.manage.sys.config;
|
||||
// 改为
|
||||
package cn.novalon.manage.sys.config;
|
||||
```
|
||||
|
||||
**Step 4: 提交更改**
|
||||
|
||||
```bash
|
||||
git add manage-sys/src/main/java/cn/novalon/manage/sys/config/SecurityConfig.java
|
||||
git rm manage-app/src/main/java/cn/novalon/manage/app/config/SecurityConfig.java
|
||||
git commit -m "refactor: move SecurityConfig to sys module"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: 将WebSocketConfig从app模块移到sys模块
|
||||
|
||||
**Files:**
|
||||
- Create: `manage-sys/src/main/java/cn/novalon/manage/sys/config/WebSocketConfig.java`
|
||||
- Delete: `manage-app/src/main/java/cn/novalon/manage/app/config/WebSocketConfig.java`
|
||||
|
||||
**Step 1: 移动WebSocketConfig.java**
|
||||
|
||||
```bash
|
||||
mv manage-app/src/main/java/cn/novalon/manage/app/config/WebSocketConfig.java \
|
||||
manage-sys/src/main/java/cn/novalon/manage/sys/config/
|
||||
```
|
||||
|
||||
**Step 2: 更新WebSocketConfig.java的包声明**
|
||||
|
||||
```java
|
||||
// 将
|
||||
package cn.novalon.manage.sys.config;
|
||||
// 改为
|
||||
package cn.novalon.manage.sys.config;
|
||||
```
|
||||
|
||||
**Step 3: 提交更改**
|
||||
|
||||
```bash
|
||||
git add manage-sys/src/main/java/cn/novalon/manage/sys/config/WebSocketConfig.java
|
||||
git rm manage-app/src/main/java/cn/novalon/manage/app/config/WebSocketConfig.java
|
||||
git commit -m "refactor: move WebSocketConfig to sys module"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 5: 将GlobalExceptionHandler移到common模块并重构
|
||||
|
||||
**Files:**
|
||||
- Create: `manage-common/src/main/java/cn/novalon/manage/common/handler/GlobalExceptionHandler.java`
|
||||
- Create: `manage-common/src/main/java/cn/novalon/manage/common/handler/ExceptionLogService.java`
|
||||
- Delete: `manage-app/src/main/java/cn/novalon/manage/app/handler/GlobalExceptionHandler.java`
|
||||
|
||||
**Step 1: 创建common模块的handler目录**
|
||||
|
||||
```bash
|
||||
mkdir -p manage-common/src/main/java/cn/novalon/manage/common/handler
|
||||
```
|
||||
|
||||
**Step 2: 创建异常日志服务接口**
|
||||
|
||||
```java
|
||||
package cn.novalon.manage.common.handler;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* 异常日志服务接口
|
||||
*
|
||||
* 文件定义:定义异常日志记录的抽象接口
|
||||
* 涉及业务:异常日志记录、错误追踪
|
||||
* 算法:使用响应式编程实现异步日志记录
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-13
|
||||
*/
|
||||
public interface ExceptionLogService {
|
||||
Mono<Void> logException(String title, String exceptionName, String exceptionMsg,
|
||||
String methodName, String ip, String stackTrace);
|
||||
}
|
||||
```
|
||||
|
||||
**Step 3: 重构GlobalExceptionHandler,移除对sys模块的依赖**
|
||||
|
||||
```java
|
||||
package cn.novalon.manage.common.handler;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
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 org.springframework.web.server.ResponseStatusException;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.ServerWebInputException;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 全局异常处理器
|
||||
*
|
||||
* 文件定义:统一处理系统中抛出的各种异常,返回标准化的错误响应
|
||||
* 涉及业务:异常捕获、错误日志记录、错误响应格式化
|
||||
* 算法:使用@RestControllerAdvice注解实现全局异常拦截
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-13
|
||||
*/
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
|
||||
|
||||
private final ExceptionLogService exceptionLogService;
|
||||
|
||||
public GlobalExceptionHandler(ExceptionLogService exceptionLogService) {
|
||||
this.exceptionLogService = exceptionLogService;
|
||||
}
|
||||
|
||||
@ExceptionHandler(RuntimeException.class)
|
||||
public ResponseEntity<Map<String, Object>> handleRuntimeException(RuntimeException ex, ServerWebExchange exchange) {
|
||||
logger.warn("Runtime exception: ", ex);
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
if (ex.getMessage() != null && ex.getMessage().contains("not found")) {
|
||||
response.put("code", HttpStatus.NOT_FOUND.value());
|
||||
response.put("message", ex.getMessage());
|
||||
response.put("timestamp", LocalDateTime.now());
|
||||
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response);
|
||||
}
|
||||
response.put("code", HttpStatus.BAD_REQUEST.value());
|
||||
response.put("message", ex.getMessage());
|
||||
response.put("timestamp", LocalDateTime.now());
|
||||
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
|
||||
}
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
public ResponseEntity<Map<String, Object>> handleException(Exception ex, ServerWebExchange exchange) {
|
||||
logger.error("Exception occurred: ", ex);
|
||||
|
||||
exceptionLogService.logException(
|
||||
"System Exception",
|
||||
ex.getClass().getSimpleName(),
|
||||
ex.getMessage(),
|
||||
exchange.getRequest().getPath().value(),
|
||||
getClientIp(exchange),
|
||||
getStackTrace(ex)
|
||||
).subscribe();
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("code", HttpStatus.INTERNAL_SERVER_ERROR.value());
|
||||
response.put("message", "Internal server error");
|
||||
response.put("timestamp", LocalDateTime.now());
|
||||
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
|
||||
}
|
||||
|
||||
@ExceptionHandler(IllegalArgumentException.class)
|
||||
public ResponseEntity<Map<String, Object>> handleIllegalArgumentException(IllegalArgumentException ex, ServerWebExchange exchange) {
|
||||
logger.warn("Illegal argument: ", ex);
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("code", HttpStatus.BAD_REQUEST.value());
|
||||
response.put("message", ex.getMessage());
|
||||
response.put("timestamp", LocalDateTime.now());
|
||||
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
|
||||
}
|
||||
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public ResponseEntity<Map<String, Object>> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex, ServerWebExchange exchange) {
|
||||
logger.warn("Validation failed: ", ex);
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("code", HttpStatus.BAD_REQUEST.value());
|
||||
response.put("message", "Validation failed");
|
||||
response.put("timestamp", LocalDateTime.now());
|
||||
|
||||
Map<String, String> fieldErrors = ex.getBindingResult()
|
||||
.getFieldErrors()
|
||||
.stream()
|
||||
.collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage, (e1, e2) -> e1));
|
||||
|
||||
response.put("errors", fieldErrors);
|
||||
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
|
||||
}
|
||||
|
||||
@ExceptionHandler(ServerWebInputException.class)
|
||||
public ResponseEntity<Map<String, Object>> handleServerWebInputException(ServerWebInputException ex, ServerWebExchange exchange) {
|
||||
logger.warn("Invalid input: ", ex);
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("code", HttpStatus.BAD_REQUEST.value());
|
||||
response.put("message", "Invalid input");
|
||||
response.put("timestamp", LocalDateTime.now());
|
||||
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
|
||||
}
|
||||
|
||||
@ExceptionHandler(ResponseStatusException.class)
|
||||
public ResponseEntity<Map<String, Object>> handleResponseStatusException(ResponseStatusException ex, ServerWebExchange exchange) {
|
||||
logger.warn("Response status exception: ", ex);
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("code", ex.getStatusCode().value());
|
||||
response.put("message", ex.getReason());
|
||||
response.put("timestamp", LocalDateTime.now());
|
||||
|
||||
return ResponseEntity.status(ex.getStatusCode()).body(response);
|
||||
}
|
||||
|
||||
@ExceptionHandler(DuplicateKeyException.class)
|
||||
public ResponseEntity<Map<String, Object>> handleDuplicateKeyException(DuplicateKeyException ex, ServerWebExchange exchange) {
|
||||
logger.warn("Duplicate key: ", ex);
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("code", HttpStatus.CONFLICT.value());
|
||||
response.put("message", "Duplicate key violation");
|
||||
response.put("timestamp", LocalDateTime.now());
|
||||
|
||||
return ResponseEntity.status(HttpStatus.CONFLICT).body(response);
|
||||
}
|
||||
|
||||
@ExceptionHandler(DataIntegrityViolationException.class)
|
||||
public ResponseEntity<Map<String, Object>> handleDataIntegrityViolationException(DataIntegrityViolationException ex, ServerWebExchange exchange) {
|
||||
logger.warn("Data integrity violation: ", ex);
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("code", HttpStatus.CONFLICT.value());
|
||||
response.put("message", "Data integrity violation");
|
||||
response.put("timestamp", LocalDateTime.now());
|
||||
|
||||
return ResponseEntity.status(HttpStatus.CONFLICT).body(response);
|
||||
}
|
||||
|
||||
private String getClientIp(ServerWebExchange exchange) {
|
||||
return exchange.getRequest().getHeaders().getFirst("X-Forwarded-For",
|
||||
exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
|
||||
}
|
||||
|
||||
private String getStackTrace(Exception ex) {
|
||||
StringBuilder stackTrace = new StringBuilder();
|
||||
for (StackTraceElement element : ex.getStackTrace()) {
|
||||
stackTrace.append(element.toString()).append("\n");
|
||||
}
|
||||
return stackTrace.toString();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 4: 移动GlobalExceptionHandler.java**
|
||||
|
||||
```bash
|
||||
mv manage-app/src/main/java/cn/novalon/manage/app/handler/GlobalExceptionHandler.java \
|
||||
manage-common/src/main/java/cn/novalon/manage/common/handler/
|
||||
```
|
||||
|
||||
**Step 5: 更新GlobalExceptionHandler.java的包声明**
|
||||
|
||||
```java
|
||||
// 将
|
||||
package cn.novalon.manage.sys.handler;
|
||||
// 改为
|
||||
package cn.novalon.manage.common.handler;
|
||||
```
|
||||
|
||||
**Step 6: 在sys模块实现ExceptionLogService接口**
|
||||
|
||||
```java
|
||||
package cn.novalon.manage.sys.handler;
|
||||
|
||||
import cn.novalon.manage.common.handler.ExceptionLogService;
|
||||
import cn.novalon.manage.sys.core.domain.SysExceptionLog;
|
||||
import cn.novalon.manage.sys.core.service.ISysExceptionLogService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* 异常日志服务实现
|
||||
*
|
||||
* 文件定义:实现异常日志记录接口,使用sys模块的异常日志服务
|
||||
* 涉及业务:异常日志记录、错误追踪
|
||||
* 算法:使用响应式编程实现异步日志记录
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-13
|
||||
*/
|
||||
@Service
|
||||
public class ExceptionLogServiceImpl implements ExceptionLogService {
|
||||
|
||||
private final ISysExceptionLogService exceptionLogService;
|
||||
|
||||
public ExceptionLogServiceImpl(ISysExceptionLogService exceptionLogService) {
|
||||
this.exceptionLogService = exceptionLogService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> logException(String title, String exceptionName, String exceptionMsg,
|
||||
String methodName, String ip, String stackTrace) {
|
||||
SysExceptionLog exceptionLog = new SysExceptionLog();
|
||||
exceptionLog.setTitle(title);
|
||||
exceptionLog.setExceptionName(exceptionName);
|
||||
exceptionLog.setExceptionMsg(exceptionMsg);
|
||||
exceptionLog.setMethodName(methodName);
|
||||
exceptionLog.setIp(ip);
|
||||
exceptionLog.setCreateTime(LocalDateTime.now());
|
||||
exceptionLog.setStackTrace(stackTrace);
|
||||
|
||||
return exceptionLogService.save(exceptionLog).then();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 7: 在sys模块的配置中注册ExceptionLogServiceImpl**
|
||||
|
||||
```java
|
||||
package cn.novalon.manage.sys.config;
|
||||
|
||||
import cn.novalon.manage.common.handler.ExceptionLogService;
|
||||
import cn.novalon.manage.sys.handler.ExceptionLogServiceImpl;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* 异常日志配置类
|
||||
*
|
||||
* 文件定义:配置异常日志服务的实现
|
||||
* 涉及业务:异常日志记录、错误追踪
|
||||
* 算法:使用Spring的依赖注入实现接口和实现的绑定
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-13
|
||||
*/
|
||||
@Configuration
|
||||
public class ExceptionLogConfig {
|
||||
|
||||
@Bean
|
||||
public ExceptionLogService exceptionLogService(ExceptionLogServiceImpl exceptionLogServiceImpl) {
|
||||
return exceptionLogServiceImpl;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 8: 提交更改**
|
||||
|
||||
```bash
|
||||
git add manage-common/src/main/java/cn/novalon/manage/common/handler/GlobalExceptionHandler.java
|
||||
git add manage-common/src/main/java/cn/novalon/manage/common/handler/ExceptionLogService.java
|
||||
git add manage-sys/src/main/java/cn/novalon/manage/sys/handler/ExceptionLogServiceImpl.java
|
||||
git add manage-sys/src/main/java/cn/novalon/manage/sys/config/ExceptionLogConfig.java
|
||||
git rm manage-app/src/main/java/cn/novalon/manage/app/handler/GlobalExceptionHandler.java
|
||||
git commit -m "refactor: move GlobalExceptionHandler to common module with dependency inversion"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 6: 更新app模块的ManageApplication.java
|
||||
|
||||
**Files:**
|
||||
- Modify: `manage-app/src/main/java/cn/novalon/manage/app/ManageApplication.java`
|
||||
|
||||
**Step 1: 更新ManageApplication.java的组件扫描配置**
|
||||
|
||||
```java
|
||||
package cn.novalon.manage.app;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories;
|
||||
|
||||
/**
|
||||
* 管理应用主类
|
||||
*
|
||||
* 文件定义:Spring Boot应用启动类,配置组件扫描和功能启用
|
||||
* 涉及业务:应用启动、组件扫描、功能配置
|
||||
* 算法:使用Spring Boot自动配置和注解驱动
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-13
|
||||
*/
|
||||
@SpringBootApplication
|
||||
@ConfigurationPropertiesScan(basePackages = "cn.novalon.manage")
|
||||
@ComponentScan(basePackages = {"cn.novalon.manage.sys", "cn.novalon.manage.db"})
|
||||
@EnableR2dbcRepositories(basePackages = "cn.novalon.manage.db.repository")
|
||||
public class ManageApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(ManageApplication.class, args);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: 提交更改**
|
||||
|
||||
```bash
|
||||
git add manage-app/src/main/java/cn/novalon/manage/app/ManageApplication.java
|
||||
git commit -m "refactor: update ManageApplication component scan configuration"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 7: 更新app模块的pom.xml
|
||||
|
||||
**Files:**
|
||||
- Modify: `manage-app/pom.xml`
|
||||
|
||||
**Step 1: 确保app模块依赖sys和db模块**
|
||||
|
||||
```xml
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.novalon.manage</groupId>
|
||||
<artifactId>manage-sys</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.novalon.manage</groupId>
|
||||
<artifactId>manage-db</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.micrometer</groupId>
|
||||
<artifactId>micrometer-registry-prometheus</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>r2dbc-postgresql</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.flywaydb</groupId>
|
||||
<artifactId>flyway-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.flywaydb</groupId>
|
||||
<artifactId>flyway-database-postgresql</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
```
|
||||
|
||||
**Step 2: 移除不需要的依赖**
|
||||
|
||||
```xml
|
||||
<!-- 移除以下依赖 -->
|
||||
<dependency>
|
||||
<groupId>io.github.resilience4j</groupId>
|
||||
<artifactId>resilience4j-spring-boot3</artifactId>
|
||||
<version>2.2.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.resilience4j</groupId>
|
||||
<artifactId>resilience4j-reactor</artifactId>
|
||||
<version>2.2.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
**Step 3: 提交更改**
|
||||
|
||||
```bash
|
||||
git add manage-app/pom.xml
|
||||
git commit -m "refactor: update app module dependencies"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 8: 编译和测试验证
|
||||
|
||||
**Files:**
|
||||
- Test: `manage-app`, `manage-sys`, `manage-db`, `manage-gateway`
|
||||
|
||||
**Step 1: 清理并编译所有模块**
|
||||
|
||||
```bash
|
||||
mvn clean compile -DskipTests
|
||||
```
|
||||
|
||||
**Expected:** 所有模块编译成功,无错误
|
||||
|
||||
**Step 2: 运行单元测试**
|
||||
|
||||
```bash
|
||||
mvn test
|
||||
```
|
||||
|
||||
**Expected:** 所有测试通过
|
||||
|
||||
**Step 3: 启动应用验证**
|
||||
|
||||
```bash
|
||||
cd manage-app
|
||||
mvn spring-boot:run
|
||||
```
|
||||
|
||||
**Expected:** 应用成功启动,无错误日志
|
||||
|
||||
**Step 4: 提交更改**
|
||||
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "refactor: complete module architecture refactoring"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 9: 更新文档
|
||||
|
||||
**Files:**
|
||||
- Create: `docs/architecture/module-architecture.md`
|
||||
|
||||
**Step 1: 创建模块架构文档**
|
||||
|
||||
```markdown
|
||||
# 模块架构设计
|
||||
|
||||
## 模块职责划分
|
||||
|
||||
### manage-app
|
||||
应用启动和配置模块,包含:
|
||||
- ManageApplication.java:应用启动类
|
||||
- application.yml:应用配置文件
|
||||
- flyway脚本:数据库迁移脚本
|
||||
- 应用级配置:WebFluxConfig、MultipartConfig、OpenApiConfig、GlobalExceptionHandler
|
||||
|
||||
### manage-sys
|
||||
业务逻辑模块,包含:
|
||||
- domain:领域对象(SysUser、SysRole、SysMenu等)
|
||||
- repository:数据访问接口
|
||||
- service:业务逻辑接口和实现
|
||||
- handler:业务处理器(用户、角色、菜单等)
|
||||
- 业务级配置:SecurityConfig、WebSocketConfig
|
||||
- 其他:filter、security、websocket、primitive、command、dto
|
||||
|
||||
### manage-gateway
|
||||
网关模块,包含:
|
||||
- GatewayApplication.java:网关启动类
|
||||
- 路由配置:SystemRouter
|
||||
- 限流配置:RateLimitConfig
|
||||
|
||||
### manage-db
|
||||
数据访问实现模块,包含:
|
||||
- entity:数据库实体
|
||||
- dao:数据访问对象
|
||||
- repository:repository实现
|
||||
- converter:实体和领域对象转换器
|
||||
|
||||
### manage-common
|
||||
通用工具和配置模块,包含:
|
||||
- 工具类:SnowflakeId等
|
||||
- 通用DTO:PageRequest、PageResponse
|
||||
- 基础配置:JwtProperties、CacheConfig
|
||||
|
||||
## 依赖关系
|
||||
|
||||
```
|
||||
manage-gateway → 无依赖(独立模块)
|
||||
manage-app → manage-sys + manage-db
|
||||
manage-sys → manage-common
|
||||
manage-db → manage-sys
|
||||
manage-common → 无依赖
|
||||
```
|
||||
|
||||
## 依赖倒置实现
|
||||
|
||||
通过manage-app模块的依赖注入,实现依赖倒置:
|
||||
- sys模块定义repository接口
|
||||
- db模块实现repository接口
|
||||
- app模块通过@ComponentScan扫描db模块的repository实现
|
||||
- app模块通过@EnableR2dbcRepositories启用R2DBC repository
|
||||
- common模块定义ExceptionLogService接口
|
||||
- sys模块实现ExceptionLogService接口
|
||||
- app模块通过配置注册ExceptionLogService实现
|
||||
```
|
||||
|
||||
**Step 2: 提交文档**
|
||||
|
||||
```bash
|
||||
git add docs/architecture/module-architecture.md
|
||||
git commit -m "docs: add module architecture documentation"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 验证清单
|
||||
|
||||
### 编译验证
|
||||
- [ ] manage-common编译成功
|
||||
- [ ] manage-sys编译成功
|
||||
- [ ] manage-db编译成功
|
||||
- [ ] manage-app编译成功
|
||||
- [ ] manage-gateway编译成功
|
||||
|
||||
### 功能验证
|
||||
- [ ] 应用启动成功
|
||||
- [ ] 数据库连接正常
|
||||
- [ ] API访问正常
|
||||
- [ ] WebSocket连接正常
|
||||
- [ ] 安全认证正常
|
||||
- [ ] 限流功能正常
|
||||
|
||||
### 依赖验证
|
||||
- [ ] manage-sys不依赖manage-db
|
||||
- [ ] manage-db依赖manage-sys
|
||||
- [ ] manage-app依赖manage-sys和manage-db
|
||||
- [ ] manage-gateway无依赖
|
||||
|
||||
### 测试验证
|
||||
- [ ] 单元测试全部通过
|
||||
- [ ] 集成测试全部通过
|
||||
- [ ] E2E测试全部通过
|
||||
|
||||
---
|
||||
|
||||
## 回滚计划
|
||||
|
||||
如果重构过程中出现问题,可以使用以下命令回滚:
|
||||
|
||||
```bash
|
||||
# 回滚到重构前的状态
|
||||
git reset --hard <commit-hash-before-refactoring>
|
||||
|
||||
# 或者使用git reflog查找之前的提交
|
||||
git reflog
|
||||
git reset --hard HEAD@{n}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **循环依赖**:确保manage-sys不依赖manage-db
|
||||
2. **包声明**:移动文件后记得更新包声明
|
||||
3. **import语句**:更新所有import语句以匹配新的包结构
|
||||
4. **配置文件**:确保application.yml中的配置正确
|
||||
5. **组件扫描**:确保ManageApplication.java中的@ComponentScan配置正确
|
||||
6. **测试覆盖**:重构后确保所有测试仍然通过
|
||||
@@ -0,0 +1,9 @@
|
||||
FROM openjdk:21-jdk-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY manage-app/target/manage-app-1.0.0.jar app.jar
|
||||
|
||||
EXPOSE 8081
|
||||
|
||||
ENTRYPOINT ["java", "-jar", "app.jar"]
|
||||
@@ -0,0 +1,141 @@
|
||||
<?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.novalon.gym.manage</groupId>
|
||||
<artifactId>gym-manage-api</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
|
||||
<groupId>cn.novalon.gym.manage</groupId>
|
||||
<artifactId>manage-app</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>Manage App</name>
|
||||
<description>Application module for Novalon Manage API</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.novalon.gym.manage</groupId>
|
||||
<artifactId>manage-sys</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.novalon.gym.manage</groupId>
|
||||
<artifactId>manage-notify</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.novalon.gym.manage</groupId>
|
||||
<artifactId>manage-file</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.novalon.gym.manage</groupId>
|
||||
<artifactId>manage-db</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.resilience4j</groupId>
|
||||
<artifactId>resilience4j-spring-boot3</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.resilience4j</groupId>
|
||||
<artifactId>resilience4j-reactor</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.reactivex.rxjava3</groupId>
|
||||
<artifactId>rxjava</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.micrometer</groupId>
|
||||
<artifactId>micrometer-registry-prometheus</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>r2dbc-postgresql</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.r2dbc</groupId>
|
||||
<artifactId>r2dbc-h2</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.flywaydb</groupId>
|
||||
<artifactId>flyway-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.flywaydb</groupId>
|
||||
<artifactId>flyway-database-postgresql</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>testcontainers</artifactId>
|
||||
<version>1.21.4</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<version>1.21.4</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<version>1.21.4</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webflux-ui</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<mainClass>cn.novalon.manage.app.ManageApplication</mainClass>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package cn.novalon.gym.manage.app;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration;
|
||||
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories;
|
||||
|
||||
@SpringBootApplication(scanBasePackages = "cn.novalon.gym.manage", exclude = {ReactiveUserDetailsServiceAutoConfiguration.class})
|
||||
@EnableR2dbcRepositories(basePackages = {"cn.novalon.gym.manage.db.dao", "cn.novalon.gym.manage.sys.audit.repository"})
|
||||
public class ManageApplication {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ManageApplication.class);
|
||||
|
||||
public static void main(String[] args) {
|
||||
logger.info("应用程序启动中...");
|
||||
logger.info("包扫描路径: cn.novalon.gym.manage");
|
||||
|
||||
// 使用简单的启动方式,避免自动配置问题
|
||||
SpringApplication.run(ManageApplication.class, args);
|
||||
logger.info("应用程序启动完成");
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package cn.novalon.gym.manage.app;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* 最小化应用程序启动类
|
||||
* 避免复杂的自动配置问题,专注于核心功能
|
||||
*/
|
||||
@SpringBootApplication(
|
||||
scanBasePackages = {
|
||||
"cn.novalon.gym.manage.app.config",
|
||||
"cn.novalon.gym.manage.app.controller",
|
||||
"cn.novalon.gym.manage.app.service"
|
||||
}
|
||||
)
|
||||
public class MinimalApplication {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(MinimalApplication.class);
|
||||
|
||||
public static void main(String[] args) {
|
||||
logger.info("最小化应用程序启动中...");
|
||||
|
||||
// 设置系统属性,避免自动配置问题
|
||||
System.setProperty("spring.autoconfigure.exclude",
|
||||
"org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration");
|
||||
|
||||
// 禁用复杂的自动配置
|
||||
System.setProperty("spring.main.lazy-initialization", "true");
|
||||
System.setProperty("spring.main.banner-mode", "off");
|
||||
|
||||
try {
|
||||
SpringApplication.run(MinimalApplication.class, args);
|
||||
logger.info("最小化应用程序启动完成");
|
||||
} catch (Exception e) {
|
||||
logger.error("应用程序启动失败: {}", e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package cn.novalon.gym.manage.app;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration;
|
||||
|
||||
/**
|
||||
* 简化的应用程序启动类
|
||||
* 避免复杂的自动配置问题
|
||||
*/
|
||||
@SpringBootApplication(
|
||||
scanBasePackages = "cn.novalon.gym.manage.app",
|
||||
exclude = {ReactiveUserDetailsServiceAutoConfiguration.class}
|
||||
)
|
||||
public class SimpleManageApplication {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(SimpleManageApplication.class);
|
||||
|
||||
public static void main(String[] args) {
|
||||
logger.info("简化版应用程序启动中...");
|
||||
logger.info("包扫描路径: cn.novalon.gym.manage.app");
|
||||
|
||||
// 设置系统属性,避免自动配置问题
|
||||
System.setProperty("spring.autoconfigure.exclude",
|
||||
"org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration");
|
||||
|
||||
SpringApplication.run(SimpleManageApplication.class, args);
|
||||
logger.info("简化版应用程序启动完成");
|
||||
}
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
package cn.novalon.gym.manage.app.config;
|
||||
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.codec.json.Jackson2JsonDecoder;
|
||||
import org.springframework.http.codec.json.Jackson2JsonEncoder;
|
||||
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
/**
|
||||
* Jackson配置类
|
||||
*
|
||||
* 用于统一时间格式化配置
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-26
|
||||
*/
|
||||
@Configuration
|
||||
public class JacksonConfig {
|
||||
|
||||
private static final String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
|
||||
|
||||
@Bean
|
||||
public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
|
||||
ObjectMapper objectMapper = builder.createXmlMapper(false).build();
|
||||
|
||||
JavaTimeModule javaTimeModule = new JavaTimeModule();
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_TIME_FORMAT);
|
||||
|
||||
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter));
|
||||
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter));
|
||||
|
||||
objectMapper.registerModule(javaTimeModule);
|
||||
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
||||
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
|
||||
|
||||
return objectMapper;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Jackson2JsonEncoder jackson2JsonEncoder(ObjectMapper objectMapper) {
|
||||
return new Jackson2JsonEncoder(objectMapper);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Jackson2JsonDecoder jackson2JsonDecoder(ObjectMapper objectMapper) {
|
||||
return new Jackson2JsonDecoder(objectMapper);
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package cn.novalon.gym.manage.app.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.codec.multipart.DefaultPartHttpMessageReader;
|
||||
import org.springframework.http.codec.multipart.MultipartHttpMessageReader;
|
||||
|
||||
@Configuration
|
||||
public class MultipartConfig {
|
||||
|
||||
@Bean
|
||||
public MultipartHttpMessageReader multipartHttpMessageReader() {
|
||||
DefaultPartHttpMessageReader partReader = new DefaultPartHttpMessageReader();
|
||||
partReader.setMaxHeadersSize(8192);
|
||||
partReader.setMaxDiskUsagePerPart(10 * 1024 * 1024);
|
||||
partReader.setEnableLoggingRequestDetails(true);
|
||||
return new MultipartHttpMessageReader(partReader);
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package cn.novalon.gym.manage.app.config;
|
||||
|
||||
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 io.swagger.v3.oas.models.servers.Server;
|
||||
import io.swagger.v3.oas.models.tags.Tag;
|
||||
import org.springdoc.core.models.GroupedOpenApi;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* OpenAPI配置类
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-14
|
||||
*/
|
||||
@Configuration
|
||||
public class OpenApiConfig {
|
||||
|
||||
@Bean
|
||||
public OpenAPI customOpenAPI() {
|
||||
return new OpenAPI()
|
||||
.info(new Info()
|
||||
.title("Novalon Manage System API")
|
||||
.version("1.0.0")
|
||||
.description("Novalon 管理系统 RESTful API 文档")
|
||||
.contact(new Contact()
|
||||
.name("Novalon Team")
|
||||
.email("support@novalon.cn"))
|
||||
.license(new License()
|
||||
.name("Apache 2.0")
|
||||
.url("https://www.apache.org/licenses/LICENSE-2.0")))
|
||||
.servers(List.of(
|
||||
new Server().url("http://localhost:8084").description("开发环境"),
|
||||
new Server().url("https://api.novalon.cn").description("生产环境")))
|
||||
.tags(Arrays.asList(
|
||||
new Tag().name("用户管理").description("用户相关操作"),
|
||||
new Tag().name("角色管理").description("角色相关操作"),
|
||||
new Tag().name("配置管理").description("系统配置相关操作"),
|
||||
new Tag().name("字典管理").description("字典数据相关操作"),
|
||||
new Tag().name("通知管理").description("系统通知相关操作"),
|
||||
new Tag().name("文件管理").description("文件上传下载相关操作"),
|
||||
new Tag().name("日志管理").description("操作日志相关操作"),
|
||||
new Tag().name("认证管理").description("登录认证相关操作"),
|
||||
new Tag().name("统计信息").description("系统统计相关操作")));
|
||||
}
|
||||
|
||||
@Bean
|
||||
public GroupedOpenApi allApi() {
|
||||
return GroupedOpenApi.builder()
|
||||
.group("all")
|
||||
.pathsToMatch("/api/**")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package cn.novalon.gym.manage.app.config;
|
||||
|
||||
import io.github.resilience4j.ratelimiter.RateLimiter;
|
||||
import io.github.resilience4j.ratelimiter.RateLimiterConfig;
|
||||
import io.github.resilience4j.ratelimiter.RateLimiterRegistry;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
@Configuration
|
||||
public class RateLimitConfig {
|
||||
|
||||
@Value("${rate.limit.limit-for-period:100}")
|
||||
private int limitForPeriod;
|
||||
|
||||
@Value("${rate.limit.limit-refresh-period:1s}")
|
||||
private Duration limitRefreshPeriod;
|
||||
|
||||
@Value("${rate.limit.timeout-duration:0}")
|
||||
private Duration timeoutDuration;
|
||||
|
||||
@Bean
|
||||
public RateLimiterRegistry rateLimiterRegistry() {
|
||||
RateLimiterConfig config = RateLimiterConfig.custom()
|
||||
.limitForPeriod(limitForPeriod)
|
||||
.limitRefreshPeriod(limitRefreshPeriod)
|
||||
.timeoutDuration(timeoutDuration)
|
||||
.build();
|
||||
|
||||
return RateLimiterRegistry.of(config);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Qualifier("apiRateLimiter")
|
||||
public RateLimiter apiRateLimiter(RateLimiterRegistry registry) {
|
||||
return registry.rateLimiter("apiRateLimiter");
|
||||
}
|
||||
}
|
||||
+198
@@ -0,0 +1,198 @@
|
||||
package cn.novalon.gym.manage.app.config;
|
||||
|
||||
import cn.novalon.gym.manage.sys.handler.auth.SysAuthHandler;
|
||||
import cn.novalon.gym.manage.sys.handler.auth.PasswordDiagnosticHandler;
|
||||
import cn.novalon.gym.manage.sys.handler.config.SysConfigHandler;
|
||||
import cn.novalon.gym.manage.sys.handler.dictionary.DictionaryHandler;
|
||||
import cn.novalon.gym.manage.sys.handler.dict.SysDictHandler;
|
||||
import cn.novalon.gym.manage.sys.handler.log.SysLogHandler;
|
||||
import cn.novalon.gym.manage.sys.handler.log.OperationLogHandler;
|
||||
import cn.novalon.gym.manage.sys.handler.menu.MenuHandler;
|
||||
import cn.novalon.gym.manage.sys.handler.role.SysRoleHandler;
|
||||
import cn.novalon.gym.manage.sys.handler.permission.SysPermissionHandler;
|
||||
import cn.novalon.gym.manage.sys.handler.stats.StatsHandler;
|
||||
import cn.novalon.gym.manage.sys.handler.user.SysUserHandler;
|
||||
import cn.novalon.gym.manage.notify.handler.SysNoticeHandler;
|
||||
import cn.novalon.gym.manage.notify.handler.SysUserMessageHandler;
|
||||
import cn.novalon.gym.manage.file.handler.SysFileHandler;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
|
||||
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
|
||||
|
||||
/**
|
||||
* 系统路由配置类
|
||||
*
|
||||
* 文件定义:配置WebFlux函数式路由,将HTTP请求映射到对应的Handler方法
|
||||
* 涉及业务:用户、角色、字典、菜单、公告、文件等所有RESTful API路由
|
||||
* 算法:使用RouterFunctions.route()构建函数式路由规则
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-13
|
||||
*/
|
||||
@Configuration
|
||||
public class SystemRouter {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> systemRoutes(
|
||||
DictionaryHandler dictionaryHandler,
|
||||
SysUserHandler userHandler,
|
||||
MenuHandler menuHandler,
|
||||
SysRoleHandler roleHandler,
|
||||
SysConfigHandler configHandler,
|
||||
SysLogHandler logHandler,
|
||||
OperationLogHandler operationLogHandler,
|
||||
SysAuthHandler authHandler,
|
||||
StatsHandler statsHandler,
|
||||
SysDictHandler dictHandler,
|
||||
SysNoticeHandler noticeHandler,
|
||||
SysUserMessageHandler messageHandler,
|
||||
SysFileHandler fileHandler,
|
||||
SysPermissionHandler permissionHandler,
|
||||
PasswordDiagnosticHandler passwordDiagnosticHandler) {
|
||||
|
||||
return route()
|
||||
// ========== 诊断路由 ==========
|
||||
.GET("/api/diagnostic/password", passwordDiagnosticHandler::diagnose)
|
||||
|
||||
// ========== 字典路由 ==========
|
||||
.GET("/api/dictionaries", dictionaryHandler::getAllDictionaries)
|
||||
.GET("/api/dictionaries/{id}", dictionaryHandler::getDictionaryById)
|
||||
.GET("/api/dictionaries/type/{type}", dictionaryHandler::getDictionariesByType)
|
||||
.GET("/api/dictionaries/check/exists", dictionaryHandler::checkTypeAndCodeExists)
|
||||
.POST("/api/dictionaries", dictionaryHandler::createDictionary)
|
||||
.PUT("/api/dictionaries/{id}", dictionaryHandler::updateDictionary)
|
||||
.DELETE("/api/dictionaries/{id}", dictionaryHandler::deleteDictionary)
|
||||
|
||||
// ========== 用户路由 ==========
|
||||
.GET("/api/users", userHandler::getAllUsers)
|
||||
.GET("/api/users/page", userHandler::getUsersByPage)
|
||||
.GET("/api/users/count", userHandler::getUserCount)
|
||||
.GET("/api/users/username/{username}", userHandler::getUserByUsername)
|
||||
.GET("/api/users/check/username", userHandler::checkUsernameExists)
|
||||
.GET("/api/users/check/email", userHandler::checkEmailExists)
|
||||
.POST("/api/users", userHandler::createUser)
|
||||
.GET("/api/users/{id}", userHandler::getUserById)
|
||||
.PUT("/api/users/{id}", userHandler::updateUser)
|
||||
.DELETE("/api/users/{id}", userHandler::deleteUser)
|
||||
.POST("/api/users/{id}/action/change-password", userHandler::changePassword)
|
||||
.POST("/api/users/{id}/action/logical-delete", userHandler::logicalDeleteUser)
|
||||
.POST("/api/users/logical-delete", userHandler::logicalDeleteUsers)
|
||||
.POST("/api/users/action/restore", userHandler::restoreUsers)
|
||||
.POST("/api/users/{id}/action/restore", userHandler::restoreUser)
|
||||
.GET("/api/users/{id}/roles", userHandler::getUserRoles)
|
||||
.POST("/api/users/{id}/roles", userHandler::assignRoles)
|
||||
|
||||
// ========== 菜单路由 ==========
|
||||
.GET("/api/menus", menuHandler::getAllMenus)
|
||||
.GET("/api/menus/tree", menuHandler::getMenuTree)
|
||||
.GET("/api/menus/{id}", menuHandler::getMenuById)
|
||||
.POST("/api/menus", menuHandler::createMenu)
|
||||
.PUT("/api/menus/{id}", menuHandler::updateMenu)
|
||||
.DELETE("/api/menus/{id}", menuHandler::deleteMenu)
|
||||
|
||||
// ========== 角色路由 ==========
|
||||
.GET("/api/roles", roleHandler::getAllRoles)
|
||||
.GET("/api/roles/page", roleHandler::getRolesByPage)
|
||||
.GET("/api/roles/count", roleHandler::getRoleCount)
|
||||
.GET("/api/roles/name/{roleName}", roleHandler::getRoleByName)
|
||||
.GET("/api/roles/check-name", roleHandler::checkNameExists)
|
||||
.GET("/api/roles/{id}", roleHandler::getRoleById)
|
||||
.POST("/api/roles", roleHandler::createRole)
|
||||
.PUT("/api/roles/{id}", roleHandler::updateRole)
|
||||
.DELETE("/api/roles/{id}", roleHandler::deleteRole)
|
||||
.POST("/api/roles/{id}/restore", roleHandler::restoreRole)
|
||||
.GET("/api/roles/{id}/permissions", permissionHandler::getPermissionsByRoleId)
|
||||
.POST("/api/roles/{id}/permissions", permissionHandler::assignPermissionsToRole)
|
||||
|
||||
// ========== 配置路由 ==========
|
||||
.GET("/api/config", configHandler::getAllConfigs)
|
||||
.GET("/api/config/{id}", configHandler::getConfigById)
|
||||
.GET("/api/config/key/{configKey}", configHandler::getConfigByKey)
|
||||
.POST("/api/config", configHandler::createConfig)
|
||||
.PUT("/api/config/{id}", configHandler::updateConfig)
|
||||
.DELETE("/api/config/{id}", configHandler::deleteConfig)
|
||||
|
||||
// ========== 日志路由 ==========
|
||||
.GET("/api/logs/login", logHandler::getAllLoginLogs)
|
||||
.GET("/api/logs/login/page", logHandler::getLoginLogsByPage)
|
||||
.GET("/api/logs/login/count", logHandler::getLoginLogCount)
|
||||
.GET("/api/logs/login/today/count", logHandler::getTodayLoginCount)
|
||||
.GET("/api/logs/login/recent", logHandler::getRecentLoginLogs)
|
||||
.GET("/api/logs/login/{id}", logHandler::getLoginLogById)
|
||||
.POST("/api/logs/login", logHandler::createLoginLog)
|
||||
.GET("/api/logs/exception", logHandler::getAllExceptionLogs)
|
||||
.GET("/api/logs/exception/page", logHandler::getExceptionLogsByPage)
|
||||
.GET("/api/logs/exception/count", logHandler::getExceptionLogCount)
|
||||
.GET("/api/logs/exception/{id}", logHandler::getExceptionLogById)
|
||||
.POST("/api/logs/exception", logHandler::createExceptionLog)
|
||||
.GET("/api/logs/operation", operationLogHandler::getAllOperationLogs)
|
||||
.GET("/api/logs/operation/export", operationLogHandler::exportOperationLogs)
|
||||
.GET("/api/logs/operation/page", operationLogHandler::getOperationLogsByPage)
|
||||
.GET("/api/logs/operation/count", operationLogHandler::getOperationLogCount)
|
||||
.GET("/api/logs/operation/{id}", operationLogHandler::getOperationLogById)
|
||||
.POST("/api/logs/operation", operationLogHandler::createOperationLog)
|
||||
|
||||
// ========== 认证路由 ==========
|
||||
.POST("/api/auth/login", authHandler::login)
|
||||
.POST("/api/auth/register", authHandler::register)
|
||||
.POST("/api/auth/logout", authHandler::logout)
|
||||
|
||||
// ========== 统计路由 ==========
|
||||
.GET("/api/stats/overview", statsHandler::getOverview)
|
||||
|
||||
// ========== 数据字典路由 ==========
|
||||
.GET("/api/dict/types", dictHandler::getAllDictTypes)
|
||||
.GET("/api/dict/types/{id}", dictHandler::getDictTypeById)
|
||||
.GET("/api/dict/types/type/{dictType}", dictHandler::getDictTypeByType)
|
||||
.POST("/api/dict/types", dictHandler::createDictType)
|
||||
.PUT("/api/dict/types/{id}", dictHandler::updateDictType)
|
||||
.DELETE("/api/dict/types/{id}", dictHandler::deleteDictType)
|
||||
.GET("/api/dict/data", dictHandler::getAllDictData)
|
||||
.GET("/api/dict/data/type/{dictType}", dictHandler::getDictDataByType)
|
||||
.GET("/api/dict/data/{id}", dictHandler::getDictDataById)
|
||||
.POST("/api/dict/data", dictHandler::createDictData)
|
||||
.PUT("/api/dict/data/{id}", dictHandler::updateDictData)
|
||||
.DELETE("/api/dict/data/{id}", dictHandler::deleteDictData)
|
||||
|
||||
// ========== 公告路由 ==========
|
||||
.GET("/api/notices", noticeHandler::getAllNotices)
|
||||
.GET("/api/notices/{id}", noticeHandler::getNoticeById)
|
||||
.GET("/api/notices/status/{status}", noticeHandler::getNoticesByStatus)
|
||||
.POST("/api/notices", noticeHandler::createNotice)
|
||||
.PUT("/api/notices/{id}", noticeHandler::updateNotice)
|
||||
.DELETE("/api/notices/{id}", noticeHandler::deleteNotice)
|
||||
|
||||
// ========== 消息路由 ==========
|
||||
.GET("/api/messages/user/{userId}", messageHandler::getMessagesByUser)
|
||||
.GET("/api/messages/user/{userId}/unread", messageHandler::getUnreadCount)
|
||||
.GET("/api/messages/user/{userId}/unread/list", messageHandler::getUnreadList)
|
||||
.POST("/api/messages", messageHandler::createMessage)
|
||||
.PUT("/api/messages/{id}/read", messageHandler::markAsRead)
|
||||
.DELETE("/api/messages/{id}", messageHandler::deleteMessage)
|
||||
|
||||
// ========== 文件路由 ==========
|
||||
.GET("/api/files", fileHandler::getAllFiles)
|
||||
.GET("/api/files/{id}", fileHandler::getFileById)
|
||||
.POST("/api/files/upload", fileHandler::uploadFile)
|
||||
.GET("/api/files/{id}/download", fileHandler::downloadFile)
|
||||
.GET("/api/files/download/{fileName}", fileHandler::downloadFileByName)
|
||||
.GET("/api/files/{id}/preview", fileHandler::previewFile)
|
||||
.GET("/api/files/preview/{fileName}", fileHandler::previewFileByName)
|
||||
.DELETE("/api/files/{id}", fileHandler::deleteFile)
|
||||
|
||||
// ========== 权限路由 ==========
|
||||
.GET("/api/permissions", permissionHandler::getAllPermissions)
|
||||
.GET("/api/permissions/{id}", permissionHandler::getPermissionById)
|
||||
.GET("/api/permissions/code/{code}", permissionHandler::getPermissionByCode)
|
||||
.GET("/api/permissions/check-code", permissionHandler::checkCodeExists)
|
||||
.GET("/api/permissions/count", permissionHandler::getPermissionCount)
|
||||
.POST("/api/permissions", permissionHandler::createPermission)
|
||||
.PUT("/api/permissions/{id}", permissionHandler::updatePermission)
|
||||
.DELETE("/api/permissions/{id}", permissionHandler::deletePermission)
|
||||
|
||||
.build();
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package cn.novalon.gym.manage.app.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.codec.ServerCodecConfigurer;
|
||||
import org.springframework.web.reactive.config.WebFluxConfigurer;
|
||||
|
||||
/**
|
||||
* WebFlux配置类
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-14
|
||||
*/
|
||||
@Configuration
|
||||
public class WebFluxConfig implements WebFluxConfigurer {
|
||||
|
||||
@Override
|
||||
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
|
||||
configurer.defaultCodecs().maxInMemorySize(16 * 1024 * 1024);
|
||||
}
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
cn.novalon.manage.app.config.OpenApiConfig
|
||||
cn.novalon.manage.app.config.WebFluxConfig
|
||||
cn.novalon.manage.app.config.SystemRouter
|
||||
cn.novalon.manage.app.config.MultipartConfig
|
||||
cn.novalon.manage.app.config.RateLimitConfig
|
||||
@@ -0,0 +1,22 @@
|
||||
spring:
|
||||
r2dbc:
|
||||
url: r2dbc:postgresql://localhost:55432/manage_system
|
||||
username: novalon
|
||||
password: novalon123
|
||||
flyway:
|
||||
enabled: true
|
||||
locations: classpath:db/migration
|
||||
baseline-on-migrate: true
|
||||
validate-on-migrate: true
|
||||
|
||||
rate:
|
||||
limit:
|
||||
limit-for-period: 10000
|
||||
limit-refresh-period: 1s
|
||||
timeout-duration: 0
|
||||
|
||||
logging:
|
||||
level:
|
||||
cn.novalon.manage: DEBUG
|
||||
org.springframework.r2dbc: DEBUG
|
||||
org.springframework.web: TRACE
|
||||
@@ -0,0 +1,36 @@
|
||||
# 本地开发环境配置
|
||||
spring:
|
||||
config:
|
||||
activate:
|
||||
on-profile: local
|
||||
r2dbc:
|
||||
url: r2dbc:postgresql://localhost:55432/manage_system
|
||||
username: novalon
|
||||
password: novalon123
|
||||
pool:
|
||||
initial-size: 5
|
||||
max-size: 20
|
||||
max-idle-time: 10m
|
||||
max-life-time: 30m
|
||||
acquire-timeout: 3s
|
||||
datasource:
|
||||
url: jdbc:postgresql://localhost:55432/manage_system
|
||||
username: novalon
|
||||
password: novalon123
|
||||
driver-class-name: org.postgresql.Driver
|
||||
flyway:
|
||||
enabled: true
|
||||
locations: classpath:db/migration
|
||||
baseline-on-migrate: true
|
||||
baseline-version: 0
|
||||
validate-on-migrate: true
|
||||
sql:
|
||||
init:
|
||||
mode: always
|
||||
|
||||
logging:
|
||||
level:
|
||||
cn.novalon.manage: DEBUG
|
||||
org.springframework.r2dbc: DEBUG
|
||||
cn.novalon.manage.db: DEBUG
|
||||
org.flywaydb: DEBUG
|
||||
@@ -0,0 +1,17 @@
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: health,info,metrics
|
||||
endpoint:
|
||||
health:
|
||||
show-details: always
|
||||
metrics:
|
||||
enabled: true
|
||||
info:
|
||||
env:
|
||||
enabled: true
|
||||
metrics:
|
||||
export:
|
||||
simple:
|
||||
enabled: true
|
||||
@@ -0,0 +1,12 @@
|
||||
spring:
|
||||
r2dbc:
|
||||
url: r2dbc:postgresql://postgres:5432/novalon_manage
|
||||
username: ${DB_USERNAME}
|
||||
password: ${DB_PASSWORD}
|
||||
flyway:
|
||||
enabled: true
|
||||
|
||||
logging:
|
||||
level:
|
||||
cn.novalon.manage: INFO
|
||||
org.springframework.r2dbc: INFO
|
||||
@@ -0,0 +1,62 @@
|
||||
server:
|
||||
port: 8084
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: manage-app
|
||||
r2dbc:
|
||||
url: r2dbc:postgresql://localhost:55432/manage_system
|
||||
username: novalon
|
||||
password: novalon123
|
||||
pool:
|
||||
initial-size: 5
|
||||
max-size: 20
|
||||
max-idle-time: 30m
|
||||
max-life-time: 1h
|
||||
acquire-timeout: 5s
|
||||
flyway:
|
||||
enabled: true
|
||||
locations: classpath:db/migration
|
||||
baseline-on-migrate: true
|
||||
validate-on-migrate: true
|
||||
sql:
|
||||
init:
|
||||
mode: never
|
||||
security:
|
||||
user:
|
||||
name: disabled
|
||||
password: disabled
|
||||
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: health,info,metrics,env,loggers
|
||||
base-path: /actuator
|
||||
endpoint:
|
||||
health:
|
||||
show-details: always
|
||||
metrics:
|
||||
tags:
|
||||
application: ${spring.application.name}
|
||||
environment: ${spring.profiles.active}
|
||||
|
||||
logging:
|
||||
level:
|
||||
cn.novalon.manage: DEBUG
|
||||
org.springframework.r2dbc: DEBUG
|
||||
cn.novalon.manage.db: DEBUG
|
||||
org.flywaydb: INFO
|
||||
|
||||
springdoc:
|
||||
api-docs:
|
||||
path: /api-docs
|
||||
enabled: true
|
||||
swagger-ui:
|
||||
path: /swagger-ui.html
|
||||
enabled: true
|
||||
tags-sorter: alpha
|
||||
operations-sorter: alpha
|
||||
show-actuator: false
|
||||
default-consumes-media-type: application/json
|
||||
default-produces-media-type: application/json
|
||||
@@ -0,0 +1,68 @@
|
||||
server:
|
||||
port: 8084
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: gym-manage-api
|
||||
r2dbc:
|
||||
url: r2dbc:postgresql://${DB_HOST:localhost}:${DB_PORT:55432}/${DB_NAME:manage_system}
|
||||
username: ${DB_USERNAME:postgres}
|
||||
password: ${DB_PASSWORD:postgres}
|
||||
pool:
|
||||
initial-size: 10
|
||||
max-size: 50
|
||||
max-idle-time: 30m
|
||||
max-life-time: 1h
|
||||
acquire-timeout: 5s
|
||||
datasource:
|
||||
url: jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:55432}/${DB_NAME:manage_system}
|
||||
username: ${DB_USERNAME:postgres}
|
||||
password: ${DB_PASSWORD:postgres}
|
||||
driver-class-name: org.postgresql.Driver
|
||||
flyway:
|
||||
enabled: true
|
||||
locations: classpath:db/migration
|
||||
baseline-on-migrate: true
|
||||
baseline-version: 0
|
||||
validate-on-migrate: true
|
||||
security:
|
||||
user:
|
||||
name: disabled
|
||||
password: disabled
|
||||
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: health,info,metrics,env,loggers
|
||||
base-path: /actuator
|
||||
endpoint:
|
||||
health:
|
||||
show-details: always
|
||||
metrics:
|
||||
tags:
|
||||
application: ${spring.application.name}
|
||||
environment: ${spring.profiles.active}
|
||||
|
||||
logging:
|
||||
level:
|
||||
cn.novalon.manage: DEBUG
|
||||
org.springframework.r2dbc: DEBUG
|
||||
cn.novalon.manage.db: DEBUG
|
||||
|
||||
jwt:
|
||||
secret: ${JWT_SECRET:U2FsdGVkX1+vZ5Y9QmKxL8nN3rP7tW2jH4fG6dA8sB1cE5yN0zX3qV7wM4}
|
||||
expiration: ${JWT_EXPIRATION:86400000}
|
||||
|
||||
springdoc:
|
||||
api-docs:
|
||||
path: /api-docs
|
||||
enabled: true
|
||||
swagger-ui:
|
||||
path: /swagger-ui.html
|
||||
enabled: true
|
||||
tags-sorter: alpha
|
||||
operations-sorter: alpha
|
||||
show-actuator: false
|
||||
default-consumes-media-type: application/json
|
||||
default-produces-media-type: application/json
|
||||
@@ -0,0 +1,30 @@
|
||||
╔═══════════════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ██╗ ██████╗ ███╗ ██╗ ║
|
||||
║ ████╗ ██║██╔═══██╗██║ ██║██╔══██╗██║ ██╔═══██╗████╗ ██║ ║
|
||||
║ ██╔██╗ ██║██║ ██║██║ ██║███████║██║ ██║ ██║██╔██╗ ██║ ║
|
||||
║ ██║╚██╗██║██║ ██║╚██╗ ██╔╝██╔══██║██║ ██║ ██║██║╚██╗██║ ║
|
||||
║ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║███████╗╚██████╔╝██║ ╚████║ ║
|
||||
║ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═══╝ ║
|
||||
║ ║
|
||||
║ ███╗ ███╗ █████╗ ███╗ ██╗ █████╗ ██████╗ ███████╗ ║
|
||||
║ ████╗ ████║██╔══██╗████╗ ██║██╔══██╗██╔════╝ ██╔════╝ ║
|
||||
║ ██╔████╔██║███████║██╔██╗ ██║███████║██║ ███╗█████╗ ║
|
||||
║ ██║╚██╔╝██║██╔══██║██║╚██╗██║██╔══██║██║ ██║██╔══╝ ║
|
||||
║ ██║ ╚═╝ ██║██║ ██║██║ ╚████║██║ ██║╚██████╔╝███████╗ ║
|
||||
║ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝ ║
|
||||
║ ║
|
||||
║ ███████╗██╗ ██╗███████╗████████╗███████╗███╗ ███╗ ║
|
||||
║ ██╔════╝╚██╗ ██╔╝██╔════╝╚══██╔══╝██╔════╝████╗ ████║ ║
|
||||
║ ███████╗ ╚████╔╝ ███████╗ ██║ █████╗ ██╔████╔██║ ║
|
||||
║ ╚════██║ ╚██╔╝ ╚════██║ ██║ ██╔══╝ ██║╚██╔╝██║ ║
|
||||
║ ███████║ ██║ ███████║ ██║ ███████╗██║ ╚═╝ ██║ ║
|
||||
║ ╚══════╝ ╚═╝ ╚══════╝ ╚═╝ ╚══════╝╚═╝ ╚═╝ ║
|
||||
║ ║
|
||||
╚═══════════════════════════════════════════════════════════════════╝
|
||||
|
||||
:: Novalon Manage System ::
|
||||
Version: ${application.version:Unknown}
|
||||
Spring Boot: ${spring-boot.version}
|
||||
Java: ${java.version}
|
||||
PID: ${PID}
|
||||
@@ -0,0 +1,84 @@
|
||||
-- H2数据库测试数据
|
||||
-- 用于测试环境
|
||||
|
||||
-- 插入测试角色
|
||||
MERGE INTO sys_role (id, role_name, role_key, role_sort, status, create_by, update_by)
|
||||
KEY(id)
|
||||
VALUES
|
||||
(1, '超级管理员', 'admin', 1, 1, 'system', 'system'),
|
||||
(2, '测试管理员', 'test_admin', 2, 1, 'system', 'system'),
|
||||
(3, '普通用户', 'normal_user', 3, 1, 'system', 'system'),
|
||||
(4, '访客', 'guest', 4, 1, 'system', 'system');
|
||||
|
||||
-- 插入测试用户
|
||||
-- BCrypt哈希值对应明文密码: Test@123
|
||||
MERGE INTO sys_user (id, username, password, email, phone, nickname, status, create_by, update_by)
|
||||
KEY(id)
|
||||
VALUES
|
||||
(1, 'admin', '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C', 'admin@novalon.com', '13800138000', '超级管理员', 1, 'system', 'system'),
|
||||
(2, 'testadmin', '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C', 'testadmin@novalon.com', '13800138001', '测试管理员', 1, 'system', 'system'),
|
||||
(3, 'normaluser', '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C', 'normaluser@novalon.com', '13800138002', '普通用户', 1, 'system', 'system'),
|
||||
(4, 'guestuser', '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C', 'guestuser@novalon.com', '13800138003', '访客用户', 1, 'system', 'system'),
|
||||
(5, 'disableduser', '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C', 'disableduser@novalon.com', '13800138004', '禁用用户', 0, 'system', 'system'),
|
||||
(10, 'e2e_test_user', '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C', 'e2e@test.com', '13900139000', 'E2E测试用户', 1, 'system', 'system');
|
||||
|
||||
-- 为用户分配角色
|
||||
INSERT INTO user_role (user_id, role_id, created_by)
|
||||
VALUES
|
||||
(1, 1, 'system'),
|
||||
(2, 2, 'system'),
|
||||
(3, 3, 'system'),
|
||||
(4, 4, 'system'),
|
||||
(10, 1, 'system');
|
||||
|
||||
-- 插入测试菜单
|
||||
INSERT INTO sys_menu (id, menu_name, parent_id, order_num, path, component, menu_type, visible, status, perms, icon, create_by, update_by)
|
||||
VALUES
|
||||
(1, '系统管理', 0, 1, '/system', 'Layout', 'M', '1', '1', '', 'system', 'system', 'system'),
|
||||
(2, '用户管理', 1, 1, 'user', 'system/user/index', 'C', '1', '1', 'system:user:list', 'user', 'system', 'system'),
|
||||
(3, '角色管理', 1, 2, 'role', 'system/role/index', 'C', '1', '1', 'system:role:list', 'role', 'system', 'system'),
|
||||
(4, '菜单管理', 1, 3, 'menu', 'system/menu/index', 'C', '1', '1', 'system:menu:list', 'menu', 'system', 'system'),
|
||||
(5, '测试菜单', 0, 99, '/test', 'Layout', 'M', '1', '1', '', 'test', 'system', 'system'),
|
||||
(6, '用户测试', 5, 1, 'user-test', 'system/user-test/index', 'C', '1', '1', 'system:user:test', 'user', 'system', 'system');
|
||||
|
||||
-- 插入测试权限
|
||||
INSERT INTO sys_permission (id, permission_name, permission_code, resource, action, description, status, create_by, update_by)
|
||||
VALUES
|
||||
(1, '系统管理', 'system:manage', '/api/system', 'GET', '系统管理权限', 1, 'system', 'system'),
|
||||
(2, '用户管理', 'system:user:manage', '/api/users', 'GET', '用户管理权限', 1, 'system', 'system'),
|
||||
(3, '用户查询', 'system:user:list', '/api/users', 'GET', '用户查询权限', 1, 'system', 'system'),
|
||||
(4, '用户新增', 'system:user:add', '/api/users', 'POST', '用户新增权限', 1, 'system', 'system'),
|
||||
(5, '用户编辑', 'system:user:edit', '/api/users', 'PUT', '用户编辑权限', 1, 'system', 'system'),
|
||||
(6, '用户删除', 'system:user:delete', '/api/users', 'DELETE', '用户删除权限', 1, 'system', 'system'),
|
||||
(7, '测试权限', 'test:permission', '/api/test', 'GET', '测试权限', 1, 'system', 'system'),
|
||||
(8, '用户测试权限', 'system:user:test', '/api/users/test', 'GET', '用户测试权限', 1, 'system', 'system');
|
||||
|
||||
-- 为角色分配权限
|
||||
INSERT INTO sys_role_permission (role_id, permission_id, created_by, updated_by)
|
||||
SELECT 1, id, 'system', 'system' FROM sys_permission
|
||||
UNION ALL
|
||||
SELECT 2, id, 'system', 'system' FROM sys_permission WHERE id IN (7, 8);
|
||||
|
||||
-- 插入字典类型
|
||||
INSERT INTO sys_dict_type (id, dict_name, dict_type, status, remark, create_by, update_by)
|
||||
VALUES
|
||||
(1, '用户状态', 'user_status', '0', '用户状态列表', 'system', 'system'),
|
||||
(2, '菜单状态', 'menu_status', '0', '菜单状态列表', 'system', 'system'),
|
||||
(3, '角色状态', 'role_status', '0', '角色状态列表', 'system', 'system');
|
||||
|
||||
-- 插入字典数据
|
||||
INSERT INTO sys_dict_data (id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, update_by)
|
||||
VALUES
|
||||
(1, 1, '正常', '1', 'user_status', '', 'primary', 'Y', '0', 'system', 'system'),
|
||||
(2, 2, '停用', '0', 'user_status', '', 'danger', 'N', '0', 'system', 'system'),
|
||||
(3, 1, '正常', '0', 'menu_status', '', 'primary', 'Y', '0', 'system', 'system'),
|
||||
(4, 2, '停用', '1', 'menu_status', '', 'danger', 'N', '0', 'system', 'system'),
|
||||
(5, 1, '正常', '0', 'role_status', '', 'primary', 'Y', '0', 'system', 'system'),
|
||||
(6, 2, '停用', '1', 'role_status', '', 'danger', 'N', '0', 'system', 'system');
|
||||
|
||||
-- 插入系统配置
|
||||
INSERT INTO sys_config (id, config_name, config_key, config_value, config_type, create_by, update_by)
|
||||
VALUES
|
||||
(1, '用户管理-用户初始密码', 'sys.user.initPassword', '123456', 'Y', 'system', 'system'),
|
||||
(2, '主框架页-默认皮肤样式名称', 'sys.index.skinName', 'skin-blue', 'Y', 'system', 'system'),
|
||||
(3, '用户自助-验证码开关', 'sys.account.captchaEnabled', 'true', 'Y', 'system', 'system');
|
||||
@@ -0,0 +1,253 @@
|
||||
-- H2数据库Schema for Integration Testing
|
||||
-- Create用户表
|
||||
CREATE TABLE IF NOT EXISTS sys_user (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
username VARCHAR(50) NOT NULL UNIQUE,
|
||||
password VARCHAR(255) NOT NULL,
|
||||
email VARCHAR(100),
|
||||
phone VARCHAR(20),
|
||||
nickname VARCHAR(100),
|
||||
role_id BIGINT,
|
||||
status INTEGER DEFAULT 1,
|
||||
create_by VARCHAR(50),
|
||||
update_by VARCHAR(50),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create角色表
|
||||
CREATE TABLE IF NOT EXISTS sys_role (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
role_name VARCHAR(100) NOT NULL,
|
||||
role_key VARCHAR(100) NOT NULL UNIQUE,
|
||||
role_sort INTEGER DEFAULT 0,
|
||||
status INTEGER DEFAULT 1,
|
||||
create_by VARCHAR(50),
|
||||
update_by VARCHAR(50),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create用户角色关联表
|
||||
CREATE TABLE IF NOT EXISTS user_role (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL,
|
||||
role_id BIGINT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
created_by VARCHAR(50),
|
||||
CONSTRAINT fk_user_role_user FOREIGN KEY (user_id) REFERENCES sys_user(id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_user_role_role FOREIGN KEY (role_id) REFERENCES sys_role(id) ON DELETE CASCADE,
|
||||
CONSTRAINT uk_user_role UNIQUE (user_id, role_id)
|
||||
);
|
||||
|
||||
-- Create菜单表
|
||||
CREATE TABLE IF NOT EXISTS sys_menu (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
menu_name VARCHAR(50) NOT NULL,
|
||||
parent_id BIGINT DEFAULT 0,
|
||||
order_num INTEGER DEFAULT 0,
|
||||
path VARCHAR(200),
|
||||
component VARCHAR(200),
|
||||
menu_type VARCHAR(1) DEFAULT 'C',
|
||||
visible VARCHAR(1) DEFAULT '1',
|
||||
status VARCHAR(1) DEFAULT '1',
|
||||
perms VARCHAR(100),
|
||||
icon VARCHAR(100),
|
||||
create_by VARCHAR(50),
|
||||
update_by VARCHAR(50),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create权限表
|
||||
CREATE TABLE IF NOT EXISTS sys_permission (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
permission_name VARCHAR(100) NOT NULL,
|
||||
permission_code VARCHAR(100) NOT NULL UNIQUE,
|
||||
resource VARCHAR(200),
|
||||
action VARCHAR(20),
|
||||
description VARCHAR(500),
|
||||
status INTEGER DEFAULT 1,
|
||||
create_by VARCHAR(50),
|
||||
update_by VARCHAR(50),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create角色权限关联表
|
||||
CREATE TABLE IF NOT EXISTS sys_role_permission (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
role_id BIGINT NOT NULL,
|
||||
permission_id BIGINT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
created_by VARCHAR(50),
|
||||
updated_by VARCHAR(50),
|
||||
CONSTRAINT fk_role_permission_role FOREIGN KEY (role_id) REFERENCES sys_role(id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_role_permission_permission FOREIGN KEY (permission_id) REFERENCES sys_permission(id) ON DELETE CASCADE,
|
||||
CONSTRAINT uk_role_permission UNIQUE (role_id, permission_id)
|
||||
);
|
||||
|
||||
-- Create字典类型表
|
||||
CREATE TABLE IF NOT EXISTS sys_dict_type (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
dict_name VARCHAR(100) NOT NULL,
|
||||
dict_type VARCHAR(100) NOT NULL UNIQUE,
|
||||
status VARCHAR(1) DEFAULT '0',
|
||||
remark VARCHAR(500),
|
||||
create_by VARCHAR(50),
|
||||
update_by VARCHAR(50),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create字典数据表
|
||||
CREATE TABLE IF NOT EXISTS sys_dict_data (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
dict_sort INTEGER DEFAULT 0,
|
||||
dict_label VARCHAR(100) NOT NULL,
|
||||
dict_value VARCHAR(100) NOT NULL,
|
||||
dict_type VARCHAR(100) NOT NULL,
|
||||
css_class VARCHAR(100),
|
||||
list_class VARCHAR(100),
|
||||
is_default VARCHAR(1) DEFAULT 'N',
|
||||
status VARCHAR(1) DEFAULT '0',
|
||||
create_by VARCHAR(50),
|
||||
update_by VARCHAR(50),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create字典表(通用字典)
|
||||
CREATE TABLE IF NOT EXISTS sys_dictionary (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
type VARCHAR(100) NOT NULL,
|
||||
code VARCHAR(100) NOT NULL,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
dict_value VARCHAR(500),
|
||||
remark VARCHAR(500),
|
||||
sort INTEGER DEFAULT 0,
|
||||
create_by VARCHAR(50),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create系统配置表
|
||||
CREATE TABLE IF NOT EXISTS sys_config (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
config_name VARCHAR(100) NOT NULL,
|
||||
config_key VARCHAR(100) NOT NULL UNIQUE,
|
||||
config_value VARCHAR(500) NOT NULL,
|
||||
config_type VARCHAR(1) DEFAULT 'N',
|
||||
create_by VARCHAR(50),
|
||||
update_by VARCHAR(50),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create登录日志表
|
||||
CREATE TABLE IF NOT EXISTS sys_login_log (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
username VARCHAR(50),
|
||||
ip VARCHAR(50),
|
||||
location VARCHAR(255),
|
||||
browser VARCHAR(50),
|
||||
os VARCHAR(50),
|
||||
status VARCHAR(1),
|
||||
message VARCHAR(255),
|
||||
login_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create异常日志表
|
||||
CREATE TABLE IF NOT EXISTS sys_exception_log (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
username VARCHAR(50),
|
||||
title VARCHAR(100),
|
||||
exception_name VARCHAR(100),
|
||||
method_name VARCHAR(255),
|
||||
method_params TEXT,
|
||||
exception_msg TEXT,
|
||||
exception_stack TEXT,
|
||||
ip VARCHAR(50),
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create操作日志表
|
||||
CREATE TABLE IF NOT EXISTS operation_log (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
username VARCHAR(50),
|
||||
operation VARCHAR(100),
|
||||
method VARCHAR(200),
|
||||
params TEXT,
|
||||
result TEXT,
|
||||
ip VARCHAR(50),
|
||||
duration BIGINT,
|
||||
status VARCHAR(1) DEFAULT '0',
|
||||
error_msg TEXT,
|
||||
create_by VARCHAR(50),
|
||||
update_by VARCHAR(50),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create系统公告表
|
||||
CREATE TABLE IF NOT EXISTS sys_notice (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
notice_title VARCHAR(50) NOT NULL,
|
||||
notice_type VARCHAR(1) NOT NULL,
|
||||
notice_content TEXT,
|
||||
status VARCHAR(1) DEFAULT '0',
|
||||
create_by VARCHAR(50),
|
||||
update_by VARCHAR(50),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create用户消息表
|
||||
CREATE TABLE IF NOT EXISTS sys_user_message (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL,
|
||||
notice_id BIGINT,
|
||||
message_title VARCHAR(255),
|
||||
message_content TEXT,
|
||||
is_read VARCHAR(1) DEFAULT '0',
|
||||
read_time TIMESTAMP,
|
||||
create_by VARCHAR(50),
|
||||
update_by VARCHAR(50),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create文件管理表
|
||||
CREATE TABLE IF NOT EXISTS sys_file (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
file_name VARCHAR(255) NOT NULL,
|
||||
file_path VARCHAR(500) NOT NULL,
|
||||
file_size BIGINT,
|
||||
file_type VARCHAR(100),
|
||||
file_extension VARCHAR(10),
|
||||
storage_type VARCHAR(50),
|
||||
create_by VARCHAR(50),
|
||||
update_by VARCHAR(50),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create索引
|
||||
CREATE INDEX IF NOT EXISTS idx_user_role_user_id ON user_role(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_user_role_role_id ON user_role(role_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_sys_menu_parent_id ON sys_menu(parent_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_sys_dict_type ON sys_dict_data(dict_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_sys_login_log_username ON sys_login_log(username);
|
||||
CREATE INDEX IF NOT EXISTS idx_operation_log_username ON operation_log(username);
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package cn.novalon.gym.manage.app.config;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.http.codec.multipart.MultipartHttpMessageReader;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class MultipartConfigTest {
|
||||
|
||||
private MultipartConfig multipartConfig;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
multipartConfig = new MultipartConfig();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMultipartConfig() {
|
||||
assertThat(multipartConfig).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMultipartHttpMessageReader() {
|
||||
MultipartHttpMessageReader reader = multipartConfig.multipartHttpMessageReader();
|
||||
|
||||
assertThat(reader).isNotNull();
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package cn.novalon.gym.manage.app.config;
|
||||
|
||||
import io.github.resilience4j.ratelimiter.RateLimiter;
|
||||
import io.github.resilience4j.ratelimiter.RateLimiterRegistry;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.time.Duration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class RateLimitConfigTest {
|
||||
|
||||
@Test
|
||||
void testRateLimiterRegistry() throws Exception {
|
||||
RateLimitConfig rateLimitConfig = new RateLimitConfig();
|
||||
|
||||
setField(rateLimitConfig, "limitForPeriod", 100);
|
||||
setField(rateLimitConfig, "limitRefreshPeriod", Duration.ofSeconds(1));
|
||||
setField(rateLimitConfig, "timeoutDuration", Duration.ZERO);
|
||||
|
||||
RateLimiterRegistry registry = rateLimitConfig.rateLimiterRegistry();
|
||||
|
||||
assertThat(registry).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testApiRateLimiter() throws Exception {
|
||||
RateLimitConfig rateLimitConfig = new RateLimitConfig();
|
||||
|
||||
setField(rateLimitConfig, "limitForPeriod", 100);
|
||||
setField(rateLimitConfig, "limitRefreshPeriod", Duration.ofSeconds(1));
|
||||
setField(rateLimitConfig, "timeoutDuration", Duration.ZERO);
|
||||
|
||||
RateLimiterRegistry registry = rateLimitConfig.rateLimiterRegistry();
|
||||
RateLimiter rateLimiter = rateLimitConfig.apiRateLimiter(registry);
|
||||
|
||||
assertThat(rateLimiter).isNotNull();
|
||||
assertThat(rateLimiter.getName()).isEqualTo("apiRateLimiter");
|
||||
}
|
||||
|
||||
private void setField(Object target, String fieldName, Object value) throws Exception {
|
||||
Field field = target.getClass().getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
field.set(target, value);
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
package cn.novalon.gym.manage.app.integration;
|
||||
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* 数据库初始化验证测试
|
||||
*
|
||||
* 注意:此测试需要完整的数据库初始化,暂时禁用。
|
||||
* TODO: 修复数据库初始化问题
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-04-03
|
||||
*/
|
||||
@Disabled("暂时禁用:数据库初始化问题需要修复")
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
@ActiveProfiles("test")
|
||||
class DatabaseInitTest {
|
||||
|
||||
@Autowired
|
||||
private R2dbcEntityTemplate r2dbcEntityTemplate;
|
||||
|
||||
@Test
|
||||
void testSysUserTableExists() {
|
||||
r2dbcEntityTemplate.getDatabaseClient()
|
||||
.sql("SELECT COUNT(*) FROM sys_user")
|
||||
.fetch()
|
||||
.one()
|
||||
.as(StepVerifier::create)
|
||||
.expectNextCount(1)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOperationLogTableExists() {
|
||||
r2dbcEntityTemplate.getDatabaseClient()
|
||||
.sql("SELECT COUNT(*) FROM operation_log")
|
||||
.fetch()
|
||||
.one()
|
||||
.as(StepVerifier::create)
|
||||
.expectNextCount(1)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAllTablesCreated() {
|
||||
r2dbcEntityTemplate.getDatabaseClient()
|
||||
.sql("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'PUBLIC'")
|
||||
.fetch()
|
||||
.all()
|
||||
.map(row -> row.get("TABLE_NAME"))
|
||||
.collectList()
|
||||
.as(StepVerifier::create)
|
||||
.assertNext(tables -> {
|
||||
System.out.println("Created tables: " + tables);
|
||||
assert tables.contains("SYS_USER") : "SYS_USER table not found";
|
||||
assert tables.contains("OPERATION_LOG") : "OPERATION_LOG table not found";
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
package cn.novalon.gym.manage.app.integration;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
/**
|
||||
* 手动创建表测试
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-04-03
|
||||
*/
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
@ActiveProfiles("test")
|
||||
class ManualTableCreationTest {
|
||||
|
||||
@Autowired
|
||||
private R2dbcEntityTemplate r2dbcEntityTemplate;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
r2dbcEntityTemplate.getDatabaseClient()
|
||||
.sql("CREATE TABLE IF NOT EXISTS operation_log (" +
|
||||
"id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
|
||||
"username VARCHAR(50), " +
|
||||
"operation VARCHAR(100), " +
|
||||
"method VARCHAR(200), " +
|
||||
"params TEXT, " +
|
||||
"result TEXT, " +
|
||||
"ip VARCHAR(50), " +
|
||||
"duration BIGINT, " +
|
||||
"status VARCHAR(1) DEFAULT '0', " +
|
||||
"error_msg TEXT, " +
|
||||
"create_by VARCHAR(50), " +
|
||||
"update_by VARCHAR(50), " +
|
||||
"created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, " +
|
||||
"updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, " +
|
||||
"deleted_at TIMESTAMP)")
|
||||
.then()
|
||||
.as(StepVerifier::create)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOperationLogTableExists() {
|
||||
r2dbcEntityTemplate.getDatabaseClient()
|
||||
.sql("SELECT COUNT(*) FROM operation_log")
|
||||
.fetch()
|
||||
.one()
|
||||
.as(StepVerifier::create)
|
||||
.expectNextCount(1)
|
||||
.verifyComplete();
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
package cn.novalon.gym.manage.app.integration;
|
||||
|
||||
import cn.novalon.gym.manage.app.ManageApplication;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.test.context.support.WithMockUser;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
/**
|
||||
* 操作日志导出功能集成测试
|
||||
*
|
||||
* 注意:此测试存在超时问题,暂时禁用。
|
||||
* TODO: 修复Excel导出的超时问题
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-04-03
|
||||
*/
|
||||
@Disabled("暂时禁用:Excel导出功能存在超时问题,需要优化")
|
||||
@SpringBootTest(
|
||||
classes = ManageApplication.class,
|
||||
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT
|
||||
)
|
||||
@ActiveProfiles("test")
|
||||
class OperationLogExportIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
private WebTestClient webTestClient;
|
||||
|
||||
@Test
|
||||
@WithMockUser(username = "admin", roles = {"ADMIN"})
|
||||
void testExportOperationLogs_ShouldReturnExcelFile() {
|
||||
webTestClient.get()
|
||||
.uri("/api/logs/operation/export")
|
||||
.accept(MediaType.APPLICATION_OCTET_STREAM)
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectHeader().contentType(MediaType.APPLICATION_OCTET_STREAM)
|
||||
.expectHeader().valueMatches("Content-Disposition", "attachment; filename=\"operation_logs_.*\\.xlsx\"")
|
||||
.expectBody(byte[].class)
|
||||
.value(bytes -> {
|
||||
assert bytes != null;
|
||||
assert bytes.length > 0;
|
||||
assert bytes[0] == 0x50;
|
||||
assert bytes[1] == 0x4B;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(username = "admin", roles = {"ADMIN"})
|
||||
void testExportOperationLogsWithKeyword_ShouldReturnFilteredExcel() {
|
||||
webTestClient.get()
|
||||
.uri(uriBuilder -> uriBuilder
|
||||
.path("/api/logs/operation/export")
|
||||
.queryParam("keyword", "test")
|
||||
.build())
|
||||
.accept(MediaType.APPLICATION_OCTET_STREAM)
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectHeader().contentType(MediaType.APPLICATION_OCTET_STREAM)
|
||||
.expectBody(byte[].class)
|
||||
.value(bytes -> {
|
||||
assert bytes != null;
|
||||
assert bytes.length > 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
package cn.novalon.gym.manage.app.integration;
|
||||
|
||||
import cn.novalon.gym.manage.sys.core.domain.OperationLog;
|
||||
import cn.novalon.gym.manage.sys.core.service.IOperationLogService;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.test.context.support.WithMockUser;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* 操作日志集成测试
|
||||
*
|
||||
* 注意:此测试需要完整的Spring上下文,暂时禁用。
|
||||
* TODO: 优化集成测试配置
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-04-03
|
||||
*/
|
||||
@Disabled("暂时禁用:集成测试配置需要优化")
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
@ActiveProfiles("test")
|
||||
class OperationLogIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
private WebTestClient webTestClient;
|
||||
|
||||
@Autowired
|
||||
private IOperationLogService logService;
|
||||
|
||||
@Autowired
|
||||
private R2dbcEntityTemplate r2dbcEntityTemplate;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
webTestClient = webTestClient.mutate()
|
||||
.responseTimeout(Duration.ofSeconds(10))
|
||||
.build();
|
||||
|
||||
r2dbcEntityTemplate.getDatabaseClient()
|
||||
.sql("CREATE TABLE IF NOT EXISTS operation_log (" +
|
||||
"id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
|
||||
"username VARCHAR(50), " +
|
||||
"operation VARCHAR(100), " +
|
||||
"method VARCHAR(200), " +
|
||||
"params TEXT, " +
|
||||
"result TEXT, " +
|
||||
"ip VARCHAR(50), " +
|
||||
"duration BIGINT, " +
|
||||
"status VARCHAR(1) DEFAULT '0', " +
|
||||
"error_msg TEXT, " +
|
||||
"create_by VARCHAR(50), " +
|
||||
"update_by VARCHAR(50), " +
|
||||
"created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, " +
|
||||
"updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, " +
|
||||
"deleted_at TIMESTAMP)")
|
||||
.then()
|
||||
.as(StepVerifier::create)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(username = "test_user", roles = {"admin"})
|
||||
void testCreateUserOperation_ShouldLogOperation() {
|
||||
String userJson = """
|
||||
{
|
||||
"username": "test_integration_user",
|
||||
"password": "Test123!@#",
|
||||
"email": "test@example.com",
|
||||
"phone": "13900139000",
|
||||
"nickname": "集成测试用户"
|
||||
}
|
||||
""";
|
||||
|
||||
webTestClient.post()
|
||||
.uri("/api/users")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.bodyValue(userJson)
|
||||
.exchange()
|
||||
.expectStatus().isCreated()
|
||||
.expectBody()
|
||||
.jsonPath("$.id").exists()
|
||||
.jsonPath("$.username").isEqualTo("test_integration_user");
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(username = "test_user", roles = {"admin"})
|
||||
void testDeleteUserOperation_ShouldLogOperation() {
|
||||
String userJson = """
|
||||
{
|
||||
"username": "test_delete_user",
|
||||
"password": "Test123!@#",
|
||||
"email": "delete@example.com",
|
||||
"phone": "13900139001",
|
||||
"nickname": "待删除用户"
|
||||
}
|
||||
""";
|
||||
|
||||
webTestClient.post()
|
||||
.uri("/api/users")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.bodyValue(userJson)
|
||||
.exchange()
|
||||
.expectStatus().isCreated()
|
||||
.expectBody()
|
||||
.jsonPath("$.id").value(id -> {
|
||||
Long userId = Long.valueOf(id.toString());
|
||||
|
||||
webTestClient.delete()
|
||||
.uri("/api/users/{id}", userId)
|
||||
.exchange()
|
||||
.expectStatus().isNoContent();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(username = "test_user", roles = {"admin"})
|
||||
void testFailedOperation_ShouldLogError() {
|
||||
String userJson = """
|
||||
{
|
||||
"username": "admin",
|
||||
"password": "Test123!@#",
|
||||
"email": "duplicate@example.com",
|
||||
"phone": "13900139002",
|
||||
"nickname": "重复用户"
|
||||
}
|
||||
""";
|
||||
|
||||
webTestClient.post()
|
||||
.uri("/api/users")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.bodyValue(userJson)
|
||||
.exchange()
|
||||
.expectStatus().isCreated();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFindAllOperationLogs_ShouldReturnLogs() {
|
||||
StepVerifier.create(logService.findAll().take(5))
|
||||
.expectNextCount(0)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCountOperationLogs_ShouldReturnCount() {
|
||||
StepVerifier.create(logService.count())
|
||||
.expectNextCount(1)
|
||||
.verifyComplete();
|
||||
}
|
||||
}
|
||||
+224
@@ -0,0 +1,224 @@
|
||||
package cn.novalon.gym.manage.app.integration;
|
||||
|
||||
import cn.novalon.gym.manage.common.util.StatusConstants;
|
||||
import cn.novalon.gym.manage.sys.core.domain.SysUser;
|
||||
import cn.novalon.gym.manage.sys.core.domain.SysRole;
|
||||
import cn.novalon.gym.manage.sys.core.domain.UserRole;
|
||||
import cn.novalon.gym.manage.sys.core.repository.ISysUserRepository;
|
||||
import cn.novalon.gym.manage.sys.core.repository.ISysRoleRepository;
|
||||
import cn.novalon.gym.manage.sys.core.repository.IUserRoleRepository;
|
||||
import cn.novalon.gym.manage.sys.core.service.impl.SysUserService;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* 用户服务集成测试
|
||||
*
|
||||
* 使用PostgreSQL数据库进行集成测试
|
||||
*
|
||||
* 注意:此测试需要完整的Spring上下文,暂时禁用。
|
||||
* TODO: 优化集成测试配置
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-04-02
|
||||
*/
|
||||
@Disabled("暂时禁用:集成测试配置需要优化")
|
||||
@SpringBootTest
|
||||
@ActiveProfiles("test")
|
||||
class SysUserServiceIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
private ISysUserRepository userRepository;
|
||||
|
||||
@Autowired
|
||||
private ISysRoleRepository roleRepository;
|
||||
|
||||
@Autowired
|
||||
private IUserRoleRepository userRoleRepository;
|
||||
|
||||
@Autowired
|
||||
private R2dbcEntityTemplate r2dbcEntityTemplate;
|
||||
|
||||
@Autowired
|
||||
private SysUserService userService;
|
||||
|
||||
@Autowired
|
||||
private PasswordEncoder passwordEncoder;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
r2dbcEntityTemplate.delete(SysUser.class).all().block();
|
||||
r2dbcEntityTemplate.delete(SysRole.class).all().block();
|
||||
r2dbcEntityTemplate.delete(UserRole.class).all().block();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateAndFindUser() {
|
||||
SysUser user = new SysUser();
|
||||
user.setUsername("testuser");
|
||||
user.setPassword("password123");
|
||||
user.setEmail("test@example.com");
|
||||
user.setNickname("Test User");
|
||||
user.setPhone("13800138000");
|
||||
|
||||
StepVerifier.create(userService.createUser(user))
|
||||
.expectNextMatches(createdUser -> {
|
||||
assertNotNull(createdUser.getId());
|
||||
assertEquals("testuser", createdUser.getUsername());
|
||||
assertEquals("test@example.com", createdUser.getEmail());
|
||||
assertTrue(createdUser.getPassword().startsWith("$2"));
|
||||
assertEquals(StatusConstants.ENABLED, createdUser.getStatus());
|
||||
return true;
|
||||
})
|
||||
.verifyComplete();
|
||||
|
||||
StepVerifier.create(userService.findByUsername("testuser"))
|
||||
.expectNextMatches(foundUser -> {
|
||||
assertEquals("testuser", foundUser.getUsername());
|
||||
assertEquals("test@example.com", foundUser.getEmail());
|
||||
return true;
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUpdateUser() {
|
||||
SysUser user = new SysUser();
|
||||
user.setUsername("updateuser");
|
||||
user.setPassword("password123");
|
||||
user.setEmail("update@example.com");
|
||||
|
||||
SysUser createdUser = userService.createUser(user).block();
|
||||
assertNotNull(createdUser);
|
||||
|
||||
createdUser.setEmail("updated@example.com");
|
||||
createdUser.setNickname("Updated User");
|
||||
|
||||
StepVerifier.create(userService.updateUser(createdUser))
|
||||
.expectNextMatches(updatedUser -> {
|
||||
assertEquals("updated@example.com", updatedUser.getEmail());
|
||||
assertEquals("Updated User", updatedUser.getNickname());
|
||||
return true;
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDeleteUser() {
|
||||
SysUser user = new SysUser();
|
||||
user.setUsername("deleteuser");
|
||||
user.setPassword("password123");
|
||||
user.setEmail("delete@example.com");
|
||||
|
||||
SysUser createdUser = userService.createUser(user).block();
|
||||
assertNotNull(createdUser);
|
||||
|
||||
StepVerifier.create(userService.deleteUser(createdUser.getId()))
|
||||
.verifyComplete();
|
||||
|
||||
StepVerifier.create(userService.findById(createdUser.getId()))
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testChangePassword() {
|
||||
SysUser user = new SysUser();
|
||||
user.setUsername("pwduser");
|
||||
user.setPassword("oldPassword");
|
||||
user.setEmail("pwd@example.com");
|
||||
|
||||
SysUser createdUser = userService.createUser(user).block();
|
||||
assertNotNull(createdUser);
|
||||
|
||||
StepVerifier.create(userService.changePassword(createdUser.getId(), "oldPassword", "newPassword"))
|
||||
.expectNextMatches(updatedUser -> {
|
||||
assertNotEquals(createdUser.getPassword(), updatedUser.getPassword());
|
||||
assertTrue(passwordEncoder.matches("newPassword", updatedUser.getPassword()));
|
||||
return true;
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAssignRolesToUser() {
|
||||
SysRole role1 = new SysRole();
|
||||
role1.setRoleName("Test Role 1");
|
||||
role1.setRoleKey("test_role_1");
|
||||
role1.setStatus(1);
|
||||
|
||||
SysRole role2 = new SysRole();
|
||||
role2.setRoleName("Test Role 2");
|
||||
role2.setRoleKey("test_role_2");
|
||||
role2.setStatus(1);
|
||||
|
||||
SysRole createdRole1 = roleRepository.save(role1).block();
|
||||
SysRole createdRole2 = roleRepository.save(role2).block();
|
||||
assertNotNull(createdRole1);
|
||||
assertNotNull(createdRole2);
|
||||
|
||||
SysUser user = new SysUser();
|
||||
user.setUsername("roleuser");
|
||||
user.setPassword("password123");
|
||||
user.setEmail("role@example.com");
|
||||
|
||||
SysUser createdUser = userService.createUser(user).block();
|
||||
assertNotNull(createdUser);
|
||||
|
||||
StepVerifier.create(userService.assignRolesToUser(createdUser.getId(),
|
||||
Arrays.asList(createdRole1.getId(), createdRole2.getId())))
|
||||
.verifyComplete();
|
||||
|
||||
StepVerifier.create(userRoleRepository.findByUserId(createdUser.getId()).collectList())
|
||||
.expectNextMatches(userRoles -> {
|
||||
assertEquals(2, userRoles.size());
|
||||
return true;
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFindAllUsers() {
|
||||
for (int i = 1; i <= 3; i++) {
|
||||
SysUser user = new SysUser();
|
||||
user.setUsername("user" + i);
|
||||
user.setPassword("password" + i);
|
||||
user.setEmail("user" + i + "@example.com");
|
||||
userService.createUser(user).block();
|
||||
}
|
||||
|
||||
StepVerifier.create(userService.findAll(false).collectList())
|
||||
.expectNextMatches(users -> {
|
||||
assertEquals(3, users.size());
|
||||
return true;
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExistsByUsername() {
|
||||
SysUser user = new SysUser();
|
||||
user.setUsername("existinguser");
|
||||
user.setPassword("password123");
|
||||
user.setEmail("existing@example.com");
|
||||
userService.createUser(user).block();
|
||||
|
||||
StepVerifier.create(userService.existsByUsername("existinguser"))
|
||||
.expectNext(true)
|
||||
.verifyComplete();
|
||||
|
||||
StepVerifier.create(userService.existsByUsername("nonexistinguser"))
|
||||
.expectNext(false)
|
||||
.verifyComplete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
spring:
|
||||
r2dbc:
|
||||
url: r2dbc:postgresql://localhost:55432/manage_system
|
||||
username: novalon
|
||||
password: novalon123
|
||||
pool:
|
||||
enabled: true
|
||||
initial-size: 2
|
||||
max-size: 10
|
||||
|
||||
flyway:
|
||||
enabled: true
|
||||
locations: classpath:db/migration
|
||||
baseline-on-migrate: true
|
||||
validate-on-migrate: true
|
||||
|
||||
sql:
|
||||
init:
|
||||
mode: never
|
||||
|
||||
security:
|
||||
enabled: false
|
||||
|
||||
jwt:
|
||||
secret: test-secret-key-for-integration-testing
|
||||
expiration: 86400000
|
||||
|
||||
logging:
|
||||
level:
|
||||
cn.novalon.manage: DEBUG
|
||||
org.springframework.r2dbc: DEBUG
|
||||
@@ -0,0 +1,80 @@
|
||||
-- H2数据库测试数据
|
||||
-- 用于测试环境
|
||||
|
||||
-- 插入测试角色
|
||||
INSERT INTO sys_role (id, role_name, role_key, role_sort, status, create_by, update_by)
|
||||
VALUES
|
||||
(1, '超级管理员', 'admin', 1, 1, 'system', 'system'),
|
||||
(2, '测试管理员', 'test_admin', 2, 1, 'system', 'system'),
|
||||
(3, '普通用户', 'normal_user', 3, 1, 'system', 'system'),
|
||||
(4, '访客', 'guest', 4, 1, 'system', 'system');
|
||||
|
||||
-- 插入测试用户
|
||||
-- BCrypt哈希值对应明文密码: Test@123
|
||||
INSERT INTO sys_user (id, username, password, email, phone, nickname, status, create_by, update_by)
|
||||
VALUES
|
||||
(1, 'admin', '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C', 'admin@novalon.com', '13800138000', '超级管理员', 1, 'system', 'system'),
|
||||
(2, 'testadmin', '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C', 'testadmin@novalon.com', '13800138001', '测试管理员', 1, 'system', 'system'),
|
||||
(3, 'normaluser', '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C', 'normaluser@novalon.com', '13800138002', '普通用户', 1, 'system', 'system'),
|
||||
(4, 'guestuser', '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C', 'guestuser@novalon.com', '13800138003', '访客用户', 1, 'system', 'system'),
|
||||
(5, 'disableduser', '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C', 'disableduser@novalon.com', '13800138004', '禁用用户', 0, 'system', 'system');
|
||||
|
||||
-- 为用户分配角色
|
||||
INSERT INTO user_role (user_id, role_id, created_by)
|
||||
VALUES
|
||||
(1, 1, 'system'),
|
||||
(2, 2, 'system'),
|
||||
(3, 3, 'system'),
|
||||
(4, 4, 'system');
|
||||
|
||||
-- 插入测试菜单
|
||||
INSERT INTO sys_menu (id, menu_name, parent_id, order_num, path, component, menu_type, visible, status, perms, icon, created_by, updated_by)
|
||||
VALUES
|
||||
(1, '系统管理', 0, 1, '/system', 'Layout', 'M', '1', '1', '', 'system', 'system', 'system'),
|
||||
(2, '用户管理', 1, 1, 'user', 'system/user/index', 'C', '1', '1', 'system:user:list', 'user', 'system', 'system'),
|
||||
(3, '角色管理', 1, 2, 'role', 'system/role/index', 'C', '1', '1', 'system:role:list', 'role', 'system', 'system'),
|
||||
(4, '菜单管理', 1, 3, 'menu', 'system/menu/index', 'C', '1', '1', 'system:menu:list', 'menu', 'system', 'system'),
|
||||
(5, '测试菜单', 0, 99, '/test', 'Layout', 'M', '1', '1', '', 'test', 'system', 'system'),
|
||||
(6, '用户测试', 5, 1, 'user-test', 'system/user-test/index', 'C', '1', '1', 'system:user:test', 'user', 'system', 'system');
|
||||
|
||||
-- 插入测试权限
|
||||
INSERT INTO sys_permission (id, permission_name, permission_key, permission_type, parent_id, status, created_by, updated_by)
|
||||
VALUES
|
||||
(1, '系统管理', 'system:manage', 'menu', 0, 1, 'system', 'system'),
|
||||
(2, '用户管理', 'system:user:manage', 'menu', 1, 1, 'system', 'system'),
|
||||
(3, '用户查询', 'system:user:list', 'button', 2, 1, 'system', 'system'),
|
||||
(4, '用户新增', 'system:user:add', 'button', 2, 1, 'system', 'system'),
|
||||
(5, '用户编辑', 'system:user:edit', 'button', 2, 1, 'system', 'system'),
|
||||
(6, '用户删除', 'system:user:delete', 'button', 2, 1, 'system', 'system'),
|
||||
(7, '测试权限', 'test:permission', 'menu', 0, 1, 'system', 'system'),
|
||||
(8, '用户测试权限', 'system:user:test', 'button', 7, 1, 'system', 'system');
|
||||
|
||||
-- 为角色分配权限
|
||||
INSERT INTO sys_role_permission (role_id, permission_id, created_by, updated_by)
|
||||
SELECT 1, id, 'system', 'system' FROM sys_permission
|
||||
UNION ALL
|
||||
SELECT 2, id, 'system', 'system' FROM sys_permission WHERE id IN (7, 8);
|
||||
|
||||
-- 插入字典类型
|
||||
INSERT INTO sys_dict_type (id, dict_name, dict_type, status, remark, created_by, updated_by)
|
||||
VALUES
|
||||
(1, '用户状态', 'user_status', '0', '用户状态列表', 'system', 'system'),
|
||||
(2, '菜单状态', 'menu_status', '0', '菜单状态列表', 'system', 'system'),
|
||||
(3, '角色状态', 'role_status', '0', '角色状态列表', 'system', 'system');
|
||||
|
||||
-- 插入字典数据
|
||||
INSERT INTO sys_dict_data (id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, created_by, updated_by)
|
||||
VALUES
|
||||
(1, 1, '正常', '1', 'user_status', '', 'primary', 'Y', '0', 'system', 'system'),
|
||||
(2, 2, '停用', '0', 'user_status', '', 'danger', 'N', '0', 'system', 'system'),
|
||||
(3, 1, '正常', '0', 'menu_status', '', 'primary', 'Y', '0', 'system', 'system'),
|
||||
(4, 2, '停用', '1', 'menu_status', '', 'danger', 'N', '0', 'system', 'system'),
|
||||
(5, 1, '正常', '0', 'role_status', '', 'primary', 'Y', '0', 'system', 'system'),
|
||||
(6, 2, '停用', '1', 'role_status', '', 'danger', 'N', '0', 'system', 'system');
|
||||
|
||||
-- 插入系统配置
|
||||
INSERT INTO sys_config (id, config_name, config_key, config_value, config_type, remark, created_by, updated_by)
|
||||
VALUES
|
||||
(1, '用户管理-用户初始密码', 'sys.user.initPassword', '123456', 'Y', '初始化用户密码', 'system', 'system'),
|
||||
(2, '主框架页-默认皮肤样式名称', 'sys.index.skinName', 'skin-blue', 'Y', '默认皮肤', 'system', 'system'),
|
||||
(3, '用户自助-验证码开关', 'sys.account.captchaEnabled', 'true', 'Y', '是否开启验证码功能', 'system', 'system');
|
||||
@@ -0,0 +1,76 @@
|
||||
-- H2数据库Schema for Integration Testing
|
||||
-- 创建用户表
|
||||
CREATE TABLE IF NOT EXISTS sys_user (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
username VARCHAR(50) NOT NULL UNIQUE,
|
||||
password VARCHAR(255) NOT NULL,
|
||||
email VARCHAR(100),
|
||||
phone VARCHAR(20),
|
||||
nickname VARCHAR(100),
|
||||
role_id BIGINT,
|
||||
status INTEGER DEFAULT 1,
|
||||
create_by VARCHAR(50),
|
||||
update_by VARCHAR(50),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP
|
||||
);
|
||||
|
||||
-- 创建角色表
|
||||
CREATE TABLE IF NOT EXISTS sys_role (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
role_name VARCHAR(100) NOT NULL,
|
||||
role_key VARCHAR(100) NOT NULL UNIQUE,
|
||||
role_sort INTEGER DEFAULT 0,
|
||||
status INTEGER DEFAULT 1,
|
||||
create_by VARCHAR(50),
|
||||
update_by VARCHAR(50),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP
|
||||
);
|
||||
|
||||
-- 创建用户角色关联表
|
||||
CREATE TABLE IF NOT EXISTS user_role (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL,
|
||||
role_id BIGINT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
created_by VARCHAR(50),
|
||||
CONSTRAINT fk_user_role_user FOREIGN KEY (user_id) REFERENCES sys_user(id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_user_role_role FOREIGN KEY (role_id) REFERENCES sys_role(id) ON DELETE CASCADE,
|
||||
CONSTRAINT uk_user_role UNIQUE (user_id, role_id)
|
||||
);
|
||||
|
||||
-- 创建索引
|
||||
CREATE INDEX IF NOT EXISTS idx_user_role_user_id ON user_role(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_user_role_role_id ON user_role(role_id);
|
||||
|
||||
-- 创建审计日志表
|
||||
CREATE TABLE IF NOT EXISTS audit_log (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
entity_type VARCHAR(100) NOT NULL,
|
||||
entity_id BIGINT,
|
||||
operation_type VARCHAR(20) NOT NULL,
|
||||
operator VARCHAR(100),
|
||||
operation_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
before_data CLOB,
|
||||
after_data CLOB,
|
||||
changed_fields CLOB,
|
||||
ip_address VARCHAR(50),
|
||||
user_agent CLOB,
|
||||
description CLOB,
|
||||
create_by VARCHAR(50),
|
||||
update_by VARCHAR(50),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP
|
||||
);
|
||||
|
||||
-- 创建审计日志索引
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_log_entity_type ON audit_log(entity_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_log_entity_id ON audit_log(entity_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_log_operation_type ON audit_log(operation_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_log_operator ON audit_log(operator);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_log_operation_time ON audit_log(operation_time);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_log_entity ON audit_log(entity_type, entity_id);
|
||||
@@ -0,0 +1,52 @@
|
||||
<?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.novalon.gym.manage</groupId>
|
||||
<artifactId>gym-manage-api</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>manage-audit</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>Manage Audit</name>
|
||||
<description>Audit module for Novalon Manage API</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.novalon.gym.manage</groupId>
|
||||
<artifactId>manage-common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.novalon.gym.manage</groupId>
|
||||
<artifactId>manage-db</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,80 @@
|
||||
<?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.novalon.gym.manage</groupId>
|
||||
<artifactId>gym-manage-api</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>manage-common</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>Manage Common</name>
|
||||
<description>Common module for Novalon Manage API</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-r2dbc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-cache</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||
<artifactId>caffeine</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-collections4</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-impl</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-jackson</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package cn.novalon.gym.manage.common.config;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.cache.caffeine.CaffeineCacheManager;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 缓存配置类
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-13
|
||||
*/
|
||||
@Configuration
|
||||
@EnableCaching
|
||||
public class CacheConfig {
|
||||
|
||||
@Bean
|
||||
public CacheManager cacheManager() {
|
||||
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
|
||||
cacheManager.setCaffeine(caffeineCacheBuilder());
|
||||
return cacheManager;
|
||||
}
|
||||
|
||||
private Caffeine<Object, Object> caffeineCacheBuilder() {
|
||||
return Caffeine.newBuilder()
|
||||
.initialCapacity(100)
|
||||
.maximumSize(500)
|
||||
.expireAfterWrite(30, TimeUnit.MINUTES)
|
||||
.recordStats();
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package cn.novalon.gym.manage.common.config;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
/**
|
||||
* JWT配置属性类
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-13
|
||||
*/
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "jwt")
|
||||
@Validated
|
||||
public class JwtProperties {
|
||||
|
||||
private String secret = "default-secret-key-change-in-production";
|
||||
private long expiration = 86400000;
|
||||
|
||||
public String getSecret() {
|
||||
return secret;
|
||||
}
|
||||
|
||||
public void setSecret(String secret) {
|
||||
this.secret = secret;
|
||||
}
|
||||
|
||||
public long getExpiration() {
|
||||
return expiration;
|
||||
}
|
||||
|
||||
public void setExpiration(long expiration) {
|
||||
this.expiration = expiration;
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package cn.novalon.gym.manage.common.dao;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 查询字段注解
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-13
|
||||
*/
|
||||
@Target(ElementType.FIELD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface QueryField {
|
||||
|
||||
String propName() default "";
|
||||
|
||||
String blurry() default "";
|
||||
|
||||
Type type() default Type.EQUAL;
|
||||
|
||||
Type orPropVal() default Type.EQUAL;
|
||||
|
||||
String[] orPropNames() default {};
|
||||
|
||||
enum Type {
|
||||
EQUAL,
|
||||
GREATER_THAN,
|
||||
LESS_THAN,
|
||||
LESS_THAN_NQ,
|
||||
INNER_LIKE,
|
||||
LEFT_LIKE,
|
||||
NOT_LEFT_LIKE,
|
||||
RIGHT_LIKE,
|
||||
IN,
|
||||
OR,
|
||||
IS_NULL,
|
||||
IS_NOT_NULL
|
||||
}
|
||||
}
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
package cn.novalon.gym.manage.common.dao;
|
||||
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.relational.core.query.Criteria;
|
||||
import org.springframework.data.relational.core.query.Query;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 查询工具类
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-13
|
||||
*/
|
||||
public class QueryUtil {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(QueryUtil.class);
|
||||
|
||||
public static <Q> Query getQuery(Q query) {
|
||||
return getQuery(query, true);
|
||||
}
|
||||
|
||||
public static <Q> Query getQueryAll(Q query) {
|
||||
return getQuery(query, false);
|
||||
}
|
||||
|
||||
public static <Q> Query getQuery(Q query, Boolean enabled) {
|
||||
Criteria criteria = Criteria.empty();
|
||||
if (enabled) {
|
||||
criteria = criteria.and("deletedAt").isNull();
|
||||
}
|
||||
if (query == null) {
|
||||
log.info("Query object is null, returning empty criteria");
|
||||
return Query.query(criteria);
|
||||
}
|
||||
System.out.println("=== QueryUtil.getQuery START ===");
|
||||
System.out.println("Query object class: " + query.getClass().getName());
|
||||
log.info("=== QueryUtil.getQuery START ===");
|
||||
log.info("Query object class: {}", query.getClass().getName());
|
||||
try {
|
||||
List<Field> fields = getAllFields(query.getClass(), new ArrayList<>());
|
||||
log.info("Found {} fields to process", fields.size());
|
||||
System.out.println("Found " + fields.size() + " fields to process");
|
||||
for (Field field : fields) {
|
||||
boolean accessible = Modifier.isStatic(field.getModifiers()) ? field.canAccess(null)
|
||||
: field.canAccess(query);
|
||||
field.setAccessible(true);
|
||||
QueryField q = field.getAnnotation(QueryField.class);
|
||||
if (q != null) {
|
||||
String propName = q.propName();
|
||||
String blurry = q.blurry();
|
||||
String attributeName = isBlank(propName) ? field.getName() : propName;
|
||||
Object val = field.get(query);
|
||||
log.info("Processing field: {}, value: {}, blurry: {}", attributeName, val, blurry);
|
||||
System.out.println("Processing field: " + attributeName + ", value: " + val + ", blurry: " + blurry);
|
||||
if (val == null || "".equals(val)) {
|
||||
log.info("Field {} has null or empty value, skipping", attributeName);
|
||||
System.out.println("Field " + attributeName + " has null or empty value, skipping");
|
||||
continue;
|
||||
}
|
||||
if (StringUtils.isNotBlank(blurry)) {
|
||||
log.info("Field {} has blurry search configuration: {}", attributeName, blurry);
|
||||
System.out.println("Field " + attributeName + " has blurry search configuration: " + blurry);
|
||||
String[] blurrys = blurry.split(",");
|
||||
Criteria orCriteria = Criteria.empty();
|
||||
for (String s : blurrys) {
|
||||
orCriteria = orCriteria.or(s).like("%" + val + "%");
|
||||
}
|
||||
criteria = criteria.and(orCriteria);
|
||||
log.info("Added OR criteria for blurry search: {} with value: {}", blurry, val);
|
||||
System.out.println("Added OR criteria for blurry search: " + blurry + " with value: " + val);
|
||||
continue;
|
||||
}
|
||||
switch (q.type()) {
|
||||
case EQUAL:
|
||||
criteria = criteria.and(attributeName).is(val);
|
||||
break;
|
||||
case GREATER_THAN:
|
||||
criteria = criteria.and(attributeName).greaterThanOrEquals(val);
|
||||
break;
|
||||
case LESS_THAN:
|
||||
criteria = criteria.and(attributeName).lessThanOrEquals(val);
|
||||
break;
|
||||
case LESS_THAN_NQ:
|
||||
criteria = criteria.and(attributeName).lessThan(val);
|
||||
break;
|
||||
case INNER_LIKE:
|
||||
criteria = criteria.and(attributeName).like("%" + val + "%");
|
||||
break;
|
||||
case LEFT_LIKE:
|
||||
criteria = criteria.and(attributeName).like("%" + val);
|
||||
break;
|
||||
case NOT_LEFT_LIKE:
|
||||
criteria = criteria.and(attributeName).notLike("%" + val);
|
||||
break;
|
||||
case RIGHT_LIKE:
|
||||
criteria = criteria.and(attributeName).like(val + "%");
|
||||
break;
|
||||
case IN:
|
||||
if (val instanceof Collection && CollectionUtils.isNotEmpty((Collection<?>) val)) {
|
||||
criteria = criteria.and(attributeName).in((Collection<?>) val);
|
||||
}
|
||||
break;
|
||||
case OR:
|
||||
QueryField.Type orValue = q.orPropVal();
|
||||
String[] orPropNames = q.orPropNames();
|
||||
Criteria orPredicate = Criteria.empty();
|
||||
if (QueryField.Type.IS_NULL.equals(orValue)) {
|
||||
for (String prop : orPropNames) {
|
||||
orPredicate = orPredicate.or(prop).isNull();
|
||||
}
|
||||
}
|
||||
if (QueryField.Type.IS_NOT_NULL.equals(orValue)) {
|
||||
for (String prop : orPropNames) {
|
||||
orPredicate = orPredicate.or(prop).isNotNull();
|
||||
}
|
||||
}
|
||||
criteria = criteria.and(orPredicate);
|
||||
break;
|
||||
case IS_NULL:
|
||||
criteria = criteria.and(attributeName).isNull();
|
||||
break;
|
||||
case IS_NOT_NULL:
|
||||
criteria = criteria.and(attributeName).isNotNull();
|
||||
break;
|
||||
}
|
||||
}
|
||||
field.setAccessible(accessible);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
return Query.query(criteria);
|
||||
}
|
||||
|
||||
public static boolean isBlank(final CharSequence cs) {
|
||||
int strLen;
|
||||
if (cs == null || (strLen = cs.length()) == 0) {
|
||||
return true;
|
||||
}
|
||||
for (int i = 0; i < strLen; i++) {
|
||||
if (!Character.isWhitespace(cs.charAt(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static List<Field> getAllFields(Class<?> clazz, List<Field> fields) {
|
||||
if (clazz != null) {
|
||||
fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
|
||||
getAllFields(clazz.getSuperclass(), fields);
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package cn.novalon.gym.manage.common.domain.query;
|
||||
|
||||
/**
|
||||
* 菜单查询条件对象
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-13
|
||||
*/
|
||||
public class SysMenuQuery {
|
||||
|
||||
private String menuName;
|
||||
private String menuType;
|
||||
private String status;
|
||||
|
||||
public String getMenuName() {
|
||||
return menuName;
|
||||
}
|
||||
|
||||
public void setMenuName(String menuName) {
|
||||
this.menuName = menuName;
|
||||
}
|
||||
|
||||
public String getMenuType() {
|
||||
return menuType;
|
||||
}
|
||||
|
||||
public void setMenuType(String menuType) {
|
||||
this.menuType = menuType;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package cn.novalon.gym.manage.common.domain.query;
|
||||
|
||||
/**
|
||||
* 角色查询条件对象
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-13
|
||||
*/
|
||||
public class SysRoleQuery {
|
||||
|
||||
private String roleName;
|
||||
private String roleKey;
|
||||
private Integer status;
|
||||
|
||||
public String getRoleName() {
|
||||
return roleName;
|
||||
}
|
||||
|
||||
public void setRoleName(String roleName) {
|
||||
this.roleName = roleName;
|
||||
}
|
||||
|
||||
public String getRoleKey() {
|
||||
return roleKey;
|
||||
}
|
||||
|
||||
public void setRoleKey(String roleKey) {
|
||||
this.roleKey = roleKey;
|
||||
}
|
||||
|
||||
public Integer getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(Integer status) {
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
package cn.novalon.gym.manage.common.domain.query;
|
||||
|
||||
/**
|
||||
* 用户查询条件对象
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-13
|
||||
*/
|
||||
public class SysUserQuery {
|
||||
|
||||
private String username;
|
||||
private String email;
|
||||
private Integer status;
|
||||
private Long roleId;
|
||||
private String keyword;
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public Integer getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(Integer status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public Long getRoleId() {
|
||||
return roleId;
|
||||
}
|
||||
|
||||
public void setRoleId(Long roleId) {
|
||||
this.roleId = roleId;
|
||||
}
|
||||
|
||||
public String getKeyword() {
|
||||
return keyword;
|
||||
}
|
||||
|
||||
public void setKeyword(String keyword) {
|
||||
this.keyword = keyword;
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
package cn.novalon.gym.manage.common.dto;
|
||||
|
||||
/**
|
||||
* 分页请求参数封装类
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-13
|
||||
*/
|
||||
public class PageRequest {
|
||||
private int page = 0;
|
||||
private int size = 10;
|
||||
private String sort = "id";
|
||||
private String order = "asc";
|
||||
private String keyword;
|
||||
|
||||
public int getPage() {
|
||||
return page;
|
||||
}
|
||||
|
||||
public void setPage(int page) {
|
||||
this.page = page;
|
||||
}
|
||||
|
||||
public int getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public void setSize(int size) {
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
public String getSort() {
|
||||
return sort;
|
||||
}
|
||||
|
||||
public void setSort(String sort) {
|
||||
this.sort = sort;
|
||||
}
|
||||
|
||||
public String getOrder() {
|
||||
return order;
|
||||
}
|
||||
|
||||
public void setOrder(String order) {
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
public String getKeyword() {
|
||||
return keyword;
|
||||
}
|
||||
|
||||
public void setKeyword(String keyword) {
|
||||
this.keyword = keyword;
|
||||
}
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
package cn.novalon.gym.manage.common.dto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 分页响应结果封装类
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-13
|
||||
*/
|
||||
public class PageResponse<T> {
|
||||
private List<T> content;
|
||||
private int totalPages;
|
||||
private long totalElements;
|
||||
private int currentPage;
|
||||
private int pageSize;
|
||||
private boolean first;
|
||||
private boolean last;
|
||||
|
||||
public PageResponse() {
|
||||
}
|
||||
|
||||
public PageResponse(List<T> content, int totalPages, long totalElements, int currentPage, int pageSize) {
|
||||
this.content = content;
|
||||
this.totalPages = totalPages;
|
||||
this.totalElements = totalElements;
|
||||
this.currentPage = currentPage;
|
||||
this.pageSize = pageSize;
|
||||
this.first = currentPage == 0;
|
||||
this.last = currentPage >= totalPages - 1;
|
||||
}
|
||||
|
||||
public List<T> getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public void setContent(List<T> content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public int getTotalPages() {
|
||||
return totalPages;
|
||||
}
|
||||
|
||||
public void setTotalPages(int totalPages) {
|
||||
this.totalPages = totalPages;
|
||||
}
|
||||
|
||||
public long getTotalElements() {
|
||||
return totalElements;
|
||||
}
|
||||
|
||||
public void setTotalElements(long totalElements) {
|
||||
this.totalElements = totalElements;
|
||||
}
|
||||
|
||||
public int getCurrentPage() {
|
||||
return currentPage;
|
||||
}
|
||||
|
||||
public void setCurrentPage(int currentPage) {
|
||||
this.currentPage = currentPage;
|
||||
}
|
||||
|
||||
public int getPageSize() {
|
||||
return pageSize;
|
||||
}
|
||||
|
||||
public void setPageSize(int pageSize) {
|
||||
this.pageSize = pageSize;
|
||||
}
|
||||
|
||||
public boolean isFirst() {
|
||||
return first;
|
||||
}
|
||||
|
||||
public void setFirst(boolean first) {
|
||||
this.first = first;
|
||||
}
|
||||
|
||||
public boolean isLast() {
|
||||
return last;
|
||||
}
|
||||
|
||||
public void setLast(boolean last) {
|
||||
this.last = last;
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package cn.novalon.gym.manage.common.exception;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class BaseException extends RuntimeException {
|
||||
|
||||
private final String errorCode;
|
||||
private final Map<String, Object> context;
|
||||
|
||||
protected BaseException(String errorCode, String message) {
|
||||
super(message);
|
||||
this.errorCode = errorCode;
|
||||
this.context = new HashMap<>();
|
||||
}
|
||||
|
||||
protected BaseException(String errorCode, String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.errorCode = errorCode;
|
||||
this.context = new HashMap<>();
|
||||
}
|
||||
|
||||
public String getErrorCode() {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
public Map<String, Object> getContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
public BaseException addContext(String key, Object value) {
|
||||
context.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public abstract HttpStatus getHttpStatus();
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package cn.novalon.gym.manage.common.exception;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
public class BusinessException extends BaseException {
|
||||
|
||||
public BusinessException(String errorCode, String message) {
|
||||
super(errorCode, message);
|
||||
}
|
||||
|
||||
public BusinessException(String errorCode, String message, Throwable cause) {
|
||||
super(errorCode, message, cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpStatus getHttpStatus() {
|
||||
return HttpStatus.BAD_REQUEST;
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package cn.novalon.gym.manage.common.exception;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
public class ConflictException extends BusinessException {
|
||||
|
||||
public ConflictException(String errorCode, String message) {
|
||||
super(errorCode, message);
|
||||
}
|
||||
|
||||
public ConflictException(String errorCode, String message, Throwable cause) {
|
||||
super(errorCode, message, cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpStatus getHttpStatus() {
|
||||
return HttpStatus.CONFLICT;
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package cn.novalon.gym.manage.common.exception;
|
||||
|
||||
public class ErrorCode {
|
||||
|
||||
public static final String VALIDATION_PREFIX = "VALIDATION_";
|
||||
public static final String NOT_FOUND_PREFIX = "NOT_FOUND_";
|
||||
public static final String PERMISSION_PREFIX = "PERMISSION_";
|
||||
public static final String CONFLICT_PREFIX = "CONFLICT_";
|
||||
public static final String SYSTEM_PREFIX = "SYSTEM_";
|
||||
|
||||
public static final String VALIDATION_REQUIRED = VALIDATION_PREFIX + "001";
|
||||
public static final String VALIDATION_INVALID_FORMAT = VALIDATION_PREFIX + "002";
|
||||
public static final String VALIDATION_INVALID_LENGTH = VALIDATION_PREFIX + "003";
|
||||
public static final String VALIDATION_INVALID_VALUE = VALIDATION_PREFIX + "004";
|
||||
|
||||
public static final String NOT_FOUND_USER = NOT_FOUND_PREFIX + "001";
|
||||
public static final String NOT_FOUND_ROLE = NOT_FOUND_PREFIX + "002";
|
||||
public static final String NOT_FOUND_MENU = NOT_FOUND_PREFIX + "003";
|
||||
public static final String NOT_FOUND_DICTIONARY = NOT_FOUND_PREFIX + "004";
|
||||
|
||||
public static final String PERMISSION_DENIED = PERMISSION_PREFIX + "001";
|
||||
public static final String PERMISSION_INSUFFICIENT = PERMISSION_PREFIX + "002";
|
||||
|
||||
public static final String CONFLICT_DUPLICATE = CONFLICT_PREFIX + "001";
|
||||
public static final String CONFLICT_DUPLICATE_USER = CONFLICT_PREFIX + "002";
|
||||
public static final String CONFLICT_DUPLICATE_ROLE = CONFLICT_PREFIX + "003";
|
||||
public static final String CONFLICT_DUPLICATE_DICTIONARY = CONFLICT_PREFIX + "004";
|
||||
|
||||
public static final String SYSTEM_INTERNAL_ERROR = SYSTEM_PREFIX + "001";
|
||||
public static final String SYSTEM_DATABASE_ERROR = SYSTEM_PREFIX + "002";
|
||||
public static final String SYSTEM_NETWORK_ERROR = SYSTEM_PREFIX + "003";
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package cn.novalon.gym.manage.common.exception;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
public class NotFoundException extends BusinessException {
|
||||
|
||||
public NotFoundException(String errorCode, String message) {
|
||||
super(errorCode, message);
|
||||
}
|
||||
|
||||
public NotFoundException(String errorCode, String message, Throwable cause) {
|
||||
super(errorCode, message, cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpStatus getHttpStatus() {
|
||||
return HttpStatus.NOT_FOUND;
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package cn.novalon.gym.manage.common.exception;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
public class PermissionException extends BusinessException {
|
||||
|
||||
public PermissionException(String errorCode, String message) {
|
||||
super(errorCode, message);
|
||||
}
|
||||
|
||||
public PermissionException(String errorCode, String message, Throwable cause) {
|
||||
super(errorCode, message, cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpStatus getHttpStatus() {
|
||||
return HttpStatus.FORBIDDEN;
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package cn.novalon.gym.manage.common.exception;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
public class SystemException extends BaseException {
|
||||
|
||||
public SystemException(String errorCode, String message) {
|
||||
super(errorCode, message);
|
||||
}
|
||||
|
||||
public SystemException(String errorCode, String message, Throwable cause) {
|
||||
super(errorCode, message, cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpStatus getHttpStatus() {
|
||||
return HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package cn.novalon.gym.manage.common.exception;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
public class ValidationException extends BusinessException {
|
||||
|
||||
public ValidationException(String errorCode, String message) {
|
||||
super(errorCode, message);
|
||||
}
|
||||
|
||||
public ValidationException(String errorCode, String message, Throwable cause) {
|
||||
super(errorCode, message, cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpStatus getHttpStatus() {
|
||||
return HttpStatus.BAD_REQUEST;
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package cn.novalon.gym.manage.common.handler;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* 默认异常日志服务实现
|
||||
* 临时实现,用于解决启动时的依赖注入问题
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-04-15
|
||||
*/
|
||||
@Service
|
||||
public class DefaultExceptionLogService implements IExceptionLogService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(DefaultExceptionLogService.class);
|
||||
|
||||
@Override
|
||||
public Mono<Void> logException(String title, String exceptionName, String exceptionMsg,
|
||||
String methodName, String ip, String stackTrace) {
|
||||
logger.warn("异常日志记录 (临时实现): title={}, exceptionName={}, methodName={}, ip={}",
|
||||
title, exceptionName, methodName, ip);
|
||||
logger.warn("异常信息: {}", exceptionMsg);
|
||||
if (stackTrace != null && stackTrace.length() > 500) {
|
||||
logger.warn("堆栈跟踪 (截断): {}", stackTrace.substring(0, 500) + "...");
|
||||
} else if (stackTrace != null) {
|
||||
logger.warn("堆栈跟踪: {}", stackTrace);
|
||||
}
|
||||
return Mono.empty();
|
||||
}
|
||||
}
|
||||
+198
@@ -0,0 +1,198 @@
|
||||
package cn.novalon.gym.manage.common.handler;
|
||||
|
||||
import cn.novalon.gym.manage.common.exception.BaseException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
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 org.springframework.web.server.ResponseStatusException;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.ServerWebInputException;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 全局异常处理器
|
||||
*
|
||||
* 文件定义:统一处理系统中抛出的各种异常,返回标准化的错误响应
|
||||
* 涉及业务:异常捕获、错误日志记录、错误响应格式化
|
||||
* 算法:使用@RestControllerAdvice注解实现全局异常拦截
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-13
|
||||
*/
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
|
||||
|
||||
private final IExceptionLogService exceptionLogService;
|
||||
|
||||
public GlobalExceptionHandler(IExceptionLogService exceptionLogService) {
|
||||
this.exceptionLogService = exceptionLogService;
|
||||
}
|
||||
|
||||
@ExceptionHandler(BaseException.class)
|
||||
public ResponseEntity<Map<String, Object>> handleBaseException(BaseException ex, ServerWebExchange exchange) {
|
||||
logger.warn("Business exception: ", ex);
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("code", ex.getErrorCode());
|
||||
response.put("message", ex.getMessage());
|
||||
response.put("timestamp", LocalDateTime.now());
|
||||
|
||||
if (!ex.getContext().isEmpty()) {
|
||||
response.put("context", ex.getContext());
|
||||
}
|
||||
|
||||
return ResponseEntity.status(ex.getHttpStatus()).body(response);
|
||||
}
|
||||
|
||||
@ExceptionHandler(RuntimeException.class)
|
||||
public ResponseEntity<Map<String, Object>> handleRuntimeException(RuntimeException ex, ServerWebExchange exchange) {
|
||||
logger.warn("Runtime exception: ", ex);
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
if (ex.getMessage() != null && ex.getMessage().contains("not found")) {
|
||||
response.put("code", HttpStatus.NOT_FOUND.value());
|
||||
response.put("message", ex.getMessage());
|
||||
response.put("timestamp", LocalDateTime.now());
|
||||
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response);
|
||||
}
|
||||
response.put("code", HttpStatus.BAD_REQUEST.value());
|
||||
response.put("message", ex.getMessage());
|
||||
response.put("timestamp", LocalDateTime.now());
|
||||
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
|
||||
}
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
public ResponseEntity<Map<String, Object>> handleException(Exception ex, ServerWebExchange exchange) {
|
||||
logger.error("Exception occurred: ", ex);
|
||||
|
||||
exceptionLogService.logException(
|
||||
"System Exception",
|
||||
ex.getClass().getSimpleName(),
|
||||
ex.getMessage(),
|
||||
exchange.getRequest().getPath().value(),
|
||||
getClientIp(exchange),
|
||||
getStackTrace(ex)
|
||||
).subscribe();
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("code", HttpStatus.INTERNAL_SERVER_ERROR.value());
|
||||
response.put("message", "Internal server error");
|
||||
response.put("timestamp", LocalDateTime.now());
|
||||
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
|
||||
}
|
||||
|
||||
@ExceptionHandler(IllegalArgumentException.class)
|
||||
public ResponseEntity<Map<String, Object>> handleIllegalArgumentException(IllegalArgumentException ex, ServerWebExchange exchange) {
|
||||
logger.warn("Illegal argument: ", ex);
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("code", HttpStatus.BAD_REQUEST.value());
|
||||
response.put("message", ex.getMessage());
|
||||
response.put("timestamp", LocalDateTime.now());
|
||||
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
|
||||
}
|
||||
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public ResponseEntity<Map<String, Object>> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex, ServerWebExchange exchange) {
|
||||
logger.warn("Validation failed: ", ex);
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("code", HttpStatus.BAD_REQUEST.value());
|
||||
response.put("message", "Validation failed");
|
||||
response.put("timestamp", LocalDateTime.now());
|
||||
|
||||
Map<String, String> fieldErrors = ex.getBindingResult()
|
||||
.getFieldErrors()
|
||||
.stream()
|
||||
.collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage, (e1, e2) -> e1));
|
||||
|
||||
response.put("errors", fieldErrors);
|
||||
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
|
||||
}
|
||||
|
||||
@ExceptionHandler(ServerWebInputException.class)
|
||||
public ResponseEntity<Map<String, Object>> handleServerWebInputException(ServerWebInputException ex, ServerWebExchange exchange) {
|
||||
logger.warn("Invalid input: ", ex);
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("code", HttpStatus.BAD_REQUEST.value());
|
||||
response.put("message", "Invalid input");
|
||||
response.put("timestamp", LocalDateTime.now());
|
||||
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
|
||||
}
|
||||
|
||||
@ExceptionHandler(ResponseStatusException.class)
|
||||
public ResponseEntity<Map<String, Object>> handleResponseStatusException(ResponseStatusException ex, ServerWebExchange exchange) {
|
||||
logger.warn("Response status exception: ", ex);
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("code", ex.getStatusCode().value());
|
||||
response.put("message", ex.getReason());
|
||||
response.put("timestamp", LocalDateTime.now());
|
||||
|
||||
return ResponseEntity.status(ex.getStatusCode()).body(response);
|
||||
}
|
||||
|
||||
@ExceptionHandler(DuplicateKeyException.class)
|
||||
public ResponseEntity<Map<String, Object>> handleDuplicateKeyException(DuplicateKeyException ex, ServerWebExchange exchange) {
|
||||
logger.warn("Duplicate key: ", ex);
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("code", HttpStatus.CONFLICT.value());
|
||||
response.put("message", "Duplicate key violation");
|
||||
response.put("timestamp", LocalDateTime.now());
|
||||
|
||||
return ResponseEntity.status(HttpStatus.CONFLICT).body(response);
|
||||
}
|
||||
|
||||
@ExceptionHandler(DataIntegrityViolationException.class)
|
||||
public ResponseEntity<Map<String, Object>> handleDataIntegrityViolationException(DataIntegrityViolationException ex, ServerWebExchange exchange) {
|
||||
logger.warn("Data integrity violation: ", ex);
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("code", HttpStatus.CONFLICT.value());
|
||||
response.put("message", "Data integrity violation");
|
||||
response.put("timestamp", LocalDateTime.now());
|
||||
|
||||
return ResponseEntity.status(HttpStatus.CONFLICT).body(response);
|
||||
}
|
||||
|
||||
private String getClientIp(ServerWebExchange exchange) {
|
||||
String ip = exchange.getRequest().getHeaders().getFirst("X-Forwarded-For");
|
||||
if (ip == null || ip.isEmpty()) {
|
||||
ip = exchange.getRequest().getHeaders().getFirst("X-Real-IP");
|
||||
}
|
||||
if (ip == null || ip.isEmpty()) {
|
||||
ip = exchange.getRequest().getRemoteAddress() != null
|
||||
? exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()
|
||||
: "127.0.0.1";
|
||||
}
|
||||
return ip;
|
||||
}
|
||||
|
||||
private String getStackTrace(Exception ex) {
|
||||
StringBuilder stackTrace = new StringBuilder();
|
||||
for (StackTraceElement element : ex.getStackTrace()) {
|
||||
stackTrace.append(element.toString()).append("\n");
|
||||
}
|
||||
return stackTrace.toString();
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package cn.novalon.gym.manage.common.handler;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* 异常日志服务接口
|
||||
*
|
||||
* 文件定义:定义异常日志记录的抽象接口
|
||||
* 涉及业务:异常日志记录、错误追踪
|
||||
* 算法:使用响应式编程实现异步日志记录
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-04-14
|
||||
*/
|
||||
public interface IExceptionLogService {
|
||||
Mono<Void> logException(String title, String exceptionName, String exceptionMsg,
|
||||
String methodName, String ip, String stackTrace);
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package cn.novalon.gym.manage.common.util;
|
||||
|
||||
/**
|
||||
* 数据库字段名常量定义
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-13
|
||||
*/
|
||||
public class FieldConstants {
|
||||
|
||||
public static final String USERNAME = "username";
|
||||
public static final String PASSWORD = "password";
|
||||
public static final String EMAIL = "email";
|
||||
public static final String PHONE = "phone";
|
||||
public static final String STATUS = "status";
|
||||
public static final String ROLE_NAME = "roleName";
|
||||
public static final String ROLE_KEY = "roleKey";
|
||||
public static final String MENU_NAME = "menuName";
|
||||
public static final String MENU_TYPE = "menuType";
|
||||
public static final String ROLE_ID = "roleId";
|
||||
public static final String PARENT_ID = "parentId";
|
||||
|
||||
private FieldConstants() {
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package cn.novalon.gym.manage.common.util;
|
||||
|
||||
/**
|
||||
* 菜单类型常量定义
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-13
|
||||
*/
|
||||
public class MenuTypeConstants {
|
||||
|
||||
public static final String DIRECTORY = "M";
|
||||
public static final String MENU = "C";
|
||||
public static final String BUTTON = "F";
|
||||
|
||||
private MenuTypeConstants() {
|
||||
}
|
||||
}
|
||||
+224
@@ -0,0 +1,224 @@
|
||||
package cn.novalon.gym.manage.common.util;
|
||||
|
||||
import cn.novalon.gym.manage.common.exception.ErrorCode;
|
||||
import cn.novalon.gym.manage.common.exception.SystemException;
|
||||
import cn.novalon.gym.manage.common.exception.ValidationException;
|
||||
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
|
||||
/**
|
||||
* 雪花算法ID生成器
|
||||
*
|
||||
* 文件定义:基于Twitter Snowflake算法的分布式唯一ID生成器
|
||||
* 涉及业务:为系统所有实体生成唯一ID,支持分布式环境下的ID生成
|
||||
* 算法:使用雪花算法,结合时间戳、机器ID和序列号生成唯一ID,支持高并发场景
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-13
|
||||
*/
|
||||
public final class SnowflakeId {
|
||||
|
||||
private static final int DEFAULT_WORKER_BITS = 10;
|
||||
private static final int DEFAULT_SEQ_BITS = 12;
|
||||
private static final long DEFAULT_EPOCH = 1582136402000L;
|
||||
private static final int MAX_RETRIES = 10;
|
||||
private static final long MAX_BACKWARD_MS = 50;
|
||||
private static final int SPIN_THRESHOLD = 5;
|
||||
private static final long TIME_CACHE_DURATION_MS = 16;
|
||||
|
||||
private static final AtomicLong lastTimestamp = new AtomicLong(-1L);
|
||||
private static final AtomicLong sequence = new AtomicLong(0);
|
||||
private static volatile SnowflakeConfig config;
|
||||
private static volatile long workerId;
|
||||
private static volatile long lastTimeCacheMs;
|
||||
private static volatile int timeCacheHits;
|
||||
|
||||
static {
|
||||
configure(DEFAULT_WORKER_BITS, DEFAULT_SEQ_BITS, DEFAULT_EPOCH);
|
||||
}
|
||||
|
||||
private static void configure(int workerBits, int seqBits, long epoch) {
|
||||
validateBits(workerBits, seqBits);
|
||||
config = new SnowflakeConfig(epoch, workerBits, seqBits);
|
||||
workerId = resolveWorkerId(config.maxWorkerId);
|
||||
lastTimeCacheMs = 0;
|
||||
timeCacheHits = 0;
|
||||
}
|
||||
|
||||
public static long nextId() {
|
||||
for (int i = 0; i < MAX_RETRIES; i++) {
|
||||
try {
|
||||
return nextIdInternal();
|
||||
} catch (ClockBackwardException e) {
|
||||
long backwardMs = e.getBackwardMs();
|
||||
if (backwardMs > MAX_BACKWARD_MS) {
|
||||
throw e;
|
||||
}
|
||||
if (i < SPIN_THRESHOLD) {
|
||||
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1));
|
||||
} else {
|
||||
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(10));
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new SystemException(ErrorCode.SYSTEM_INTERNAL_ERROR,
|
||||
"Failed to generate ID after " + MAX_RETRIES + " retries");
|
||||
}
|
||||
|
||||
private static long nextIdInternal() {
|
||||
long currentTs = timeGen();
|
||||
long lastTs;
|
||||
long seq;
|
||||
|
||||
do {
|
||||
lastTs = lastTimestamp.get();
|
||||
|
||||
if (currentTs < lastTs) {
|
||||
long backwardMs = lastTs - currentTs;
|
||||
if (backwardMs <= MAX_BACKWARD_MS) {
|
||||
lastTimestamp.set(currentTs);
|
||||
lastTs = currentTs;
|
||||
} else {
|
||||
throw new ClockBackwardException(backwardMs);
|
||||
}
|
||||
}
|
||||
|
||||
if (currentTs == lastTs) {
|
||||
seq = sequence.incrementAndGet() & config.sequenceMask;
|
||||
if (seq == 0) {
|
||||
currentTs = waitNextMillis(currentTs);
|
||||
}
|
||||
} else {
|
||||
seq = 0;
|
||||
}
|
||||
} while (!lastTimestamp.compareAndSet(lastTs, currentTs));
|
||||
|
||||
return ((currentTs - config.epoch) << config.timestampShift)
|
||||
| (workerId << config.workerShift)
|
||||
| seq;
|
||||
}
|
||||
|
||||
private static long waitNextMillis(long currentTs) {
|
||||
long deadline = currentTs + 2;
|
||||
int spinCount = 0;
|
||||
|
||||
while (currentTs <= lastTimestamp.get()) {
|
||||
if (currentTs >= deadline) {
|
||||
return currentTs;
|
||||
}
|
||||
|
||||
if (spinCount < 10) {
|
||||
spinCount++;
|
||||
} else if (spinCount < 50) {
|
||||
LockSupport.parkNanos(100_000);
|
||||
spinCount++;
|
||||
} else {
|
||||
LockSupport.parkNanos(500_000);
|
||||
}
|
||||
currentTs = timeGen();
|
||||
}
|
||||
return currentTs;
|
||||
}
|
||||
|
||||
private static long timeGen() {
|
||||
long now = System.currentTimeMillis();
|
||||
long cached = lastTimeCacheMs;
|
||||
|
||||
if (now - cached < TIME_CACHE_DURATION_MS) {
|
||||
timeCacheHits++;
|
||||
return cached;
|
||||
}
|
||||
|
||||
synchronized (SnowflakeId.class) {
|
||||
cached = lastTimeCacheMs;
|
||||
if (now - cached < TIME_CACHE_DURATION_MS) {
|
||||
timeCacheHits++;
|
||||
return cached;
|
||||
}
|
||||
lastTimeCacheMs = now;
|
||||
return now;
|
||||
}
|
||||
}
|
||||
|
||||
public static int getTimeCacheHits() {
|
||||
return timeCacheHits;
|
||||
}
|
||||
|
||||
public static void resetTimeCache() {
|
||||
synchronized (SnowflakeId.class) {
|
||||
lastTimeCacheMs = 0;
|
||||
timeCacheHits = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static void validateBits(int workerBits, int seqBits) {
|
||||
if (workerBits < 0 || workerBits > 22) {
|
||||
throw new ValidationException(ErrorCode.VALIDATION_INVALID_VALUE, "WorkerID位数必须在0-22之间");
|
||||
}
|
||||
if (seqBits < 0 || seqBits > 22) {
|
||||
throw new ValidationException(ErrorCode.VALIDATION_INVALID_VALUE, "序列号位数必须在0-22之间");
|
||||
}
|
||||
if (workerBits + seqBits > 22) {
|
||||
throw new ValidationException(ErrorCode.VALIDATION_INVALID_VALUE,
|
||||
"WorkerID和序列号位数总和不能超过22位,当前为: " + (workerBits + seqBits));
|
||||
}
|
||||
if (workerBits + seqBits == 0) {
|
||||
throw new ValidationException(ErrorCode.VALIDATION_INVALID_VALUE, "WorkerID和序列号位数总和不能为0");
|
||||
}
|
||||
}
|
||||
|
||||
private static long resolveWorkerId(long maxWorkerId) {
|
||||
long id = generateNewId();
|
||||
if (id < 0 || id > maxWorkerId) {
|
||||
throw new SystemException(ErrorCode.SYSTEM_INTERNAL_ERROR,
|
||||
"WorkerID超出有效范围: " + id + " (有效范围: 0-" + maxWorkerId + ")");
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
private static long generateNewId() {
|
||||
long newId = ThreadLocalRandom.current().nextLong(config.maxWorkerId + 1);
|
||||
return newId;
|
||||
}
|
||||
|
||||
public static void config(int workerBits, int seqBits, long epoch) {
|
||||
configure(workerBits, seqBits, epoch);
|
||||
}
|
||||
|
||||
public static long getWorkerId() {
|
||||
return workerId;
|
||||
}
|
||||
|
||||
private static class SnowflakeConfig {
|
||||
final long epoch;
|
||||
final int timestampShift;
|
||||
final int workerShift;
|
||||
final long sequenceMask;
|
||||
final long maxWorkerId;
|
||||
|
||||
SnowflakeConfig(long epoch, int workerBits, int seqBits) {
|
||||
this.epoch = epoch;
|
||||
this.timestampShift = workerBits + seqBits;
|
||||
this.workerShift = seqBits;
|
||||
this.sequenceMask = ~(-1L << seqBits);
|
||||
this.maxWorkerId = ~(-1L << workerBits);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ClockBackwardException extends RuntimeException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final long backwardMs;
|
||||
|
||||
ClockBackwardException(long backwardMs) {
|
||||
super("Clock moved backwards by " + backwardMs + "ms");
|
||||
this.backwardMs = backwardMs;
|
||||
}
|
||||
|
||||
public long getBackwardMs() {
|
||||
return backwardMs;
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package cn.novalon.gym.manage.common.util;
|
||||
|
||||
/**
|
||||
* 状态常量定义
|
||||
*
|
||||
* 文件定义:系统通用的状态常量定义类
|
||||
* 涉及业务:为系统提供统一的状态码定义,包括启用、禁用、删除等状态
|
||||
* 算法:无复杂算法,主要为常量定义
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-13
|
||||
*/
|
||||
public class StatusConstants {
|
||||
|
||||
public static final Integer DISABLED = 0;
|
||||
public static final Integer ENABLED = 1;
|
||||
public static final Integer DELETED = 2;
|
||||
|
||||
private StatusConstants() {
|
||||
}
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
cn.novalon.manage.common.config.CacheConfig
|
||||
cn.novalon.manage.common.config.JwtProperties
|
||||
@@ -0,0 +1,115 @@
|
||||
<?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.novalon.gym.manage</groupId>
|
||||
<artifactId>gym-manage-api</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>manage-db</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>Manage DB</name>
|
||||
<description>Database module for Novalon Manage API</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.novalon.gym.manage</groupId>
|
||||
<artifactId>manage-sys</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.novalon.gym.manage</groupId>
|
||||
<artifactId>manage-notify</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.novalon.gym.manage</groupId>
|
||||
<artifactId>manage-file</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.novalon.gym.manage</groupId>
|
||||
<artifactId>manage-common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-r2dbc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-jdbc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-r2dbc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>r2dbc-postgresql</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.r2dbc</groupId>
|
||||
<artifactId>r2dbc-h2</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.flywaydb</groupId>
|
||||
<artifactId>flyway-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.flywaydb</groupId>
|
||||
<artifactId>flyway-database-postgresql</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct</artifactId>
|
||||
<version>1.5.5.Final</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct-processor</artifactId>
|
||||
<version>1.5.5.Final</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-collections4</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package cn.novalon.gym.manage.db.config;
|
||||
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = "cn.novalon.gym.manage.db.repository")
|
||||
public class RepositoryScanConfig {
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
package cn.novalon.gym.manage.db.converter;
|
||||
|
||||
import cn.novalon.gym.manage.sys.audit.domain.AuditLog;
|
||||
import cn.novalon.gym.manage.db.entity.AuditLogEntity;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 审计日志实体转换器
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-04-08
|
||||
*/
|
||||
@Component
|
||||
public class AuditLogConverter {
|
||||
|
||||
public AuditLog toDomain(AuditLogEntity entity) {
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
AuditLog domain = new AuditLog();
|
||||
domain.setId(entity.getId());
|
||||
domain.setEntityType(entity.getEntityType());
|
||||
domain.setEntityId(entity.getEntityId());
|
||||
domain.setOperationType(entity.getOperationType());
|
||||
domain.setOperator(entity.getOperator());
|
||||
domain.setOperationTime(entity.getOperationTime());
|
||||
domain.setBeforeData(entity.getBeforeData());
|
||||
domain.setAfterData(entity.getAfterData());
|
||||
domain.setChangedFields(entity.getChangedFields());
|
||||
domain.setIpAddress(entity.getIpAddress());
|
||||
domain.setUserAgent(entity.getUserAgent());
|
||||
domain.setDescription(entity.getDescription());
|
||||
domain.setCreateBy(entity.getCreateBy());
|
||||
domain.setUpdateBy(entity.getUpdateBy());
|
||||
domain.setCreatedAt(entity.getCreatedAt());
|
||||
domain.setUpdatedAt(entity.getUpdatedAt());
|
||||
domain.setDeletedAt(entity.getDeletedAt());
|
||||
return domain;
|
||||
}
|
||||
|
||||
public AuditLogEntity toEntity(AuditLog domain) {
|
||||
if (domain == null) {
|
||||
return null;
|
||||
}
|
||||
AuditLogEntity entity = new AuditLogEntity();
|
||||
entity.setId(domain.getId());
|
||||
entity.setEntityType(domain.getEntityType());
|
||||
entity.setEntityId(domain.getEntityId());
|
||||
entity.setOperationType(domain.getOperationType());
|
||||
entity.setOperator(domain.getOperator());
|
||||
entity.setOperationTime(domain.getOperationTime());
|
||||
entity.setBeforeData(domain.getBeforeData());
|
||||
entity.setAfterData(domain.getAfterData());
|
||||
entity.setChangedFields(domain.getChangedFields());
|
||||
entity.setIpAddress(domain.getIpAddress());
|
||||
entity.setUserAgent(domain.getUserAgent());
|
||||
entity.setDescription(domain.getDescription());
|
||||
entity.setCreateBy(domain.getCreateBy());
|
||||
entity.setUpdateBy(domain.getUpdateBy());
|
||||
entity.setCreatedAt(domain.getCreatedAt());
|
||||
entity.setUpdatedAt(domain.getUpdatedAt());
|
||||
entity.setDeletedAt(domain.getDeletedAt());
|
||||
return entity;
|
||||
}
|
||||
|
||||
public List<AuditLog> toDomainList(List<AuditLogEntity> entities) {
|
||||
if (entities == null) {
|
||||
return null;
|
||||
}
|
||||
return entities.stream()
|
||||
.map(this::toDomain)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<AuditLogEntity> toEntityList(List<AuditLog> domains) {
|
||||
if (domains == null) {
|
||||
return null;
|
||||
}
|
||||
return domains.stream()
|
||||
.map(this::toEntity)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
package cn.novalon.gym.manage.db.converter;
|
||||
|
||||
import cn.novalon.gym.manage.sys.core.domain.Dictionary;
|
||||
import cn.novalon.gym.manage.db.entity.DictionaryEntity;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 字典实体转换器
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-13
|
||||
*/
|
||||
@Component
|
||||
public class DictionaryConverter {
|
||||
|
||||
public DictionaryEntity toEntity(Dictionary domain) {
|
||||
if (domain == null) {
|
||||
return null;
|
||||
}
|
||||
DictionaryEntity entity = new DictionaryEntity();
|
||||
entity.setId(domain.getId());
|
||||
entity.setType(domain.getType());
|
||||
entity.setCode(domain.getCode());
|
||||
entity.setName(domain.getName());
|
||||
entity.setValue(domain.getValue());
|
||||
entity.setRemark(domain.getRemark());
|
||||
entity.setSort(domain.getSort());
|
||||
entity.setCreateBy(domain.getCreateBy());
|
||||
entity.setCreatedAt(domain.getCreatedAt());
|
||||
entity.setUpdatedAt(domain.getUpdatedAt());
|
||||
return entity;
|
||||
}
|
||||
|
||||
public Dictionary toDomain(DictionaryEntity entity) {
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
Dictionary domain = new Dictionary();
|
||||
domain.setId(entity.getId());
|
||||
domain.setType(entity.getType());
|
||||
domain.setCode(entity.getCode());
|
||||
domain.setName(entity.getName());
|
||||
domain.setValue(entity.getValue());
|
||||
domain.setRemark(entity.getRemark());
|
||||
domain.setSort(entity.getSort());
|
||||
domain.setCreateBy(entity.getCreateBy());
|
||||
domain.setCreatedAt(entity.getCreatedAt());
|
||||
domain.setUpdatedAt(entity.getUpdatedAt());
|
||||
return domain;
|
||||
}
|
||||
|
||||
public List<DictionaryEntity> toEntityList(List<Dictionary> domains) {
|
||||
if (domains == null) {
|
||||
return null;
|
||||
}
|
||||
return domains.stream()
|
||||
.map(this::toEntity)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<Dictionary> toDomainList(List<DictionaryEntity> entities) {
|
||||
if (entities == null) {
|
||||
return null;
|
||||
}
|
||||
return entities.stream()
|
||||
.map(this::toDomain)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
package cn.novalon.gym.manage.db.converter;
|
||||
|
||||
import cn.novalon.gym.manage.sys.core.domain.OperationLog;
|
||||
import cn.novalon.gym.manage.db.entity.OperationLogEntity;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 操作日志实体转换器
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-13
|
||||
*/
|
||||
@Component
|
||||
public class OperationLogConverter {
|
||||
|
||||
public OperationLog toDomain(OperationLogEntity entity) {
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
OperationLog domain = new OperationLog();
|
||||
domain.setId(entity.getId());
|
||||
domain.setUsername(entity.getUsername());
|
||||
domain.setOperation(entity.getOperation());
|
||||
domain.setMethod(entity.getMethod());
|
||||
domain.setParams(entity.getParams());
|
||||
domain.setResult(entity.getResult());
|
||||
domain.setIp(entity.getIp());
|
||||
domain.setDuration(entity.getDuration());
|
||||
domain.setStatus(entity.getStatus());
|
||||
domain.setErrorMsg(entity.getErrorMsg());
|
||||
domain.setCreatedAt(entity.getCreatedAt());
|
||||
domain.setUpdatedAt(entity.getUpdatedAt());
|
||||
domain.setDeletedAt(entity.getDeletedAt());
|
||||
return domain;
|
||||
}
|
||||
|
||||
public OperationLogEntity toEntity(OperationLog domain) {
|
||||
if (domain == null) {
|
||||
return null;
|
||||
}
|
||||
OperationLogEntity entity = new OperationLogEntity();
|
||||
entity.setId(domain.getId());
|
||||
entity.setUsername(domain.getUsername());
|
||||
entity.setOperation(domain.getOperation());
|
||||
entity.setMethod(domain.getMethod());
|
||||
entity.setParams(domain.getParams());
|
||||
entity.setResult(domain.getResult());
|
||||
entity.setIp(domain.getIp());
|
||||
entity.setDuration(domain.getDuration());
|
||||
entity.setStatus(domain.getStatus());
|
||||
entity.setErrorMsg(domain.getErrorMsg());
|
||||
entity.setCreatedAt(domain.getCreatedAt());
|
||||
entity.setUpdatedAt(domain.getUpdatedAt());
|
||||
entity.setDeletedAt(domain.getDeletedAt());
|
||||
return entity;
|
||||
}
|
||||
|
||||
public List<OperationLog> toDomainList(List<OperationLogEntity> entities) {
|
||||
if (entities == null) {
|
||||
return null;
|
||||
}
|
||||
return entities.stream()
|
||||
.map(this::toDomain)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<OperationLogEntity> toEntityList(List<OperationLog> domains) {
|
||||
if (domains == null) {
|
||||
return null;
|
||||
}
|
||||
return domains.stream()
|
||||
.map(this::toEntity)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
package cn.novalon.gym.manage.db.converter;
|
||||
|
||||
import cn.novalon.gym.manage.sys.core.domain.SysConfig;
|
||||
import cn.novalon.gym.manage.db.entity.SysConfigEntity;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 系统配置实体转换器
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-13
|
||||
*/
|
||||
@Component
|
||||
public class SysConfigConverter {
|
||||
|
||||
public SysConfig toDomain(SysConfigEntity entity) {
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
SysConfig domain = new SysConfig();
|
||||
domain.setId(entity.getId());
|
||||
domain.setConfigName(entity.getConfigName());
|
||||
domain.setConfigKey(entity.getConfigKey());
|
||||
domain.setConfigValue(entity.getConfigValue());
|
||||
domain.setConfigType(entity.getConfigType());
|
||||
domain.setCreatedAt(entity.getCreatedAt());
|
||||
domain.setUpdatedAt(entity.getUpdatedAt());
|
||||
return domain;
|
||||
}
|
||||
|
||||
public SysConfigEntity toEntity(SysConfig domain) {
|
||||
if (domain == null) {
|
||||
return null;
|
||||
}
|
||||
SysConfigEntity entity = new SysConfigEntity();
|
||||
entity.setId(domain.getId());
|
||||
entity.setConfigName(domain.getConfigName());
|
||||
entity.setConfigKey(domain.getConfigKey());
|
||||
entity.setConfigValue(domain.getConfigValue());
|
||||
entity.setConfigType(domain.getConfigType());
|
||||
entity.setCreateBy(domain.getCreateBy());
|
||||
entity.setUpdateBy(domain.getUpdateBy());
|
||||
entity.setCreatedAt(domain.getCreatedAt());
|
||||
entity.setUpdatedAt(domain.getUpdatedAt());
|
||||
entity.setDeletedAt(domain.getDeletedAt());
|
||||
return entity;
|
||||
}
|
||||
|
||||
public List<SysConfig> toDomainList(List<SysConfigEntity> entities) {
|
||||
if (entities == null) {
|
||||
return null;
|
||||
}
|
||||
return entities.stream()
|
||||
.map(this::toDomain)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<SysConfigEntity> toEntityList(List<SysConfig> domains) {
|
||||
if (domains == null) {
|
||||
return null;
|
||||
}
|
||||
return domains.stream()
|
||||
.map(this::toEntity)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
package cn.novalon.gym.manage.db.converter;
|
||||
|
||||
import cn.novalon.gym.manage.sys.core.domain.SysDictData;
|
||||
import cn.novalon.gym.manage.db.entity.SysDictDataEntity;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 字典数据实体转换器
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-13
|
||||
*/
|
||||
@Component
|
||||
public class SysDictDataConverter {
|
||||
|
||||
public SysDictData toDomain(SysDictDataEntity entity) {
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
SysDictData domain = new SysDictData();
|
||||
domain.setId(entity.getId());
|
||||
domain.setDictSort(entity.getDictSort());
|
||||
domain.setDictLabel(entity.getDictLabel());
|
||||
domain.setDictValue(entity.getDictValue());
|
||||
domain.setDictType(entity.getDictType());
|
||||
domain.setCssClass(entity.getCssClass());
|
||||
domain.setListClass(entity.getListClass());
|
||||
domain.setIsDefault(entity.getIsDefault());
|
||||
domain.setStatus(entity.getStatus());
|
||||
domain.setCreatedAt(entity.getCreatedAt());
|
||||
domain.setUpdatedAt(entity.getUpdatedAt());
|
||||
return domain;
|
||||
}
|
||||
|
||||
public SysDictDataEntity toEntity(SysDictData domain) {
|
||||
if (domain == null) {
|
||||
return null;
|
||||
}
|
||||
SysDictDataEntity entity = new SysDictDataEntity();
|
||||
entity.setId(domain.getId());
|
||||
entity.setDictSort(domain.getDictSort());
|
||||
entity.setDictLabel(domain.getDictLabel());
|
||||
entity.setDictValue(domain.getDictValue());
|
||||
entity.setDictType(domain.getDictType());
|
||||
entity.setCssClass(domain.getCssClass());
|
||||
entity.setListClass(domain.getListClass());
|
||||
entity.setIsDefault(domain.getIsDefault());
|
||||
entity.setStatus(domain.getStatus());
|
||||
entity.setCreatedAt(domain.getCreatedAt());
|
||||
entity.setUpdatedAt(domain.getUpdatedAt());
|
||||
return entity;
|
||||
}
|
||||
|
||||
public List<SysDictData> toDomainList(List<SysDictDataEntity> entities) {
|
||||
if (entities == null) {
|
||||
return null;
|
||||
}
|
||||
return entities.stream()
|
||||
.map(this::toDomain)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<SysDictDataEntity> toEntityList(List<SysDictData> domains) {
|
||||
if (domains == null) {
|
||||
return null;
|
||||
}
|
||||
return domains.stream()
|
||||
.map(this::toEntity)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
package cn.novalon.gym.manage.db.converter;
|
||||
|
||||
import cn.novalon.gym.manage.sys.core.domain.SysDictType;
|
||||
import cn.novalon.gym.manage.db.entity.SysDictTypeEntity;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 字典类型实体转换器
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-13
|
||||
*/
|
||||
@Component
|
||||
public class SysDictTypeConverter {
|
||||
|
||||
public SysDictType toDomain(SysDictTypeEntity entity) {
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
SysDictType domain = new SysDictType();
|
||||
domain.setId(entity.getId());
|
||||
domain.setDictName(entity.getDictName());
|
||||
domain.setDictType(entity.getDictType());
|
||||
domain.setStatus(entity.getStatus());
|
||||
domain.setRemark(entity.getRemark());
|
||||
domain.setCreatedAt(entity.getCreatedAt());
|
||||
domain.setUpdatedAt(entity.getUpdatedAt());
|
||||
return domain;
|
||||
}
|
||||
|
||||
public SysDictTypeEntity toEntity(SysDictType domain) {
|
||||
if (domain == null) {
|
||||
return null;
|
||||
}
|
||||
SysDictTypeEntity entity = new SysDictTypeEntity();
|
||||
entity.setId(domain.getId());
|
||||
entity.setDictName(domain.getDictName());
|
||||
entity.setDictType(domain.getDictType());
|
||||
entity.setStatus(domain.getStatus());
|
||||
entity.setRemark(domain.getRemark());
|
||||
entity.setCreatedAt(domain.getCreatedAt());
|
||||
entity.setUpdatedAt(domain.getUpdatedAt());
|
||||
return entity;
|
||||
}
|
||||
|
||||
public List<SysDictType> toDomainList(List<SysDictTypeEntity> entities) {
|
||||
if (entities == null) {
|
||||
return null;
|
||||
}
|
||||
return entities.stream()
|
||||
.map(this::toDomain)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<SysDictTypeEntity> toEntityList(List<SysDictType> domains) {
|
||||
if (domains == null) {
|
||||
return null;
|
||||
}
|
||||
return domains.stream()
|
||||
.map(this::toEntity)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
package cn.novalon.gym.manage.db.converter;
|
||||
|
||||
import cn.novalon.gym.manage.sys.core.domain.SysExceptionLog;
|
||||
import cn.novalon.gym.manage.db.entity.SysExceptionLogEntity;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 异常日志实体转换器
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-13
|
||||
*/
|
||||
@Component
|
||||
public class SysExceptionLogConverter {
|
||||
|
||||
public SysExceptionLog toDomain(SysExceptionLogEntity entity) {
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
SysExceptionLog domain = new SysExceptionLog();
|
||||
domain.setId(entity.getId());
|
||||
domain.setUsername(entity.getUsername());
|
||||
domain.setTitle(entity.getTitle());
|
||||
domain.setExceptionName(entity.getExceptionName());
|
||||
domain.setMethodName(entity.getMethodName());
|
||||
domain.setMethodParams(entity.getMethodParams());
|
||||
domain.setExceptionMsg(entity.getExceptionMsg());
|
||||
domain.setExceptionStack(entity.getExceptionStack());
|
||||
domain.setIp(entity.getIp());
|
||||
domain.setCreateTime(entity.getCreateTime());
|
||||
return domain;
|
||||
}
|
||||
|
||||
public SysExceptionLogEntity toEntity(SysExceptionLog domain) {
|
||||
if (domain == null) {
|
||||
return null;
|
||||
}
|
||||
SysExceptionLogEntity entity = new SysExceptionLogEntity();
|
||||
entity.setId(domain.getId());
|
||||
entity.setUsername(domain.getUsername());
|
||||
entity.setTitle(domain.getTitle());
|
||||
entity.setExceptionName(domain.getExceptionName());
|
||||
entity.setMethodName(domain.getMethodName());
|
||||
entity.setMethodParams(domain.getMethodParams());
|
||||
entity.setExceptionMsg(domain.getExceptionMsg());
|
||||
entity.setExceptionStack(domain.getExceptionStack());
|
||||
entity.setIp(domain.getIp());
|
||||
entity.setCreateTime(domain.getCreateTime());
|
||||
return entity;
|
||||
}
|
||||
|
||||
public List<SysExceptionLog> toDomainList(List<SysExceptionLogEntity> entities) {
|
||||
if (entities == null) {
|
||||
return null;
|
||||
}
|
||||
return entities.stream()
|
||||
.map(this::toDomain)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<SysExceptionLogEntity> toEntityList(List<SysExceptionLog> domains) {
|
||||
if (domains == null) {
|
||||
return null;
|
||||
}
|
||||
return domains.stream()
|
||||
.map(this::toEntity)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
package cn.novalon.gym.manage.db.converter;
|
||||
|
||||
import cn.novalon.gym.manage.file.core.domain.SysFile;
|
||||
import cn.novalon.gym.manage.db.entity.SysFileEntity;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 文件实体转换器
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-13
|
||||
*/
|
||||
@Component
|
||||
public class SysFileConverter {
|
||||
|
||||
public SysFile toDomain(SysFileEntity entity) {
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
SysFile domain = new SysFile();
|
||||
domain.setId(entity.getId());
|
||||
domain.setFileName(entity.getFileName());
|
||||
domain.setFilePath(entity.getFilePath());
|
||||
domain.setFileSize(entity.getFileSize());
|
||||
domain.setFileType(entity.getFileType());
|
||||
domain.setStorageType(entity.getStorageType());
|
||||
domain.setCreateBy(entity.getCreateBy());
|
||||
domain.setCreatedAt(entity.getCreatedAt());
|
||||
return domain;
|
||||
}
|
||||
|
||||
public SysFileEntity toEntity(SysFile domain) {
|
||||
if (domain == null) {
|
||||
return null;
|
||||
}
|
||||
SysFileEntity entity = new SysFileEntity();
|
||||
entity.setId(domain.getId());
|
||||
entity.setFileName(domain.getFileName());
|
||||
entity.setFilePath(domain.getFilePath());
|
||||
entity.setFileSize(domain.getFileSize());
|
||||
entity.setFileType(domain.getFileType());
|
||||
entity.setStorageType(domain.getStorageType());
|
||||
entity.setCreateBy(domain.getCreateBy());
|
||||
entity.setCreatedAt(domain.getCreatedAt());
|
||||
return entity;
|
||||
}
|
||||
|
||||
public List<SysFile> toDomainList(List<SysFileEntity> entities) {
|
||||
if (entities == null) {
|
||||
return null;
|
||||
}
|
||||
return entities.stream()
|
||||
.map(this::toDomain)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<SysFileEntity> toEntityList(List<SysFile> domains) {
|
||||
if (domains == null) {
|
||||
return null;
|
||||
}
|
||||
return domains.stream()
|
||||
.map(this::toEntity)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
package cn.novalon.gym.manage.db.converter;
|
||||
|
||||
import cn.novalon.gym.manage.sys.core.domain.SysLoginLog;
|
||||
import cn.novalon.gym.manage.db.entity.SysLoginLogEntity;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 登录日志实体转换器
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-13
|
||||
*/
|
||||
@Component
|
||||
public class SysLoginLogConverter {
|
||||
|
||||
public SysLoginLog toDomain(SysLoginLogEntity entity) {
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
SysLoginLog domain = new SysLoginLog();
|
||||
domain.setId(entity.getId());
|
||||
domain.setUsername(entity.getUsername());
|
||||
domain.setIp(entity.getIp());
|
||||
domain.setLocation(entity.getLocation());
|
||||
domain.setBrowser(entity.getBrowser());
|
||||
domain.setOs(entity.getOs());
|
||||
domain.setStatus(entity.getStatus());
|
||||
domain.setMessage(entity.getMessage());
|
||||
domain.setLoginTime(entity.getLoginTime());
|
||||
return domain;
|
||||
}
|
||||
|
||||
public SysLoginLogEntity toEntity(SysLoginLog domain) {
|
||||
if (domain == null) {
|
||||
return null;
|
||||
}
|
||||
SysLoginLogEntity entity = new SysLoginLogEntity();
|
||||
entity.setId(domain.getId());
|
||||
entity.setUsername(domain.getUsername());
|
||||
entity.setIp(domain.getIp());
|
||||
entity.setLocation(domain.getLocation());
|
||||
entity.setBrowser(domain.getBrowser());
|
||||
entity.setOs(domain.getOs());
|
||||
entity.setStatus(domain.getStatus());
|
||||
entity.setMessage(domain.getMessage());
|
||||
entity.setLoginTime(domain.getLoginTime());
|
||||
return entity;
|
||||
}
|
||||
|
||||
public List<SysLoginLog> toDomainList(List<SysLoginLogEntity> entities) {
|
||||
if (entities == null) {
|
||||
return null;
|
||||
}
|
||||
return entities.stream()
|
||||
.map(this::toDomain)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<SysLoginLogEntity> toEntityList(List<SysLoginLog> domains) {
|
||||
if (domains == null) {
|
||||
return null;
|
||||
}
|
||||
return domains.stream()
|
||||
.map(this::toEntity)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
package cn.novalon.gym.manage.db.converter;
|
||||
|
||||
import cn.novalon.gym.manage.sys.core.domain.SysMenu;
|
||||
import cn.novalon.gym.manage.db.entity.SysMenuEntity;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 菜单实体转换器
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-13
|
||||
*/
|
||||
@Component
|
||||
public class SysMenuConverter {
|
||||
|
||||
public SysMenu toDomain(SysMenuEntity entity) {
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
SysMenu domain = new SysMenu();
|
||||
domain.setId(entity.getId());
|
||||
domain.setMenuName(entity.getMenuName());
|
||||
domain.setParentId(entity.getParentId());
|
||||
domain.setOrderNum(entity.getOrderNum());
|
||||
domain.setMenuType(entity.getMenuType());
|
||||
domain.setPerms(entity.getPerms());
|
||||
domain.setComponent(entity.getComponent());
|
||||
domain.setStatus(entity.getStatus());
|
||||
domain.setCreateBy(entity.getCreateBy());
|
||||
domain.setUpdateBy(entity.getUpdateBy());
|
||||
domain.setCreatedAt(entity.getCreatedAt());
|
||||
domain.setUpdatedAt(entity.getUpdatedAt());
|
||||
domain.setDeletedAt(entity.getDeletedAt());
|
||||
return domain;
|
||||
}
|
||||
|
||||
public SysMenuEntity toEntity(SysMenu domain) {
|
||||
if (domain == null) {
|
||||
return null;
|
||||
}
|
||||
SysMenuEntity entity = new SysMenuEntity();
|
||||
entity.setId(domain.getId());
|
||||
entity.setMenuName(domain.getMenuName());
|
||||
entity.setParentId(domain.getParentId());
|
||||
entity.setOrderNum(domain.getOrderNum());
|
||||
entity.setMenuType(domain.getMenuType());
|
||||
entity.setPerms(domain.getPerms());
|
||||
entity.setComponent(domain.getComponent());
|
||||
entity.setStatus(domain.getStatus());
|
||||
entity.setCreateBy(domain.getCreateBy());
|
||||
entity.setUpdateBy(domain.getUpdateBy());
|
||||
entity.setCreatedAt(domain.getCreatedAt());
|
||||
entity.setUpdatedAt(domain.getUpdatedAt());
|
||||
entity.setDeletedAt(domain.getDeletedAt());
|
||||
return entity;
|
||||
}
|
||||
|
||||
public List<SysMenu> toDomainList(List<SysMenuEntity> entities) {
|
||||
if (entities == null) {
|
||||
return null;
|
||||
}
|
||||
return entities.stream()
|
||||
.map(this::toDomain)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<SysMenuEntity> toEntityList(List<SysMenu> domains) {
|
||||
if (domains == null) {
|
||||
return null;
|
||||
}
|
||||
return domains.stream()
|
||||
.map(this::toEntity)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
package cn.novalon.gym.manage.db.converter;
|
||||
|
||||
import cn.novalon.gym.manage.notify.core.domain.SysNotice;
|
||||
import cn.novalon.gym.manage.db.entity.SysNoticeEntity;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 通知公告实体转换器
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-13
|
||||
*/
|
||||
@Component
|
||||
public class SysNoticeConverter {
|
||||
|
||||
public SysNotice toDomain(SysNoticeEntity entity) {
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
SysNotice domain = new SysNotice();
|
||||
domain.setId(entity.getId());
|
||||
domain.setNoticeTitle(entity.getNoticeTitle());
|
||||
domain.setNoticeType(entity.getNoticeType());
|
||||
domain.setNoticeContent(entity.getNoticeContent());
|
||||
domain.setStatus(entity.getStatus());
|
||||
domain.setCreatedAt(entity.getCreatedAt());
|
||||
domain.setUpdatedAt(entity.getUpdatedAt());
|
||||
domain.setDeletedAt(entity.getDeletedAt());
|
||||
return domain;
|
||||
}
|
||||
|
||||
public SysNoticeEntity toEntity(SysNotice domain) {
|
||||
if (domain == null) {
|
||||
return null;
|
||||
}
|
||||
SysNoticeEntity entity = new SysNoticeEntity();
|
||||
entity.setId(domain.getId());
|
||||
entity.setNoticeTitle(domain.getNoticeTitle());
|
||||
entity.setNoticeType(domain.getNoticeType());
|
||||
entity.setNoticeContent(domain.getNoticeContent());
|
||||
entity.setStatus(domain.getStatus());
|
||||
entity.setCreatedAt(domain.getCreatedAt());
|
||||
entity.setUpdatedAt(domain.getUpdatedAt());
|
||||
entity.setDeletedAt(domain.getDeletedAt());
|
||||
return entity;
|
||||
}
|
||||
|
||||
public List<SysNotice> toDomainList(List<SysNoticeEntity> entities) {
|
||||
if (entities == null) {
|
||||
return null;
|
||||
}
|
||||
return entities.stream()
|
||||
.map(this::toDomain)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<SysNoticeEntity> toEntityList(List<SysNotice> domains) {
|
||||
if (domains == null) {
|
||||
return null;
|
||||
}
|
||||
return domains.stream()
|
||||
.map(this::toEntity)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
package cn.novalon.gym.manage.db.converter;
|
||||
|
||||
import cn.novalon.gym.manage.sys.core.domain.SysPermission;
|
||||
import cn.novalon.gym.manage.db.entity.SysPermissionEntity;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 权限实体转换器
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-25
|
||||
*/
|
||||
@Component
|
||||
public class SysPermissionConverter {
|
||||
|
||||
public SysPermission toDomain(SysPermissionEntity entity) {
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
SysPermission domain = new SysPermission();
|
||||
domain.setId(entity.getId());
|
||||
domain.setPermissionName(entity.getPermissionName());
|
||||
domain.setPermissionCode(entity.getPermissionCode());
|
||||
domain.setResource(entity.getResource());
|
||||
domain.setAction(entity.getAction());
|
||||
domain.setDescription(entity.getDescription());
|
||||
domain.setStatus(entity.getStatus());
|
||||
domain.setCreatedAt(entity.getCreatedAt());
|
||||
domain.setUpdatedAt(entity.getUpdatedAt());
|
||||
domain.setDeletedAt(entity.getDeletedAt());
|
||||
return domain;
|
||||
}
|
||||
|
||||
public SysPermissionEntity toEntity(SysPermission domain) {
|
||||
if (domain == null) {
|
||||
return null;
|
||||
}
|
||||
SysPermissionEntity entity = new SysPermissionEntity();
|
||||
entity.setId(domain.getId());
|
||||
entity.setPermissionName(domain.getPermissionName());
|
||||
entity.setPermissionCode(domain.getPermissionCode());
|
||||
entity.setResource(domain.getResource());
|
||||
entity.setAction(domain.getAction());
|
||||
entity.setDescription(domain.getDescription());
|
||||
entity.setStatus(domain.getStatus());
|
||||
entity.setCreatedAt(domain.getCreatedAt());
|
||||
entity.setUpdatedAt(domain.getUpdatedAt());
|
||||
entity.setDeletedAt(domain.getDeletedAt());
|
||||
return entity;
|
||||
}
|
||||
|
||||
public List<SysPermission> toDomainList(List<SysPermissionEntity> entities) {
|
||||
if (entities == null) {
|
||||
return null;
|
||||
}
|
||||
return entities.stream()
|
||||
.map(this::toDomain)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<SysPermissionEntity> toEntityList(List<SysPermission> domains) {
|
||||
if (domains == null) {
|
||||
return null;
|
||||
}
|
||||
return domains.stream()
|
||||
.map(this::toEntity)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
package cn.novalon.gym.manage.db.converter;
|
||||
|
||||
import cn.novalon.gym.manage.sys.core.domain.SysRole;
|
||||
import cn.novalon.gym.manage.db.entity.SysRoleEntity;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 角色实体转换器
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-13
|
||||
*/
|
||||
@Component
|
||||
public class SysRoleConverter {
|
||||
|
||||
public SysRole toDomain(SysRoleEntity entity) {
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
SysRole domain = new SysRole();
|
||||
domain.setId(entity.getId());
|
||||
domain.setRoleName(entity.getRoleName());
|
||||
domain.setRoleKey(entity.getRoleKey());
|
||||
domain.setRoleSort(entity.getRoleSort());
|
||||
domain.setStatus(entity.getStatus());
|
||||
domain.setCreatedAt(entity.getCreatedAt());
|
||||
domain.setUpdatedAt(entity.getUpdatedAt());
|
||||
domain.setDeletedAt(entity.getDeletedAt());
|
||||
return domain;
|
||||
}
|
||||
|
||||
public SysRoleEntity toEntity(SysRole domain) {
|
||||
if (domain == null) {
|
||||
return null;
|
||||
}
|
||||
SysRoleEntity entity = new SysRoleEntity();
|
||||
entity.setId(domain.getId());
|
||||
entity.setRoleName(domain.getRoleName());
|
||||
entity.setRoleKey(domain.getRoleKey());
|
||||
entity.setRoleSort(domain.getRoleSort());
|
||||
entity.setStatus(domain.getStatus());
|
||||
entity.setCreatedAt(domain.getCreatedAt());
|
||||
entity.setUpdatedAt(domain.getUpdatedAt());
|
||||
entity.setDeletedAt(domain.getDeletedAt());
|
||||
return entity;
|
||||
}
|
||||
|
||||
public List<SysRole> toDomainList(List<SysRoleEntity> entities) {
|
||||
if (entities == null) {
|
||||
return null;
|
||||
}
|
||||
return entities.stream()
|
||||
.map(this::toDomain)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<SysRoleEntity> toEntityList(List<SysRole> domains) {
|
||||
if (domains == null) {
|
||||
return null;
|
||||
}
|
||||
return domains.stream()
|
||||
.map(this::toEntity)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
package cn.novalon.gym.manage.db.converter;
|
||||
|
||||
import cn.novalon.gym.manage.sys.core.domain.SysRolePermission;
|
||||
import cn.novalon.gym.manage.db.entity.SysRolePermissionEntity;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 角色权限关联实体转换器
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-25
|
||||
*/
|
||||
@Component
|
||||
public class SysRolePermissionConverter {
|
||||
|
||||
public SysRolePermission toDomain(SysRolePermissionEntity entity) {
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
SysRolePermission domain = new SysRolePermission();
|
||||
domain.setId(entity.getId());
|
||||
domain.setRoleId(entity.getRoleId());
|
||||
domain.setPermissionId(entity.getPermissionId());
|
||||
domain.setCreatedAt(entity.getCreatedAt());
|
||||
domain.setUpdatedAt(entity.getUpdatedAt());
|
||||
return domain;
|
||||
}
|
||||
|
||||
public SysRolePermissionEntity toEntity(SysRolePermission domain) {
|
||||
if (domain == null) {
|
||||
return null;
|
||||
}
|
||||
SysRolePermissionEntity entity = new SysRolePermissionEntity();
|
||||
entity.setId(domain.getId());
|
||||
entity.setRoleId(domain.getRoleId());
|
||||
entity.setPermissionId(domain.getPermissionId());
|
||||
entity.setCreatedAt(domain.getCreatedAt());
|
||||
entity.setUpdatedAt(domain.getUpdatedAt());
|
||||
return entity;
|
||||
}
|
||||
|
||||
public List<SysRolePermission> toDomainList(List<SysRolePermissionEntity> entities) {
|
||||
if (entities == null) {
|
||||
return null;
|
||||
}
|
||||
return entities.stream()
|
||||
.map(this::toDomain)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<SysRolePermissionEntity> toEntityList(List<SysRolePermission> domains) {
|
||||
if (domains == null) {
|
||||
return null;
|
||||
}
|
||||
return domains.stream()
|
||||
.map(this::toEntity)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
package cn.novalon.gym.manage.db.converter;
|
||||
|
||||
import cn.novalon.gym.manage.sys.core.domain.SysUser;
|
||||
import cn.novalon.gym.manage.db.entity.SysUserEntity;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 用户实体转换器
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-13
|
||||
*/
|
||||
@Component
|
||||
public class SysUserConverter {
|
||||
|
||||
public SysUser toDomain(SysUserEntity entity) {
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
SysUser domain = new SysUser();
|
||||
domain.setId(entity.getId());
|
||||
domain.setUsername(entity.getUsername());
|
||||
domain.setPassword(entity.getPassword());
|
||||
domain.setEmail(entity.getEmail());
|
||||
domain.setPhone(entity.getPhone());
|
||||
domain.setNickname(entity.getNickname());
|
||||
domain.setRoleId(entity.getRoleId());
|
||||
domain.setStatus(entity.getStatus());
|
||||
domain.setCreatedAt(entity.getCreatedAt());
|
||||
domain.setUpdatedAt(entity.getUpdatedAt());
|
||||
domain.setDeletedAt(entity.getDeletedAt());
|
||||
return domain;
|
||||
}
|
||||
|
||||
public SysUserEntity toEntity(SysUser domain) {
|
||||
if (domain == null) {
|
||||
return null;
|
||||
}
|
||||
SysUserEntity entity = new SysUserEntity();
|
||||
entity.setId(domain.getId());
|
||||
entity.setUsername(domain.getUsername());
|
||||
entity.setPassword(domain.getPassword());
|
||||
entity.setEmail(domain.getEmail());
|
||||
entity.setPhone(domain.getPhone());
|
||||
entity.setNickname(domain.getNickname());
|
||||
entity.setRoleId(domain.getRoleId());
|
||||
entity.setStatus(domain.getStatus());
|
||||
entity.setCreatedAt(domain.getCreatedAt());
|
||||
entity.setUpdatedAt(domain.getUpdatedAt());
|
||||
entity.setDeletedAt(domain.getDeletedAt());
|
||||
return entity;
|
||||
}
|
||||
|
||||
public List<SysUser> toDomainList(List<SysUserEntity> entities) {
|
||||
if (entities == null) {
|
||||
return null;
|
||||
}
|
||||
return entities.stream()
|
||||
.map(this::toDomain)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<SysUserEntity> toEntityList(List<SysUser> domains) {
|
||||
if (domains == null) {
|
||||
return null;
|
||||
}
|
||||
return domains.stream()
|
||||
.map(this::toEntity)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
package cn.novalon.gym.manage.db.converter;
|
||||
|
||||
import cn.novalon.gym.manage.notify.core.domain.SysUserMessage;
|
||||
import cn.novalon.gym.manage.db.entity.SysUserMessageEntity;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 用户消息实体转换器
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-13
|
||||
*/
|
||||
@Component
|
||||
public class SysUserMessageConverter {
|
||||
|
||||
public SysUserMessage toDomain(SysUserMessageEntity entity) {
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
SysUserMessage domain = new SysUserMessage();
|
||||
domain.setId(entity.getId());
|
||||
domain.setUserId(entity.getUserId());
|
||||
domain.setTitle(entity.getTitle());
|
||||
domain.setContent(entity.getContent());
|
||||
domain.setMessageType(entity.getMessageType());
|
||||
domain.setIsRead(entity.getIsRead());
|
||||
domain.setCreateTime(entity.getCreateTime());
|
||||
return domain;
|
||||
}
|
||||
|
||||
public SysUserMessageEntity toEntity(SysUserMessage domain) {
|
||||
if (domain == null) {
|
||||
return null;
|
||||
}
|
||||
SysUserMessageEntity entity = new SysUserMessageEntity();
|
||||
entity.setId(domain.getId());
|
||||
entity.setUserId(domain.getUserId());
|
||||
entity.setTitle(domain.getTitle());
|
||||
entity.setContent(domain.getContent());
|
||||
entity.setMessageType(domain.getMessageType());
|
||||
entity.setIsRead(domain.getIsRead());
|
||||
entity.setCreateTime(domain.getCreateTime());
|
||||
return entity;
|
||||
}
|
||||
|
||||
public List<SysUserMessage> toDomainList(List<SysUserMessageEntity> entities) {
|
||||
if (entities == null) {
|
||||
return null;
|
||||
}
|
||||
return entities.stream()
|
||||
.map(this::toDomain)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<SysUserMessageEntity> toEntityList(List<SysUserMessage> domains) {
|
||||
if (domains == null) {
|
||||
return null;
|
||||
}
|
||||
return domains.stream()
|
||||
.map(this::toEntity)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package cn.novalon.gym.manage.db.converter;
|
||||
|
||||
import cn.novalon.gym.manage.db.entity.UserRoleEntity;
|
||||
import cn.novalon.gym.manage.sys.core.domain.UserRole;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class UserRoleConverter {
|
||||
|
||||
public UserRole toDomain(UserRoleEntity entity) {
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
UserRole domain = new UserRole();
|
||||
domain.setId(entity.getId());
|
||||
domain.setUserId(entity.getUserId());
|
||||
domain.setRoleId(entity.getRoleId());
|
||||
domain.setCreatedAt(entity.getCreatedAt());
|
||||
domain.setCreatedBy(entity.getCreatedBy());
|
||||
return domain;
|
||||
}
|
||||
|
||||
public UserRoleEntity toEntity(UserRole domain) {
|
||||
if (domain == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
UserRoleEntity entity = new UserRoleEntity();
|
||||
entity.setId(domain.getId());
|
||||
entity.setUserId(domain.getUserId());
|
||||
entity.setRoleId(domain.getRoleId());
|
||||
entity.setCreatedAt(domain.getCreatedAt());
|
||||
entity.setCreatedBy(domain.getCreatedBy());
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package cn.novalon.gym.manage.db.dao;
|
||||
|
||||
import cn.novalon.gym.manage.db.entity.AuditLogEntity;
|
||||
import org.springframework.data.r2dbc.repository.R2dbcRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 审计日志数据访问接口
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-04-08
|
||||
*/
|
||||
@Repository
|
||||
public interface AuditLogDao extends R2dbcRepository<AuditLogEntity, Long> {
|
||||
|
||||
Flux<AuditLogEntity> findByEntityTypeAndDeletedAtIsNull(String entityType);
|
||||
|
||||
Flux<AuditLogEntity> findByEntityIdAndDeletedAtIsNull(Long entityId);
|
||||
|
||||
Flux<AuditLogEntity> findByEntityTypeAndEntityIdAndDeletedAtIsNull(String entityType, Long entityId);
|
||||
|
||||
Flux<AuditLogEntity> findByOperatorAndDeletedAtIsNull(String operator);
|
||||
|
||||
Flux<AuditLogEntity> findByOperationTypeAndDeletedAtIsNull(String operationType);
|
||||
|
||||
Flux<AuditLogEntity> findByOperationTimeBetweenAndDeletedAtIsNull(LocalDateTime startTime, LocalDateTime endTime);
|
||||
|
||||
Flux<AuditLogEntity> findByEntityTypeAndOperationTimeBetweenAndDeletedAtIsNull(
|
||||
String entityType,
|
||||
LocalDateTime startTime,
|
||||
LocalDateTime endTime
|
||||
);
|
||||
|
||||
Flux<AuditLogEntity> findByOperatorAndOperationTimeBetweenAndDeletedAtIsNull(
|
||||
String operator,
|
||||
LocalDateTime startTime,
|
||||
LocalDateTime endTime
|
||||
);
|
||||
|
||||
Mono<Long> countByEntityTypeAndDeletedAtIsNull(String entityType);
|
||||
|
||||
Mono<Long> countByOperationTypeAndDeletedAtIsNull(String operationType);
|
||||
|
||||
Mono<Long> countByOperatorAndDeletedAtIsNull(String operator);
|
||||
|
||||
Mono<Long> countByOperationTimeBetweenAndDeletedAtIsNull(LocalDateTime startTime, LocalDateTime endTime);
|
||||
|
||||
Flux<AuditLogEntity> findByDeletedAtIsNull();
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package cn.novalon.gym.manage.db.dao;
|
||||
|
||||
import cn.novalon.gym.manage.db.entity.DictionaryEntity;
|
||||
import org.springframework.data.r2dbc.repository.R2dbcRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* 字典数据访问接口
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-13
|
||||
*/
|
||||
@Repository
|
||||
public interface DictionaryDao extends R2dbcRepository<DictionaryEntity, Long> {
|
||||
|
||||
Flux<DictionaryEntity> findByType(String type);
|
||||
|
||||
Mono<DictionaryEntity> findByTypeAndCode(String type, String code);
|
||||
|
||||
Mono<DictionaryEntity> findByTypeAndCodeAndDeletedAtIsNull(String type, String code);
|
||||
|
||||
Flux<DictionaryEntity> findByDeletedAtIsNull();
|
||||
|
||||
Flux<DictionaryEntity> findByDeletedAtIsNullOrderBySortAsc();
|
||||
|
||||
Mono<Void> deleteByIdAndDeletedAtIsNull(Long id);
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package cn.novalon.gym.manage.db.dao;
|
||||
|
||||
import cn.novalon.gym.manage.db.entity.OperationLogEntity;
|
||||
import org.springframework.data.r2dbc.repository.R2dbcRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Repository
|
||||
public interface OperationLogDao extends R2dbcRepository<OperationLogEntity, Long> {
|
||||
|
||||
Flux<OperationLogEntity> findByUsernameAndDeletedAtIsNull(String username);
|
||||
|
||||
Flux<OperationLogEntity> findByDeletedAtIsNull();
|
||||
|
||||
Mono<Long> countByDeletedAtIsNull();
|
||||
|
||||
Mono<Long> countByCreatedAtAfterAndDeletedAtIsNull(LocalDateTime dateTime);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package cn.novalon.gym.manage.db.dao;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 查询字段注解
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-13
|
||||
*/
|
||||
@Target(ElementType.FIELD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface QueryField {
|
||||
|
||||
String propName() default "";
|
||||
|
||||
String blurry() default "";
|
||||
|
||||
Type type() default Type.EQUAL;
|
||||
|
||||
Type orPropVal() default Type.EQUAL;
|
||||
|
||||
String[] orPropNames() default {};
|
||||
|
||||
enum Type {
|
||||
EQUAL,
|
||||
GREATER_THAN,
|
||||
LESS_THAN,
|
||||
LESS_THAN_NQ,
|
||||
INNER_LIKE,
|
||||
LEFT_LIKE,
|
||||
NOT_LEFT_LIKE,
|
||||
RIGHT_LIKE,
|
||||
IN,
|
||||
OR,
|
||||
IS_NULL,
|
||||
IS_NOT_NULL
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
package cn.novalon.gym.manage.db.dao;
|
||||
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.relational.core.query.Criteria;
|
||||
import org.springframework.data.relational.core.query.Query;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 查询工具类
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-13
|
||||
*/
|
||||
public class QueryUtil {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(QueryUtil.class);
|
||||
|
||||
public static <Q> Query getQuery(Q query) {
|
||||
return getQuery(query, true);
|
||||
}
|
||||
|
||||
public static <Q> Query getQueryAll(Q query) {
|
||||
return getQuery(query, false);
|
||||
}
|
||||
|
||||
public static <Q> Query getQuery(Q query, Boolean enabled) {
|
||||
Criteria criteria = Criteria.empty();
|
||||
if (enabled) {
|
||||
criteria = criteria.and("deletedAt").isNull();
|
||||
}
|
||||
if (query == null) {
|
||||
log.info("Query object is null, returning empty criteria");
|
||||
return Query.query(criteria);
|
||||
}
|
||||
System.out.println("=== QueryUtil.getQuery START ===");
|
||||
System.out.println("Query object class: " + query.getClass().getName());
|
||||
log.info("=== QueryUtil.getQuery START ===");
|
||||
log.info("Query object class: {}", query.getClass().getName());
|
||||
try {
|
||||
List<Field> fields = getAllFields(query.getClass(), new ArrayList<>());
|
||||
log.info("Found {} fields to process", fields.size());
|
||||
System.out.println("Found " + fields.size() + " fields to process");
|
||||
for (Field field : fields) {
|
||||
boolean accessible = Modifier.isStatic(field.getModifiers()) ? field.canAccess(null)
|
||||
: field.canAccess(query);
|
||||
field.setAccessible(true);
|
||||
QueryField q = field.getAnnotation(QueryField.class);
|
||||
if (q != null) {
|
||||
String propName = q.propName();
|
||||
String blurry = q.blurry();
|
||||
String attributeName = isBlank(propName) ? field.getName() : propName;
|
||||
Object val = field.get(query);
|
||||
log.info("Processing field: {}, value: {}, blurry: {}", attributeName, val, blurry);
|
||||
System.out.println("Processing field: " + attributeName + ", value: " + val + ", blurry: " + blurry);
|
||||
if (val == null || "".equals(val)) {
|
||||
log.info("Field {} has null or empty value, skipping", attributeName);
|
||||
System.out.println("Field " + attributeName + " has null or empty value, skipping");
|
||||
continue;
|
||||
}
|
||||
if (StringUtils.isNotBlank(blurry)) {
|
||||
log.info("Field {} has blurry search configuration: {}", attributeName, blurry);
|
||||
System.out.println("Field " + attributeName + " has blurry search configuration: " + blurry);
|
||||
String[] blurrys = blurry.split(",");
|
||||
Criteria orCriteria = null;
|
||||
for (int i = 0; i < blurrys.length; i++) {
|
||||
String s = blurrys[i];
|
||||
if (i == 0) {
|
||||
orCriteria = Criteria.where(s).like("%" + val + "%");
|
||||
} else {
|
||||
orCriteria = orCriteria.or(s).like("%" + val + "%");
|
||||
}
|
||||
}
|
||||
if (orCriteria != null) {
|
||||
criteria = criteria.and(orCriteria);
|
||||
log.info("Added OR criteria for blurry search: {} with value: {}", blurry, val);
|
||||
System.out.println("Added OR criteria for blurry search: " + blurry + " with value: " + val);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
switch (q.type()) {
|
||||
case EQUAL:
|
||||
criteria = criteria.and(attributeName).is(val);
|
||||
break;
|
||||
case GREATER_THAN:
|
||||
criteria = criteria.and(attributeName).greaterThanOrEquals(val);
|
||||
break;
|
||||
case LESS_THAN:
|
||||
criteria = criteria.and(attributeName).lessThanOrEquals(val);
|
||||
break;
|
||||
case LESS_THAN_NQ:
|
||||
criteria = criteria.and(attributeName).lessThan(val);
|
||||
break;
|
||||
case INNER_LIKE:
|
||||
criteria = criteria.and(attributeName).like("%" + val + "%");
|
||||
break;
|
||||
case LEFT_LIKE:
|
||||
criteria = criteria.and(attributeName).like("%" + val);
|
||||
break;
|
||||
case NOT_LEFT_LIKE:
|
||||
criteria = criteria.and(attributeName).notLike("%" + val);
|
||||
break;
|
||||
case RIGHT_LIKE:
|
||||
criteria = criteria.and(attributeName).like(val + "%");
|
||||
break;
|
||||
case IN:
|
||||
if (val instanceof Collection && CollectionUtils.isNotEmpty((Collection<?>) val)) {
|
||||
criteria = criteria.and(attributeName).in((Collection<?>) val);
|
||||
}
|
||||
break;
|
||||
case OR:
|
||||
QueryField.Type orValue = q.orPropVal();
|
||||
String[] orPropNames = q.orPropNames();
|
||||
Criteria orPredicate = Criteria.empty();
|
||||
if (QueryField.Type.IS_NULL.equals(orValue)) {
|
||||
for (String prop : orPropNames) {
|
||||
orPredicate = orPredicate.or(prop).isNull();
|
||||
}
|
||||
}
|
||||
if (QueryField.Type.IS_NOT_NULL.equals(orValue)) {
|
||||
for (String prop : orPropNames) {
|
||||
orPredicate = orPredicate.or(prop).isNotNull();
|
||||
}
|
||||
}
|
||||
criteria = criteria.and(orPredicate);
|
||||
break;
|
||||
case IS_NULL:
|
||||
criteria = criteria.and(attributeName).isNull();
|
||||
break;
|
||||
case IS_NOT_NULL:
|
||||
criteria = criteria.and(attributeName).isNotNull();
|
||||
break;
|
||||
}
|
||||
}
|
||||
field.setAccessible(accessible);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
return Query.query(criteria);
|
||||
}
|
||||
|
||||
public static boolean isBlank(final CharSequence cs) {
|
||||
int strLen;
|
||||
if (cs == null || (strLen = cs.length()) == 0) {
|
||||
return true;
|
||||
}
|
||||
for (int i = 0; i < strLen; i++) {
|
||||
if (!Character.isWhitespace(cs.charAt(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static List<Field> getAllFields(Class<?> clazz, List<Field> fields) {
|
||||
if (clazz != null) {
|
||||
fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
|
||||
getAllFields(clazz.getSuperclass(), fields);
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package cn.novalon.gym.manage.db.dao;
|
||||
|
||||
import cn.novalon.gym.manage.db.entity.SysConfigEntity;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.r2dbc.repository.R2dbcRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@Repository
|
||||
public interface SysConfigDao extends R2dbcRepository<SysConfigEntity, Long> {
|
||||
|
||||
Mono<SysConfigEntity> findByConfigKeyAndDeletedAtIsNull(String configKey);
|
||||
|
||||
Flux<SysConfigEntity> findByDeletedAtIsNull();
|
||||
|
||||
Flux<SysConfigEntity> findByDeletedAtIsNull(Sort sort);
|
||||
|
||||
Mono<Long> countByDeletedAtIsNull();
|
||||
|
||||
Mono<Void> deleteByIdAndDeletedAtIsNull(Long id);
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package cn.novalon.gym.manage.db.dao;
|
||||
|
||||
import cn.novalon.gym.manage.db.entity.SysDictDataEntity;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.r2dbc.repository.R2dbcRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@Repository
|
||||
public interface SysDictDataDao extends R2dbcRepository<SysDictDataEntity, Long> {
|
||||
|
||||
Flux<SysDictDataEntity> findByDictTypeAndStatusAndDeletedAtIsNull(String dictType, String status);
|
||||
|
||||
Flux<SysDictDataEntity> findByDictTypeAndDeletedAtIsNull(String dictType);
|
||||
|
||||
Flux<SysDictDataEntity> findByDictTypeAndDeletedAtIsNull(String dictType, Sort sort);
|
||||
|
||||
Flux<SysDictDataEntity> findByDeletedAtIsNull();
|
||||
|
||||
Flux<SysDictDataEntity> findByDeletedAtIsNull(Sort sort);
|
||||
|
||||
Mono<Long> countByDeletedAtIsNull();
|
||||
|
||||
Mono<Void> deleteByIdAndDeletedAtIsNull(Long id);
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package cn.novalon.gym.manage.db.dao;
|
||||
|
||||
import cn.novalon.gym.manage.db.entity.SysDictTypeEntity;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.r2dbc.repository.R2dbcRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@Repository
|
||||
public interface SysDictTypeDao extends R2dbcRepository<SysDictTypeEntity, Long> {
|
||||
|
||||
Mono<SysDictTypeEntity> findByDictTypeAndDeletedAtIsNull(String dictType);
|
||||
|
||||
Flux<SysDictTypeEntity> findByDeletedAtIsNull();
|
||||
|
||||
Flux<SysDictTypeEntity> findByDeletedAtIsNull(Sort sort);
|
||||
|
||||
Mono<Long> countByDeletedAtIsNull();
|
||||
|
||||
Mono<Void> deleteByIdAndDeletedAtIsNull(Long id);
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package cn.novalon.gym.manage.db.dao;
|
||||
|
||||
import cn.novalon.gym.manage.db.entity.SysExceptionLogEntity;
|
||||
import org.springframework.data.r2dbc.repository.R2dbcRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Repository
|
||||
public interface SysExceptionLogDao extends R2dbcRepository<SysExceptionLogEntity, Long> {
|
||||
|
||||
Flux<SysExceptionLogEntity> findByUsername(String username);
|
||||
|
||||
Flux<SysExceptionLogEntity> findByUsernameOrderByCreateTimeDesc(String username);
|
||||
|
||||
Flux<SysExceptionLogEntity> findByCreateTimeBetweenOrderByCreateTimeDesc(LocalDateTime startTime, LocalDateTime endTime);
|
||||
|
||||
Flux<SysExceptionLogEntity> findAllByOrderByCreateTimeDesc();
|
||||
|
||||
Mono<Long> count();
|
||||
|
||||
Mono<Long> countByUsername(String username);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package cn.novalon.gym.manage.db.dao;
|
||||
|
||||
import cn.novalon.gym.manage.db.entity.SysFileEntity;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.r2dbc.repository.R2dbcRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@Repository
|
||||
public interface SysFileDao extends R2dbcRepository<SysFileEntity, Long> {
|
||||
|
||||
Flux<SysFileEntity> findByCreateBy(String createBy);
|
||||
|
||||
Flux<SysFileEntity> findByCreateBy(String createBy, Sort sort);
|
||||
|
||||
Flux<SysFileEntity> findByCreateByOrderByCreatedAtDesc(String createBy);
|
||||
|
||||
Flux<SysFileEntity> findByDeletedAtIsNull();
|
||||
|
||||
Flux<SysFileEntity> findByDeletedAtIsNull(Sort sort);
|
||||
|
||||
Flux<SysFileEntity> findByDeletedAtIsNullOrderByCreatedAtDesc();
|
||||
|
||||
Mono<Long> countByDeletedAtIsNull();
|
||||
|
||||
Mono<Void> deleteByIdAndDeletedAtIsNull(Long id);
|
||||
|
||||
Flux<SysFileEntity> findByFilePathContaining(String fileName);
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package cn.novalon.gym.manage.db.dao;
|
||||
|
||||
import cn.novalon.gym.manage.db.entity.SysLoginLogEntity;
|
||||
import org.springframework.data.r2dbc.repository.R2dbcRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Repository
|
||||
public interface SysLoginLogDao extends R2dbcRepository<SysLoginLogEntity, Long> {
|
||||
|
||||
Flux<SysLoginLogEntity> findByUsername(String username);
|
||||
|
||||
Flux<SysLoginLogEntity> findByUsernameOrderByLoginTimeDesc(String username);
|
||||
|
||||
Flux<SysLoginLogEntity> findByLoginTimeBetweenOrderByLoginTimeDesc(LocalDateTime startTime, LocalDateTime endTime);
|
||||
|
||||
Flux<SysLoginLogEntity> findAllByOrderByLoginTimeDesc();
|
||||
|
||||
Mono<Long> count();
|
||||
|
||||
Mono<Long> countByUsername(String username);
|
||||
|
||||
Mono<Long> countByLoginTimeBetween(LocalDateTime startTime, LocalDateTime endTime);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package cn.novalon.gym.manage.db.dao;
|
||||
|
||||
import cn.novalon.gym.manage.db.entity.SysMenuEntity;
|
||||
import org.springframework.data.r2dbc.repository.R2dbcRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@Repository
|
||||
public interface SysMenuDao extends R2dbcRepository<SysMenuEntity, Long> {
|
||||
|
||||
Mono<SysMenuEntity> findByIdAndDeletedAtIsNull(Long id);
|
||||
|
||||
Flux<SysMenuEntity> findByParentIdAndDeletedAtIsNull(Long parentId);
|
||||
|
||||
Flux<SysMenuEntity> findByDeletedAtIsNull();
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package cn.novalon.gym.manage.db.dao;
|
||||
|
||||
import cn.novalon.gym.manage.db.entity.SysNoticeEntity;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.r2dbc.repository.R2dbcRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@Repository
|
||||
public interface SysNoticeDao extends R2dbcRepository<SysNoticeEntity, Long> {
|
||||
|
||||
Flux<SysNoticeEntity> findByStatusAndDeletedAtIsNull(String status);
|
||||
|
||||
Flux<SysNoticeEntity> findByStatusAndDeletedAtIsNull(String status, Sort sort);
|
||||
|
||||
Flux<SysNoticeEntity> findByDeletedAtIsNull();
|
||||
|
||||
Flux<SysNoticeEntity> findByDeletedAtIsNull(Sort sort);
|
||||
|
||||
Mono<Long> countByDeletedAtIsNull();
|
||||
|
||||
Mono<Void> deleteByIdAndDeletedAtIsNull(Long id);
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package cn.novalon.gym.manage.db.dao;
|
||||
|
||||
import cn.novalon.gym.manage.db.entity.SysPermissionEntity;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.r2dbc.repository.R2dbcRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@Repository
|
||||
public interface SysPermissionDao extends R2dbcRepository<SysPermissionEntity, Long> {
|
||||
|
||||
Mono<SysPermissionEntity> findByIdAndDeletedAtIsNull(Long id);
|
||||
|
||||
Mono<SysPermissionEntity> findByPermissionCodeAndDeletedAtIsNull(String permissionCode);
|
||||
|
||||
Flux<SysPermissionEntity> findByDeletedAtIsNull();
|
||||
|
||||
Flux<SysPermissionEntity> findByDeletedAtIsNull(Sort sort);
|
||||
|
||||
Mono<Long> countByDeletedAtIsNull();
|
||||
|
||||
Mono<Boolean> existsByPermissionCodeAndDeletedAtIsNull(String permissionCode);
|
||||
|
||||
@org.springframework.data.r2dbc.repository.Query("""
|
||||
SELECT p.* FROM sys_permission p
|
||||
INNER JOIN sys_role_permission rp ON p.id = rp.permission_id
|
||||
WHERE rp.role_id = :roleId AND p.deleted_at IS NULL
|
||||
""")
|
||||
Flux<SysPermissionEntity> findByRoleId(Long roleId);
|
||||
|
||||
@org.springframework.data.r2dbc.repository.Query("""
|
||||
SELECT DISTINCT p.* FROM sys_permission p
|
||||
INNER JOIN sys_role_permission rp ON p.id = rp.permission_id
|
||||
WHERE rp.role_id IN (:roleIds) AND p.deleted_at IS NULL
|
||||
""")
|
||||
Flux<SysPermissionEntity> findByRoleIds(java.util.List<Long> roleIds);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package cn.novalon.gym.manage.db.dao;
|
||||
|
||||
import cn.novalon.gym.manage.db.entity.SysRoleEntity;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.r2dbc.repository.R2dbcRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@Repository
|
||||
public interface SysRoleDao extends R2dbcRepository<SysRoleEntity, Long> {
|
||||
|
||||
Mono<SysRoleEntity> findByIdAndDeletedAtIsNull(Long id);
|
||||
|
||||
Mono<SysRoleEntity> findByRoleKeyAndDeletedAtIsNull(String roleKey);
|
||||
|
||||
Flux<SysRoleEntity> findByDeletedAtIsNull();
|
||||
|
||||
Flux<SysRoleEntity> findByDeletedAtIsNull(Sort sort);
|
||||
|
||||
Flux<SysRoleEntity> findByRoleNameLikeAndRoleKeyLikeAndDeletedAtIsNull(String roleName, String roleKey, Sort sort);
|
||||
|
||||
Mono<Long> countByDeletedAtIsNull();
|
||||
|
||||
Mono<Long> countByRoleNameLikeAndRoleKeyLikeAndDeletedAtIsNull(String roleName, String roleKey);
|
||||
|
||||
Mono<SysRoleEntity> findByRoleNameAndDeletedAtIsNull(String roleName);
|
||||
|
||||
Mono<Boolean> existsByRoleNameAndDeletedAtIsNull(String roleName);
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package cn.novalon.gym.manage.db.dao;
|
||||
|
||||
import cn.novalon.gym.manage.db.entity.SysRolePermissionEntity;
|
||||
import org.springframework.data.r2dbc.repository.Modifying;
|
||||
import org.springframework.data.r2dbc.repository.R2dbcRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@Repository
|
||||
public interface SysRolePermissionDao extends R2dbcRepository<SysRolePermissionEntity, Long> {
|
||||
|
||||
Flux<SysRolePermissionEntity> findByRoleId(Long roleId);
|
||||
|
||||
Flux<SysRolePermissionEntity> findByPermissionId(Long permissionId);
|
||||
|
||||
Flux<Long> findPermissionIdsByRoleId(Long roleId);
|
||||
|
||||
Flux<Long> findRoleIdsByPermissionId(Long permissionId);
|
||||
|
||||
@Modifying
|
||||
@org.springframework.data.r2dbc.repository.Query("""
|
||||
DELETE FROM sys_role_permission
|
||||
WHERE role_id = :roleId AND permission_id IN (:permissionIds)
|
||||
""")
|
||||
Mono<Void> deleteByRoleIdAndPermissionIds(Long roleId, java.util.List<Long> permissionIds);
|
||||
|
||||
@Modifying
|
||||
@org.springframework.data.r2dbc.repository.Query("""
|
||||
DELETE FROM sys_role_permission
|
||||
WHERE permission_id = :permissionId AND role_id IN (:roleIds)
|
||||
""")
|
||||
Mono<Void> deleteByPermissionIdAndRoleIds(Long permissionId, java.util.List<Long> roleIds);
|
||||
|
||||
@Modifying
|
||||
@org.springframework.data.r2dbc.repository.Query("""
|
||||
DELETE FROM sys_role_permission WHERE role_id = :roleId
|
||||
""")
|
||||
Mono<Void> deleteByRoleId(Long roleId);
|
||||
|
||||
@Modifying
|
||||
@org.springframework.data.r2dbc.repository.Query("""
|
||||
DELETE FROM sys_role_permission WHERE permission_id = :permissionId
|
||||
""")
|
||||
Mono<Void> deleteByPermissionId(Long permissionId);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package cn.novalon.gym.manage.db.dao;
|
||||
|
||||
import cn.novalon.gym.manage.db.entity.SysUserEntity;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.r2dbc.repository.R2dbcRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* 用户数据访问接口
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-13
|
||||
*/
|
||||
@Repository
|
||||
public interface SysUserDao extends R2dbcRepository<SysUserEntity, Long> {
|
||||
|
||||
Mono<SysUserEntity> findByUsernameAndDeletedAtIsNull(String username);
|
||||
|
||||
Mono<SysUserEntity> findByEmailAndDeletedAtIsNull(String email);
|
||||
|
||||
Mono<SysUserEntity> findByIdAndDeletedAtIsNull(Long id);
|
||||
|
||||
Flux<SysUserEntity> findAll();
|
||||
|
||||
Flux<SysUserEntity> findAll(Sort sort);
|
||||
|
||||
Flux<SysUserEntity> findByDeletedAtIsNull();
|
||||
|
||||
Flux<SysUserEntity> findByDeletedAtIsNull(Sort sort);
|
||||
|
||||
Mono<Long> countByDeletedAtIsNull();
|
||||
|
||||
Flux<SysUserEntity> findByRoleId(Long roleId);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user