refactor(backend): 重命名后端项目为 gym-manage-api,修改包名为 cn.novalon.gym.manage

This commit is contained in:
张翔
2026-04-17 18:35:50 +08:00
parent 666189b676
commit deb961c427
916 changed files with 108360 additions and 38328 deletions
@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<suppressions xmlns="https://jeremylong.github.io/DependencyCheck/dependency-suppression.1.3.xsd">
</suppressions>
+223
View File
@@ -0,0 +1,223 @@
<?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-sys</artifactId>
<packaging>jar</packaging>
<name>Manage Sys</name>
<description>System Management Module</description>
<dependencies>
<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-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webflux-ui</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</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>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot3</artifactId>
</dependency>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-reactor</artifactId>
</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>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.r2dbc</groupId>
<artifactId>r2dbc-h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>r2dbc-postgresql</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.4.2</version>
<executions>
<execution>
<id>default-jar</id>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>21</source>
<target>21</target>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.5.5.Final</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>0.2.0</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.12</version>
<executions>
<execution>
<id>prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>verify</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
<execution>
<id>check</id>
<phase>verify</phase>
<goals>
<goal>check</goal>
</goals>
<configuration>
<rules>
<rule>
<element>BUNDLE</element>
<limits>
<limit>
<counter>INSTRUCTION</counter>
<value>COVEREDRATIO</value>
<minimum>0.80</minimum>
</limit>
</limits>
</rule>
</rules>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-maven-plugin</artifactId>
<version>4.8.6.0</version>
<dependencies>
<dependency>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs</artifactId>
<version>4.8.6</version>
</dependency>
</dependencies>
<executions>
<execution>
<id>spotbugs-check</id>
<phase>verify</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
<configuration>
<effort>Max</effort>
<threshold>High</threshold>
<failOnError>true</failOnError>
<excludeFilterFile>spotbugs-exclude.xml</excludeFilterFile>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter>
<Match>
<Class name="~.*\.entity\..*" />
</Match>
<Match>
<Class name="~.*\.dto\..*" />
</Match>
<Match>
<Class name="~.*\.converter\..*" />
</Match>
<Match>
<Class name="~.*\.mapper\..*Impl" />
</Match>
<Match>
<Package name="~cn\.novalon\.manage\.sys\.ManageSysApplication" />
</Match>
</FindBugsFilter>
@@ -0,0 +1,300 @@
package cn.novalon.gym.manage.sys.audit;
import cn.novalon.gym.manage.sys.audit.domain.AuditLog;
import cn.novalon.gym.manage.sys.audit.service.IAuditLogService;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Persistable;
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.ArrayList;
import java.util.List;
/**
* 审计日志切面
*
* 文件定义:使用AOP自动拦截Repository操作,记录审计日志
* 涉及业务:自动记录所有数据变更操作,包括变更前后对比
* 算法:使用异步方式记录日志,不阻塞主流程
*
* @author 张翔
* @date 2026-04-01
*/
@Aspect
@Component
public class AuditLogAspect {
private static final Logger logger = LoggerFactory.getLogger(AuditLogAspect.class);
private final IAuditLogService auditLogService;
private final ObjectMapper objectMapper;
public AuditLogAspect(IAuditLogService auditLogService, ObjectMapper objectMapper) {
this.auditLogService = auditLogService;
this.objectMapper = objectMapper;
}
@Around("execution(* cn.novalon.gym.manage.db.repository.*Repository.save(..)) || " +
"execution(* cn.novalon.gym.manage.db.repository.*Repository.delete(..)) || " +
"execution(* cn.novalon.gym.manage.db.repository.*Repository.deleteById(..))")
public Object logAuditEvent(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().getName();
String className = joinPoint.getTarget().getClass().getSimpleName();
Object[] args = joinPoint.getArgs();
String operationType = determineOperationType(methodName);
String entityType = extractEntityType(className);
logger.debug("拦截审计操作: {}.{}, 操作类型: {}, 实体类型: {}",
className, methodName, operationType, entityType);
try {
if ("save".equals(methodName) && args.length > 0) {
return handleSaveOperation(joinPoint, args[0], entityType, operationType);
} else if ("delete".equals(methodName) || "deleteById".equals(methodName)) {
return handleDeleteOperation(joinPoint, args, entityType, operationType);
}
return joinPoint.proceed();
} catch (Throwable error) {
logger.error("审计日志记录失败: {}", error.getMessage(), error);
throw error;
}
}
private Object handleSaveOperation(ProceedingJoinPoint joinPoint, Object entity,
String entityType, String operationType) throws Throwable {
try {
final String[] beforeDataHolder = {null};
final Long[] entityIdHolder = {null};
final String[] operationTypeHolder = {operationType};
if (entity instanceof Persistable) {
Persistable<?> persistable = (Persistable<?>) entity;
entityIdHolder[0] = persistable.getId() != null ?
((Number) persistable.getId()).longValue() : null;
if (entityIdHolder[0] != null) {
beforeDataHolder[0] = fetchEntityBeforeData(entityType, entityIdHolder[0]);
operationTypeHolder[0] = "UPDATE";
} else {
operationTypeHolder[0] = "CREATE";
}
}
Object result = joinPoint.proceed();
if (result instanceof Mono) {
return ((Mono<?>) result).flatMap(savedEntity -> {
String afterData = serializeEntity(savedEntity);
Long finalEntityId = entityIdHolder[0] != null ? entityIdHolder[0] : extractEntityId(savedEntity);
String finalOperationType = operationTypeHolder[0];
String finalBeforeData = beforeDataHolder[0];
logger.debug("保存操作审计日志: entityType={}, entityIdHolder={}, extractedEntityId={}, finalEntityId={}",
entityType, entityIdHolder[0], extractEntityId(savedEntity), finalEntityId);
return createAndSaveAuditLog(
entityType, finalEntityId, finalOperationType,
finalBeforeData, afterData, savedEntity
).thenReturn(savedEntity);
});
}
return result;
} catch (Throwable error) {
logger.error("保存操作审计日志记录失败", error);
throw error;
}
}
private Object handleDeleteOperation(ProceedingJoinPoint joinPoint, Object[] args,
String entityType, String operationType) throws Throwable {
try {
Long entityId = null;
String beforeData = null;
if (args.length > 0) {
if (args[0] instanceof Number) {
entityId = ((Number) args[0]).longValue();
beforeData = fetchEntityBeforeData(entityType, entityId);
} else if (args[0] instanceof Persistable) {
Persistable<?> persistable = (Persistable<?>) args[0];
entityId = persistable.getId() != null ?
((Number) persistable.getId()).longValue() : null;
beforeData = serializeEntity(args[0]);
}
}
Object result = joinPoint.proceed();
if (result instanceof Mono) {
Long finalEntityId = entityId;
String finalBeforeData = beforeData;
return ((Mono<?>) result).flatMap(deleted ->
createAndSaveAuditLog(
entityType, finalEntityId, "DELETE",
finalBeforeData, null, null
).thenReturn(deleted)
);
} else if (result instanceof Flux) {
Long finalEntityId = entityId;
String finalBeforeData = beforeData;
return ((Flux<?>) result).flatMap(deleted ->
createAndSaveAuditLog(
entityType, finalEntityId, "DELETE",
finalBeforeData, null, null
).thenReturn(deleted)
);
}
return result;
} catch (Throwable error) {
logger.error("删除操作审计日志记录失败", error);
throw error;
}
}
private Mono<Void> createAndSaveAuditLog(String entityType, Long entityId,
String operationType, String beforeData,
String afterData, Object entity) {
logger.debug("创建审计日志: entityType={}, entityId={}, operationType={}", entityType, entityId, operationType);
return ReactiveSecurityContextHolder.getContext()
.map(ctx -> ctx.getAuthentication().getPrincipal())
.defaultIfEmpty("system")
.flatMap(principal -> {
AuditLog auditLog = new AuditLog();
auditLog.setEntityType(entityType);
auditLog.setEntityId(entityId != null ? entityId : 0L);
auditLog.setOperationType(operationType);
auditLog.setOperator(principal instanceof String ? (String) principal : "system");
auditLog.setBeforeData(beforeData);
auditLog.setAfterData(afterData);
logger.debug("审计日志对象: entityId={}, entityType={}, operationType={}",
auditLog.getEntityId(), auditLog.getEntityType(), auditLog.getOperationType());
if (beforeData != null && afterData != null) {
String[] changedFields = extractChangedFields(beforeData, afterData);
auditLog.setChangedFields(changedFields);
}
auditLog.setDescription(generateDescription(entityType, operationType, entityId));
return auditLogService.save(auditLog)
.doOnSuccess(saved -> logger.debug("审计日志保存成功: {} - {}",
entityType, operationType))
.doOnError(error -> logger.error("审计日志保存失败: {}",
error.getMessage()))
.then();
})
.onErrorResume(error -> {
logger.error("创建审计日志失败,但不影响主流程: {}", error.getMessage());
return Mono.empty();
});
}
private String determineOperationType(String methodName) {
if (methodName.startsWith("save")) {
return "SAVE";
} else if (methodName.startsWith("delete")) {
return "DELETE";
}
return "UNKNOWN";
}
private String extractEntityType(String className) {
if (className.contains("User")) {
return "User";
} else if (className.contains("Role")) {
return "Role";
} else if (className.contains("Menu")) {
return "Menu";
} else if (className.contains("Permission")) {
return "Permission";
}
return className.replace("Repository", "").replace("Impl", "");
}
private String fetchEntityBeforeData(String entityType, Long entityId) {
return null;
}
private String serializeEntity(Object entity) {
try {
return objectMapper.writeValueAsString(entity);
} catch (Exception e) {
logger.error("序列化实体失败: {}", e.getMessage());
return null;
}
}
private Long extractEntityId(Object entity) {
logger.debug("提取实体ID: entity class={}", entity.getClass().getName());
if (entity instanceof Persistable) {
Persistable<?> persistable = (Persistable<?>) entity;
Object id = persistable.getId();
logger.debug("Persistable实体ID: id={}, isNew={}", id, persistable.isNew());
return id != null ? ((Number) id).longValue() : null;
}
logger.debug("实体不是Persistable类型");
return null;
}
private String[] extractChangedFields(String beforeData, String afterData) {
try {
JsonNode beforeNode = objectMapper.readTree(beforeData);
JsonNode afterNode = objectMapper.readTree(afterData);
List<String> changedFields = new ArrayList<>();
beforeNode.fieldNames().forEachRemaining(fieldName -> {
JsonNode beforeValue = beforeNode.get(fieldName);
JsonNode afterValue = afterNode.get(fieldName);
if (afterValue == null || !beforeValue.equals(afterValue)) {
changedFields.add(fieldName);
}
});
afterNode.fieldNames().forEachRemaining(fieldName -> {
if (!beforeNode.has(fieldName)) {
changedFields.add(fieldName);
}
});
return changedFields.toArray(new String[0]);
} catch (Exception e) {
logger.error("提取变更字段失败: {}", e.getMessage());
return new String[0];
}
}
private String generateDescription(String entityType, String operationType, Long entityId) {
String operation = "";
switch (operationType) {
case "CREATE":
operation = "创建";
break;
case "UPDATE":
operation = "更新";
break;
case "DELETE":
operation = "删除";
break;
default:
operation = "操作";
}
return String.format("%s%s (ID: %s)", operation, entityType,
entityId != null ? entityId : "未知");
}
}
@@ -0,0 +1,28 @@
package cn.novalon.gym.manage.sys.audit;
import java.lang.annotation.*;
/**
* 操作日志注解
* 标记需要记录操作日志的方法
*
* @author 张翔
* @date 2026-04-03
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperationLog {
/**
* 操作名称
* 例如:"创建用户"、"删除角色"
*/
String operation();
/**
* 模块名称
* 例如:"用户管理"、"角色管理"
*/
String module();
}
@@ -0,0 +1,154 @@
package cn.novalon.gym.manage.sys.audit;
import cn.novalon.gym.manage.sys.core.domain.OperationLog;
import cn.novalon.gym.manage.sys.core.service.IOperationLogService;
import cn.novalon.gym.manage.sys.util.IpUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@Aspect
@Component
public class OperationLogAspect {
private static final Logger logger = LoggerFactory.getLogger(OperationLogAspect.class);
private static final int MAX_PARAM_LENGTH = 2000;
private static final int MAX_RESULT_LENGTH = 5000;
private final IOperationLogService logService;
private final ObjectMapper objectMapper;
public OperationLogAspect(IOperationLogService logService, ObjectMapper objectMapper) {
this.logService = logService;
this.objectMapper = objectMapper;
}
@Around("@annotation(operationLogAnnotation)")
public Object around(ProceedingJoinPoint point, cn.novalon.gym.manage.sys.audit.OperationLog operationLogAnnotation) throws Throwable {
long startTime = System.currentTimeMillis();
ServerRequest serverRequest = extractServerRequest(point.getArgs());
String ip = IpUtils.getClientIp(serverRequest);
String method = point.getSignature().toShortString();
String params = serializeParams(point.getArgs());
try {
Object result = point.proceed();
if (result instanceof Mono) {
return getCurrentUsername()
.flatMap(username -> ((Mono<?>) result)
.flatMap(res -> {
long duration = System.currentTimeMillis() - startTime;
return saveLogAsync(operationLogAnnotation, username, ip, method, params, res, duration, "0", null)
.onErrorResume(e -> Mono.empty())
.thenReturn(res);
})
.onErrorResume(error -> {
long duration = System.currentTimeMillis() - startTime;
return saveLogAsync(operationLogAnnotation, username, ip, method, params, null, duration, "1", error.getMessage())
.onErrorResume(e -> Mono.empty())
.then(Mono.error(error));
})
);
} else if (result instanceof Flux) {
return getCurrentUsername()
.flatMapMany(username -> ((Flux<?>) result)
.collectList()
.flatMapMany(res -> {
long duration = System.currentTimeMillis() - startTime;
return saveLogAsync(operationLogAnnotation, username, ip, method, params, res, duration, "0", null)
.onErrorResume(e -> Mono.empty())
.thenMany(Flux.fromIterable(res));
})
.onErrorResume(error -> {
long duration = System.currentTimeMillis() - startTime;
return saveLogAsync(operationLogAnnotation, username, ip, method, params, null, duration, "1", error.getMessage())
.onErrorResume(e -> Mono.empty())
.thenMany(Flux.error(error));
})
);
}
return result;
} catch (Throwable error) {
long duration = System.currentTimeMillis() - startTime;
getCurrentUsername()
.flatMap(username -> saveLogAsync(operationLogAnnotation, username, ip, method, params, null, duration, "1", error.getMessage()))
.subscribe();
throw error;
}
}
private ServerRequest extractServerRequest(Object[] args) {
if (args == null || args.length == 0) return null;
for (Object arg : args) {
if (arg instanceof ServerRequest) return (ServerRequest) arg;
}
return null;
}
private Mono<String> getCurrentUsername() {
return ReactiveSecurityContextHolder.getContext()
.map(ctx -> ctx.getAuthentication().getPrincipal())
.map(principal -> principal instanceof String ? (String) principal : "system")
.defaultIfEmpty("system")
.onErrorReturn("system");
}
private String serializeParams(Object[] args) {
try {
if (args == null || args.length == 0) return null;
String json = objectMapper.writeValueAsString(args);
if (json.length() > MAX_PARAM_LENGTH) {
return json.substring(0, MAX_PARAM_LENGTH) + "...(truncated)";
}
return json;
} catch (Exception e) {
logger.warn("序列化参数失败: {}", e.getMessage());
return null;
}
}
private String serializeResult(Object result) {
try {
if (result == null) return null;
String json = objectMapper.writeValueAsString(result);
if (json.length() > MAX_RESULT_LENGTH) {
return json.substring(0, MAX_RESULT_LENGTH) + "...(truncated)";
}
return json;
} catch (Exception e) {
logger.warn("序列化结果失败: {}", e.getMessage());
return null;
}
}
private Mono<Void> saveLogAsync(cn.novalon.gym.manage.sys.audit.OperationLog annotation,
String username, String ip, String method,
String params, Object result, long duration,
String status, String errorMsg) {
OperationLog log = new OperationLog();
log.setUsername(username);
log.setOperation(annotation.module() + " - " + annotation.operation());
log.setMethod(method);
log.setParams(params);
log.setResult(serializeResult(result));
log.setIp(ip);
log.setDuration(duration);
log.setStatus(status);
log.setErrorMsg(errorMsg);
return logService.save(log)
.doOnSuccess(saved -> logger.debug("操作日志保存成功: {} - {}",
annotation.module(), annotation.operation()))
.doOnError(error -> logger.error("操作日志保存失败: {}", error.getMessage()))
.then();
}
}
@@ -0,0 +1,131 @@
package cn.novalon.gym.manage.sys.audit.controller;
import cn.novalon.gym.manage.sys.audit.domain.AuditLog;
import cn.novalon.gym.manage.sys.audit.dto.AuditLogQueryRequest;
import cn.novalon.gym.manage.sys.audit.dto.AuditLogStatistics;
import cn.novalon.gym.manage.sys.audit.service.IAuditLogService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
/**
* 审计日志控制器
*
* 文件定义:提供审计日志的查询和统计接口
* 涉及业务:审计日志查询、统计分析
* 算法:使用响应式编程处理查询请求
*
* @author 张翔
* @date 2026-04-01
*/
@RestController
@RequestMapping("/api/audit-logs")
@Tag(name = "审计日志", description = "审计日志查询和统计接口")
public class AuditLogController {
private final IAuditLogService auditLogService;
public AuditLogController(IAuditLogService auditLogService) {
this.auditLogService = auditLogService;
}
@GetMapping("/{id}")
@Operation(summary = "根据ID查询审计日志", description = "根据ID查询单个审计日志详情")
public Mono<AuditLog> findById(
@Parameter(description = "审计日志ID") @PathVariable Long id) {
return auditLogService.findById(id);
}
@GetMapping
@Operation(summary = "查询审计日志列表", description = "根据条件查询审计日志列表")
public Flux<AuditLog> query(AuditLogQueryRequest request) {
if (request.getEntityType() != null && request.getEntityId() != null) {
return auditLogService.findByEntityTypeAndEntityId(
request.getEntityType(),
request.getEntityId());
} else if (request.getEntityType() != null) {
return auditLogService.findByEntityType(request.getEntityType());
} else if (request.getOperator() != null) {
return auditLogService.findByOperator(request.getOperator());
} else if (request.getOperationType() != null) {
return auditLogService.findByOperationType(request.getOperationType());
} else if (request.getStartTime() != null && request.getEndTime() != null) {
return auditLogService.findByOperationTimeBetween(
request.getStartTime(),
request.getEndTime());
}
return Flux.empty();
}
@GetMapping("/entity-type/{entityType}")
@Operation(summary = "按实体类型查询", description = "根据实体类型查询审计日志")
public Flux<AuditLog> findByEntityType(
@Parameter(description = "实体类型") @PathVariable String entityType) {
return auditLogService.findByEntityType(entityType);
}
@GetMapping("/entity/{entityId}")
@Operation(summary = "按实体ID查询", description = "根据实体ID查询审计日志")
public Flux<AuditLog> findByEntityId(
@Parameter(description = "实体ID") @PathVariable Long entityId) {
return auditLogService.findByEntityId(entityId);
}
@GetMapping("/operator/{operator}")
@Operation(summary = "按操作人查询", description = "根据操作人查询审计日志")
public Flux<AuditLog> findByOperator(
@Parameter(description = "操作人") @PathVariable String operator) {
return auditLogService.findByOperator(operator);
}
@GetMapping("/operation-type/{operationType}")
@Operation(summary = "按操作类型查询", description = "根据操作类型查询审计日志")
public Flux<AuditLog> findByOperationType(
@Parameter(description = "操作类型") @PathVariable String operationType) {
return auditLogService.findByOperationType(operationType);
}
@GetMapping("/time-range")
@Operation(summary = "按时间范围查询", description = "根据时间范围查询审计日志")
public Flux<AuditLog> findByTimeRange(
@Parameter(description = "开始时间") @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime startTime,
@Parameter(description = "结束时间") @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime endTime) {
return auditLogService.findByOperationTimeBetween(startTime, endTime);
}
@GetMapping("/statistics")
@Operation(summary = "审计日志统计", description = "获取审计日志的统计信息")
public Mono<AuditLogStatistics> getStatistics() {
AuditLogStatistics statistics = new AuditLogStatistics();
return Mono.just(statistics);
}
@GetMapping("/count/entity-type/{entityType}")
@Operation(summary = "按实体类型统计", description = "统计指定实体类型的审计日志数量")
public Mono<Long> countByEntityType(
@Parameter(description = "实体类型") @PathVariable String entityType) {
return auditLogService.countByEntityType(entityType);
}
@GetMapping("/count/operator/{operator}")
@Operation(summary = "按操作人统计", description = "统计指定操作人的审计日志数量")
public Mono<Long> countByOperator(
@Parameter(description = "操作人") @PathVariable String operator) {
return auditLogService.countByOperator(operator);
}
@GetMapping("/count/operation-type/{operationType}")
@Operation(summary = "按操作类型统计", description = "统计指定操作类型的审计日志数量")
public Mono<Long> countByOperationType(
@Parameter(description = "操作类型") @PathVariable String operationType) {
return auditLogService.countByOperationType(operationType);
}
}
@@ -0,0 +1,167 @@
package cn.novalon.gym.manage.sys.audit.domain;
import cn.novalon.gym.manage.sys.core.domain.BaseDomain;
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalDateTime;
/**
* 审计日志领域对象
*
* @author 张翔
* @date 2026-04-01
*/
@Schema(description = "审计日志实体")
public class AuditLog extends BaseDomain {
@Schema(description = "实体类型(如User, Role等)", example = "User")
private String entityType;
@Schema(description = "实体ID", example = "1")
private Long entityId;
@Schema(description = "操作类型(CREATE, UPDATE, DELETE", example = "UPDATE")
private String operationType;
@Schema(description = "操作人", example = "admin")
private String operator;
@Schema(description = "操作时间")
private LocalDateTime operationTime;
@Schema(description = "变更前数据(JSON格式)")
private String beforeData;
@Schema(description = "变更后数据(JSON格式)")
private String afterData;
@Schema(description = "变更字段列表")
private String[] changedFields;
@Schema(description = "IP地址", example = "192.168.1.100")
private String ipAddress;
@Schema(description = "用户代理")
private String userAgent;
@Schema(description = "操作描述", example = "更新用户信息")
private String description;
public AuditLog() {
this.operationTime = LocalDateTime.now();
}
public String getEntityType() {
return entityType;
}
public void setEntityType(String entityType) {
this.entityType = entityType;
}
public Long getEntityId() {
return entityId;
}
public void setEntityId(Long entityId) {
this.entityId = entityId;
}
public String getOperationType() {
return operationType;
}
public void setOperationType(String operationType) {
this.operationType = operationType;
}
public String getOperator() {
return operator;
}
public void setOperator(String operator) {
this.operator = operator;
}
public LocalDateTime getOperationTime() {
return operationTime;
}
public void setOperationTime(LocalDateTime operationTime) {
this.operationTime = operationTime;
}
public String getBeforeData() {
return beforeData;
}
public void setBeforeData(String beforeData) {
this.beforeData = beforeData;
}
public String getAfterData() {
return afterData;
}
public void setAfterData(String afterData) {
this.afterData = afterData;
}
public String[] getChangedFields() {
return changedFields;
}
public void setChangedFields(String[] changedFields) {
this.changedFields = changedFields;
}
public String getIpAddress() {
return ipAddress;
}
public void setIpAddress(String ipAddress) {
this.ipAddress = ipAddress;
}
public String getUserAgent() {
return userAgent;
}
public void setUserAgent(String userAgent) {
this.userAgent = userAgent;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
AuditLog auditLog = (AuditLog) o;
return java.util.Objects.equals(entityType, auditLog.entityType) &&
java.util.Objects.equals(entityId, auditLog.entityId) &&
java.util.Objects.equals(operationType, auditLog.operationType) &&
java.util.Objects.equals(operator, auditLog.operator) &&
java.util.Objects.equals(operationTime, auditLog.operationTime) &&
java.util.Objects.equals(beforeData, auditLog.beforeData) &&
java.util.Objects.equals(afterData, auditLog.afterData) &&
java.util.Arrays.equals(changedFields, auditLog.changedFields) &&
java.util.Objects.equals(ipAddress, auditLog.ipAddress) &&
java.util.Objects.equals(userAgent, auditLog.userAgent) &&
java.util.Objects.equals(description, auditLog.description);
}
@Override
public int hashCode() {
return java.util.Objects.hash(super.hashCode(), entityType, entityId, operationType, operator,
operationTime, beforeData, afterData, java.util.Arrays.hashCode(changedFields),
ipAddress, userAgent, description);
}
}
@@ -0,0 +1,187 @@
package cn.novalon.gym.manage.sys.audit.domain;
import io.swagger.v3.oas.annotations.media.Schema;
import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.Table;
import java.time.LocalDateTime;
/**
* 审计日志归档实体
*
* @author 张翔
* @date 2026-04-01
*/
@Table("audit_log_archive")
@Schema(description = "审计日志归档实体")
public class AuditLogArchive {
@Id
@Schema(description = "主键ID")
private Long id;
@Column("entity_type")
@Schema(description = "实体类型(如User, Role等)", example = "User")
private String entityType;
@Column("entity_id")
@Schema(description = "实体ID", example = "1")
private Long entityId;
@Column("operation_type")
@Schema(description = "操作类型(CREATE, UPDATE, DELETE", example = "UPDATE")
private String operationType;
@Column("operator")
@Schema(description = "操作人", example = "admin")
private String operator;
@Column("operation_time")
@Schema(description = "操作时间")
private LocalDateTime operationTime;
@Column("before_data")
@Schema(description = "变更前数据(JSON格式)")
private String beforeData;
@Column("after_data")
@Schema(description = "变更后数据(JSON格式)")
private String afterData;
@Column("changed_fields")
@Schema(description = "变更字段列表")
private String[] changedFields;
@Column("ip_address")
@Schema(description = "IP地址", example = "192.168.1.100")
private String ipAddress;
@Column("user_agent")
@Schema(description = "用户代理")
private String userAgent;
@Column("description")
@Schema(description = "操作描述", example = "更新用户信息")
private String description;
@Column("created_at")
@Schema(description = "记录创建时间")
private LocalDateTime createdAt;
@Column("archived_at")
@Schema(description = "归档时间")
private LocalDateTime archivedAt;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getEntityType() {
return entityType;
}
public void setEntityType(String entityType) {
this.entityType = entityType;
}
public Long getEntityId() {
return entityId;
}
public void setEntityId(Long entityId) {
this.entityId = entityId;
}
public String getOperationType() {
return operationType;
}
public void setOperationType(String operationType) {
this.operationType = operationType;
}
public String getOperator() {
return operator;
}
public void setOperator(String operator) {
this.operator = operator;
}
public LocalDateTime getOperationTime() {
return operationTime;
}
public void setOperationTime(LocalDateTime operationTime) {
this.operationTime = operationTime;
}
public String getBeforeData() {
return beforeData;
}
public void setBeforeData(String beforeData) {
this.beforeData = beforeData;
}
public String getAfterData() {
return afterData;
}
public void setAfterData(String afterData) {
this.afterData = afterData;
}
public String[] getChangedFields() {
return changedFields;
}
public void setChangedFields(String[] changedFields) {
this.changedFields = changedFields;
}
public String getIpAddress() {
return ipAddress;
}
public void setIpAddress(String ipAddress) {
this.ipAddress = ipAddress;
}
public String getUserAgent() {
return userAgent;
}
public void setUserAgent(String userAgent) {
this.userAgent = userAgent;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getArchivedAt() {
return archivedAt;
}
public void setArchivedAt(LocalDateTime archivedAt) {
this.archivedAt = archivedAt;
}
}
@@ -0,0 +1,137 @@
package cn.novalon.gym.manage.sys.audit.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalDateTime;
/**
* 审计日志查询请求
*
* @author 张翔
* @date 2026-04-01
*/
@Schema(description = "审计日志查询请求")
public class AuditLogQueryRequest {
@Schema(description = "实体类型", example = "User")
private String entityType;
@Schema(description = "实体ID", example = "1")
private Long entityId;
@Schema(description = "操作类型", example = "UPDATE")
private String operationType;
@Schema(description = "操作人", example = "admin")
private String operator;
@Schema(description = "开始时间")
private LocalDateTime startTime;
@Schema(description = "结束时间")
private LocalDateTime endTime;
@Schema(description = "页码", example = "1")
private Integer page = 1;
@Schema(description = "每页大小", example = "20")
private Integer size = 20;
public String getEntityType() {
return entityType;
}
public void setEntityType(String entityType) {
this.entityType = entityType;
}
public Long getEntityId() {
return entityId;
}
public void setEntityId(Long entityId) {
this.entityId = entityId;
}
public String getOperationType() {
return operationType;
}
public void setOperationType(String operationType) {
this.operationType = operationType;
}
public String getOperator() {
return operator;
}
public void setOperator(String operator) {
this.operator = operator;
}
public LocalDateTime getStartTime() {
return startTime;
}
public void setStartTime(LocalDateTime startTime) {
this.startTime = startTime;
}
public LocalDateTime getEndTime() {
return endTime;
}
public void setEndTime(LocalDateTime endTime) {
this.endTime = endTime;
}
public Integer getPage() {
return page;
}
public void setPage(Integer page) {
this.page = page;
}
public Integer getSize() {
return size;
}
public void setSize(Integer size) {
this.size = size;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AuditLogQueryRequest that = (AuditLogQueryRequest) o;
return java.util.Objects.equals(entityType, that.entityType) &&
java.util.Objects.equals(entityId, that.entityId) &&
java.util.Objects.equals(operationType, that.operationType) &&
java.util.Objects.equals(operator, that.operator) &&
java.util.Objects.equals(startTime, that.startTime) &&
java.util.Objects.equals(endTime, that.endTime) &&
java.util.Objects.equals(page, that.page) &&
java.util.Objects.equals(size, that.size);
}
@Override
public int hashCode() {
return java.util.Objects.hash(entityType, entityId, operationType, operator, startTime, endTime, page, size);
}
@Override
public String toString() {
return "AuditLogQueryRequest{" +
"entityType='" + entityType + '\'' +
", entityId=" + entityId +
", operationType='" + operationType + '\'' +
", operator='" + operator + '\'' +
", startTime=" + startTime +
", endTime=" + endTime +
", page=" + page +
", size=" + size +
'}';
}
}
@@ -0,0 +1,59 @@
package cn.novalon.gym.manage.sys.audit.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.Map;
/**
* 审计日志统计信息
*
* @author 张翔
* @date 2026-04-01
*/
@Schema(description = "审计日志统计信息")
public class AuditLogStatistics {
@Schema(description = "总记录数")
private Long totalCount;
@Schema(description = "按实体类型统计")
private Map<String, Long> countByEntityType;
@Schema(description = "按操作类型统计")
private Map<String, Long> countByOperationType;
@Schema(description = "按操作人统计")
private Map<String, Long> countByOperator;
public Long getTotalCount() {
return totalCount;
}
public void setTotalCount(Long totalCount) {
this.totalCount = totalCount;
}
public Map<String, Long> getCountByEntityType() {
return countByEntityType;
}
public void setCountByEntityType(Map<String, Long> countByEntityType) {
this.countByEntityType = countByEntityType;
}
public Map<String, Long> getCountByOperationType() {
return countByOperationType;
}
public void setCountByOperationType(Map<String, Long> countByOperationType) {
this.countByOperationType = countByOperationType;
}
public Map<String, Long> getCountByOperator() {
return countByOperator;
}
public void setCountByOperator(Map<String, Long> countByOperator) {
this.countByOperator = countByOperator;
}
}
@@ -0,0 +1,33 @@
package cn.novalon.gym.manage.sys.audit.repository;
import cn.novalon.gym.manage.sys.audit.domain.AuditLogArchive;
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-01
*/
@Repository
public interface IAuditLogArchiveRepository extends R2dbcRepository<AuditLogArchive, Long> {
Flux<AuditLogArchive> findByEntityType(String entityType);
Flux<AuditLogArchive> findByEntityId(Long entityId);
Flux<AuditLogArchive> findByOperator(String operator);
Flux<AuditLogArchive> findByOperationTimeBetween(LocalDateTime startTime, LocalDateTime endTime);
Flux<AuditLogArchive> findByArchivedAtBetween(LocalDateTime startTime, LocalDateTime endTime);
Mono<Long> countByEntityType(String entityType);
Mono<Long> countByOperator(String operator);
}
@@ -0,0 +1,56 @@
package cn.novalon.gym.manage.sys.audit.repository;
import cn.novalon.gym.manage.sys.audit.domain.AuditLog;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
/**
* 审计日志仓储接口
*
* @author 张翔
* @date 2026-04-01
*/
public interface IAuditLogRepository {
Mono<AuditLog> findById(Long id);
Mono<AuditLog> save(AuditLog auditLog);
Mono<Void> deleteById(Long id);
Flux<AuditLog> findAll();
Flux<AuditLog> findByEntityType(String entityType);
Flux<AuditLog> findByEntityId(Long entityId);
Flux<AuditLog> findByEntityTypeAndEntityId(String entityType, Long entityId);
Flux<AuditLog> findByOperator(String operator);
Flux<AuditLog> findByOperationType(String operationType);
Flux<AuditLog> findByOperationTimeBetween(LocalDateTime startTime, LocalDateTime endTime);
Flux<AuditLog> findByEntityTypeAndOperationTimeBetween(
String entityType,
LocalDateTime startTime,
LocalDateTime endTime
);
Flux<AuditLog> findByOperatorAndOperationTimeBetween(
String operator,
LocalDateTime startTime,
LocalDateTime endTime
);
Mono<Long> countByEntityType(String entityType);
Mono<Long> countByOperationType(String operationType);
Mono<Long> countByOperator(String operator);
Mono<Long> countByOperationTimeBetween(LocalDateTime startTime, LocalDateTime endTime);
}
@@ -0,0 +1,42 @@
package cn.novalon.gym.manage.sys.audit.scheduler;
import cn.novalon.gym.manage.sys.audit.service.IAuditLogArchiveService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
/**
* 审计日志归档定时任务
*
* 文件定义:定时执行审计日志归档任务
* 涉及业务:定期将历史审计日志移动到归档表
* 算法:使用Spring Scheduler定时执行归档任务
*
* @author 张翔
* @date 2026-04-01
*/
@Component
public class AuditLogArchiveScheduler {
private static final Logger logger = LoggerFactory.getLogger(AuditLogArchiveScheduler.class);
private final IAuditLogArchiveService auditLogArchiveService;
public AuditLogArchiveScheduler(IAuditLogArchiveService auditLogArchiveService) {
this.auditLogArchiveService = auditLogArchiveService;
}
@Scheduled(cron = "0 0 2 * * ?")
public void archiveOldLogs() {
logger.info("开始执行审计日志归档定时任务");
int daysToKeep = 30;
auditLogArchiveService.archiveOldLogs(daysToKeep)
.subscribe(
count -> logger.info("审计日志归档定时任务完成,共归档 {} 条记录", count),
error -> logger.error("审计日志归档定时任务失败: {}", error.getMessage())
);
}
}
@@ -0,0 +1,41 @@
package cn.novalon.gym.manage.sys.audit.service;
import cn.novalon.gym.manage.sys.audit.domain.AuditLog;
import cn.novalon.gym.manage.sys.audit.domain.AuditLogArchive;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
/**
* 审计日志归档服务接口
*
* 文件定义:定义审计日志归档的业务逻辑接口
* 涉及业务:审计日志的归档、查询、清理等操作
* 算法:定期将历史审计日志移动到归档表
*
* @author 张翔
* @date 2026-04-14
*/
public interface IAuditLogArchiveService {
Mono<Long> archiveOldLogs(int daysToKeep);
Mono<AuditLogArchive> archiveLog(AuditLog auditLog);
Flux<AuditLogArchive> findArchivedLogsByDateRange(LocalDateTime startDate, LocalDateTime endDate);
Flux<AuditLogArchive> findArchivedLogsByEntityType(String entityType);
Mono<AuditLogArchive> findArchivedLogById(Long id);
Mono<Long> countArchivedLogs();
Mono<Long> countArchivedLogsByDateRange(LocalDateTime startDate, LocalDateTime endDate);
Mono<Void> deleteArchivedLogsOlderThan(LocalDateTime date);
Mono<Long> getArchiveStatistics();
Mono<Boolean> isLogArchived(Long auditLogId);
}
@@ -0,0 +1,69 @@
package cn.novalon.gym.manage.sys.audit.service;
import cn.novalon.gym.manage.sys.audit.domain.AuditLog;
import cn.novalon.gym.manage.common.dto.PageRequest;
import cn.novalon.gym.manage.common.dto.PageResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
import java.util.List;
/**
* 审计日志服务接口
*
* @author 张翔
* @date 2026-04-08
*/
public interface IAuditLogService {
Mono<AuditLog> findById(Long id);
Flux<AuditLog> findAll();
Flux<AuditLog> findAll(boolean includeDeleted);
Mono<PageResponse<AuditLog>> findAuditLogsByPage(PageRequest pageRequest);
Mono<Long> count();
Flux<AuditLog> findByEntityType(String entityType);
Flux<AuditLog> findByEntityId(Long entityId);
Flux<AuditLog> findByEntityTypeAndEntityId(String entityType, Long entityId);
Flux<AuditLog> findByOperator(String operator);
Flux<AuditLog> findByOperationType(String operationType);
Flux<AuditLog> findByOperationTimeBetween(LocalDateTime startTime, LocalDateTime endTime);
Flux<AuditLog> findByEntityTypeAndOperationTimeBetween(String entityType, LocalDateTime startTime,
LocalDateTime endTime);
Flux<AuditLog> findByOperatorAndOperationTimeBetween(String operator, LocalDateTime startTime,
LocalDateTime endTime);
Mono<Long> countByEntityType(String entityType);
Mono<Long> countByOperationType(String operationType);
Mono<Long> countByOperator(String operator);
Mono<Long> countByOperationTimeBetween(LocalDateTime startTime, LocalDateTime endTime);
Mono<AuditLog> save(AuditLog auditLog);
Mono<AuditLog> saveAsync(AuditLog auditLog);
Mono<Void> deleteById(Long id);
Mono<Void> logicalDeleteById(Long id);
Mono<Void> logicalDeleteByIds(List<Long> ids);
Mono<Void> restoreById(Long id);
Mono<Void> restoreByIds(List<Long> ids);
}
@@ -0,0 +1,142 @@
package cn.novalon.gym.manage.sys.audit.service.impl;
import cn.novalon.gym.manage.sys.audit.domain.AuditLog;
import cn.novalon.gym.manage.sys.audit.domain.AuditLogArchive;
import cn.novalon.gym.manage.sys.audit.repository.IAuditLogArchiveRepository;
import cn.novalon.gym.manage.sys.audit.repository.IAuditLogRepository;
import cn.novalon.gym.manage.sys.audit.service.IAuditLogArchiveService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
/**
* 审计日志归档服务实现类
*
* 文件定义:实现审计日志归档的业务逻辑
* 涉及业务:审计日志的归档、查询、清理等操作
* 算法:定期将历史审计日志移动到归档表
*
* @author 张翔
* @date 2026-04-14
*/
@Service
public class AuditLogArchiveService implements IAuditLogArchiveService {
private static final Logger logger = LoggerFactory.getLogger(AuditLogArchiveService.class);
private final IAuditLogRepository auditLogRepository;
private final IAuditLogArchiveRepository auditLogArchiveRepository;
public AuditLogArchiveService(IAuditLogRepository auditLogRepository,
IAuditLogArchiveRepository auditLogArchiveRepository) {
this.auditLogRepository = auditLogRepository;
this.auditLogArchiveRepository = auditLogArchiveRepository;
}
@Override
@Transactional
public Mono<Long> archiveOldLogs(int daysToKeep) {
LocalDateTime archiveBefore = LocalDateTime.now().minusDays(daysToKeep);
logger.info("开始归档审计日志,归档时间点: {}", archiveBefore);
return auditLogRepository.findByOperationTimeBetween(LocalDateTime.MIN, archiveBefore)
.flatMap(this::archiveLog)
.count()
.doOnSuccess(count -> logger.info("归档完成,共归档 {} 条日志", count))
.doOnError(error -> logger.error("归档失败: {}", error.getMessage()));
}
@Override
@Transactional
public Mono<AuditLogArchive> archiveLog(AuditLog auditLog) {
AuditLogArchive archive = convertToArchive(auditLog);
return auditLogArchiveRepository.save(archive)
.doOnSuccess(saved -> {
logger.debug("审计日志归档成功: ID={}, 操作类型={}",
saved.getId(), saved.getOperationType());
auditLogRepository.deleteById(auditLog.getId())
.doOnSuccess(v -> logger.debug("原始日志删除成功: ID={}", auditLog.getId()))
.doOnError(error -> logger.error("原始日志删除失败: ID={}, {}",
auditLog.getId(), error.getMessage()))
.subscribe();
})
.doOnError(error -> logger.error("审计日志归档失败: ID={}, {}",
auditLog.getId(), error.getMessage()));
}
@Override
public Flux<AuditLogArchive> findArchivedLogsByDateRange(LocalDateTime startDate, LocalDateTime endDate) {
return auditLogArchiveRepository.findByOperationTimeBetween(startDate, endDate);
}
@Override
public Flux<AuditLogArchive> findArchivedLogsByEntityType(String entityType) {
return auditLogArchiveRepository.findByEntityType(entityType);
}
@Override
public Mono<AuditLogArchive> findArchivedLogById(Long id) {
return auditLogArchiveRepository.findById(id);
}
@Override
public Mono<Long> countArchivedLogs() {
return auditLogArchiveRepository.count();
}
@Override
public Mono<Long> countArchivedLogsByDateRange(LocalDateTime startDate, LocalDateTime endDate) {
return auditLogArchiveRepository.findByOperationTimeBetween(startDate, endDate)
.count();
}
@Override
@Transactional
public Mono<Void> deleteArchivedLogsOlderThan(LocalDateTime date) {
return auditLogArchiveRepository.findByOperationTimeBetween(LocalDateTime.MIN, date)
.flatMap(archive -> auditLogArchiveRepository.deleteById(archive.getId()))
.then()
.doOnSuccess(v -> logger.info("删除早于 {} 的归档日志完成", date))
.doOnError(error -> logger.error("删除归档日志失败: {}", error.getMessage()));
}
@Override
public Mono<Long> getArchiveStatistics() {
return auditLogArchiveRepository.count()
.doOnNext(count -> logger.info("归档日志统计: {} 条记录", count));
}
@Override
public Mono<Boolean> isLogArchived(Long auditLogId) {
return auditLogArchiveRepository.findAll()
.filter(archive -> archive.getEntityId() != null && archive.getEntityId().equals(auditLogId))
.hasElements()
.doOnNext(archived -> logger.debug("日志 ID={} 是否已归档: {}", auditLogId, archived));
}
private AuditLogArchive convertToArchive(AuditLog auditLog) {
AuditLogArchive archive = new AuditLogArchive();
archive.setEntityType(auditLog.getEntityType());
archive.setEntityId(auditLog.getEntityId());
archive.setOperationType(auditLog.getOperationType());
archive.setOperator(auditLog.getOperator());
archive.setOperationTime(auditLog.getOperationTime());
archive.setIpAddress(auditLog.getIpAddress());
archive.setUserAgent(auditLog.getUserAgent());
archive.setArchivedAt(LocalDateTime.now());
if (auditLog.getDescription() != null) {
archive.setDescription(auditLog.getDescription());
}
return archive;
}
}
@@ -0,0 +1,206 @@
package cn.novalon.gym.manage.sys.audit.service.impl;
import cn.novalon.gym.manage.sys.audit.domain.AuditLog;
import cn.novalon.gym.manage.sys.audit.repository.IAuditLogRepository;
import cn.novalon.gym.manage.sys.audit.service.IAuditLogService;
import cn.novalon.gym.manage.common.dto.PageRequest;
import cn.novalon.gym.manage.common.dto.PageResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.Executor;
/**
* 审计日志服务实现类
*
* 文件定义:实现审计日志管理的核心业务逻辑
* 涉及业务:审计日志的保存、查询、统计、删除等操作
* 算法:使用R2DBC进行响应式数据库操作,支持分页查询、条件查询、批量操作
*
* @author 张翔
* @date 2026-04-08
*/
@Service
public class AuditLogService implements IAuditLogService {
private static final Logger logger = LoggerFactory.getLogger(AuditLogService.class);
private final IAuditLogRepository auditLogRepository;
private final Executor auditLogExecutor;
public AuditLogService(IAuditLogRepository auditLogRepository,
Executor auditLogExecutor) {
this.auditLogRepository = auditLogRepository;
this.auditLogExecutor = auditLogExecutor;
}
@Override
public Mono<AuditLog> findById(Long id) {
return auditLogRepository.findById(id);
}
@Override
public Flux<AuditLog> findAll() {
return auditLogRepository.findAll();
}
@Override
public Flux<AuditLog> findAll(boolean includeDeleted) {
if (includeDeleted) {
return auditLogRepository.findAll();
} else {
return auditLogRepository.findAll();
}
}
@Override
public Mono<PageResponse<AuditLog>> findAuditLogsByPage(PageRequest pageRequest) {
return auditLogRepository.findAll()
.collectList()
.map(auditLogs -> {
int total = auditLogs.size();
int pageSize = pageRequest.getSize();
int pageNumber = pageRequest.getPage();
int fromIndex = pageNumber * pageSize;
int toIndex = Math.min(fromIndex + pageSize, total);
List<AuditLog> pageContent = auditLogs.subList(fromIndex, toIndex);
int totalPages = (int) Math.ceil((double) total / pageSize);
return new PageResponse<>(pageContent, totalPages, total, pageNumber, pageSize);
});
}
@Override
public Mono<Long> count() {
return auditLogRepository.findAll()
.count();
}
@Override
public Flux<AuditLog> findByEntityType(String entityType) {
return auditLogRepository.findByEntityType(entityType);
}
@Override
public Flux<AuditLog> findByEntityId(Long entityId) {
return auditLogRepository.findByEntityId(entityId);
}
@Override
public Flux<AuditLog> findByEntityTypeAndEntityId(String entityType, Long entityId) {
return auditLogRepository.findByEntityTypeAndEntityId(entityType, entityId);
}
@Override
public Flux<AuditLog> findByOperator(String operator) {
return auditLogRepository.findByOperator(operator);
}
@Override
public Flux<AuditLog> findByOperationType(String operationType) {
return auditLogRepository.findByOperationType(operationType);
}
@Override
public Flux<AuditLog> findByOperationTimeBetween(LocalDateTime startTime, LocalDateTime endTime) {
return auditLogRepository.findByOperationTimeBetween(startTime, endTime);
}
@Override
public Flux<AuditLog> findByEntityTypeAndOperationTimeBetween(String entityType, LocalDateTime startTime, LocalDateTime endTime) {
return auditLogRepository.findByEntityTypeAndOperationTimeBetween(entityType, startTime, endTime);
}
@Override
public Flux<AuditLog> findByOperatorAndOperationTimeBetween(String operator, LocalDateTime startTime, LocalDateTime endTime) {
return auditLogRepository.findByOperatorAndOperationTimeBetween(operator, startTime, endTime);
}
@Override
public Mono<Long> countByEntityType(String entityType) {
return auditLogRepository.countByEntityType(entityType);
}
@Override
public Mono<Long> countByOperationType(String operationType) {
return auditLogRepository.countByOperationType(operationType);
}
@Override
public Mono<Long> countByOperator(String operator) {
return auditLogRepository.countByOperator(operator);
}
@Override
public Mono<Long> countByOperationTimeBetween(LocalDateTime startTime, LocalDateTime endTime) {
return auditLogRepository.countByOperationTimeBetween(startTime, endTime);
}
@Override
public Mono<AuditLog> save(AuditLog auditLog) {
return auditLogRepository.save(auditLog);
}
@Override
@Async("auditLogExecutor")
public Mono<AuditLog> saveAsync(AuditLog auditLog) {
logger.debug("异步保存审计日志: {} - {}", auditLog.getEntityType(), auditLog.getOperationType());
return auditLogRepository.save(auditLog)
.doOnSuccess(saved -> logger.debug("审计日志保存成功: ID={}", saved.getId()))
.doOnError(error -> logger.error("审计日志保存失败: {}", error.getMessage()))
.subscribeOn(Schedulers.fromExecutor(auditLogExecutor));
}
@Override
@Transactional
public Mono<Void> deleteById(Long id) {
return auditLogRepository.deleteById(id);
}
@Override
@Transactional
public Mono<Void> logicalDeleteById(Long id) {
return auditLogRepository.findById(id)
.flatMap(auditLog -> {
auditLog.setDeletedAt(LocalDateTime.now());
return auditLogRepository.save(auditLog);
})
.then();
}
@Override
@Transactional
public Mono<Void> logicalDeleteByIds(List<Long> ids) {
return Flux.fromIterable(ids)
.flatMap(this::logicalDeleteById)
.then();
}
@Override
@Transactional
public Mono<Void> restoreById(Long id) {
return auditLogRepository.findById(id)
.flatMap(auditLog -> {
auditLog.setDeletedAt(null);
return auditLogRepository.save(auditLog);
})
.then();
}
@Override
@Transactional
public Mono<Void> restoreByIds(List<Long> ids) {
return Flux.fromIterable(ids)
.flatMap(this::restoreById)
.then();
}
}
@@ -0,0 +1,66 @@
package cn.novalon.gym.manage.sys.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
/**
* 异步配置类
*
* 文件定义:配置异步线程池,用于审计日志等异步处理
* 涉及业务:提供统一的异步处理能力
* 算法:使用ThreadPoolTaskExecutor管理线程池
*
* @author 张翔
* @date 2026-04-01
*/
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
private static final Logger logger = LoggerFactory.getLogger(AsyncConfig.class);
@Bean(name = "auditLogExecutor")
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setKeepAliveSeconds(60);
executor.setThreadNamePrefix("audit-log-");
executor.setRejectedExecutionHandler((r, exec) -> {
logger.warn("审计日志线程池已满,任务被拒绝,将降级为同步处理");
if (!exec.isShutdown()) {
r.run();
}
});
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
executor.initialize();
logger.info("审计日志异步线程池初始化完成: corePoolSize={}, maxPoolSize={}, queueCapacity={}",
executor.getCorePoolSize(), executor.getMaxPoolSize(), executor.getQueueCapacity());
return executor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (throwable, method, params) -> {
logger.error("异步任务执行异常 - 方法: {}, 参数: {}, 异常: {}",
method.getName(), params, throwable.getMessage(), throwable);
};
}
}
@@ -0,0 +1,33 @@
package cn.novalon.gym.manage.sys.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.domain.ReactiveAuditorAware;
import org.springframework.data.r2dbc.config.EnableR2dbcAuditing;
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
/**
* R2DBC审计配置类
*
* 文件定义:启用Spring Data R2DBC的审计功能,自动填充创建人、修改人等字段
* 涉及业务:用户操作审计、数据变更追踪
* 算法:使用ReactiveSecurityContextHolder获取当前认证用户
*
* @author 张翔
* @date 2026-04-01
*/
@Configuration
@EnableR2dbcAuditing(auditorAwareRef = "reactiveAuditorAware")
public class AuditingConfig {
@Bean
public ReactiveAuditorAware<String> reactiveAuditorAware() {
return () -> ReactiveSecurityContextHolder.getContext()
.map(securityContext -> securityContext.getAuthentication())
.map(authentication -> {
Object principal = authentication.getPrincipal();
return principal instanceof String ? (String) principal : "system";
})
.defaultIfEmpty("system");
}
}
@@ -0,0 +1,48 @@
package cn.novalon.gym.manage.sys.config;
import cn.novalon.gym.manage.sys.core.service.impl.SysExceptionLogService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.RequestPredicates.*;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
/**
* 异常日志配置类
*
* @author 张翔
* @date 2026-04-15
*/
@Configuration
public class ExceptionLogConfig {
private static final Logger logger = LoggerFactory.getLogger(ExceptionLogConfig.class);
/**
* 配置异常日志的路由
*/
@Bean
public RouterFunction<ServerResponse> exceptionLogRoutes(SysExceptionLogService exceptionLogService) {
logger.info("配置异常日志路由");
return route()
.GET("/api/exception-logs", request ->
ServerResponse.ok().body(exceptionLogService.findAll(), cn.novalon.gym.manage.sys.core.domain.SysExceptionLog.class))
.GET("/api/exception-logs/{id}", request -> {
Long id = Long.valueOf(request.pathVariable("id"));
return exceptionLogService.findById(id)
.flatMap(log -> ServerResponse.ok().bodyValue(log))
.switchIfEmpty(ServerResponse.notFound().build());
})
.GET("/api/exception-logs/username/{username}", request -> {
String username = request.pathVariable("username");
return ServerResponse.ok().body(exceptionLogService.findByUsername(username),
cn.novalon.gym.manage.sys.core.domain.SysExceptionLog.class);
})
.build();
}
}
@@ -0,0 +1,35 @@
package cn.novalon.gym.manage.sys.config;
import jakarta.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* 密码编码器配置
*
* @author 张翔
* @date 2026-03-26
*/
@Configuration
public class PasswordEncoderConfig {
private static final Logger logger = LoggerFactory.getLogger(PasswordEncoderConfig.class);
@Bean
@Primary
public PasswordEncoder passwordEncoder() {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(12);
logger.info("创建主密码编码器: BCryptPasswordEncoder(strength=12), 类型: {}", encoder.getClass().getName());
return encoder;
}
@PostConstruct
public void init() {
logger.info("PasswordEncoderConfig 已加载");
}
}
@@ -0,0 +1,71 @@
package cn.novalon.gym.manage.sys.config;
import cn.novalon.gym.manage.sys.security.JwtAuthenticationFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;
/**
* 安全配置类
*
* @author 张翔
* @date 2026-03-13
*/
@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {
private static final Logger logger = LoggerFactory.getLogger(SecurityConfig.class);
private final JwtAuthenticationFilter jwtAuthenticationFilter;
private final Environment environment;
public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter, Environment environment) {
this.jwtAuthenticationFilter = jwtAuthenticationFilter;
this.environment = environment;
}
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
String[] activeProfiles = environment.getActiveProfiles();
final boolean isDevOrTest;
isDevOrTest = java.util.Arrays.stream(activeProfiles)
.anyMatch(profile -> "dev".equals(profile) || "test".equals(profile) || "h2-test".equals(profile));
logger.info("SecurityConfig初始化: 当前环境={}, Swagger启用状态={}",
activeProfiles.length > 0 ? String.join(",", activeProfiles) : "default", isDevOrTest);
http
.csrf(ServerHttpSecurity.CsrfSpec::disable)
.httpBasic(ServerHttpSecurity.HttpBasicSpec::disable)
.formLogin(ServerHttpSecurity.FormLoginSpec::disable)
.addFilterBefore(jwtAuthenticationFilter, SecurityWebFiltersOrder.AUTHENTICATION)
.authorizeExchange(spec -> {
spec.pathMatchers("/api/auth/**").permitAll()
.pathMatchers("/api/public/**").permitAll()
.pathMatchers("/ws/**").permitAll()
.pathMatchers("/actuator/**").permitAll();
if (isDevOrTest) {
spec.pathMatchers("/swagger-ui.html").permitAll()
.pathMatchers("/swagger-ui/**").permitAll()
.pathMatchers("/api-docs/**").permitAll()
.pathMatchers("/v3/api-docs/**").permitAll()
.pathMatchers("/swagger-resources/**").permitAll()
.pathMatchers("/webjars/**").permitAll()
.pathMatchers("/api/diagnostic/**").permitAll();
logger.info("SecurityConfig: Swagger路径和诊断端点已放行");
}
spec.anyExchange().authenticated();
});
return http.build();
}
}
@@ -0,0 +1,21 @@
package cn.novalon.gym.manage.sys.core.command;
/**
* 创建菜单命令对象
*
* @author 张翔
* @date 2026-03-13
*/
public record CreateMenuCommand(
Long parentId,
String menuName,
String menuType,
Integer orderNum,
String component,
String perms,
Integer status) {
public static CreateMenuCommand of(Long parentId, String menuName, String menuType, Integer orderNum,
String component, String perms, Integer status) {
return new CreateMenuCommand(parentId, menuName, menuType, orderNum, component, perms, status);
}
}
@@ -0,0 +1,42 @@
package cn.novalon.gym.manage.sys.core.command;
import cn.novalon.gym.manage.common.exception.ErrorCode;
import cn.novalon.gym.manage.common.exception.ValidationException;
/**
* 创建公告命令对象
*
* @author 张翔
* @date 2026-03-13
*/
public record CreateNoticeCommand(
String noticeTitle,
String noticeContent,
String noticeType,
String status) {
public static CreateNoticeCommand of(String noticeTitle, String noticeContent, String noticeType, String status) {
validateNoticeTitle(noticeTitle);
validateNoticeContent(noticeContent);
validateNoticeType(noticeType);
return new CreateNoticeCommand(noticeTitle, noticeContent, noticeType, status);
}
private static void validateNoticeTitle(String noticeTitle) {
if (noticeTitle == null || noticeTitle.trim().isEmpty()) {
throw new ValidationException(ErrorCode.VALIDATION_REQUIRED, "Notice title is required");
}
}
private static void validateNoticeContent(String noticeContent) {
if (noticeContent == null || noticeContent.trim().isEmpty()) {
throw new ValidationException(ErrorCode.VALIDATION_REQUIRED, "Notice content is required");
}
}
private static void validateNoticeType(String noticeType) {
if (noticeType != null && !noticeType.equals("1") && !noticeType.equals("2")) {
throw new ValidationException(ErrorCode.VALIDATION_INVALID_VALUE,
"Invalid notice type. Notice type must be 1 (notification) or 2 (announcement)");
}
}
}
@@ -0,0 +1,30 @@
package cn.novalon.gym.manage.sys.core.command;
import cn.novalon.gym.manage.common.exception.ErrorCode;
import cn.novalon.gym.manage.common.exception.ValidationException;
import cn.novalon.gym.manage.common.util.StatusConstants;
/**
* 创建角色命令对象
*
* @author 张翔
* @date 2026-03-13
*/
public record CreateRoleCommand(
String roleName,
String roleKey,
Integer roleSort,
Integer status
) {
public static CreateRoleCommand of(String roleName, String roleKey, Integer roleSort, Integer status) {
validateStatus(status);
return new CreateRoleCommand(roleName, roleKey, roleSort, status);
}
private static void validateStatus(Integer status) {
if (status != null && status != StatusConstants.ENABLED && status != StatusConstants.DISABLED) {
throw new ValidationException(ErrorCode.VALIDATION_INVALID_VALUE,
"Invalid status value. Status must be 0 (disabled) or 1 (enabled)");
}
}
}
@@ -0,0 +1,33 @@
package cn.novalon.gym.manage.sys.core.command;
import cn.novalon.gym.manage.sys.primitive.Email;
import cn.novalon.gym.manage.sys.primitive.Password;
import cn.novalon.gym.manage.sys.primitive.Username;
/**
* 创建用户命令对象
*
* @author 张翔
* @date 2026-03-13
*/
public record CreateUserCommand(
Username username,
Password password,
Email email,
String nickname,
String phone,
Long roleId,
Integer status
) {
public static CreateUserCommand of(String username, String password, String email, String nickname, String phone, Long roleId, Integer status) {
return new CreateUserCommand(
Username.of(username),
Password.of(password),
Email.of(email),
nickname,
phone,
roleId,
status
);
}
}
@@ -0,0 +1,23 @@
package cn.novalon.gym.manage.sys.core.command;
/**
* 更新菜单命令对象
*
* @author 张翔
* @date 2026-03-13
*/
public record UpdateMenuCommand(
Long id,
Long parentId,
String menuName,
String menuType,
Integer orderNum,
String component,
String perms,
Integer status
) {
public static UpdateMenuCommand of(Long id, Long parentId, String menuName, String menuType, Integer orderNum,
String component, String perms, Integer status) {
return new UpdateMenuCommand(id, parentId, menuName, menuType, orderNum, component, perms, status);
}
}
@@ -0,0 +1,19 @@
package cn.novalon.gym.manage.sys.core.command;
/**
* 更新角色命令对象
*
* @author 张翔
* @date 2026-03-13
*/
public record UpdateRoleCommand(
Long id,
String roleName,
String roleKey,
Integer roleSort,
Integer status
) {
public static UpdateRoleCommand of(Long id, String roleName, String roleKey, Integer roleSort, Integer status) {
return new UpdateRoleCommand(id, roleName, roleKey, roleSort, status);
}
}
@@ -0,0 +1,25 @@
package cn.novalon.gym.manage.sys.core.command;
/**
* 更新用户命令对象
*
* @author 张翔
* @date 2026-03-13
*/
public record UpdateUserCommand(
Long id,
String username,
String password,
String email,
Long roleId,
Integer status,
boolean clearRole
) {
public static UpdateUserCommand of(Long id, String username, String password, String email, Long roleId, Integer status) {
return new UpdateUserCommand(id, username, password, email, roleId, status, false);
}
public static UpdateUserCommand of(Long id, String username, String password, String email, Long roleId, Integer status, boolean clearRole) {
return new UpdateUserCommand(id, username, password, email, roleId, status, clearRole);
}
}
@@ -0,0 +1,91 @@
package cn.novalon.gym.manage.sys.core.domain;
import cn.novalon.gym.manage.common.util.SnowflakeId;
import java.time.LocalDateTime;
/**
* 基础领域对象
*
* @author 张翔
* @date 2026-03-13
*/
public abstract class BaseDomain {
protected Long id;
protected String createBy;
protected String updateBy;
protected LocalDateTime createdAt;
protected LocalDateTime updatedAt;
protected LocalDateTime deletedAt;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getCreateBy() {
return createBy;
}
public void setCreateBy(String createBy) {
this.createBy = createBy;
}
public String getUpdateBy() {
return updateBy;
}
public void setUpdateBy(String updateBy) {
this.updateBy = updateBy;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
public LocalDateTime getDeletedAt() {
return deletedAt;
}
public void setDeletedAt(LocalDateTime deletedAt) {
this.deletedAt = deletedAt;
}
/**
* 生成主键ID
*
* @return 主键ID
*/
public Long generateId() {
this.id = SnowflakeId.nextId();
return this.id;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BaseDomain that = (BaseDomain) o;
return id != null && id.equals(that.id);
}
@Override
public int hashCode() {
return id != null ? id.hashCode() : 0;
}
}
@@ -0,0 +1,123 @@
package cn.novalon.gym.manage.sys.core.domain;
import java.time.LocalDateTime;
/**
* 字典领域对象
*
* @author 张翔
* @date 2026-03-13
*/
public class Dictionary {
private Long id;
private String type;
private String code;
private String name;
private String value;
private String remark;
private Integer sort;
private String createBy;
private String updateBy;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private LocalDateTime deletedAt;
public Dictionary() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
public Integer getSort() {
return sort;
}
public void setSort(Integer sort) {
this.sort = sort;
}
public String getCreateBy() {
return createBy;
}
public void setCreateBy(String createBy) {
this.createBy = createBy;
}
public String getUpdateBy() {
return updateBy;
}
public void setUpdateBy(String updateBy) {
this.updateBy = updateBy;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
public LocalDateTime getDeletedAt() {
return deletedAt;
}
public void setDeletedAt(LocalDateTime deletedAt) {
this.deletedAt = deletedAt;
}
}
@@ -0,0 +1,92 @@
package cn.novalon.gym.manage.sys.core.domain;
/**
* 操作日志领域对象
*
* @author 张翔
* @date 2026-03-13
*/
public class OperationLog extends BaseDomain {
private String username;
private String operation;
private String method;
private String params;
private String result;
private String ip;
private Long duration;
private String status;
private String errorMsg;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getOperation() {
return operation;
}
public void setOperation(String operation) {
this.operation = operation;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public String getParams() {
return params;
}
public void setParams(String params) {
this.params = params;
}
public String getResult() {
return result;
}
public void setResult(String result) {
this.result = result;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public Long getDuration() {
return duration;
}
public void setDuration(Long duration) {
this.duration = duration;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getErrorMsg() {
return errorMsg;
}
public void setErrorMsg(String errorMsg) {
this.errorMsg = errorMsg;
}
}
@@ -0,0 +1,44 @@
package cn.novalon.gym.manage.sys.core.domain;
import java.time.LocalDateTime;
/**
* 系统配置领域对象
*
* @author 张翔
* @date 2026-03-13
*/
public class SysConfig {
private Long id;
private String configName;
private String configKey;
private String configValue;
private String configType;
private String createBy;
private String updateBy;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private LocalDateTime deletedAt;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getConfigName() { return configName; }
public void setConfigName(String configName) { this.configName = configName; }
public String getConfigKey() { return configKey; }
public void setConfigKey(String configKey) { this.configKey = configKey; }
public String getConfigValue() { return configValue; }
public void setConfigValue(String configValue) { this.configValue = configValue; }
public String getConfigType() { return configType; }
public void setConfigType(String configType) { this.configType = configType; }
public String getCreateBy() { return createBy; }
public void setCreateBy(String createBy) { this.createBy = createBy; }
public String getUpdateBy() { return updateBy; }
public void setUpdateBy(String updateBy) { this.updateBy = updateBy; }
public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
public LocalDateTime getUpdatedAt() { return updatedAt; }
public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
public LocalDateTime getDeletedAt() { return deletedAt; }
public void setDeletedAt(LocalDateTime deletedAt) { this.deletedAt = deletedAt; }
}
@@ -0,0 +1,59 @@
package cn.novalon.gym.manage.sys.core.domain;
import java.time.LocalDateTime;
/**
* 字典数据领域对象
*
* @author 张翔
* @date 2026-03-13
*/
public class SysDictData {
private Long id;
private Long dictTypeId;
private String dictLabel;
private String dictValue;
private Integer dictSort;
private String dictType;
private String cssClass;
private String listClass;
private String isDefault;
private String status;
private String createBy;
private String updateBy;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private LocalDateTime deletedAt;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public Long getDictTypeId() { return dictTypeId; }
public void setDictTypeId(Long dictTypeId) { this.dictTypeId = dictTypeId; }
public String getDictLabel() { return dictLabel; }
public void setDictLabel(String dictLabel) { this.dictLabel = dictLabel; }
public String getDictValue() { return dictValue; }
public void setDictValue(String dictValue) { this.dictValue = dictValue; }
public Integer getDictSort() { return dictSort; }
public void setDictSort(Integer dictSort) { this.dictSort = dictSort; }
public String getDictType() { return dictType; }
public void setDictType(String dictType) { this.dictType = dictType; }
public String getCssClass() { return cssClass; }
public void setCssClass(String cssClass) { this.cssClass = cssClass; }
public String getListClass() { return listClass; }
public void setListClass(String listClass) { this.listClass = listClass; }
public String getIsDefault() { return isDefault; }
public void setIsDefault(String isDefault) { this.isDefault = isDefault; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
public String getCreateBy() { return createBy; }
public void setCreateBy(String createBy) { this.createBy = createBy; }
public String getUpdateBy() { return updateBy; }
public void setUpdateBy(String updateBy) { this.updateBy = updateBy; }
public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
public LocalDateTime getUpdatedAt() { return updatedAt; }
public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
public LocalDateTime getDeletedAt() { return deletedAt; }
public void setDeletedAt(LocalDateTime deletedAt) { this.deletedAt = deletedAt; }
}
@@ -0,0 +1,44 @@
package cn.novalon.gym.manage.sys.core.domain;
import java.time.LocalDateTime;
/**
* 字典类型领域对象
*
* @author 张翔
* @date 2026-03-13
*/
public class SysDictType {
private Long id;
private String dictName;
private String dictType;
private String status;
private String remark;
private String createBy;
private String updateBy;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private LocalDateTime deletedAt;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getDictName() { return dictName; }
public void setDictName(String dictName) { this.dictName = dictName; }
public String getDictType() { return dictType; }
public void setDictType(String dictType) { this.dictType = dictType; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
public String getRemark() { return remark; }
public void setRemark(String remark) { this.remark = remark; }
public String getCreateBy() { return createBy; }
public void setCreateBy(String createBy) { this.createBy = createBy; }
public String getUpdateBy() { return updateBy; }
public void setUpdateBy(String updateBy) { this.updateBy = updateBy; }
public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
public LocalDateTime getUpdatedAt() { return updatedAt; }
public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
public LocalDateTime getDeletedAt() { return deletedAt; }
public void setDeletedAt(LocalDateTime deletedAt) { this.deletedAt = deletedAt; }
}
@@ -0,0 +1,44 @@
package cn.novalon.gym.manage.sys.core.domain;
import java.time.LocalDateTime;
/**
* 异常日志领域对象
*
* @author 张翔
* @date 2026-03-13
*/
public class SysExceptionLog {
private Long id;
private String username;
private String title;
private String exceptionName;
private String methodName;
private String methodParams;
private String exceptionMsg;
private String exceptionStack;
private String ip;
private LocalDateTime createTime;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getExceptionName() { return exceptionName; }
public void setExceptionName(String exceptionName) { this.exceptionName = exceptionName; }
public String getMethodName() { return methodName; }
public void setMethodName(String methodName) { this.methodName = methodName; }
public String getMethodParams() { return methodParams; }
public void setMethodParams(String methodParams) { this.methodParams = methodParams; }
public String getExceptionMsg() { return exceptionMsg; }
public void setExceptionMsg(String exceptionMsg) { this.exceptionMsg = exceptionMsg; }
public String getExceptionStack() { return exceptionStack; }
public void setExceptionStack(String exceptionStack) { this.exceptionStack = exceptionStack; }
public String getIp() { return ip; }
public void setIp(String ip) { this.ip = ip; }
public LocalDateTime getCreateTime() { return createTime; }
public void setCreateTime(LocalDateTime createTime) { this.createTime = createTime; }
}
@@ -0,0 +1,41 @@
package cn.novalon.gym.manage.sys.core.domain;
import java.time.LocalDateTime;
/**
* 登录日志领域对象
*
* @author 张翔
* @date 2026-03-13
*/
public class SysLoginLog {
private Long id;
private String username;
private String ip;
private String location;
private String browser;
private String os;
private String status;
private String message;
private LocalDateTime loginTime;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getIp() { return ip; }
public void setIp(String ip) { this.ip = ip; }
public String getLocation() { return location; }
public void setLocation(String location) { this.location = location; }
public String getBrowser() { return browser; }
public void setBrowser(String browser) { this.browser = browser; }
public String getOs() { return os; }
public void setOs(String os) { this.os = os; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
public LocalDateTime getLoginTime() { return loginTime; }
public void setLoginTime(LocalDateTime loginTime) { this.loginTime = loginTime; }
}
@@ -0,0 +1,103 @@
package cn.novalon.gym.manage.sys.core.domain;
import java.util.List;
/**
* 菜单领域对象
*
* @author 张翔
* @date 2026-03-13
*/
public class SysMenu extends BaseDomain {
private String menuName;
private Long parentId;
private Integer orderNum;
private String menuType;
private String perms;
private String component;
private Integer status;
private String createBy;
private String updateBy;
private List<SysMenu> children;
public String getMenuName() {
return menuName;
}
public void setMenuName(String menuName) {
this.menuName = menuName;
}
public Long getParentId() {
return parentId;
}
public void setParentId(Long parentId) {
this.parentId = parentId;
}
public Integer getOrderNum() {
return orderNum;
}
public void setOrderNum(Integer orderNum) {
this.orderNum = orderNum;
}
public String getMenuType() {
return menuType;
}
public void setMenuType(String menuType) {
this.menuType = menuType;
}
public String getPerms() {
return perms;
}
public void setPerms(String perms) {
this.perms = perms;
}
public String getComponent() {
return component;
}
public void setComponent(String component) {
this.component = component;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getCreateBy() {
return createBy;
}
public void setCreateBy(String createBy) {
this.createBy = createBy;
}
public String getUpdateBy() {
return updateBy;
}
public void setUpdateBy(String updateBy) {
this.updateBy = updateBy;
}
public List<SysMenu> getChildren() {
return children;
}
public void setChildren(List<SysMenu> children) {
this.children = children;
}
}
@@ -0,0 +1,94 @@
package cn.novalon.gym.manage.sys.core.domain;
import cn.novalon.gym.manage.common.util.SnowflakeId;
import io.swagger.v3.oas.annotations.media.Schema;
/**
* 权限领域对象
*
* @author 张翔
* @date 2026-03-25
*/
@Schema(description = "系统权限实体")
public class SysPermission extends BaseDomain {
@Schema(description = "权限名称", example = "用户管理")
private String permissionName;
@Schema(description = "权限编码", example = "system:user:view")
private String permissionCode;
@Schema(description = "资源路径", example = "/api/users")
private String resource;
@Schema(description = "操作类型", example = "GET")
private String action;
@Schema(description = "描述", example = "查看用户列表")
private String description;
@Schema(description = "状态:0-禁用,1-正常", example = "1")
private Integer status;
public String getPermissionName() {
return permissionName;
}
public void setPermissionName(String permissionName) {
this.permissionName = permissionName;
}
public String getPermissionCode() {
return permissionCode;
}
public void setPermissionCode(String permissionCode) {
this.permissionCode = permissionCode;
}
public String getResource() {
return resource;
}
public void setResource(String resource) {
this.resource = resource;
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
/**
* 删除权限
*/
public void delete() {
this.deletedAt = java.time.LocalDateTime.now();
}
/**
* 恢复权限
*/
public void restore() {
this.deletedAt = null;
}
}
@@ -0,0 +1,74 @@
package cn.novalon.gym.manage.sys.core.domain;
import cn.novalon.gym.manage.common.util.SnowflakeId;
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalDateTime;
/**
* 角色领域对象
*
* @author 张翔
* @date 2026-03-13
*/
@Schema(description = "系统角色实体")
public class SysRole extends BaseDomain {
@Schema(description = "角色名称", example = "管理员")
private String roleName;
@Schema(description = "角色权限字符串", example = "admin")
private String roleKey;
@Schema(description = "显示顺序", example = "1")
private Integer roleSort;
@Schema(description = "状态:0-禁用,1-正常", example = "1")
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 getRoleSort() {
return roleSort;
}
public void setRoleSort(Integer roleSort) {
this.roleSort = roleSort;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
/**
* 删除角色
*/
public void delete() {
this.deletedAt = LocalDateTime.now();
}
/**
* 恢复角色
*/
public void restore() {
this.deletedAt = null;
}
}
@@ -0,0 +1,36 @@
package cn.novalon.gym.manage.sys.core.domain;
import cn.novalon.gym.manage.common.util.SnowflakeId;
import io.swagger.v3.oas.annotations.media.Schema;
/**
* 角色权限关联领域对象
*
* @author 张翔
* @date 2026-03-25
*/
@Schema(description = "角色权限关联实体")
public class SysRolePermission extends BaseDomain {
@Schema(description = "角色ID", example = "1")
private Long roleId;
@Schema(description = "权限ID", example = "1")
private Long permissionId;
public Long getRoleId() {
return roleId;
}
public void setRoleId(Long roleId) {
this.roleId = roleId;
}
public Long getPermissionId() {
return permissionId;
}
public void setPermissionId(Long permissionId) {
this.permissionId = permissionId;
}
}
@@ -0,0 +1,110 @@
package cn.novalon.gym.manage.sys.core.domain;
import cn.novalon.gym.manage.common.util.SnowflakeId;
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalDateTime;
/**
* 用户领域对象
*
* @author 张翔
* @date 2026-03-13
*/
@Schema(description = "系统用户实体")
public class SysUser extends BaseDomain {
@Schema(description = "用户名", example = "admin")
private String username;
@Schema(description = "密码(加密后)", example = "$2a$10$...")
private String password;
@Schema(description = "昵称", example = "管理员")
private String nickname;
@Schema(description = "邮箱", example = "admin@example.com")
private String email;
@Schema(description = "手机号", example = "13800138000")
private String phone;
@Schema(description = "头像", example = "https://example.com/avatar.jpg")
private String avatar;
@Schema(description = "角色ID", example = "1")
private Long roleId;
@Schema(description = "状态:0-禁用,1-正常", example = "1")
private Integer status;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getAvatar() {
return avatar;
}
public void setAvatar(String avatar) {
this.avatar = avatar;
}
public Long getRoleId() {
return roleId;
}
public void setRoleId(Long roleId) {
this.roleId = roleId;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
/**
* 删除用户
*/
public void delete() {
this.deletedAt = LocalDateTime.now();
}
}
@@ -0,0 +1,63 @@
package cn.novalon.gym.manage.sys.core.domain;
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalDateTime;
@Schema(description = "用户角色关联实体")
public class UserRole {
@Schema(description = "主键ID")
private Long id;
@Schema(description = "用户ID")
private Long userId;
@Schema(description = "角色ID")
private Long roleId;
@Schema(description = "创建时间")
private LocalDateTime createdAt;
@Schema(description = "创建人")
private String createdBy;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public Long getRoleId() {
return roleId;
}
public void setRoleId(Long roleId) {
this.roleId = roleId;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public String getCreatedBy() {
return createdBy;
}
public void setCreatedBy(String createdBy) {
this.createdBy = createdBy;
}
}
@@ -0,0 +1,27 @@
package cn.novalon.gym.manage.sys.core.exception;
/**
* 字典已存在异常
*
* @author 张翔
* @date 2026-03-13
*/
public class DictionaryAlreadyExistsException extends RuntimeException {
private final String type;
private final String code;
public DictionaryAlreadyExistsException(String type, String code) {
super("Dictionary with type '" + type + "' and code '" + code + "' already exists");
this.type = type;
this.code = code;
}
public String getType() {
return type;
}
public String getCode() {
return code;
}
}
@@ -0,0 +1,85 @@
package cn.novalon.gym.manage.sys.core.query;
import java.time.LocalDateTime;
/**
* 操作日志查询对象
*
* @author 张翔
* @date 2026-03-13
*/
public class OperationLogQuery {
private String username;
private String operation;
private String status;
private String keyword;
private LocalDateTime startTime;
private LocalDateTime endTime;
private String ip;
private String method;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getOperation() {
return operation;
}
public void setOperation(String operation) {
this.operation = operation;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getKeyword() {
return keyword;
}
public void setKeyword(String keyword) {
this.keyword = keyword;
}
public LocalDateTime getStartTime() {
return startTime;
}
public void setStartTime(LocalDateTime startTime) {
this.startTime = startTime;
}
public LocalDateTime getEndTime() {
return endTime;
}
public void setEndTime(LocalDateTime endTime) {
this.endTime = endTime;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
}
@@ -0,0 +1,47 @@
package cn.novalon.gym.manage.sys.core.query;
/**
* 异常日志查询对象
*
* @author 张翔
* @date 2026-03-13
*/
public class SysExceptionLogQuery {
private String username;
private String title;
private String exceptionName;
private String keyword;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getExceptionName() {
return exceptionName;
}
public void setExceptionName(String exceptionName) {
this.exceptionName = exceptionName;
}
public String getKeyword() {
return keyword;
}
public void setKeyword(String keyword) {
this.keyword = keyword;
}
}
@@ -0,0 +1,47 @@
package cn.novalon.gym.manage.sys.core.query;
/**
* 登录日志查询对象
*
* @author 张翔
* @date 2026-03-13
*/
public class SysLoginLogQuery {
private String username;
private String ip;
private String status;
private String keyword;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getKeyword() {
return keyword;
}
public void setKeyword(String keyword) {
this.keyword = keyword;
}
}
@@ -0,0 +1,56 @@
package cn.novalon.gym.manage.sys.core.query;
/**
* 菜单查询对象
*
* @author 张翔
* @date 2026-03-13
*/
public class SysMenuQuery {
private String menuName;
private String menuType;
private Integer status;
private Long parentId;
private String keyword;
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 Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public Long getParentId() {
return parentId;
}
public void setParentId(Long parentId) {
this.parentId = parentId;
}
public String getKeyword() {
return keyword;
}
public void setKeyword(String keyword) {
this.keyword = keyword;
}
}
@@ -0,0 +1,50 @@
package cn.novalon.gym.manage.sys.core.query;
/**
* 角色查询对象
*
* @author 张翔
* @date 2026-03-13
*/
public class SysRoleQuery {
private String roleName;
private String roleKey;
private Integer status;
private String keyword;
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;
}
public String getKeyword() {
return keyword;
}
public void setKeyword(String keyword) {
this.keyword = keyword;
}
}
@@ -0,0 +1,60 @@
package cn.novalon.gym.manage.sys.core.query;
/**
* 用户查询对象
*
* @author 张翔
* @date 2026-03-13
*/
public class SysUserQuery {
private String username;
private String email;
private Long roleId;
private Integer status;
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 Long getRoleId() {
return roleId;
}
public void setRoleId(Long roleId) {
this.roleId = roleId;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getKeyword() {
return keyword;
}
public void setKeyword(String keyword) {
this.keyword = keyword;
}
}
@@ -0,0 +1,32 @@
package cn.novalon.gym.manage.sys.core.repository;
import cn.novalon.gym.manage.sys.core.domain.Dictionary;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* 字典仓储接口
*
* @author 张翔
* @date 2026-03-13
*/
public interface IDictionaryRepository {
Flux<Dictionary> findAll();
Flux<Dictionary> findByDeletedAtIsNullOrderBySortAsc();
Mono<Dictionary> findById(Long id);
Flux<Dictionary> findByType(String type);
Mono<Dictionary> findByTypeAndCode(String type, String code);
Mono<Boolean> existsByTypeAndCode(String type, String code);
Mono<Dictionary> save(Dictionary dictionary);
Mono<Void> deleteById(Long id);
Mono<Void> deleteByIdAndDeletedAtIsNull(Long id);
}
@@ -0,0 +1,35 @@
package cn.novalon.gym.manage.sys.core.repository;
import cn.novalon.gym.manage.common.dto.PageRequest;
import cn.novalon.gym.manage.common.dto.PageResponse;
import cn.novalon.gym.manage.sys.core.domain.OperationLog;
import cn.novalon.gym.manage.sys.core.query.OperationLogQuery;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
/**
* 操作日志仓储接口
*
* @author 张翔
* @date 2026-03-13
*/
public interface IOperationLogRepository {
Mono<OperationLog> findById(Long id);
Mono<OperationLog> save(OperationLog operationLog);
Mono<Void> deleteById(Long id);
Flux<OperationLog> findAll();
Flux<OperationLog> findByUsername(String username);
Mono<PageResponse<OperationLog>> findByQueryWithPagination(OperationLogQuery query, PageRequest pageRequest);
Mono<Long> count();
Mono<Long> countByCreatedAtAfter(LocalDateTime dateTime);
}
@@ -0,0 +1,33 @@
package cn.novalon.gym.manage.sys.core.repository;
import cn.novalon.gym.manage.sys.core.domain.SysConfig;
import org.springframework.data.domain.Sort;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* 系统配置仓储接口
*
* @author 张翔
* @date 2026-03-13
*/
public interface ISysConfigRepository {
Mono<SysConfig> findById(Long id);
Mono<SysConfig> findByConfigKeyAndDeletedAtIsNull(String configKey);
Flux<SysConfig> findByDeletedAtIsNull();
Flux<SysConfig> findAll();
Flux<SysConfig> findAll(Sort sort);
Mono<SysConfig> save(SysConfig config);
Mono<Void> deleteByIdAndDeletedAtIsNull(Long id);
Mono<Long> count();
Mono<Boolean> existsByConfigKey(String configKey);
}
@@ -0,0 +1,26 @@
package cn.novalon.gym.manage.sys.core.repository;
import cn.novalon.gym.manage.sys.core.domain.SysDictData;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* 字典数据仓储接口
*
* @author 张翔
* @date 2026-03-13
*/
public interface ISysDictDataRepository {
Flux<SysDictData> findByDeletedAtIsNull();
Flux<SysDictData> findByDictTypeAndDeletedAtIsNull(String dictType);
Flux<SysDictData> findByDictTypeAndStatusAndDeletedAtIsNull(String dictType, String status);
Mono<SysDictData> findById(Long id);
Mono<SysDictData> save(SysDictData dictData);
Mono<Void> deleteByIdAndDeletedAtIsNull(Long id);
}
@@ -0,0 +1,24 @@
package cn.novalon.gym.manage.sys.core.repository;
import cn.novalon.gym.manage.sys.core.domain.SysDictType;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* 字典类型仓储接口
*
* @author 张翔
* @date 2026-03-13
*/
public interface ISysDictTypeRepository {
Flux<SysDictType> findByDeletedAtIsNull();
Mono<SysDictType> findById(Long id);
Mono<SysDictType> findByDictTypeAndDeletedAtIsNull(String dictType);
Mono<SysDictType> save(SysDictType dictType);
Mono<Void> deleteByIdAndDeletedAtIsNull(Long id);
}
@@ -0,0 +1,32 @@
package cn.novalon.gym.manage.sys.core.repository;
import cn.novalon.gym.manage.sys.core.domain.SysExceptionLog;
import cn.novalon.gym.manage.common.dto.PageRequest;
import cn.novalon.gym.manage.common.dto.PageResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
/**
* 异常日志仓储接口
*
* @author 张翔
* @date 2026-03-13
*/
public interface ISysExceptionLogRepository {
Flux<SysExceptionLog> findAllByOrderByCreateTimeDesc();
Flux<SysExceptionLog> findByUsernameOrderByCreateTimeDesc(String username);
Flux<SysExceptionLog> findByCreateTimeBetweenOrderByCreateTimeDesc(LocalDateTime startTime, LocalDateTime endTime);
Mono<SysExceptionLog> save(SysExceptionLog exceptionLog);
Mono<SysExceptionLog> findById(Long id);
Mono<Long> count();
Mono<PageResponse<SysExceptionLog>> findExceptionLogsByPage(PageRequest pageRequest);
}
@@ -0,0 +1,34 @@
package cn.novalon.gym.manage.sys.core.repository;
import cn.novalon.gym.manage.sys.core.domain.SysLoginLog;
import cn.novalon.gym.manage.common.dto.PageRequest;
import cn.novalon.gym.manage.common.dto.PageResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
/**
* 登录日志仓储接口
*
* @author 张翔
* @date 2026-03-13
*/
public interface ISysLoginLogRepository {
Flux<SysLoginLog> findAllByOrderByLoginTimeDesc();
Flux<SysLoginLog> findByUsernameOrderByLoginTimeDesc(String username);
Flux<SysLoginLog> findByLoginTimeBetweenOrderByLoginTimeDesc(LocalDateTime startTime, LocalDateTime endTime);
Mono<SysLoginLog> save(SysLoginLog loginLog);
Mono<SysLoginLog> findById(Long id);
Mono<Long> count();
Mono<Long> countToday();
Mono<PageResponse<SysLoginLog>> findLoginLogsByPage(PageRequest pageRequest);
}
@@ -0,0 +1,38 @@
package cn.novalon.gym.manage.sys.core.repository;
import cn.novalon.gym.manage.sys.core.domain.SysMenu;
import cn.novalon.gym.manage.sys.core.query.SysMenuQuery;
import cn.novalon.gym.manage.common.dto.PageRequest;
import cn.novalon.gym.manage.common.dto.PageResponse;
import org.springframework.data.domain.Sort;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* 菜单仓储接口
*
* @author 张翔
* @date 2026-03-13
*/
public interface ISysMenuRepository {
Flux<SysMenu> findByParentId(Long parentId);
Flux<SysMenu> findByParentIdOrderBySort(Long parentId, Sort sort);
Mono<SysMenu> findById(Long id);
Mono<SysMenu> save(SysMenu sysMenu);
Mono<Void> deleteById(Long id);
Flux<SysMenu> findAll();
Flux<SysMenu> findAll(Sort sort);
Mono<Long> count();
Mono<PageResponse<SysMenu>> findByQueryWithPagination(SysMenuQuery query, PageRequest pageRequest);
Flux<SysMenu> findByStatus(String status);
}
@@ -0,0 +1,39 @@
package cn.novalon.gym.manage.sys.core.repository;
import cn.novalon.gym.manage.sys.core.domain.SysPermission;
import org.springframework.data.domain.Sort;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* 权限仓储接口
*
* @author 张翔
* @date 2026-03-25
*/
public interface ISysPermissionRepository {
Mono<SysPermission> findById(Long id);
Mono<SysPermission> findByIdIncludingDeleted(Long id);
Mono<SysPermission> save(SysPermission sysPermission);
Mono<Void> deleteById(Long id);
Flux<SysPermission> findAll();
Flux<SysPermission> findAll(Sort sort);
Mono<SysPermission> findByPermissionCode(String permissionCode);
Mono<Long> count();
Mono<Boolean> existsByPermissionCode(String permissionCode);
Mono<SysPermission> updatePermission(SysPermission permission);
Flux<SysPermission> findByRoleId(Long roleId);
Flux<SysPermission> findByRoleIds(java.util.List<Long> roleIds);
}
@@ -0,0 +1,34 @@
package cn.novalon.gym.manage.sys.core.repository;
import cn.novalon.gym.manage.sys.core.domain.SysRolePermission;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* 角色权限关联仓储接口
*
* @author 张翔
* @date 2026-03-25
*/
public interface ISysRolePermissionRepository {
Mono<SysRolePermission> save(SysRolePermission rolePermission);
Mono<Void> deleteById(Long id);
Mono<Void> deleteByRoleId(Long roleId);
Mono<Void> deleteByPermissionId(Long permissionId);
Flux<SysRolePermission> findByRoleId(Long roleId);
Flux<SysRolePermission> findByPermissionId(Long permissionId);
Flux<Long> findPermissionIdsByRoleId(Long roleId);
Flux<Long> findRoleIdsByPermissionId(Long permissionId);
Mono<Void> deleteByRoleIdAndPermissionIds(Long roleId, java.util.List<Long> permissionIds);
Mono<Void> deleteByPermissionIdAndRoleIds(Long permissionId, java.util.List<Long> roleIds);
}
@@ -0,0 +1,44 @@
package cn.novalon.gym.manage.sys.core.repository;
import cn.novalon.gym.manage.sys.core.domain.SysRole;
import cn.novalon.gym.manage.sys.core.query.SysRoleQuery;
import cn.novalon.gym.manage.common.dto.PageRequest;
import cn.novalon.gym.manage.common.dto.PageResponse;
import org.springframework.data.domain.Sort;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* 角色仓储接口
*
* @author 张翔
* @date 2026-03-13
*/
public interface ISysRoleRepository {
Mono<SysRole> findById(Long id);
Mono<SysRole> findByIdIncludingDeleted(Long id);
Mono<SysRole> save(SysRole sysRole);
Mono<Void> deleteById(Long id);
Flux<SysRole> findAll();
Flux<SysRole> findAll(Sort sort);
Flux<SysRole> findByRoleNameLikeOrRoleKeyLike(String roleName, String roleKey, Sort sort);
Mono<Long> count();
Mono<Long> countByRoleNameLikeOrRoleKeyLike(String roleName, String roleKey);
Mono<PageResponse<SysRole>> findByQueryWithPagination(SysRoleQuery query, PageRequest pageRequest);
Mono<SysRole> findByRoleName(String roleName);
Mono<Boolean> existsByRoleName(String roleName);
Mono<SysRole> updateRole(SysRole role);
}
@@ -0,0 +1,58 @@
package cn.novalon.gym.manage.sys.core.repository;
import cn.novalon.gym.manage.sys.core.domain.SysUser;
import cn.novalon.gym.manage.sys.core.query.SysUserQuery;
import cn.novalon.gym.manage.common.dto.PageRequest;
import cn.novalon.gym.manage.common.dto.PageResponse;
import org.springframework.data.domain.Sort;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.List;
/**
* 用户仓储接口
*
* @author 张翔
* @date 2026-03-13
*/
public interface ISysUserRepository {
Mono<SysUser> findByUsername(String username);
Mono<SysUser> findByEmail(String email);
Mono<SysUser> findById(Long id);
Mono<SysUser> findByIdIncludingDeleted(Long id);
Mono<SysUser> save(SysUser sysUser);
Mono<Void> deleteById(Long id);
Flux<SysUser> findAll();
Flux<SysUser> findAll(Sort sort);
Flux<SysUser> findByDeletedAtIsNull();
Flux<SysUser> findByDeletedAtIsNull(Sort sort);
Mono<Long> count();
Mono<PageResponse<SysUser>> findByQueryWithPagination(SysUserQuery query, PageRequest pageRequest);
Mono<Boolean> existsByUsername(String username);
Mono<Boolean> existsByEmail(String email);
Mono<Void> logicalDeleteById(Long id);
Mono<Void> logicalDeleteByIds(List<Long> ids);
Mono<Void> restoreById(Long id);
Mono<Void> restoreByIds(List<Long> ids);
Mono<Void> updateRoleIdToNullByRoleId(Long roleId);
}
@@ -0,0 +1,28 @@
package cn.novalon.gym.manage.sys.core.repository;
import cn.novalon.gym.manage.sys.core.domain.UserRole;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public interface IUserRoleRepository {
Mono<UserRole> save(UserRole userRole);
Mono<Void> deleteById(Long id);
Mono<Void> deleteByUserId(Long userId);
Mono<Void> deleteByRoleId(Long roleId);
Flux<UserRole> findByUserId(Long userId);
Flux<UserRole> findByRoleId(Long roleId);
Mono<Long> countByUserId(Long userId);
Mono<Long> countByRoleId(Long roleId);
Flux<UserRole> findAll();
Mono<UserRole> findById(Long id);
}
@@ -0,0 +1,15 @@
package cn.novalon.gym.manage.sys.core.service;
import cn.novalon.gym.manage.sys.core.domain.Dictionary;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public interface IDictionaryService {
Flux<Dictionary> findAll();
Mono<Dictionary> findById(Long id);
Flux<Dictionary> findByType(String type);
Mono<Boolean> checkTypeAndCodeExists(String type, String code);
Mono<Dictionary> save(Dictionary dictionary);
Mono<Dictionary> update(Long id, Dictionary dictionary);
Mono<Void> deleteById(Long id);
}
@@ -0,0 +1,24 @@
package cn.novalon.gym.manage.sys.core.service;
import cn.novalon.gym.manage.common.dto.PageRequest;
import cn.novalon.gym.manage.common.dto.PageResponse;
import cn.novalon.gym.manage.sys.core.domain.OperationLog;
import cn.novalon.gym.manage.sys.core.query.OperationLogQuery;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* 操作日志服务接口
*
* @author 张翔
* @date 2026-03-13
*/
public interface IOperationLogService {
Mono<OperationLog> save(OperationLog log);
Flux<OperationLog> findAll();
Mono<OperationLog> findById(Long id);
Flux<OperationLog> findByUsername(String username);
Mono<PageResponse<OperationLog>> findByQueryWithPagination(OperationLogQuery query, PageRequest pageRequest);
Mono<Long> count();
Mono<Long> countToday();
}
@@ -0,0 +1,20 @@
package cn.novalon.gym.manage.sys.core.service;
import cn.novalon.gym.manage.sys.core.domain.SysConfig;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* 系统配置服务接口
*
* @author 张翔
* @date 2026-03-13
*/
public interface ISysConfigService {
Flux<SysConfig> findAll();
Mono<SysConfig> findById(Long id);
Mono<SysConfig> findByConfigKey(String configKey);
Mono<SysConfig> save(SysConfig config);
Mono<Void> deleteById(Long id);
Mono<String> getConfigValue(String configKey);
}
@@ -0,0 +1,20 @@
package cn.novalon.gym.manage.sys.core.service;
import cn.novalon.gym.manage.sys.core.domain.SysDictData;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* 字典数据服务接口
*
* @author 张翔
* @date 2026-03-13
*/
public interface ISysDictDataService {
Flux<SysDictData> findAll();
Flux<SysDictData> findByDictType(String dictType);
Flux<SysDictData> findByDictTypeAndStatus(String dictType, String status);
Mono<SysDictData> findById(Long id);
Mono<SysDictData> save(SysDictData dictData);
Mono<Void> deleteById(Long id);
}
@@ -0,0 +1,19 @@
package cn.novalon.gym.manage.sys.core.service;
import cn.novalon.gym.manage.sys.core.domain.SysDictType;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* 字典类型服务接口
*
* @author 张翔
* @date 2026-03-13
*/
public interface ISysDictTypeService {
Flux<SysDictType> findAll();
Mono<SysDictType> findById(Long id);
Mono<SysDictType> findByDictType(String dictType);
Mono<SysDictType> save(SysDictType dictType);
Mono<Void> deleteById(Long id);
}
@@ -0,0 +1,19 @@
package cn.novalon.gym.manage.sys.core.service;
import cn.novalon.gym.manage.sys.core.domain.SysExceptionLog;
import cn.novalon.gym.manage.common.dto.PageRequest;
import cn.novalon.gym.manage.common.dto.PageResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
public interface ISysExceptionLogService {
Mono<SysExceptionLog> findById(Long id);
Flux<SysExceptionLog> findAll();
Flux<SysExceptionLog> findByUsername(String username);
Flux<SysExceptionLog> findByCreateTimeBetween(LocalDateTime startTime, LocalDateTime endTime);
Mono<SysExceptionLog> save(SysExceptionLog exceptionLog);
Mono<PageResponse<SysExceptionLog>> findExceptionLogsByPage(PageRequest pageRequest);
Mono<Long> count();
}
@@ -0,0 +1,27 @@
package cn.novalon.gym.manage.sys.core.service;
import cn.novalon.gym.manage.sys.core.domain.SysLoginLog;
import cn.novalon.gym.manage.common.dto.PageRequest;
import cn.novalon.gym.manage.common.dto.PageResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
/**
* 登录日志服务接口
*
* @author 张翔
* @date 2026-03-13
*/
public interface ISysLoginLogService {
Mono<SysLoginLog> findById(Long id);
Flux<SysLoginLog> findAll();
Flux<SysLoginLog> findByUsername(String username);
Flux<SysLoginLog> findByLoginTimeBetween(LocalDateTime startTime, LocalDateTime endTime);
Mono<SysLoginLog> save(SysLoginLog loginLog);
Mono<PageResponse<SysLoginLog>> findLoginLogsByPage(PageRequest pageRequest);
Mono<Long> count();
Mono<Long> countToday();
Flux<SysLoginLog> findRecent(int limit);
}
@@ -0,0 +1,25 @@
package cn.novalon.gym.manage.sys.core.service;
import cn.novalon.gym.manage.sys.core.domain.SysMenu;
import cn.novalon.gym.manage.sys.core.command.CreateMenuCommand;
import cn.novalon.gym.manage.sys.core.command.UpdateMenuCommand;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* 菜单服务接口
*
* @author 张翔
* @date 2026-03-13
*/
public interface ISysMenuService {
Mono<SysMenu> findById(Long id);
Flux<SysMenu> findAll();
Flux<SysMenu> findByParentId(Long parentId);
Mono<SysMenu> createMenu(SysMenu menu);
Mono<SysMenu> createMenu(CreateMenuCommand command);
Mono<SysMenu> updateMenu(SysMenu menu);
Mono<SysMenu> updateMenu(UpdateMenuCommand command);
Mono<Void> deleteMenu(Long id);
Flux<SysMenu> buildMenuTree(Flux<SysMenu> menus);
}
@@ -0,0 +1,28 @@
package cn.novalon.gym.manage.sys.core.service;
import cn.novalon.gym.manage.sys.core.domain.SysPermission;
import org.springframework.data.domain.Sort;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* 权限服务接口
*
* @author 张翔
* @date 2026-03-25
*/
public interface ISysPermissionService {
Mono<SysPermission> findById(Long id);
Flux<SysPermission> findAll();
Flux<SysPermission> findAll(Sort sort);
Mono<SysPermission> findByPermissionCode(String permissionCode);
Mono<Long> count();
Mono<SysPermission> createPermission(SysPermission permission);
Mono<SysPermission> updatePermission(SysPermission permission);
Mono<Void> deletePermission(Long id);
Mono<Boolean> existsByPermissionCode(String permissionCode);
Flux<SysPermission> findByRoleId(Long roleId);
Flux<SysPermission> findByRoleIds(java.util.List<Long> roleIds);
Mono<Void> assignPermissionsToRole(Long roleId, java.util.List<Long> permissionIds);
Flux<SysPermission> getPermissionsByRoleId(Long roleId);
}
@@ -0,0 +1,31 @@
package cn.novalon.gym.manage.sys.core.service;
import cn.novalon.gym.manage.sys.core.domain.SysRole;
import cn.novalon.gym.manage.common.dto.PageRequest;
import cn.novalon.gym.manage.common.dto.PageResponse;
import cn.novalon.gym.manage.sys.core.command.CreateRoleCommand;
import cn.novalon.gym.manage.sys.core.command.UpdateRoleCommand;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* 角色服务接口
*
* @author 张翔
* @date 2026-03-13
*/
public interface ISysRoleService {
Mono<SysRole> findById(Long id);
Flux<SysRole> findAll();
Mono<PageResponse<SysRole>> findRolesByPage(PageRequest pageRequest);
Mono<Long> count();
Mono<SysRole> createRole(SysRole role);
Mono<SysRole> createRole(CreateRoleCommand command);
Mono<SysRole> updateRole(SysRole role);
Mono<SysRole> updateRole(UpdateRoleCommand command);
Mono<Void> deleteRole(Long id);
Mono<SysRole> findByRoleName(String roleName);
Mono<Boolean> existsByRoleName(String roleName);
Mono<SysRole> logicalDeleteRole(Long id);
Mono<SysRole> restoreRole(Long id);
}
@@ -0,0 +1,63 @@
package cn.novalon.gym.manage.sys.core.service;
import cn.novalon.gym.manage.sys.core.domain.SysUser;
import cn.novalon.gym.manage.common.dto.PageRequest;
import cn.novalon.gym.manage.common.dto.PageResponse;
import cn.novalon.gym.manage.sys.core.command.CreateUserCommand;
import cn.novalon.gym.manage.sys.core.command.UpdateUserCommand;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.List;
/**
* 用户服务接口
*
* @author 张翔
* @date 2026-03-13
*/
public interface ISysUserService {
Mono<SysUser> findById(Long id);
Flux<SysUser> findAll();
Flux<SysUser> findAll(boolean includeDeleted);
Mono<PageResponse<SysUser>> findUsersByPage(PageRequest pageRequest);
Mono<Long> count();
Mono<SysUser> findByUsername(String username);
Mono<Boolean> existsByUsername(String username);
Mono<Boolean> existsByEmail(String email);
Mono<SysUser> createUser(SysUser user);
Mono<SysUser> createUser(CreateUserCommand command);
Mono<SysUser> updateUser(SysUser user);
Mono<SysUser> updateUser(UpdateUserCommand command);
Mono<Void> deleteUser(Long id);
Mono<Void> logicalDeleteUser(Long id);
Mono<Void> logicalDeleteUsers(List<Long> ids);
Mono<Void> restoreUser(Long id);
Mono<Void> restoreUsers(List<Long> ids);
Mono<SysUser> changePassword(Long userId, String oldPassword, String newPassword);
Mono<Void> updateRoleIdToNullByRoleId(Long roleId);
Mono<Void> assignRolesToUser(Long userId, java.util.List<Long> roleIds);
Flux<cn.novalon.gym.manage.sys.core.domain.SysRole> getUserRoles(Long userId);
Flux<Long> getUserRoleIds(Long userId);
}
@@ -0,0 +1,10 @@
package cn.novalon.gym.manage.sys.core.service;
import reactor.core.publisher.Mono;
public interface IWebSocketService {
Mono<Void> sendToUser(Long userId, Object message);
Mono<Void> broadcast(Object message);
Mono<Void> notifyNewNotice(String noticeTitle, String noticeContent);
Mono<Void> notifyNewMessage(Long userId, String title, String content);
}
@@ -0,0 +1,90 @@
package cn.novalon.gym.manage.sys.core.service.impl;
import cn.novalon.gym.manage.sys.core.domain.Dictionary;
import cn.novalon.gym.manage.sys.core.exception.DictionaryAlreadyExistsException;
import cn.novalon.gym.manage.sys.core.repository.IDictionaryRepository;
import cn.novalon.gym.manage.sys.core.service.IDictionaryService;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
/**
* 字典服务实现类
*
* @author 张翔
* @date 2026-03-14
*/
@Service
public class DictionaryService implements IDictionaryService {
private final IDictionaryRepository repository;
public DictionaryService(IDictionaryRepository repository) {
this.repository = repository;
}
@Override
public Flux<Dictionary> findAll() {
return repository.findAll();
}
@Override
public Mono<Dictionary> findById(Long id) {
return repository.findById(id);
}
@Override
public Flux<Dictionary> findByType(String type) {
return repository.findByType(type);
}
@Override
public Mono<Boolean> checkTypeAndCodeExists(String type, String code) {
return repository.existsByTypeAndCode(type, code);
}
@Override
public Mono<Dictionary> save(Dictionary dictionary) {
if (dictionary.getId() == null) {
dictionary.setCreatedAt(LocalDateTime.now());
return checkTypeAndCodeExists(dictionary.getType(), dictionary.getCode())
.flatMap(exists -> {
if (exists) {
return Mono.error(new DictionaryAlreadyExistsException(dictionary.getType(), dictionary.getCode()));
}
dictionary.setUpdatedAt(LocalDateTime.now());
return repository.save(dictionary);
});
}
dictionary.setUpdatedAt(LocalDateTime.now());
return repository.save(dictionary);
}
@Override
public Mono<Dictionary> update(Long id, Dictionary dictionary) {
return repository.findById(id)
.flatMap(existing -> {
if (dictionary.getName() != null) {
existing.setName(dictionary.getName());
}
if (dictionary.getValue() != null) {
existing.setValue(dictionary.getValue());
}
if (dictionary.getRemark() != null) {
existing.setRemark(dictionary.getRemark());
}
if (dictionary.getSort() != null) {
existing.setSort(dictionary.getSort());
}
existing.setUpdatedAt(LocalDateTime.now());
return repository.save(existing);
});
}
@Override
public Mono<Void> deleteById(Long id) {
return repository.deleteById(id);
}
}
@@ -0,0 +1,66 @@
package cn.novalon.gym.manage.sys.core.service.impl;
import cn.novalon.gym.manage.common.dto.PageRequest;
import cn.novalon.gym.manage.common.dto.PageResponse;
import cn.novalon.gym.manage.sys.core.domain.OperationLog;
import cn.novalon.gym.manage.sys.core.query.OperationLogQuery;
import cn.novalon.gym.manage.sys.core.repository.IOperationLogRepository;
import cn.novalon.gym.manage.sys.core.service.IOperationLogService;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
/**
* 操作日志服务实现类
*
* @author 张翔
* @date 2026-03-14
*/
@Service
public class OperationLogService implements IOperationLogService {
private final IOperationLogRepository logRepository;
public OperationLogService(IOperationLogRepository logRepository) {
this.logRepository = logRepository;
}
@Override
public Mono<OperationLog> save(OperationLog log) {
log.setCreatedAt(LocalDateTime.now());
return logRepository.save(log);
}
@Override
public Flux<OperationLog> findAll() {
return logRepository.findAll();
}
@Override
public Mono<OperationLog> findById(Long id) {
return logRepository.findById(id);
}
@Override
public Flux<OperationLog> findByUsername(String username) {
return logRepository.findByUsername(username);
}
@Override
public Mono<PageResponse<OperationLog>> findByQueryWithPagination(OperationLogQuery query, PageRequest pageRequest) {
return logRepository.findByQueryWithPagination(query, pageRequest);
}
@Override
public Mono<Long> count() {
return logRepository.count();
}
@Override
public Mono<Long> countToday() {
LocalDateTime startOfDay = LocalDateTime.now().withHour(0).withMinute(0).withSecond(0).withNano(0);
return logRepository.countByCreatedAtAfter(startOfDay);
}
}
@@ -0,0 +1,61 @@
package cn.novalon.gym.manage.sys.core.service.impl;
import cn.novalon.gym.manage.sys.core.domain.SysConfig;
import cn.novalon.gym.manage.sys.core.repository.ISysConfigRepository;
import cn.novalon.gym.manage.sys.core.service.ISysConfigService;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* 系统配置服务实现类
*
* @author 张翔
* @date 2026-03-14
*/
@Service
public class SysConfigService implements ISysConfigService {
private final ISysConfigRepository repository;
public SysConfigService(ISysConfigRepository repository) {
this.repository = repository;
}
@Override
public Flux<SysConfig> findAll() {
return repository.findByDeletedAtIsNull();
}
@Override
@Cacheable(value = "sysConfig", key = "#id")
public Mono<SysConfig> findById(Long id) {
return repository.findById(id);
}
@Override
@Cacheable(value = "sysConfig", key = "#configKey")
public Mono<SysConfig> findByConfigKey(String configKey) {
return repository.findByConfigKeyAndDeletedAtIsNull(configKey);
}
@Override
@CacheEvict(value = "sysConfig", allEntries = true)
public Mono<SysConfig> save(SysConfig config) {
return repository.save(config);
}
@Override
@CacheEvict(value = "sysConfig", key = "#id")
public Mono<Void> deleteById(Long id) {
return repository.deleteByIdAndDeletedAtIsNull(id);
}
@Override
public Mono<String> getConfigValue(String configKey) {
return findByConfigKey(configKey)
.map(SysConfig::getConfigValue);
}
}
@@ -0,0 +1,54 @@
package cn.novalon.gym.manage.sys.core.service.impl;
import cn.novalon.gym.manage.sys.core.domain.SysDictData;
import cn.novalon.gym.manage.sys.core.repository.ISysDictDataRepository;
import cn.novalon.gym.manage.sys.core.service.ISysDictDataService;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* 字典数据服务实现类
*
* @author 张翔
* @date 2026-03-14
*/
@Service
public class SysDictDataService implements ISysDictDataService {
private final ISysDictDataRepository repository;
public SysDictDataService(ISysDictDataRepository repository) {
this.repository = repository;
}
@Override
public Flux<SysDictData> findAll() {
return repository.findByDeletedAtIsNull();
}
@Override
public Flux<SysDictData> findByDictType(String dictType) {
return repository.findByDictTypeAndDeletedAtIsNull(dictType);
}
@Override
public Flux<SysDictData> findByDictTypeAndStatus(String dictType, String status) {
return repository.findByDictTypeAndStatusAndDeletedAtIsNull(dictType, status);
}
@Override
public Mono<SysDictData> findById(Long id) {
return repository.findById(id);
}
@Override
public Mono<SysDictData> save(SysDictData dictData) {
return repository.save(dictData);
}
@Override
public Mono<Void> deleteById(Long id) {
return repository.deleteByIdAndDeletedAtIsNull(id);
}
}
@@ -0,0 +1,49 @@
package cn.novalon.gym.manage.sys.core.service.impl;
import cn.novalon.gym.manage.sys.core.domain.SysDictType;
import cn.novalon.gym.manage.sys.core.repository.ISysDictTypeRepository;
import cn.novalon.gym.manage.sys.core.service.ISysDictTypeService;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* 字典类型服务实现类
*
* @author 张翔
* @date 2026-03-14
*/
@Service
public class SysDictTypeService implements ISysDictTypeService {
private final ISysDictTypeRepository repository;
public SysDictTypeService(ISysDictTypeRepository repository) {
this.repository = repository;
}
@Override
public Flux<SysDictType> findAll() {
return repository.findByDeletedAtIsNull();
}
@Override
public Mono<SysDictType> findById(Long id) {
return repository.findById(id);
}
@Override
public Mono<SysDictType> findByDictType(String dictType) {
return repository.findByDictTypeAndDeletedAtIsNull(dictType);
}
@Override
public Mono<SysDictType> save(SysDictType dictType) {
return repository.save(dictType);
}
@Override
public Mono<Void> deleteById(Long id) {
return repository.deleteByIdAndDeletedAtIsNull(id);
}
}
@@ -0,0 +1,67 @@
package cn.novalon.gym.manage.sys.core.service.impl;
import cn.novalon.gym.manage.sys.core.domain.SysExceptionLog;
import cn.novalon.gym.manage.sys.core.repository.ISysExceptionLogRepository;
import cn.novalon.gym.manage.sys.core.service.ISysExceptionLogService;
import cn.novalon.gym.manage.common.dto.PageRequest;
import cn.novalon.gym.manage.common.dto.PageResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
/**
* 异常日志服务实现类
*
* @author 张翔
* @date 2026-03-14
*/
@Service
public class SysExceptionLogService implements ISysExceptionLogService {
private static final Logger logger = LoggerFactory.getLogger(SysExceptionLogService.class);
private final ISysExceptionLogRepository repository;
public SysExceptionLogService(ISysExceptionLogRepository repository) {
this.repository = repository;
}
@Override
public Flux<SysExceptionLog> findAll() {
return repository.findAllByOrderByCreateTimeDesc();
}
@Override
public Flux<SysExceptionLog> findByUsername(String username) {
return repository.findByUsernameOrderByCreateTimeDesc(username);
}
@Override
public Flux<SysExceptionLog> findByCreateTimeBetween(LocalDateTime startTime, LocalDateTime endTime) {
return repository.findByCreateTimeBetweenOrderByCreateTimeDesc(startTime, endTime);
}
@Override
public Mono<SysExceptionLog> save(SysExceptionLog exceptionLog) {
return repository.save(exceptionLog);
}
@Override
public Mono<SysExceptionLog> findById(Long id) {
return repository.findById(id);
}
@Override
public Mono<PageResponse<SysExceptionLog>> findExceptionLogsByPage(PageRequest pageRequest) {
logger.info("分页查询异常日志: page={}, size={}", pageRequest.getPage(), pageRequest.getSize());
return repository.findExceptionLogsByPage(pageRequest);
}
@Override
public Mono<Long> count() {
return repository.count();
}
}
@@ -0,0 +1,79 @@
package cn.novalon.gym.manage.sys.core.service.impl;
import cn.novalon.gym.manage.sys.core.domain.SysLoginLog;
import cn.novalon.gym.manage.sys.core.repository.ISysLoginLogRepository;
import cn.novalon.gym.manage.sys.core.service.ISysLoginLogService;
import cn.novalon.gym.manage.common.dto.PageRequest;
import cn.novalon.gym.manage.common.dto.PageResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
/**
* 登录日志服务实现类
*
* @author 张翔
* @date 2026-03-14
*/
@Service
public class SysLoginLogService implements ISysLoginLogService {
private static final Logger logger = LoggerFactory.getLogger(SysLoginLogService.class);
private final ISysLoginLogRepository repository;
public SysLoginLogService(ISysLoginLogRepository repository) {
this.repository = repository;
}
@Override
public Flux<SysLoginLog> findAll() {
return repository.findAllByOrderByLoginTimeDesc();
}
@Override
public Flux<SysLoginLog> findByUsername(String username) {
return repository.findByUsernameOrderByLoginTimeDesc(username);
}
@Override
public Flux<SysLoginLog> findByLoginTimeBetween(LocalDateTime startTime, LocalDateTime endTime) {
return repository.findByLoginTimeBetweenOrderByLoginTimeDesc(startTime, endTime);
}
@Override
public Mono<SysLoginLog> save(SysLoginLog loginLog) {
return repository.save(loginLog);
}
@Override
public Mono<SysLoginLog> findById(Long id) {
return repository.findById(id);
}
@Override
public Mono<PageResponse<SysLoginLog>> findLoginLogsByPage(PageRequest pageRequest) {
logger.info("分页查询登录日志: page={}, size={}", pageRequest.getPage(), pageRequest.getSize());
return repository.findLoginLogsByPage(pageRequest);
}
@Override
public Mono<Long> count() {
return repository.count();
}
@Override
public Mono<Long> countToday() {
return repository.countToday();
}
@Override
public Flux<SysLoginLog> findRecent(int limit) {
logger.info("获取最近{}条登录日志", limit);
return repository.findAllByOrderByLoginTimeDesc()
.take(limit);
}
}
@@ -0,0 +1,122 @@
package cn.novalon.gym.manage.sys.core.service.impl;
import cn.novalon.gym.manage.sys.core.domain.SysMenu;
import cn.novalon.gym.manage.sys.core.repository.ISysMenuRepository;
import cn.novalon.gym.manage.sys.core.service.ISysMenuService;
import cn.novalon.gym.manage.sys.core.command.CreateMenuCommand;
import cn.novalon.gym.manage.sys.core.command.UpdateMenuCommand;
import cn.novalon.gym.manage.common.util.StatusConstants;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;
/**
* 系统菜单服务实现类
*
* @author 张翔
* @date 2026-03-14
*/
@Service
public class SysMenuService implements ISysMenuService {
private final ISysMenuRepository menuRepository;
public SysMenuService(ISysMenuRepository menuRepository) {
this.menuRepository = menuRepository;
}
@Override
public Mono<SysMenu> findById(Long id) {
return menuRepository.findById(id);
}
@Override
public Flux<SysMenu> findAll() {
return menuRepository.findAll();
}
@Override
public Flux<SysMenu> findByParentId(Long parentId) {
return menuRepository.findByParentId(parentId);
}
@Override
public Mono<SysMenu> createMenu(SysMenu menu) {
menu.setCreatedAt(LocalDateTime.now());
return menuRepository.save(menu);
}
@Override
public Mono<SysMenu> createMenu(CreateMenuCommand command) {
SysMenu menu = new SysMenu();
menu.setParentId(command.parentId());
menu.setMenuName(command.menuName());
menu.setMenuType(command.menuType());
menu.setOrderNum(command.orderNum());
menu.setComponent(command.component());
menu.setPerms(command.perms());
menu.setStatus(command.status() != null ? command.status() : StatusConstants.ENABLED);
menu.setCreatedAt(LocalDateTime.now());
return menuRepository.save(menu);
}
@Override
public Mono<SysMenu> updateMenu(SysMenu menu) {
menu.setUpdatedAt(LocalDateTime.now());
return menuRepository.save(menu);
}
@Override
public Mono<SysMenu> updateMenu(UpdateMenuCommand command) {
return menuRepository.findById(command.id())
.switchIfEmpty(Mono.error(new RuntimeException("Menu not found")))
.flatMap(menu -> {
if (command.parentId() != null) {
menu.setParentId(command.parentId());
}
if (command.menuName() != null) {
menu.setMenuName(command.menuName());
}
if (command.menuType() != null) {
menu.setMenuType(command.menuType());
}
if (command.orderNum() != null) {
menu.setOrderNum(command.orderNum());
}
if (command.component() != null) {
menu.setComponent(command.component());
}
if (command.perms() != null) {
menu.setPerms(command.perms());
}
if (command.status() != null) {
menu.setStatus(command.status());
}
menu.setUpdatedAt(LocalDateTime.now());
return menuRepository.save(menu);
});
}
@Override
public Mono<Void> deleteMenu(Long id) {
return menuRepository.deleteById(id);
}
@Override
public Flux<SysMenu> buildMenuTree(Flux<SysMenu> menus) {
return menus.collectList()
.map(list -> buildTree(list, 0L))
.flatMapMany(Flux::fromIterable);
}
private List<SysMenu> buildTree(List<SysMenu> menus, Long parentId) {
return menus.stream()
.filter(m -> m.getParentId().equals(parentId))
.peek(m -> m.setChildren(buildTree(menus, m.getId())))
.collect(Collectors.toList());
}
}
@@ -0,0 +1,120 @@
package cn.novalon.gym.manage.sys.core.service.impl;
import cn.novalon.gym.manage.common.util.StatusConstants;
import cn.novalon.gym.manage.sys.core.domain.SysPermission;
import cn.novalon.gym.manage.sys.core.domain.SysRolePermission;
import cn.novalon.gym.manage.sys.core.repository.ISysPermissionRepository;
import cn.novalon.gym.manage.sys.core.repository.ISysRolePermissionRepository;
import cn.novalon.gym.manage.sys.core.service.ISysPermissionService;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
import java.util.List;
/**
* 系统权限服务实现类
*
* @author 张翔
* @date 2026-03-25
*/
@Service
public class SysPermissionService implements ISysPermissionService {
private final ISysPermissionRepository permissionRepository;
private final ISysRolePermissionRepository rolePermissionRepository;
public SysPermissionService(ISysPermissionRepository permissionRepository,
ISysRolePermissionRepository rolePermissionRepository) {
this.permissionRepository = permissionRepository;
this.rolePermissionRepository = rolePermissionRepository;
}
@Override
public Mono<SysPermission> findById(Long id) {
return permissionRepository.findById(id);
}
@Override
public Flux<SysPermission> findAll() {
return permissionRepository.findAll();
}
@Override
public Flux<SysPermission> findAll(Sort sort) {
return permissionRepository.findAll(sort);
}
@Override
public Mono<SysPermission> findByPermissionCode(String permissionCode) {
return permissionRepository.findByPermissionCode(permissionCode);
}
@Override
public Mono<Long> count() {
return permissionRepository.count();
}
@Override
public Mono<SysPermission> createPermission(SysPermission permission) {
permission.setCreatedAt(LocalDateTime.now());
if (permission.getStatus() == null) {
permission.setStatus(StatusConstants.ENABLED);
}
return permissionRepository.save(permission);
}
@Override
public Mono<SysPermission> updatePermission(SysPermission permission) {
permission.setUpdatedAt(LocalDateTime.now());
return permissionRepository.updatePermission(permission);
}
@Override
public Mono<Void> deletePermission(Long id) {
return permissionRepository.findById(id)
.flatMap(permission -> {
permission.delete();
return permissionRepository.updatePermission(permission)
.then(rolePermissionRepository.deleteByPermissionId(id));
});
}
@Override
public Mono<Boolean> existsByPermissionCode(String permissionCode) {
return permissionRepository.existsByPermissionCode(permissionCode);
}
@Override
public Flux<SysPermission> findByRoleId(Long roleId) {
return permissionRepository.findByRoleId(roleId);
}
@Override
public Flux<SysPermission> findByRoleIds(List<Long> roleIds) {
return permissionRepository.findByRoleIds(roleIds);
}
@Override
@Transactional
public Mono<Void> assignPermissionsToRole(Long roleId, List<Long> permissionIds) {
return rolePermissionRepository.deleteByRoleId(roleId)
.then(Flux.fromIterable(permissionIds)
.flatMap(permissionId -> {
SysRolePermission rolePermission = new SysRolePermission();
rolePermission.setRoleId(roleId);
rolePermission.setPermissionId(permissionId);
rolePermission.setCreatedAt(LocalDateTime.now());
return rolePermissionRepository.save(rolePermission);
})
.then());
}
@Override
public Flux<SysPermission> getPermissionsByRoleId(Long roleId) {
return permissionRepository.findByRoleId(roleId);
}
}
@@ -0,0 +1,172 @@
package cn.novalon.gym.manage.sys.core.service.impl;
import cn.novalon.gym.manage.common.util.StatusConstants;
import cn.novalon.gym.manage.sys.core.domain.SysRole;
import cn.novalon.gym.manage.sys.core.query.SysRoleQuery;
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.repository.ISysRolePermissionRepository;
import cn.novalon.gym.manage.sys.core.service.ISysRoleService;
import cn.novalon.gym.manage.sys.core.service.ISysUserService;
import cn.novalon.gym.manage.sys.core.command.CreateRoleCommand;
import cn.novalon.gym.manage.sys.core.command.UpdateRoleCommand;
import cn.novalon.gym.manage.common.dto.PageRequest;
import cn.novalon.gym.manage.common.dto.PageResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
/**
* 系统角色服务实现类
*
* @author 张翔
* @date 2026-03-14
*/
@Service
public class SysRoleService implements ISysRoleService {
private static final Logger logger = LoggerFactory.getLogger(SysRoleService.class);
private final ISysRoleRepository roleRepository;
private final ISysUserService userService;
private final IUserRoleRepository userRoleRepository;
private final ISysRolePermissionRepository rolePermissionRepository;
public SysRoleService(ISysRoleRepository roleRepository, ISysUserService userService,
IUserRoleRepository userRoleRepository, ISysRolePermissionRepository rolePermissionRepository) {
this.roleRepository = roleRepository;
this.userService = userService;
this.userRoleRepository = userRoleRepository;
this.rolePermissionRepository = rolePermissionRepository;
}
@Override
public Mono<SysRole> findById(Long id) {
return roleRepository.findById(id);
}
@Override
public Flux<SysRole> findAll() {
return roleRepository.findAll();
}
@Override
public Mono<PageResponse<SysRole>> findRolesByPage(PageRequest pageRequest) {
SysRoleQuery query = new SysRoleQuery();
if (pageRequest.getKeyword() != null && !pageRequest.getKeyword().isEmpty()) {
query.setKeyword(pageRequest.getKeyword());
}
return roleRepository.findByQueryWithPagination(query, pageRequest);
}
@Override
public Mono<Long> count() {
return roleRepository.count();
}
@Override
public Mono<SysRole> createRole(SysRole role) {
role.setCreatedAt(LocalDateTime.now());
if (role.getStatus() == null) {
role.setStatus(StatusConstants.ENABLED);
}
return roleRepository.save(role);
}
@Override
public Mono<SysRole> createRole(CreateRoleCommand command) {
SysRole role = new SysRole();
role.generateId();
role.setRoleName(command.roleName());
role.setRoleKey(command.roleKey());
role.setRoleSort(command.roleSort());
role.setStatus(command.status() != null ? command.status() : StatusConstants.ENABLED);
role.setCreatedAt(LocalDateTime.now());
return roleRepository.save(role);
}
@Override
public Mono<SysRole> updateRole(SysRole role) {
role.setUpdatedAt(LocalDateTime.now());
return roleRepository.save(role);
}
@Override
public Mono<SysRole> updateRole(UpdateRoleCommand command) {
return roleRepository.findById(command.id())
.switchIfEmpty(Mono.error(new RuntimeException("Role not found")))
.flatMap(role -> {
if (command.roleName() != null) {
role.setRoleName(command.roleName());
}
if (command.roleKey() != null) {
role.setRoleKey(command.roleKey());
}
if (command.roleSort() != null) {
role.setRoleSort(command.roleSort());
}
if (command.status() != null) {
role.setStatus(command.status());
}
role.setUpdatedAt(LocalDateTime.now());
return roleRepository.save(role);
});
}
@Override
@Transactional
public Mono<Void> deleteRole(Long id) {
logger.debug("开始删除角色,ID: {}", id);
return roleRepository.findById(id)
.flatMap(role -> {
logger.debug("找到角色,开始删除关联记录");
return userRoleRepository.deleteByRoleId(id)
.doOnSuccess(v -> logger.debug("成功删除用户角色关联记录"))
.doOnError(e -> logger.error("删除用户角色关联记录失败", e))
.then(rolePermissionRepository.deleteByRoleId(id))
.doOnSuccess(v -> logger.debug("成功删除角色权限关联记录"))
.doOnError(e -> logger.error("删除角色权限关联记录失败", e))
.then(userService.updateRoleIdToNullByRoleId(id))
.doOnSuccess(v -> logger.debug("成功更新用户角色ID为null"))
.doOnError(e -> logger.error("更新用户角色ID失败", e))
.then(roleRepository.deleteById(id))
.doOnSuccess(v -> logger.debug("成功删除角色"))
.doOnError(e -> logger.error("删除角色失败", e));
});
}
@Override
public Mono<SysRole> findByRoleName(String roleName) {
return roleRepository.findByRoleName(roleName);
}
@Override
public Mono<Boolean> existsByRoleName(String roleName) {
return roleRepository.existsByRoleName(roleName);
}
@Override
public Mono<SysRole> logicalDeleteRole(Long id) {
return roleRepository.findByIdIncludingDeleted(id)
.flatMap(role -> {
role.delete();
return roleRepository.updateRole(role);
});
}
@Override
public Mono<SysRole> restoreRole(Long id) {
return roleRepository.findByIdIncludingDeleted(id)
.flatMap(role -> {
role.restore();
return roleRepository.updateRole(role);
});
}
}
@@ -0,0 +1,284 @@
package cn.novalon.gym.manage.sys.core.service.impl;
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.common.dto.PageRequest;
import cn.novalon.gym.manage.common.dto.PageResponse;
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.ISysUserService;
import cn.novalon.gym.manage.sys.core.command.CreateUserCommand;
import cn.novalon.gym.manage.sys.core.command.UpdateUserCommand;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
import java.util.List;
/**
* 用户服务实现类
*
* 文件定义:实现用户管理的核心业务逻辑
* 涉及业务:用户注册、登录、信息修改、删除、密码修改、逻辑删除等用户生命周期管理
* 算法:使用R2DBC进行响应式数据库操作,支持分页查询、条件查询、批量操作
*
* @author 张翔
* @date 2026-03-13
*/
@Service
public class SysUserService implements ISysUserService {
private static final Logger logger = LoggerFactory.getLogger(SysUserService.class);
private final ISysUserRepository userRepository;
private final ISysRoleRepository roleRepository;
private final IUserRoleRepository userRoleRepository;
private final PasswordEncoder passwordEncoder;
public SysUserService(ISysUserRepository userRepository,
ISysRoleRepository roleRepository,
IUserRoleRepository userRoleRepository,
@Qualifier("passwordEncoder") PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.roleRepository = roleRepository;
this.userRoleRepository = userRoleRepository;
this.passwordEncoder = passwordEncoder;
logger.info("使用的密码编码器类型: {}", passwordEncoder.getClass().getName());
}
@SuppressWarnings("unused")
private static final BCryptPasswordEncoder directEncoder = new BCryptPasswordEncoder(12);
@Override
public Mono<SysUser> findById(Long id) {
return userRepository.findById(id);
}
@Override
public Flux<SysUser> findAll() {
return userRepository.findAll();
}
@Override
public Flux<SysUser> findAll(boolean includeDeleted) {
if (includeDeleted) {
return userRepository.findAll();
} else {
return userRepository.findByDeletedAtIsNull();
}
}
@Override
public Mono<PageResponse<SysUser>> findUsersByPage(PageRequest pageRequest) {
return userRepository.findByQueryWithPagination(null, pageRequest);
}
@Override
public Mono<Long> count() {
return userRepository.count();
}
@Override
public Mono<SysUser> findByUsername(String username) {
return userRepository.findByUsername(username);
}
@Override
public Mono<SysUser> createUser(SysUser user) {
logger.info("SysUserService.createUser - 用户名: {}, 密码前缀: {}",
user.getUsername(),
user.getPassword() != null ? user.getPassword().substring(0, 7) : "null");
user.generateId();
if (user.getPassword() != null && !user.getPassword().startsWith("$2a$")
&& !user.getPassword().startsWith("$2b$")) {
logger.info("密码不以$2a$或$2b$开头,重新编码");
user.setPassword(passwordEncoder.encode(user.getPassword()));
logger.info("重新编码后的密码前缀: {}", user.getPassword().substring(0, 7));
} else {
logger.info("密码已编码,跳过重新编码");
}
if (user.getStatus() == null) {
user.setStatus(StatusConstants.ENABLED);
}
return userRepository.save(user);
}
@Override
public Mono<SysUser> createUser(CreateUserCommand command) {
SysUser user = new SysUser();
user.generateId();
user.setUsername(command.username().getValue());
user.setPassword(passwordEncoder.encode(command.password().getValue()));
user.setEmail(command.email().getValue());
user.setNickname(command.nickname());
user.setPhone(command.phone());
user.setRoleId(command.roleId());
user.setStatus(command.status() != null ? command.status() : StatusConstants.ENABLED);
return userRepository.save(user);
}
@Override
public Mono<SysUser> updateUser(SysUser user) {
user.setUpdatedAt(LocalDateTime.now());
return userRepository.save(user);
}
@Override
public Mono<SysUser> updateUser(UpdateUserCommand command) {
return userRepository.findById(command.id())
.switchIfEmpty(Mono.error(new RuntimeException("User not found")))
.flatMap(user -> {
if (command.username() != null) {
user.setUsername(command.username());
}
if (command.password() != null) {
user.setPassword(passwordEncoder.encode(command.password()));
}
if (command.email() != null) {
user.setEmail(command.email());
}
if (command.clearRole()) {
user.setRoleId(null);
} else if (command.roleId() != null) {
user.setRoleId(command.roleId());
}
if (command.status() != null) {
user.setStatus(command.status());
}
user.setUpdatedAt(LocalDateTime.now());
return userRepository.save(user);
});
}
@Override
@Transactional
public Mono<Void> deleteUser(Long id) {
logger.debug("开始删除用户,ID: {}", id);
return userRepository.findById(id)
.switchIfEmpty(Mono.error(new RuntimeException("User not found")))
.flatMap(user -> {
logger.debug("找到用户,开始删除关联记录");
return userRoleRepository.deleteByUserId(id)
.doOnSuccess(v -> logger.debug("成功删除用户角色关联记录"))
.doOnError(e -> logger.error("删除用户角色关联记录失败", e))
.then(userRepository.deleteById(id))
.doOnSuccess(v -> logger.debug("成功删除用户"))
.doOnError(e -> logger.error("删除用户失败", e));
});
}
@Override
public Mono<Void> updateRoleIdToNullByRoleId(Long roleId) {
return userRepository.updateRoleIdToNullByRoleId(roleId);
}
@Override
public Mono<SysUser> changePassword(Long userId, String oldPassword, String newPassword) {
return userRepository.findById(userId)
.flatMap(user -> {
if (!passwordEncoder.matches(oldPassword, user.getPassword())) {
return Mono.error(new RuntimeException("旧密码不正确"));
}
user.setPassword(passwordEncoder.encode(newPassword));
user.setUpdatedAt(LocalDateTime.now());
return userRepository.save(user);
});
}
@Override
public Mono<Boolean> existsByUsername(String username) {
return userRepository.findByUsername(username)
.map(user -> user != null)
.defaultIfEmpty(false);
}
@Override
public Mono<Boolean> existsByEmail(String email) {
return userRepository.findByEmail(email)
.map(user -> user != null)
.defaultIfEmpty(false);
}
@Override
public Mono<Void> logicalDeleteUser(Long id) {
return userRepository.findByIdIncludingDeleted(id)
.flatMap(user -> {
user.setDeletedAt(LocalDateTime.now());
return userRepository.save(user);
})
.then();
}
@Override
public Mono<Void> logicalDeleteUsers(List<Long> ids) {
return userRepository.logicalDeleteByIds(ids);
}
@Override
public Mono<Void> restoreUser(Long id) {
return userRepository.findByIdIncludingDeleted(id)
.flatMap(user -> {
user.setDeletedAt(null);
return userRepository.save(user);
})
.then();
}
@Override
public Mono<Void> restoreUsers(List<Long> ids) {
return userRepository.restoreByIds(ids);
}
@Override
@Transactional
public Mono<Void> assignRolesToUser(Long userId, List<Long> roleIds) {
logger.debug("开始为用户分配角色,用户ID: {}, 角色IDs: {}", userId, roleIds);
if (roleIds == null || roleIds.isEmpty()) {
logger.debug("角色列表为空,删除用户的所有角色关联");
return userRoleRepository.deleteByUserId(userId)
.doOnSuccess(v -> logger.debug("成功删除用户的所有角色关联"))
.doOnError(e -> logger.error("删除用户角色关联失败", e));
}
return userRoleRepository.deleteByUserId(userId)
.doOnSuccess(v -> logger.debug("成功删除用户的旧角色关联"))
.doOnError(e -> logger.error("删除用户旧角色关联失败", e))
.then(
Flux.fromIterable(roleIds)
.concatMap(roleId -> {
logger.debug("为用户分配角色ID: {}", roleId);
UserRole userRole = new UserRole();
userRole.setUserId(userId);
userRole.setRoleId(roleId);
userRole.setCreatedAt(LocalDateTime.now());
return userRoleRepository.save(userRole)
.doOnSuccess(v -> logger.debug("成功保存用户角色关联"))
.doOnError(e -> logger.error("保存用户角色关联失败", e));
})
.then());
}
@Override
public Flux<SysRole> getUserRoles(Long userId) {
return userRoleRepository.findByUserId(userId)
.flatMap(userRole -> roleRepository.findById(userRole.getRoleId()));
}
@Override
public Flux<Long> getUserRoleIds(Long userId) {
return userRoleRepository.findByUserId(userId)
.map(UserRole::getRoleId);
}
}
@@ -0,0 +1,111 @@
package cn.novalon.gym.manage.sys.core.util;
import cn.novalon.gym.manage.sys.core.domain.OperationLog;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.time.format.DateTimeFormatter;
import java.util.List;
/**
* Excel导出工具类
*
* @author 张翔
* @date 2026-04-03
*/
public class ExcelExportUtil {
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
/**
* 导出操作日志到Excel
*
* @param logs 操作日志列表
* @return Excel文件字节数组
* @throws IOException IO异常
*/
public static byte[] exportOperationLogs(List<OperationLog> logs) throws IOException {
try (Workbook workbook = new XSSFWorkbook();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
Sheet sheet = workbook.createSheet("操作日志");
CellStyle headerStyle = createHeaderStyle(workbook);
CellStyle dateStyle = createDateStyle(workbook);
Row headerRow = sheet.createRow(0);
String[] headers = {"ID", "操作人", "操作模块", "请求方法", "请求参数", "执行结果",
"IP地址", "耗时(ms)", "状态", "错误信息", "操作时间"};
for (int i = 0; i < headers.length; i++) {
Cell cell = headerRow.createCell(i);
cell.setCellValue(headers[i]);
cell.setCellStyle(headerStyle);
sheet.setColumnWidth(i, 20 * 256);
}
int rowNum = 1;
for (OperationLog log : logs) {
Row row = sheet.createRow(rowNum++);
row.createCell(0).setCellValue(log.getId() != null ? log.getId() : 0);
row.createCell(1).setCellValue(log.getUsername() != null ? log.getUsername() : "");
row.createCell(2).setCellValue(log.getOperation() != null ? log.getOperation() : "");
row.createCell(3).setCellValue(log.getMethod() != null ? log.getMethod() : "");
row.createCell(4).setCellValue(truncateText(log.getParams(), 1000));
row.createCell(5).setCellValue(truncateText(log.getResult(), 1000));
row.createCell(6).setCellValue(log.getIp() != null ? log.getIp() : "");
row.createCell(7).setCellValue(log.getDuration() != null ? log.getDuration() : 0);
row.createCell(8).setCellValue("0".equals(log.getStatus()) ? "成功" : "失败");
row.createCell(9).setCellValue(log.getErrorMsg() != null ? log.getErrorMsg() : "");
Cell dateCell = row.createCell(10);
if (log.getCreatedAt() != null) {
dateCell.setCellValue(log.getCreatedAt().format(DATE_TIME_FORMATTER));
dateCell.setCellStyle(dateStyle);
}
}
workbook.write(outputStream);
return outputStream.toByteArray();
}
}
private static CellStyle createHeaderStyle(Workbook workbook) {
CellStyle style = workbook.createCellStyle();
style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
style.setBorderBottom(BorderStyle.THIN);
style.setBorderTop(BorderStyle.THIN);
style.setBorderLeft(BorderStyle.THIN);
style.setBorderRight(BorderStyle.THIN);
style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER);
Font font = workbook.createFont();
font.setBold(true);
font.setFontHeightInPoints((short) 12);
style.setFont(font);
return style;
}
private static CellStyle createDateStyle(Workbook workbook) {
CellStyle style = workbook.createCellStyle();
style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER);
return style;
}
private static String truncateText(String text, int maxLength) {
if (text == null) {
return "";
}
if (text.length() <= maxLength) {
return text;
}
return text.substring(0, maxLength) + "...";
}
}
@@ -0,0 +1,122 @@
package cn.novalon.gym.manage.sys.core.util;
import cn.novalon.gym.manage.sys.core.domain.SysConfig;
import org.springframework.http.HttpStatus;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
import java.util.regex.Pattern;
/**
* 系统配置验证工具类
*
* @author 张翔
* @date 2026-03-31
*/
public class ValidationUtil {
// 配置键正则表达式:只允许字母、数字、下划线、点号,长度1-100
private static final Pattern CONFIG_KEY_PATTERN = Pattern.compile("^[a-zA-Z0-9_.-]{1,100}$");
// 配置名称正则表达式:允许中文、字母、数字、下划线、空格,长度1-50
private static final Pattern CONFIG_NAME_PATTERN = Pattern.compile("^[\\u4e00-\\u9fa5a-zA-Z0-9_\\\\.\\s]{1,50}$");
// 配置类型正则表达式:只允许字母、数字、下划线,长度1-20
private static final Pattern CONFIG_TYPE_PATTERN = Pattern.compile("^[a-zA-Z0-9_]{1,20}$");
/**
* 验证配置对象
*/
public static Mono<SysConfig> validateConfig(SysConfig config) {
if (config == null) {
return Mono.error(new IllegalArgumentException("配置对象不能为空"));
}
// 验证配置键
if (!isValidConfigKey(config.getConfigKey())) {
return Mono.error(new IllegalArgumentException("配置键格式无效,只允许字母、数字、下划线、点号,长度1-100"));
}
// 验证配置名称
if (!isValidConfigName(config.getConfigName())) {
return Mono.error(new IllegalArgumentException("配置名称格式无效,允许中文、字母、数字、下划线、空格,长度1-50"));
}
// 验证配置类型
if (config.getConfigType() != null && !isValidConfigType(config.getConfigType())) {
return Mono.error(new IllegalArgumentException("配置类型格式无效,只允许字母、数字、下划线,长度1-20"));
}
// 验证配置值长度
if (config.getConfigValue() != null && config.getConfigValue().length() > 5000) {
return Mono.error(new IllegalArgumentException("配置值长度不能超过5000个字符"));
}
return Mono.just(config);
}
/**
* 验证配置键
*/
public static boolean isValidConfigKey(String configKey) {
return configKey != null && CONFIG_KEY_PATTERN.matcher(configKey).matches();
}
/**
* 验证配置名称
*/
public static boolean isValidConfigName(String configName) {
return configName != null && CONFIG_NAME_PATTERN.matcher(configName).matches();
}
/**
* 验证配置类型
*/
public static boolean isValidConfigType(String configType) {
return configType == null || CONFIG_TYPE_PATTERN.matcher(configType).matches();
}
/**
* 验证ID参数
*/
public static Mono<Long> validateId(String idStr) {
try {
Long id = Long.valueOf(idStr);
if (id <= 0) {
return Mono.error(new IllegalArgumentException("ID必须大于0"));
}
return Mono.just(id);
} catch (NumberFormatException e) {
return Mono.error(new IllegalArgumentException("ID格式无效"));
}
}
/**
* 创建错误响应
*/
public static Mono<ServerResponse> createErrorResponse(String message) {
return ServerResponse.status(HttpStatus.BAD_REQUEST)
.bodyValue(new ErrorResponse(message));
}
/**
* 错误响应对象
*/
public static class ErrorResponse {
private final String message;
private final long timestamp;
public ErrorResponse(String message) {
this.message = message;
this.timestamp = System.currentTimeMillis();
}
public String getMessage() {
return message;
}
public long getTimestamp() {
return timestamp;
}
}
}
@@ -0,0 +1,15 @@
package cn.novalon.gym.manage.sys.dto.request;
import java.util.List;
public class AssignRolesRequest {
private List<Long> roleIds;
public List<Long> getRoleIds() {
return roleIds;
}
public void setRoleIds(List<Long> roleIds) {
this.roleIds = roleIds;
}
}
@@ -0,0 +1,42 @@
package cn.novalon.gym.manage.sys.dto.request;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
/**
* 登录请求DTO
*
* 文件定义:封装用户登录请求的参数
* 涉及业务:用户登录认证
* 算法:使用Jakarta Validation进行参数验证
*
* @author 张翔
* @date 2026-03-13
*/
@Schema(description = "用户登录请求")
public class LoginRequest {
@Schema(description = "用户名", example = "admin")
@NotBlank(message = "用户名不能为空")
private String username;
@Schema(description = "密码", example = "123456")
@NotBlank(message = "密码不能为空")
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
@@ -0,0 +1,88 @@
package cn.novalon.gym.manage.sys.dto.request;
import jakarta.validation.constraints.NotBlank;
/**
* 菜单创建请求DTO
*
* 文件定义:用于创建菜单的请求DTO对象,封装HTTP请求参数
* 涉及业务:菜单管理、权限分配等场景
* 算法:通过验证注解确保请求参数的有效性
*
* @author 张翔
* @date 2026-03-13
*/
public class MenuCreateRequest {
private Long parentId;
@NotBlank(message = "菜单名称不能为空")
private String menuName;
@NotBlank(message = "菜单类型不能为空")
private String menuType;
private Integer orderNum;
private String component;
private String perms;
private Integer status;
public Long getParentId() {
return parentId;
}
public void setParentId(Long parentId) {
this.parentId = parentId;
}
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 Integer getOrderNum() {
return orderNum;
}
public void setOrderNum(Integer orderNum) {
this.orderNum = orderNum;
}
public String getComponent() {
return component;
}
public void setComponent(String component) {
this.component = component;
}
public String getPerms() {
return perms;
}
public void setPerms(String perms) {
this.perms = perms;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
}
@@ -0,0 +1,84 @@
package cn.novalon.gym.manage.sys.dto.request;
/**
* 菜单更新请求DTO
*
* 文件定义:用于更新菜单的请求DTO对象,封装HTTP请求参数
* 涉及业务:菜单管理、权限分配等场景
* 算法:支持部分字段更新,通过验证注解确保请求参数的有效性
*
* @author 张翔
* @date 2026-03-13
*/
public class MenuUpdateRequest {
private Long parentId;
private String menuName;
private String menuType;
private Integer orderNum;
private String component;
private String perms;
private Integer status;
public Long getParentId() {
return parentId;
}
public void setParentId(Long parentId) {
this.parentId = parentId;
}
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 Integer getOrderNum() {
return orderNum;
}
public void setOrderNum(Integer orderNum) {
this.orderNum = orderNum;
}
public String getComponent() {
return component;
}
public void setComponent(String component) {
this.component = component;
}
public String getPerms() {
return perms;
}
public void setPerms(String perms) {
this.perms = perms;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
}
@@ -0,0 +1,34 @@
package cn.novalon.gym.manage.sys.dto.request;
import jakarta.validation.constraints.NotBlank;
/**
* 密码修改请求DTO
*
* @author 张翔
* @date 2026-03-14
*/
public class PasswordChangeRequest {
@NotBlank(message = "旧密码不能为空")
private String oldPassword;
@NotBlank(message = "新密码不能为空")
private String newPassword;
public String getOldPassword() {
return oldPassword;
}
public void setOldPassword(String oldPassword) {
this.oldPassword = oldPassword;
}
public String getNewPassword() {
return newPassword;
}
public void setNewPassword(String newPassword) {
this.newPassword = newPassword;
}
}
@@ -0,0 +1,73 @@
package cn.novalon.gym.manage.sys.dto.request;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
/**
* 角色创建请求DTO
*
* 文件定义:用于创建角色的请求DTO对象,封装HTTP请求参数
* 涉及业务:角色管理、权限分配等场景
* 算法:通过验证注解确保请求参数的有效性
*
* @author 张翔
* @date 2026-03-13
*/
@Schema(description = "角色创建请求")
public class RoleCreateRequest {
@Schema(description = "角色名称", example = "管理员")
@NotBlank(message = "角色名称不能为空")
@Size(min = 2, max = 50, message = "角色名称长度必须在2-50之间")
private String roleName;
@Schema(description = "角色权限字符串", example = "admin")
@NotBlank(message = "角色权限字符串不能为空")
@Size(min = 2, max = 50, message = "角色权限字符串长度必须在2-50之间")
@Pattern(regexp = "^[a-zA-Z0-9_-]+$", message = "角色权限字符串只能包含字母、数字、下划线和横线")
private String roleKey;
@Schema(description = "显示顺序", example = "1")
@NotNull(message = "显示顺序不能为空")
@Min(value = 1, message = "显示顺序必须大于0")
private Integer roleSort;
@Schema(description = "状态", example = "1")
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 getRoleSort() {
return roleSort;
}
public void setRoleSort(Integer roleSort) {
this.roleSort = roleSort;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
}
@@ -0,0 +1,54 @@
package cn.novalon.gym.manage.sys.dto.request;
/**
* 角色更新请求DTO
*
* 文件定义:用于更新角色的请求DTO对象,封装HTTP请求参数
* 涉及业务:角色管理、权限分配等场景
* 算法:支持部分字段更新,通过验证注解确保请求参数的有效性
*
* @author 张翔
* @date 2026-03-13
*/
public class RoleUpdateRequest {
private String roleName;
private String roleKey;
private Integer roleSort;
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 getRoleSort() {
return roleSort;
}
public void setRoleSort(Integer roleSort) {
this.roleSort = roleSort;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
}
@@ -0,0 +1,97 @@
package cn.novalon.gym.manage.sys.dto.request;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import java.util.List;
/**
* 用户注册请求DTO
*
* @author 张翔
* @date 2026-03-14
*/
@Schema(description = "用户注册请求")
public class UserRegisterRequest {
@Schema(description = "用户名", example = "testuser")
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 50, message = "用户名长度必须在3-50之间")
@Pattern(regexp = "^[a-zA-Z0-9_-]+$", message = "用户名只能包含字母、数字、下划线和横线")
private String username;
@Schema(description = "昵称", example = "测试用户")
@Size(max = 100, message = "昵称长度不能超过100")
private String nickname;
@Schema(description = "密码", example = "Admin123")
@NotBlank(message = "密码不能为空")
@Size(min = 8, max = 20, message = "密码长度必须在8-20之间")
@Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).+$", message = "密码必须包含大小写字母和数字")
private String password;
@Schema(description = "邮箱", example = "test@example.com")
@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
@Size(max = 100, message = "邮箱长度不能超过100")
private String email;
@Schema(description = "手机号", example = "13800138000")
@NotBlank(message = "手机号不能为空")
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
@Schema(description = "角色ID列表", example = "[1, 2]")
private List<Long> roles;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public List<Long> getRoles() {
return roles;
}
public void setRoles(List<Long> roles) {
this.roles = roles;
}
}
@@ -0,0 +1,59 @@
package cn.novalon.gym.manage.sys.dto.request;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Email;
/**
* 用户更新请求DTO
*
* @author 张翔
* @date 2026-03-14
*/
@Schema(description = "用户更新请求")
public class UserUpdateRequest {
@Schema(description = "邮箱", example = "newemail@example.com")
private String email;
@Schema(description = "状态:0-禁用,1-正常", example = "1")
private Integer status;
@Schema(description = "角色ID", example = "1")
private Long roleId;
@Schema(description = "是否清除角色关联", example = "false")
private Boolean clearRole;
@Email(message = "邮箱格式不正确")
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 Boolean getClearRole() {
return clearRole;
}
public void setClearRole(Boolean clearRole) {
this.clearRole = clearRole;
}
}
@@ -0,0 +1,55 @@
package cn.novalon.gym.manage.sys.dto.response;
import io.swagger.v3.oas.annotations.media.Schema;
/**
* 认证响应DTO
*
* @author 张翔
* @date 2026-03-14
*/
@Schema(description = "用户认证响应")
public class AuthResponse {
@Schema(description = "JWT访问令牌", example = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...")
private String token;
@Schema(description = "用户ID", example = "1")
private Long userId;
@Schema(description = "用户名", example = "admin")
private String username;
public AuthResponse() {
}
public AuthResponse(String token, Long userId, String username) {
this.token = token;
this.userId = userId;
this.username = username;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
@@ -0,0 +1,55 @@
package cn.novalon.gym.manage.sys.dto.response;
/**
* 文件预览响应DTO
*
* @author 张翔
* @date 2026-03-14
*/
public class FilePreviewResponse {
private String fileName;
private String fileType;
private Long fileSize;
private String previewType;
private String previewData;
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public String getFileType() {
return fileType;
}
public void setFileType(String fileType) {
this.fileType = fileType;
}
public Long getFileSize() {
return fileSize;
}
public void setFileSize(Long fileSize) {
this.fileSize = fileSize;
}
public String getPreviewType() {
return previewType;
}
public void setPreviewType(String previewType) {
this.previewType = previewType;
}
public String getPreviewData() {
return previewData;
}
public void setPreviewData(String previewData) {
this.previewData = previewData;
}
}

Some files were not shown because too many files have changed in this diff Show More