feat: 完善系统配置审计通知功能并优化异常处理
- 新增异常处理体系(BaseException及其子类) - 优化密码、邮箱、用户名等基础类型 - 添加字典管理、登录日志、操作日志的E2E测试 - 完善API集成测试和安全测试 - 添加性能测试配置和脚本 - 优化OpenAPI配置和全局异常处理器
This commit is contained in:
@@ -82,6 +82,10 @@
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webflux-ui</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
+9
@@ -6,6 +6,7 @@ import io.swagger.v3.oas.models.info.Info;
|
||||
import io.swagger.v3.oas.models.info.License;
|
||||
import io.swagger.v3.oas.models.servers.Server;
|
||||
import io.swagger.v3.oas.models.tags.Tag;
|
||||
import org.springdoc.core.models.GroupedOpenApi;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@@ -48,4 +49,12 @@ public class OpenApiConfig {
|
||||
new Tag().name("认证管理").description("登录认证相关操作"),
|
||||
new Tag().name("统计信息").description("系统统计相关操作")));
|
||||
}
|
||||
|
||||
@Bean
|
||||
public GroupedOpenApi allApi() {
|
||||
return GroupedOpenApi.builder()
|
||||
.group("all")
|
||||
.pathsToMatch("/api/**")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,3 +37,16 @@ logging:
|
||||
cn.novalon.manage: DEBUG
|
||||
org.springframework.r2dbc: DEBUG
|
||||
cn.novalon.manage.db: DEBUG
|
||||
|
||||
springdoc:
|
||||
api-docs:
|
||||
path: /api-docs
|
||||
enabled: true
|
||||
swagger-ui:
|
||||
path: /swagger-ui.html
|
||||
enabled: true
|
||||
tags-sorter: alpha
|
||||
operations-sorter: alpha
|
||||
show-actuator: false
|
||||
default-consumes-media-type: application/json
|
||||
default-produces-media-type: application/json
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package cn.novalon.manage.common.exception;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class BaseException extends RuntimeException {
|
||||
|
||||
private final String errorCode;
|
||||
private final Map<String, Object> context;
|
||||
|
||||
protected BaseException(String errorCode, String message) {
|
||||
super(message);
|
||||
this.errorCode = errorCode;
|
||||
this.context = new HashMap<>();
|
||||
}
|
||||
|
||||
protected BaseException(String errorCode, String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.errorCode = errorCode;
|
||||
this.context = new HashMap<>();
|
||||
}
|
||||
|
||||
public String getErrorCode() {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
public Map<String, Object> getContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
public BaseException addContext(String key, Object value) {
|
||||
context.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public abstract HttpStatus getHttpStatus();
|
||||
}
|
||||
+8
-17
@@ -1,28 +1,19 @@
|
||||
package cn.novalon.manage.common.exception;
|
||||
|
||||
/**
|
||||
* 业务异常类
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-13
|
||||
*/
|
||||
public class BusinessException extends RuntimeException {
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
private final String code;
|
||||
private final String message;
|
||||
public class BusinessException extends BaseException {
|
||||
|
||||
public BusinessException(String code, String message) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
public BusinessException(String errorCode, String message) {
|
||||
super(errorCode, message);
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
public BusinessException(String errorCode, String message, Throwable cause) {
|
||||
super(errorCode, message, cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return message;
|
||||
public HttpStatus getHttpStatus() {
|
||||
return HttpStatus.BAD_REQUEST;
|
||||
}
|
||||
}
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package cn.novalon.manage.common.exception;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
public class ConflictException extends BusinessException {
|
||||
|
||||
public ConflictException(String errorCode, String message) {
|
||||
super(errorCode, message);
|
||||
}
|
||||
|
||||
public ConflictException(String errorCode, String message, Throwable cause) {
|
||||
super(errorCode, message, cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpStatus getHttpStatus() {
|
||||
return HttpStatus.CONFLICT;
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package cn.novalon.manage.common.exception;
|
||||
|
||||
public class ErrorCode {
|
||||
|
||||
public static final String VALIDATION_PREFIX = "VALIDATION_";
|
||||
public static final String NOT_FOUND_PREFIX = "NOT_FOUND_";
|
||||
public static final String PERMISSION_PREFIX = "PERMISSION_";
|
||||
public static final String CONFLICT_PREFIX = "CONFLICT_";
|
||||
public static final String SYSTEM_PREFIX = "SYSTEM_";
|
||||
|
||||
public static final String VALIDATION_REQUIRED = VALIDATION_PREFIX + "001";
|
||||
public static final String VALIDATION_INVALID_FORMAT = VALIDATION_PREFIX + "002";
|
||||
public static final String VALIDATION_INVALID_LENGTH = VALIDATION_PREFIX + "003";
|
||||
public static final String VALIDATION_INVALID_VALUE = VALIDATION_PREFIX + "004";
|
||||
|
||||
public static final String NOT_FOUND_USER = NOT_FOUND_PREFIX + "001";
|
||||
public static final String NOT_FOUND_ROLE = NOT_FOUND_PREFIX + "002";
|
||||
public static final String NOT_FOUND_MENU = NOT_FOUND_PREFIX + "003";
|
||||
public static final String NOT_FOUND_DICTIONARY = NOT_FOUND_PREFIX + "004";
|
||||
|
||||
public static final String PERMISSION_DENIED = PERMISSION_PREFIX + "001";
|
||||
public static final String PERMISSION_INSUFFICIENT = PERMISSION_PREFIX + "002";
|
||||
|
||||
public static final String CONFLICT_DUPLICATE = CONFLICT_PREFIX + "001";
|
||||
public static final String CONFLICT_DUPLICATE_USER = CONFLICT_PREFIX + "002";
|
||||
public static final String CONFLICT_DUPLICATE_ROLE = CONFLICT_PREFIX + "003";
|
||||
public static final String CONFLICT_DUPLICATE_DICTIONARY = CONFLICT_PREFIX + "004";
|
||||
|
||||
public static final String SYSTEM_INTERNAL_ERROR = SYSTEM_PREFIX + "001";
|
||||
public static final String SYSTEM_DATABASE_ERROR = SYSTEM_PREFIX + "002";
|
||||
public static final String SYSTEM_NETWORK_ERROR = SYSTEM_PREFIX + "003";
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package cn.novalon.manage.common.exception;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
public class NotFoundException extends BusinessException {
|
||||
|
||||
public NotFoundException(String errorCode, String message) {
|
||||
super(errorCode, message);
|
||||
}
|
||||
|
||||
public NotFoundException(String errorCode, String message, Throwable cause) {
|
||||
super(errorCode, message, cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpStatus getHttpStatus() {
|
||||
return HttpStatus.NOT_FOUND;
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package cn.novalon.manage.common.exception;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
public class PermissionException extends BusinessException {
|
||||
|
||||
public PermissionException(String errorCode, String message) {
|
||||
super(errorCode, message);
|
||||
}
|
||||
|
||||
public PermissionException(String errorCode, String message, Throwable cause) {
|
||||
super(errorCode, message, cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpStatus getHttpStatus() {
|
||||
return HttpStatus.FORBIDDEN;
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package cn.novalon.manage.common.exception;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
public class SystemException extends BaseException {
|
||||
|
||||
public SystemException(String errorCode, String message) {
|
||||
super(errorCode, message);
|
||||
}
|
||||
|
||||
public SystemException(String errorCode, String message, Throwable cause) {
|
||||
super(errorCode, message, cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpStatus getHttpStatus() {
|
||||
return HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package cn.novalon.manage.common.exception;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
public class ValidationException extends BusinessException {
|
||||
|
||||
public ValidationException(String errorCode, String message) {
|
||||
super(errorCode, message);
|
||||
}
|
||||
|
||||
public ValidationException(String errorCode, String message, Throwable cause) {
|
||||
super(errorCode, message, cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpStatus getHttpStatus() {
|
||||
return HttpStatus.BAD_REQUEST;
|
||||
}
|
||||
}
|
||||
+17
@@ -1,5 +1,6 @@
|
||||
package cn.novalon.manage.common.handler;
|
||||
|
||||
import cn.novalon.manage.common.exception.BaseException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
@@ -40,6 +41,22 @@ public class GlobalExceptionHandler {
|
||||
this.exceptionLogService = exceptionLogService;
|
||||
}
|
||||
|
||||
@ExceptionHandler(BaseException.class)
|
||||
public ResponseEntity<Map<String, Object>> handleBaseException(BaseException ex, ServerWebExchange exchange) {
|
||||
logger.warn("Business exception: ", ex);
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("code", ex.getErrorCode());
|
||||
response.put("message", ex.getMessage());
|
||||
response.put("timestamp", LocalDateTime.now());
|
||||
|
||||
if (!ex.getContext().isEmpty()) {
|
||||
response.put("context", ex.getContext());
|
||||
}
|
||||
|
||||
return ResponseEntity.status(ex.getHttpStatus()).body(response);
|
||||
}
|
||||
|
||||
@ExceptionHandler(RuntimeException.class)
|
||||
public ResponseEntity<Map<String, Object>> handleRuntimeException(RuntimeException ex, ServerWebExchange exchange) {
|
||||
logger.warn("Runtime exception: ", ex);
|
||||
|
||||
+13
-6
@@ -1,5 +1,9 @@
|
||||
package cn.novalon.manage.common.util;
|
||||
|
||||
import cn.novalon.manage.common.exception.ErrorCode;
|
||||
import cn.novalon.manage.common.exception.SystemException;
|
||||
import cn.novalon.manage.common.exception.ValidationException;
|
||||
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
@@ -60,7 +64,8 @@ public final class SnowflakeId {
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("Failed to generate ID after " + MAX_RETRIES + " retries");
|
||||
throw new SystemException(ErrorCode.SYSTEM_INTERNAL_ERROR,
|
||||
"Failed to generate ID after " + MAX_RETRIES + " retries");
|
||||
}
|
||||
|
||||
private static long nextIdInternal() {
|
||||
@@ -151,23 +156,25 @@ public final class SnowflakeId {
|
||||
|
||||
private static void validateBits(int workerBits, int seqBits) {
|
||||
if (workerBits < 0 || workerBits > 22) {
|
||||
throw new IllegalArgumentException("WorkerID位数必须在0-22之间");
|
||||
throw new ValidationException(ErrorCode.VALIDATION_INVALID_VALUE, "WorkerID位数必须在0-22之间");
|
||||
}
|
||||
if (seqBits < 0 || seqBits > 22) {
|
||||
throw new IllegalArgumentException("序列号位数必须在0-22之间");
|
||||
throw new ValidationException(ErrorCode.VALIDATION_INVALID_VALUE, "序列号位数必须在0-22之间");
|
||||
}
|
||||
if (workerBits + seqBits > 22) {
|
||||
throw new IllegalArgumentException("WorkerID和序列号位数总和不能超过22位,当前为: " + (workerBits + seqBits));
|
||||
throw new ValidationException(ErrorCode.VALIDATION_INVALID_VALUE,
|
||||
"WorkerID和序列号位数总和不能超过22位,当前为: " + (workerBits + seqBits));
|
||||
}
|
||||
if (workerBits + seqBits == 0) {
|
||||
throw new IllegalArgumentException("WorkerID和序列号位数总和不能为0");
|
||||
throw new ValidationException(ErrorCode.VALIDATION_INVALID_VALUE, "WorkerID和序列号位数总和不能为0");
|
||||
}
|
||||
}
|
||||
|
||||
private static long resolveWorkerId(long maxWorkerId) {
|
||||
long id = generateNewId();
|
||||
if (id < 0 || id > maxWorkerId) {
|
||||
throw new IllegalStateException("WorkerID超出有效范围: " + id + " (有效范围: 0-" + maxWorkerId + ")");
|
||||
throw new SystemException(ErrorCode.SYSTEM_INTERNAL_ERROR,
|
||||
"WorkerID超出有效范围: " + id + " (有效范围: 0-" + maxWorkerId + ")");
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
+282
-45
@@ -1,60 +1,297 @@
|
||||
package cn.novalon.manage.db.dao;
|
||||
|
||||
import cn.novalon.manage.db.entity.SysUserQueryCriteria;
|
||||
import cn.novalon.manage.db.dao.QueryField;
|
||||
import cn.novalon.manage.db.dao.QueryUtil;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.data.relational.core.query.Criteria;
|
||||
import org.springframework.data.relational.core.query.Query;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* QueryUtil详细测试
|
||||
* QueryUtil详细测试 - 提升分支覆盖率
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-24
|
||||
*/
|
||||
class QueryUtilDetailedTest {
|
||||
|
||||
@Test
|
||||
void testBlurrySearchCriteria() {
|
||||
SysUserQueryCriteria criteria = new SysUserQueryCriteria();
|
||||
criteria.setKeyword("search");
|
||||
|
||||
Query query = QueryUtil.getQuery(criteria);
|
||||
|
||||
System.out.println("生成的Query: " + query);
|
||||
System.out.println("生成的Criteria: " + query.getCriteria());
|
||||
|
||||
assertTrue(true, "模糊搜索功能已实现");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBlurrySearchWithDeletedFilter() {
|
||||
SysUserQueryCriteria criteria = new SysUserQueryCriteria();
|
||||
criteria.setKeyword("search");
|
||||
|
||||
Query query = QueryUtil.getQuery(criteria, true);
|
||||
|
||||
System.out.println("带deletedAt过滤的Query: " + query);
|
||||
System.out.println("带deletedAt过滤的Criteria: " + query.getCriteria());
|
||||
|
||||
assertTrue(true, "模糊搜索和deletedAt过滤功能已实现");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOrCriteriaLogic() {
|
||||
String[] blurrys = {"username", "email"};
|
||||
String val = "search";
|
||||
|
||||
Criteria criteria = Criteria.empty();
|
||||
for (String s : blurrys) {
|
||||
criteria = criteria.or(s).like("%" + val + "%");
|
||||
static class TestQuery {
|
||||
@QueryField(propName = "name", type = QueryField.Type.EQUAL)
|
||||
private String name;
|
||||
|
||||
@QueryField(propName = "age", type = QueryField.Type.GREATER_THAN)
|
||||
private Integer age;
|
||||
|
||||
@QueryField(propName = "score", type = QueryField.Type.LESS_THAN)
|
||||
private Integer score;
|
||||
|
||||
@QueryField(propName = "status", type = QueryField.Type.INNER_LIKE)
|
||||
private String status;
|
||||
|
||||
@QueryField(propName = "email", type = QueryField.Type.LEFT_LIKE)
|
||||
private String email;
|
||||
|
||||
@QueryField(propName = "phone", type = QueryField.Type.RIGHT_LIKE)
|
||||
private String phone;
|
||||
|
||||
@QueryField(propName = "roles", type = QueryField.Type.IN)
|
||||
private List<String> roles;
|
||||
|
||||
@QueryField(propName = "keyword", blurry = "name,description,content")
|
||||
private String keyword;
|
||||
|
||||
@QueryField(propName = "deletedAt", type = QueryField.Type.IS_NULL)
|
||||
private String deletedAt;
|
||||
|
||||
@QueryField(propName = "updatedAt", type = QueryField.Type.IS_NOT_NULL)
|
||||
private String updatedAt;
|
||||
|
||||
@QueryField(propName = "orField", type = QueryField.Type.OR,
|
||||
orPropVal = QueryField.Type.IS_NULL,
|
||||
orPropNames = {"field1", "field2"})
|
||||
private String orField;
|
||||
|
||||
public TestQuery() {}
|
||||
|
||||
public TestQuery(String name, Integer age, Integer score, String status, String email,
|
||||
String phone, List<String> roles, String keyword, String deletedAt,
|
||||
String updatedAt, String orField) {
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
this.score = score;
|
||||
this.status = status;
|
||||
this.email = email;
|
||||
this.phone = phone;
|
||||
this.roles = roles;
|
||||
this.keyword = keyword;
|
||||
this.deletedAt = deletedAt;
|
||||
this.updatedAt = updatedAt;
|
||||
this.orField = orField;
|
||||
}
|
||||
|
||||
System.out.println("循环构建的Criteria: " + criteria);
|
||||
|
||||
String criteriaStr = criteria.toString();
|
||||
System.out.println("Criteria字符串: " + criteriaStr);
|
||||
|
||||
assertTrue(criteriaStr.contains("username"), "应该包含username");
|
||||
assertTrue(criteriaStr.contains("email"), "应该包含email");
|
||||
assertTrue(criteriaStr.contains("OR"), "应该包含OR");
|
||||
|
||||
public String getName() { return name; }
|
||||
public Integer getAge() { return age; }
|
||||
public Integer getScore() { return score; }
|
||||
public String getStatus() { return status; }
|
||||
public String getEmail() { return email; }
|
||||
public String getPhone() { return phone; }
|
||||
public List<String> getRoles() { return roles; }
|
||||
public String getKeyword() { return keyword; }
|
||||
public String getDeletedAt() { return deletedAt; }
|
||||
public String getUpdatedAt() { return updatedAt; }
|
||||
public String getOrField() { return orField; }
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNullQuery() {
|
||||
Query query = QueryUtil.getQuery(null);
|
||||
assertNotNull(query);
|
||||
Criteria criteria = query.getCriteria();
|
||||
assertNotNull(criteria);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testQueryWithDeletedAtFilter() {
|
||||
TestQuery testQuery = new TestQuery();
|
||||
Query query = QueryUtil.getQuery(testQuery, true);
|
||||
assertNotNull(query);
|
||||
Criteria criteria = query.getCriteria();
|
||||
assertNotNull(criteria);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testQueryWithoutDeletedAtFilter() {
|
||||
TestQuery testQuery = new TestQuery();
|
||||
Query query = QueryUtil.getQuery(testQuery, false);
|
||||
assertNotNull(query);
|
||||
Criteria criteria = query.getCriteria();
|
||||
assertNotNull(criteria);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEqualCondition() {
|
||||
TestQuery testQuery = new TestQuery("John", null, null, null, null, null, null, null, null, null, null);
|
||||
Query query = QueryUtil.getQuery(testQuery);
|
||||
assertNotNull(query);
|
||||
Criteria criteria = query.getCriteria();
|
||||
assertNotNull(criteria);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGreaterThanCondition() {
|
||||
TestQuery testQuery = new TestQuery(null, 18, null, null, null, null, null, null, null, null, null);
|
||||
Query query = QueryUtil.getQuery(testQuery);
|
||||
assertNotNull(query);
|
||||
Criteria criteria = query.getCriteria();
|
||||
assertNotNull(criteria);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLessThanCondition() {
|
||||
TestQuery testQuery = new TestQuery(null, null, 100, null, null, null, null, null, null, null, null);
|
||||
Query query = QueryUtil.getQuery(testQuery);
|
||||
assertNotNull(query);
|
||||
Criteria criteria = query.getCriteria();
|
||||
assertNotNull(criteria);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testInnerLikeCondition() {
|
||||
TestQuery testQuery = new TestQuery(null, null, null, "active", null, null, null, null, null, null, null);
|
||||
Query query = QueryUtil.getQuery(testQuery);
|
||||
assertNotNull(query);
|
||||
Criteria criteria = query.getCriteria();
|
||||
assertNotNull(criteria);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLeftLikeCondition() {
|
||||
TestQuery testQuery = new TestQuery(null, null, null, null, "@example.com", null, null, null, null, null, null);
|
||||
Query query = QueryUtil.getQuery(testQuery);
|
||||
assertNotNull(query);
|
||||
Criteria criteria = query.getCriteria();
|
||||
assertNotNull(criteria);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRightLikeCondition() {
|
||||
TestQuery testQuery = new TestQuery(null, null, null, null, null, "123", null, null, null, null, null);
|
||||
Query query = QueryUtil.getQuery(testQuery);
|
||||
assertNotNull(query);
|
||||
Criteria criteria = query.getCriteria();
|
||||
assertNotNull(criteria);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testInCondition() {
|
||||
TestQuery testQuery = new TestQuery(null, null, null, null, null, null,
|
||||
Arrays.asList("admin", "user"), null, null, null, null);
|
||||
Query query = QueryUtil.getQuery(testQuery);
|
||||
assertNotNull(query);
|
||||
Criteria criteria = query.getCriteria();
|
||||
assertNotNull(criteria);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testInConditionWithEmptyList() {
|
||||
TestQuery testQuery = new TestQuery(null, null, null, null, null, null,
|
||||
Collections.emptyList(), null, null, null, null);
|
||||
Query query = QueryUtil.getQuery(testQuery);
|
||||
assertNotNull(query);
|
||||
Criteria criteria = query.getCriteria();
|
||||
assertNotNull(criteria);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBlurrySearchSingleField() {
|
||||
TestQuery testQuery = new TestQuery(null, null, null, null, null, null, null, "test", null, null, null);
|
||||
Query query = QueryUtil.getQuery(testQuery);
|
||||
assertNotNull(query);
|
||||
Criteria criteria = query.getCriteria();
|
||||
assertNotNull(criteria);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBlurrySearchMultipleFields() {
|
||||
TestQuery testQuery = new TestQuery(null, null, null, null, null, null, null, "keyword", null, null, null);
|
||||
Query query = QueryUtil.getQuery(testQuery);
|
||||
assertNotNull(query);
|
||||
Criteria criteria = query.getCriteria();
|
||||
assertNotNull(criteria);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIsNullCondition() {
|
||||
TestQuery testQuery = new TestQuery(null, null, null, null, null, null, null, null, "null", null, null);
|
||||
Query query = QueryUtil.getQuery(testQuery);
|
||||
assertNotNull(query);
|
||||
Criteria criteria = query.getCriteria();
|
||||
assertNotNull(criteria);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIsNotNullCondition() {
|
||||
TestQuery testQuery = new TestQuery(null, null, null, null, null, null, null, null, null, "value", null);
|
||||
Query query = QueryUtil.getQuery(testQuery);
|
||||
assertNotNull(query);
|
||||
Criteria criteria = query.getCriteria();
|
||||
assertNotNull(criteria);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOrConditionIsNull() {
|
||||
TestQuery testQuery = new TestQuery(null, null, null, null, null, null, null, null, null, null, "value");
|
||||
Query query = QueryUtil.getQuery(testQuery);
|
||||
assertNotNull(query);
|
||||
Criteria criteria = query.getCriteria();
|
||||
assertNotNull(criteria);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEmptyStringValue() {
|
||||
TestQuery testQuery = new TestQuery("", null, null, null, null, null, null, null, null, null, null);
|
||||
Query query = QueryUtil.getQuery(testQuery);
|
||||
assertNotNull(query);
|
||||
Criteria criteria = query.getCriteria();
|
||||
assertNotNull(criteria);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNullFieldValue() {
|
||||
TestQuery testQuery = new TestQuery(null, null, null, null, null, null, null, null, null, null, null);
|
||||
Query query = QueryUtil.getQuery(testQuery);
|
||||
assertNotNull(query);
|
||||
Criteria criteria = query.getCriteria();
|
||||
assertNotNull(criteria);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMultipleConditions() {
|
||||
TestQuery testQuery = new TestQuery("John", 18, 100, "active", "@example.com",
|
||||
"123", Arrays.asList("admin"), "test", null, "value", null);
|
||||
Query query = QueryUtil.getQuery(testQuery);
|
||||
assertNotNull(query);
|
||||
Criteria criteria = query.getCriteria();
|
||||
assertNotNull(criteria);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testQueryAllWithoutDeletedAtFilter() {
|
||||
TestQuery testQuery = new TestQuery("John", 18, 100, "active", "@example.com",
|
||||
"123", Arrays.asList("admin"), "test", null, "value", null);
|
||||
Query query = QueryUtil.getQueryAll(testQuery);
|
||||
assertNotNull(query);
|
||||
Criteria criteria = query.getCriteria();
|
||||
assertNotNull(criteria);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIsBlankWithNull() {
|
||||
assertTrue(QueryUtil.isBlank(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIsBlankWithEmptyString() {
|
||||
assertTrue(QueryUtil.isBlank(""));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIsBlankWithWhitespace() {
|
||||
assertTrue(QueryUtil.isBlank(" "));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIsBlankWithValidString() {
|
||||
assertFalse(QueryUtil.isBlank("test"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIsBlankWithMixedWhitespace() {
|
||||
assertFalse(QueryUtil.isBlank(" test "));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,10 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webflux-ui</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
|
||||
+11
@@ -2,6 +2,8 @@ package cn.novalon.manage.file.handler;
|
||||
|
||||
import cn.novalon.manage.file.core.domain.SysFile;
|
||||
import cn.novalon.manage.file.core.service.ISysFileService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.codec.multipart.FilePart;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -15,6 +17,7 @@ import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
@Component
|
||||
@Tag(name = "文件管理", description = "文件上传下载相关操作")
|
||||
public class SysFileHandler {
|
||||
|
||||
private final ISysFileService fileService;
|
||||
@@ -23,11 +26,13 @@ public class SysFileHandler {
|
||||
this.fileService = fileService;
|
||||
}
|
||||
|
||||
@Operation(summary = "获取所有文件", description = "获取系统中所有文件列表")
|
||||
public Mono<ServerResponse> getAllFiles(ServerRequest request) {
|
||||
Flux<SysFile> files = fileService.getAllFiles();
|
||||
return ServerResponse.ok().body(files, SysFile.class);
|
||||
}
|
||||
|
||||
@Operation(summary = "根据ID获取文件", description = "根据文件ID获取文件详细信息")
|
||||
public Mono<ServerResponse> getFileById(ServerRequest request) {
|
||||
Long id = Long.parseLong(request.pathVariable("id"));
|
||||
return fileService.getFileById(id)
|
||||
@@ -35,6 +40,7 @@ public class SysFileHandler {
|
||||
.switchIfEmpty(ServerResponse.notFound().build());
|
||||
}
|
||||
|
||||
@Operation(summary = "上传文件", description = "上传文件到系统")
|
||||
public Mono<ServerResponse> uploadFile(ServerRequest request) {
|
||||
String username = request.headers().firstHeader("X-Username");
|
||||
if (username == null) {
|
||||
@@ -60,6 +66,7 @@ public class SysFileHandler {
|
||||
.switchIfEmpty(ServerResponse.badRequest().bodyValue("No file data"));
|
||||
}
|
||||
|
||||
@Operation(summary = "下载文件", description = "根据文件ID下载文件")
|
||||
public Mono<ServerResponse> downloadFile(ServerRequest request) {
|
||||
Long id = Long.parseLong(request.pathVariable("id"));
|
||||
return fileService.getFileById(id)
|
||||
@@ -78,6 +85,7 @@ public class SysFileHandler {
|
||||
.switchIfEmpty(ServerResponse.notFound().build());
|
||||
}
|
||||
|
||||
@Operation(summary = "根据文件名下载", description = "根据文件名下载文件")
|
||||
public Mono<ServerResponse> downloadFileByName(ServerRequest request) {
|
||||
String fileName = request.pathVariable("fileName");
|
||||
return fileService.getAllFiles()
|
||||
@@ -98,6 +106,7 @@ public class SysFileHandler {
|
||||
.switchIfEmpty(ServerResponse.notFound().build());
|
||||
}
|
||||
|
||||
@Operation(summary = "预览文件", description = "根据文件ID预览文件")
|
||||
public Mono<ServerResponse> previewFile(ServerRequest request) {
|
||||
Long id = Long.parseLong(request.pathVariable("id"));
|
||||
return fileService.getFileById(id)
|
||||
@@ -115,6 +124,7 @@ public class SysFileHandler {
|
||||
.switchIfEmpty(ServerResponse.notFound().build());
|
||||
}
|
||||
|
||||
@Operation(summary = "根据文件名预览", description = "根据文件名预览文件")
|
||||
public Mono<ServerResponse> previewFileByName(ServerRequest request) {
|
||||
String fileName = request.pathVariable("fileName");
|
||||
return fileService.getAllFiles()
|
||||
@@ -134,6 +144,7 @@ public class SysFileHandler {
|
||||
.switchIfEmpty(ServerResponse.notFound().build());
|
||||
}
|
||||
|
||||
@Operation(summary = "删除文件", description = "删除指定文件")
|
||||
public Mono<ServerResponse> deleteFile(ServerRequest request) {
|
||||
Long id = Long.parseLong(request.pathVariable("id"));
|
||||
return fileService.deleteFile(id)
|
||||
|
||||
@@ -26,6 +26,10 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webflux-ui</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
|
||||
+9
@@ -2,6 +2,8 @@ package cn.novalon.manage.notify.handler;
|
||||
|
||||
import cn.novalon.manage.notify.core.domain.SysNotice;
|
||||
import cn.novalon.manage.notify.core.service.ISysNoticeService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
@@ -15,6 +17,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
@Tag(name = "通知管理", description = "系统通知相关操作")
|
||||
public class SysNoticeHandler {
|
||||
|
||||
private final ISysNoticeService noticeService;
|
||||
@@ -25,11 +28,13 @@ public class SysNoticeHandler {
|
||||
this.noticeService = noticeService;
|
||||
}
|
||||
|
||||
@Operation(summary = "获取所有通知", description = "获取系统中所有通知列表")
|
||||
public Mono<ServerResponse> getAllNotices(ServerRequest request) {
|
||||
Flux<SysNotice> notices = noticeService.getAllNotices();
|
||||
return ServerResponse.ok().body(notices, SysNotice.class);
|
||||
}
|
||||
|
||||
@Operation(summary = "根据ID获取通知", description = "根据通知ID获取通知详细信息")
|
||||
public Mono<ServerResponse> getNoticeById(ServerRequest request) {
|
||||
Long id = Long.parseLong(request.pathVariable("id"));
|
||||
return noticeService.getNoticeById(id)
|
||||
@@ -37,12 +42,14 @@ public class SysNoticeHandler {
|
||||
.switchIfEmpty(ServerResponse.notFound().build());
|
||||
}
|
||||
|
||||
@Operation(summary = "根据状态获取通知", description = "根据状态获取通知列表")
|
||||
public Mono<ServerResponse> getNoticesByStatus(ServerRequest request) {
|
||||
String status = request.pathVariable("status");
|
||||
Flux<SysNotice> notices = noticeService.getNoticesByStatus(status);
|
||||
return ServerResponse.ok().body(notices, SysNotice.class);
|
||||
}
|
||||
|
||||
@Operation(summary = "创建通知", description = "创建新通知")
|
||||
public Mono<ServerResponse> createNotice(ServerRequest request) {
|
||||
return request.bodyToMono(SysNotice.class)
|
||||
.filter(notice -> notice.getNoticeTitle() != null && !notice.getNoticeTitle().trim().isEmpty())
|
||||
@@ -64,6 +71,7 @@ public class SysNoticeHandler {
|
||||
});
|
||||
}
|
||||
|
||||
@Operation(summary = "更新通知", description = "更新通知信息")
|
||||
public Mono<ServerResponse> updateNotice(ServerRequest request) {
|
||||
Long id = Long.parseLong(request.pathVariable("id"));
|
||||
return request.bodyToMono(SysNotice.class)
|
||||
@@ -72,6 +80,7 @@ public class SysNoticeHandler {
|
||||
.switchIfEmpty(ServerResponse.notFound().build());
|
||||
}
|
||||
|
||||
@Operation(summary = "删除通知", description = "删除指定通知")
|
||||
public Mono<ServerResponse> deleteNotice(ServerRequest request) {
|
||||
Long id = Long.parseLong(request.pathVariable("id"));
|
||||
return noticeService.getNoticeById(id)
|
||||
|
||||
+6
-3
@@ -1,5 +1,8 @@
|
||||
package cn.novalon.manage.sys.core.command;
|
||||
|
||||
import cn.novalon.manage.common.exception.ErrorCode;
|
||||
import cn.novalon.manage.common.exception.ValidationException;
|
||||
|
||||
/**
|
||||
* 创建公告命令对象
|
||||
*
|
||||
@@ -20,19 +23,19 @@ public record CreateNoticeCommand(
|
||||
|
||||
private static void validateNoticeTitle(String noticeTitle) {
|
||||
if (noticeTitle == null || noticeTitle.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("Notice title is required");
|
||||
throw new ValidationException(ErrorCode.VALIDATION_REQUIRED, "Notice title is required");
|
||||
}
|
||||
}
|
||||
|
||||
private static void validateNoticeContent(String noticeContent) {
|
||||
if (noticeContent == null || noticeContent.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("Notice content is required");
|
||||
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 IllegalArgumentException(
|
||||
throw new ValidationException(ErrorCode.VALIDATION_INVALID_VALUE,
|
||||
"Invalid notice type. Notice type must be 1 (notification) or 2 (announcement)");
|
||||
}
|
||||
}
|
||||
|
||||
+4
-1
@@ -1,5 +1,7 @@
|
||||
package cn.novalon.manage.sys.core.command;
|
||||
|
||||
import cn.novalon.manage.common.exception.ErrorCode;
|
||||
import cn.novalon.manage.common.exception.ValidationException;
|
||||
import cn.novalon.manage.common.util.StatusConstants;
|
||||
|
||||
/**
|
||||
@@ -21,7 +23,8 @@ public record CreateRoleCommand(
|
||||
|
||||
private static void validateStatus(Integer status) {
|
||||
if (status != null && status != StatusConstants.ENABLED && status != StatusConstants.DISABLED) {
|
||||
throw new IllegalArgumentException("Invalid status value. Status must be 0 (disabled) or 1 (enabled)");
|
||||
throw new ValidationException(ErrorCode.VALIDATION_INVALID_VALUE,
|
||||
"Invalid status value. Status must be 0 (disabled) or 1 (enabled)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+9
@@ -1,6 +1,7 @@
|
||||
package cn.novalon.manage.sys.core.domain;
|
||||
|
||||
import cn.novalon.manage.common.util.SnowflakeId;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@@ -10,11 +11,19 @@ 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() {
|
||||
|
||||
+15
-1
@@ -1,7 +1,7 @@
|
||||
package cn.novalon.manage.sys.core.domain;
|
||||
|
||||
import cn.novalon.manage.common.util.SnowflakeId;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
@@ -10,14 +10,28 @@ 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 = "角色ID", example = "1")
|
||||
private Long roleId;
|
||||
|
||||
@Schema(description = "状态:0-禁用,1-正常", example = "1")
|
||||
private Integer status;
|
||||
|
||||
public String getUsername() {
|
||||
|
||||
+4
@@ -1,5 +1,6 @@
|
||||
package cn.novalon.manage.sys.dto.request;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
/**
|
||||
@@ -12,11 +13,14 @@ import jakarta.validation.constraints.NotBlank;
|
||||
* @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;
|
||||
|
||||
|
||||
+7
@@ -1,5 +1,6 @@
|
||||
package cn.novalon.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.Size;
|
||||
@@ -10,22 +11,28 @@ import jakarta.validation.constraints.Size;
|
||||
* @author 张翔
|
||||
* @date 2026-03-14
|
||||
*/
|
||||
@Schema(description = "用户注册请求")
|
||||
public class UserRegisterRequest {
|
||||
|
||||
@Schema(description = "用户名", example = "testuser")
|
||||
@NotBlank(message = "用户名不能为空")
|
||||
@Size(min = 3, max = 50, message = "用户名长度必须在3-50之间")
|
||||
private String username;
|
||||
|
||||
@Schema(description = "昵称", example = "测试用户")
|
||||
@Size(max = 100, message = "昵称长度不能超过100")
|
||||
private String nickname;
|
||||
|
||||
@Schema(description = "密码", example = "123456")
|
||||
@NotBlank(message = "密码不能为空")
|
||||
@Size(min = 6, max = 100, message = "密码长度必须在6-100之间")
|
||||
private String password;
|
||||
|
||||
@Schema(description = "邮箱", example = "test@example.com")
|
||||
@Email(message = "邮箱格式不正确")
|
||||
private String email;
|
||||
|
||||
@Schema(description = "手机号", example = "13800138000")
|
||||
@Size(max = 20, message = "手机号长度不能超过20")
|
||||
private String phone;
|
||||
|
||||
|
||||
+6
@@ -1,5 +1,6 @@
|
||||
package cn.novalon.manage.sys.dto.request;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.Email;
|
||||
|
||||
/**
|
||||
@@ -8,14 +9,19 @@ import jakarta.validation.constraints.Email;
|
||||
* @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 = "邮箱格式不正确")
|
||||
|
||||
+8
@@ -1,15 +1,23 @@
|
||||
package cn.novalon.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() {
|
||||
|
||||
+6
@@ -6,6 +6,8 @@ import cn.novalon.manage.sys.dto.response.AuthResponse;
|
||||
import cn.novalon.manage.sys.security.JwtTokenProvider;
|
||||
import cn.novalon.manage.sys.core.domain.SysUser;
|
||||
import cn.novalon.manage.sys.core.service.ISysUserService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
@@ -33,6 +35,7 @@ import java.util.stream.Collectors;
|
||||
* @date 2026-03-13
|
||||
*/
|
||||
@Component
|
||||
@Tag(name = "认证管理", description = "登录认证相关操作")
|
||||
public class SysAuthHandler {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(SysAuthHandler.class);
|
||||
@@ -47,6 +50,7 @@ public class SysAuthHandler {
|
||||
this.jwtTokenProvider = jwtTokenProvider;
|
||||
}
|
||||
|
||||
@Operation(summary = "用户登录", description = "使用用户名和密码登录系统")
|
||||
public Mono<ServerResponse> login(ServerRequest request) {
|
||||
return request.bodyToMono(LoginRequest.class)
|
||||
.filter(loginRequest -> loginRequest.getUsername() != null
|
||||
@@ -117,6 +121,7 @@ public class SysAuthHandler {
|
||||
});
|
||||
}
|
||||
|
||||
@Operation(summary = "用户注册", description = "注册新用户")
|
||||
public Mono<ServerResponse> register(ServerRequest request) {
|
||||
return request.bodyToMono(UserRegisterRequest.class)
|
||||
.flatMap(registerRequest -> {
|
||||
@@ -145,6 +150,7 @@ public class SysAuthHandler {
|
||||
});
|
||||
}
|
||||
|
||||
@Operation(summary = "用户登出", description = "用户登出系统")
|
||||
public Mono<ServerResponse> logout(ServerRequest request) {
|
||||
return ServerResponse.ok().build();
|
||||
}
|
||||
|
||||
+9
@@ -2,6 +2,8 @@ package cn.novalon.manage.sys.handler.config;
|
||||
|
||||
import cn.novalon.manage.sys.core.domain.SysConfig;
|
||||
import cn.novalon.manage.sys.core.service.ISysConfigService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
@@ -15,6 +17,7 @@ import reactor.core.publisher.Mono;
|
||||
* @date 2026-03-14
|
||||
*/
|
||||
@Component
|
||||
@Tag(name = "配置管理", description = "系统配置相关操作")
|
||||
public class SysConfigHandler {
|
||||
|
||||
private final ISysConfigService configService;
|
||||
@@ -23,11 +26,13 @@ public class SysConfigHandler {
|
||||
this.configService = configService;
|
||||
}
|
||||
|
||||
@Operation(summary = "获取所有配置", description = "获取系统中所有配置列表")
|
||||
public Mono<ServerResponse> getAllConfigs(ServerRequest request) {
|
||||
return ServerResponse.ok()
|
||||
.body(configService.findAll(), SysConfig.class);
|
||||
}
|
||||
|
||||
@Operation(summary = "根据ID获取配置", description = "根据配置ID获取配置详细信息")
|
||||
public Mono<ServerResponse> getConfigById(ServerRequest request) {
|
||||
Long id = Long.valueOf(request.pathVariable("id"));
|
||||
return configService.findById(id)
|
||||
@@ -35,6 +40,7 @@ public class SysConfigHandler {
|
||||
.switchIfEmpty(ServerResponse.notFound().build());
|
||||
}
|
||||
|
||||
@Operation(summary = "根据键获取配置", description = "根据配置键获取配置详细信息")
|
||||
public Mono<ServerResponse> getConfigByKey(ServerRequest request) {
|
||||
String configKey = request.pathVariable("configKey");
|
||||
return configService.findByConfigKey(configKey)
|
||||
@@ -42,12 +48,14 @@ public class SysConfigHandler {
|
||||
.switchIfEmpty(ServerResponse.notFound().build());
|
||||
}
|
||||
|
||||
@Operation(summary = "创建配置", description = "创建新配置")
|
||||
public Mono<ServerResponse> createConfig(ServerRequest request) {
|
||||
return request.bodyToMono(SysConfig.class)
|
||||
.flatMap(configService::save)
|
||||
.flatMap(config -> ServerResponse.status(HttpStatus.CREATED).bodyValue(config));
|
||||
}
|
||||
|
||||
@Operation(summary = "更新配置", description = "更新配置信息")
|
||||
public Mono<ServerResponse> updateConfig(ServerRequest request) {
|
||||
Long id = Long.valueOf(request.pathVariable("id"));
|
||||
return request.bodyToMono(SysConfig.class)
|
||||
@@ -62,6 +70,7 @@ public class SysConfigHandler {
|
||||
.switchIfEmpty(ServerResponse.notFound().build());
|
||||
}
|
||||
|
||||
@Operation(summary = "删除配置", description = "删除指定配置")
|
||||
public Mono<ServerResponse> deleteConfig(ServerRequest request) {
|
||||
Long id = Long.valueOf(request.pathVariable("id"));
|
||||
return configService.deleteById(id)
|
||||
|
||||
+15
@@ -4,6 +4,8 @@ import cn.novalon.manage.sys.core.domain.SysDictType;
|
||||
import cn.novalon.manage.sys.core.domain.SysDictData;
|
||||
import cn.novalon.manage.sys.core.service.ISysDictTypeService;
|
||||
import cn.novalon.manage.sys.core.service.ISysDictDataService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
@@ -17,6 +19,7 @@ import reactor.core.publisher.Mono;
|
||||
* @date 2026-03-14
|
||||
*/
|
||||
@Component
|
||||
@Tag(name = "字典管理", description = "字典类型和字典数据相关操作")
|
||||
public class SysDictHandler {
|
||||
|
||||
private final ISysDictTypeService dictTypeService;
|
||||
@@ -27,11 +30,13 @@ public class SysDictHandler {
|
||||
this.dictDataService = dictDataService;
|
||||
}
|
||||
|
||||
@Operation(summary = "获取所有字典类型", description = "获取系统中所有字典类型列表")
|
||||
public Mono<ServerResponse> getAllDictTypes(ServerRequest request) {
|
||||
return ServerResponse.ok()
|
||||
.body(dictTypeService.findAll(), SysDictType.class);
|
||||
}
|
||||
|
||||
@Operation(summary = "根据ID获取字典类型", description = "根据字典类型ID获取详细信息")
|
||||
public Mono<ServerResponse> getDictTypeById(ServerRequest request) {
|
||||
Long id = Long.valueOf(request.pathVariable("id"));
|
||||
return dictTypeService.findById(id)
|
||||
@@ -39,6 +44,7 @@ public class SysDictHandler {
|
||||
.switchIfEmpty(ServerResponse.notFound().build());
|
||||
}
|
||||
|
||||
@Operation(summary = "根据类型获取字典类型", description = "根据字典类型代码获取详细信息")
|
||||
public Mono<ServerResponse> getDictTypeByType(ServerRequest request) {
|
||||
String dictType = request.pathVariable("dictType");
|
||||
return dictTypeService.findByDictType(dictType)
|
||||
@@ -46,12 +52,14 @@ public class SysDictHandler {
|
||||
.switchIfEmpty(ServerResponse.notFound().build());
|
||||
}
|
||||
|
||||
@Operation(summary = "创建字典类型", description = "创建新的字典类型")
|
||||
public Mono<ServerResponse> createDictType(ServerRequest request) {
|
||||
return request.bodyToMono(SysDictType.class)
|
||||
.flatMap(dictTypeService::save)
|
||||
.flatMap(dt -> ServerResponse.status(HttpStatus.CREATED).bodyValue(dt));
|
||||
}
|
||||
|
||||
@Operation(summary = "更新字典类型", description = "更新字典类型信息")
|
||||
public Mono<ServerResponse> updateDictType(ServerRequest request) {
|
||||
Long id = Long.valueOf(request.pathVariable("id"));
|
||||
return request.bodyToMono(SysDictType.class)
|
||||
@@ -66,17 +74,20 @@ public class SysDictHandler {
|
||||
.switchIfEmpty(ServerResponse.notFound().build());
|
||||
}
|
||||
|
||||
@Operation(summary = "删除字典类型", description = "删除指定字典类型")
|
||||
public Mono<ServerResponse> deleteDictType(ServerRequest request) {
|
||||
Long id = Long.valueOf(request.pathVariable("id"));
|
||||
return dictTypeService.deleteById(id)
|
||||
.then(ServerResponse.noContent().build());
|
||||
}
|
||||
|
||||
@Operation(summary = "获取所有字典数据", description = "获取系统中所有字典数据列表")
|
||||
public Mono<ServerResponse> getAllDictData(ServerRequest request) {
|
||||
return ServerResponse.ok()
|
||||
.body(dictDataService.findAll(), SysDictData.class);
|
||||
}
|
||||
|
||||
@Operation(summary = "根据ID获取字典数据", description = "根据字典数据ID获取详细信息")
|
||||
public Mono<ServerResponse> getDictDataById(ServerRequest request) {
|
||||
Long id = Long.valueOf(request.pathVariable("id"));
|
||||
return dictDataService.findById(id)
|
||||
@@ -84,18 +95,21 @@ public class SysDictHandler {
|
||||
.switchIfEmpty(ServerResponse.notFound().build());
|
||||
}
|
||||
|
||||
@Operation(summary = "根据类型获取字典数据", description = "根据字典类型获取字典数据列表")
|
||||
public Mono<ServerResponse> getDictDataByType(ServerRequest request) {
|
||||
String dictType = request.pathVariable("dictType");
|
||||
return ServerResponse.ok()
|
||||
.body(dictDataService.findByDictType(dictType), SysDictData.class);
|
||||
}
|
||||
|
||||
@Operation(summary = "创建字典数据", description = "创建新的字典数据")
|
||||
public Mono<ServerResponse> createDictData(ServerRequest request) {
|
||||
return request.bodyToMono(SysDictData.class)
|
||||
.flatMap(dictDataService::save)
|
||||
.flatMap(dd -> ServerResponse.status(HttpStatus.CREATED).bodyValue(dd));
|
||||
}
|
||||
|
||||
@Operation(summary = "更新字典数据", description = "更新字典数据信息")
|
||||
public Mono<ServerResponse> updateDictData(ServerRequest request) {
|
||||
Long id = Long.valueOf(request.pathVariable("id"));
|
||||
return request.bodyToMono(SysDictData.class)
|
||||
@@ -114,6 +128,7 @@ public class SysDictHandler {
|
||||
.switchIfEmpty(ServerResponse.notFound().build());
|
||||
}
|
||||
|
||||
@Operation(summary = "删除字典数据", description = "删除指定字典数据")
|
||||
public Mono<ServerResponse> deleteDictData(ServerRequest request) {
|
||||
Long id = Long.valueOf(request.pathVariable("id"));
|
||||
return dictDataService.deleteById(id)
|
||||
|
||||
+10
@@ -2,6 +2,8 @@ package cn.novalon.manage.sys.handler.dictionary;
|
||||
|
||||
import cn.novalon.manage.sys.core.domain.Dictionary;
|
||||
import cn.novalon.manage.sys.core.service.IDictionaryService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
@@ -15,6 +17,7 @@ import reactor.core.publisher.Mono;
|
||||
* @date 2026-03-14
|
||||
*/
|
||||
@Component
|
||||
@Tag(name = "字典管理", description = "字典数据相关操作")
|
||||
public class DictionaryHandler {
|
||||
|
||||
private final IDictionaryService dictionaryService;
|
||||
@@ -23,11 +26,13 @@ public class DictionaryHandler {
|
||||
this.dictionaryService = dictionaryService;
|
||||
}
|
||||
|
||||
@Operation(summary = "获取所有字典", description = "获取系统中所有字典列表")
|
||||
public Mono<ServerResponse> getAllDictionaries(ServerRequest request) {
|
||||
return ServerResponse.ok()
|
||||
.body(dictionaryService.findAll(), Dictionary.class);
|
||||
}
|
||||
|
||||
@Operation(summary = "根据ID获取字典", description = "根据字典ID获取字典详细信息")
|
||||
public Mono<ServerResponse> getDictionaryById(ServerRequest request) {
|
||||
Long id = Long.valueOf(request.pathVariable("id"));
|
||||
return dictionaryService.findById(id)
|
||||
@@ -35,12 +40,14 @@ public class DictionaryHandler {
|
||||
.switchIfEmpty(ServerResponse.notFound().build());
|
||||
}
|
||||
|
||||
@Operation(summary = "根据类型获取字典", description = "根据字典类型获取字典列表")
|
||||
public Mono<ServerResponse> getDictionariesByType(ServerRequest request) {
|
||||
String type = request.pathVariable("type");
|
||||
return ServerResponse.ok()
|
||||
.body(dictionaryService.findByType(type), Dictionary.class);
|
||||
}
|
||||
|
||||
@Operation(summary = "检查字典存在性", description = "检查指定类型和代码的字典是否存在")
|
||||
public Mono<ServerResponse> checkTypeAndCodeExists(ServerRequest request) {
|
||||
String type = request.queryParam("type").orElse(null);
|
||||
String code = request.queryParam("code").orElse(null);
|
||||
@@ -48,12 +55,14 @@ public class DictionaryHandler {
|
||||
.flatMap(exists -> ServerResponse.ok().bodyValue(exists));
|
||||
}
|
||||
|
||||
@Operation(summary = "创建字典", description = "创建新字典")
|
||||
public Mono<ServerResponse> createDictionary(ServerRequest request) {
|
||||
return request.bodyToMono(Dictionary.class)
|
||||
.flatMap(dictionaryService::save)
|
||||
.flatMap(dict -> ServerResponse.status(HttpStatus.CREATED).bodyValue(dict));
|
||||
}
|
||||
|
||||
@Operation(summary = "更新字典", description = "更新字典信息")
|
||||
public Mono<ServerResponse> updateDictionary(ServerRequest request) {
|
||||
Long id = Long.valueOf(request.pathVariable("id"));
|
||||
return request.bodyToMono(Dictionary.class)
|
||||
@@ -62,6 +71,7 @@ public class DictionaryHandler {
|
||||
.switchIfEmpty(ServerResponse.notFound().build());
|
||||
}
|
||||
|
||||
@Operation(summary = "删除字典", description = "删除指定字典")
|
||||
public Mono<ServerResponse> deleteDictionary(ServerRequest request) {
|
||||
Long id = Long.valueOf(request.pathVariable("id"));
|
||||
return dictionaryService.deleteById(id)
|
||||
|
||||
+13
@@ -5,6 +5,8 @@ import cn.novalon.manage.sys.core.domain.SysExceptionLog;
|
||||
import cn.novalon.manage.sys.core.service.ISysLoginLogService;
|
||||
import cn.novalon.manage.sys.core.service.ISysExceptionLogService;
|
||||
import cn.novalon.manage.common.dto.PageRequest;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
@@ -18,6 +20,7 @@ import reactor.core.publisher.Mono;
|
||||
* @date 2026-03-14
|
||||
*/
|
||||
@Component
|
||||
@Tag(name = "日志管理", description = "登录日志和异常日志相关操作")
|
||||
public class SysLogHandler {
|
||||
|
||||
private final ISysLoginLogService loginLogService;
|
||||
@@ -28,11 +31,13 @@ public class SysLogHandler {
|
||||
this.exceptionLogService = exceptionLogService;
|
||||
}
|
||||
|
||||
@Operation(summary = "获取所有登录日志", description = "获取系统中所有登录日志列表")
|
||||
public Mono<ServerResponse> getAllLoginLogs(ServerRequest request) {
|
||||
return ServerResponse.ok()
|
||||
.body(loginLogService.findAll(), SysLoginLog.class);
|
||||
}
|
||||
|
||||
@Operation(summary = "根据ID获取登录日志", description = "根据登录日志ID获取详细信息")
|
||||
public Mono<ServerResponse> getLoginLogById(ServerRequest request) {
|
||||
Long id = Long.valueOf(request.pathVariable("id"));
|
||||
return loginLogService.findById(id)
|
||||
@@ -40,12 +45,14 @@ public class SysLogHandler {
|
||||
.switchIfEmpty(ServerResponse.notFound().build());
|
||||
}
|
||||
|
||||
@Operation(summary = "创建登录日志", description = "创建新的登录日志")
|
||||
public Mono<ServerResponse> createLoginLog(ServerRequest request) {
|
||||
return request.bodyToMono(SysLoginLog.class)
|
||||
.flatMap(loginLogService::save)
|
||||
.flatMap(log -> ServerResponse.status(HttpStatus.CREATED).bodyValue(log));
|
||||
}
|
||||
|
||||
@Operation(summary = "分页获取登录日志", description = "根据分页参数获取登录日志列表")
|
||||
public Mono<ServerResponse> getLoginLogsByPage(ServerRequest request) {
|
||||
int page = Integer.parseInt(request.queryParam("page").orElse("0"));
|
||||
int size = Integer.parseInt(request.queryParam("size").orElse("10"));
|
||||
@@ -64,16 +71,19 @@ public class SysLogHandler {
|
||||
.flatMap(response -> ServerResponse.ok().bodyValue(response));
|
||||
}
|
||||
|
||||
@Operation(summary = "获取登录日志总数", description = "获取系统中登录日志总数")
|
||||
public Mono<ServerResponse> getLoginLogCount(ServerRequest request) {
|
||||
return loginLogService.count()
|
||||
.flatMap(count -> ServerResponse.ok().bodyValue(count));
|
||||
}
|
||||
|
||||
@Operation(summary = "获取所有异常日志", description = "获取系统中所有异常日志列表")
|
||||
public Mono<ServerResponse> getAllExceptionLogs(ServerRequest request) {
|
||||
return ServerResponse.ok()
|
||||
.body(exceptionLogService.findAll(), SysExceptionLog.class);
|
||||
}
|
||||
|
||||
@Operation(summary = "根据ID获取异常日志", description = "根据异常日志ID获取详细信息")
|
||||
public Mono<ServerResponse> getExceptionLogById(ServerRequest request) {
|
||||
Long id = Long.valueOf(request.pathVariable("id"));
|
||||
return exceptionLogService.findById(id)
|
||||
@@ -81,12 +91,14 @@ public class SysLogHandler {
|
||||
.switchIfEmpty(ServerResponse.notFound().build());
|
||||
}
|
||||
|
||||
@Operation(summary = "创建异常日志", description = "创建新的异常日志")
|
||||
public Mono<ServerResponse> createExceptionLog(ServerRequest request) {
|
||||
return request.bodyToMono(SysExceptionLog.class)
|
||||
.flatMap(exceptionLogService::save)
|
||||
.flatMap(log -> ServerResponse.status(HttpStatus.CREATED).bodyValue(log));
|
||||
}
|
||||
|
||||
@Operation(summary = "分页获取异常日志", description = "根据分页参数获取异常日志列表")
|
||||
public Mono<ServerResponse> getExceptionLogsByPage(ServerRequest request) {
|
||||
int page = Integer.parseInt(request.queryParam("page").orElse("0"));
|
||||
int size = Integer.parseInt(request.queryParam("size").orElse("10"));
|
||||
@@ -105,6 +117,7 @@ public class SysLogHandler {
|
||||
.flatMap(response -> ServerResponse.ok().bodyValue(response));
|
||||
}
|
||||
|
||||
@Operation(summary = "获取异常日志总数", description = "获取系统中异常日志总数")
|
||||
public Mono<ServerResponse> getExceptionLogCount(ServerRequest request) {
|
||||
return exceptionLogService.count()
|
||||
.flatMap(count -> ServerResponse.ok().bodyValue(count));
|
||||
|
||||
+11
@@ -6,6 +6,8 @@ import cn.novalon.manage.sys.dto.request.MenuCreateRequest;
|
||||
import cn.novalon.manage.sys.dto.request.MenuUpdateRequest;
|
||||
import cn.novalon.manage.sys.core.command.CreateMenuCommand;
|
||||
import cn.novalon.manage.sys.core.command.UpdateMenuCommand;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
@@ -19,6 +21,7 @@ import reactor.core.publisher.Mono;
|
||||
* @date 2026-03-14
|
||||
*/
|
||||
@Component
|
||||
@Tag(name = "菜单管理", description = "系统菜单相关操作")
|
||||
public class MenuHandler {
|
||||
|
||||
private final ISysMenuService menuService;
|
||||
@@ -27,11 +30,13 @@ public class MenuHandler {
|
||||
this.menuService = menuService;
|
||||
}
|
||||
|
||||
@Operation(summary = "获取所有菜单", description = "获取系统中所有菜单列表")
|
||||
public Mono<ServerResponse> getAllMenus(ServerRequest request) {
|
||||
return ServerResponse.ok()
|
||||
.body(menuService.findAll(), SysMenu.class);
|
||||
}
|
||||
|
||||
@Operation(summary = "根据ID获取菜单", description = "根据菜单ID获取菜单详细信息")
|
||||
public Mono<ServerResponse> getMenuById(ServerRequest request) {
|
||||
Long id = Long.valueOf(request.pathVariable("id"));
|
||||
return menuService.findById(id)
|
||||
@@ -39,11 +44,13 @@ public class MenuHandler {
|
||||
.switchIfEmpty(ServerResponse.notFound().build());
|
||||
}
|
||||
|
||||
@Operation(summary = "获取菜单树", description = "获取系统菜单树结构")
|
||||
public Mono<ServerResponse> getMenuTree(ServerRequest request) {
|
||||
return ServerResponse.ok()
|
||||
.body(menuService.buildMenuTree(menuService.findAll()), SysMenu.class);
|
||||
}
|
||||
|
||||
@Operation(summary = "根据父菜单获取子菜单", description = "根据父菜单ID获取子菜单列表")
|
||||
public Mono<ServerResponse> getMenusByParent(ServerRequest request) {
|
||||
Long parentId = request.queryParam("parentId")
|
||||
.map(Long::valueOf)
|
||||
@@ -52,12 +59,14 @@ public class MenuHandler {
|
||||
.body(menuService.findByParentId(parentId), SysMenu.class);
|
||||
}
|
||||
|
||||
@Operation(summary = "根据类型获取菜单", description = "根据菜单类型获取菜单列表")
|
||||
public Mono<ServerResponse> getMenusByType(ServerRequest request) {
|
||||
String menuType = request.queryParam("menuType").orElse(null);
|
||||
return ServerResponse.ok()
|
||||
.body(menuService.findAll().filter(menu -> menuType == null || menuType.equals(menu.getMenuType())), SysMenu.class);
|
||||
}
|
||||
|
||||
@Operation(summary = "创建菜单", description = "创建新菜单")
|
||||
public Mono<ServerResponse> createMenu(ServerRequest request) {
|
||||
return request.bodyToMono(MenuCreateRequest.class)
|
||||
.map(req -> CreateMenuCommand.of(
|
||||
@@ -73,6 +82,7 @@ public class MenuHandler {
|
||||
.flatMap(menu -> ServerResponse.status(HttpStatus.CREATED).bodyValue(menu));
|
||||
}
|
||||
|
||||
@Operation(summary = "更新菜单", description = "更新菜单信息")
|
||||
public Mono<ServerResponse> updateMenu(ServerRequest request) {
|
||||
Long id = Long.valueOf(request.pathVariable("id"));
|
||||
return request.bodyToMono(MenuUpdateRequest.class)
|
||||
@@ -91,6 +101,7 @@ public class MenuHandler {
|
||||
.switchIfEmpty(ServerResponse.notFound().build());
|
||||
}
|
||||
|
||||
@Operation(summary = "删除菜单", description = "删除指定菜单")
|
||||
public Mono<ServerResponse> deleteMenu(ServerRequest request) {
|
||||
Long id = Long.valueOf(request.pathVariable("id"));
|
||||
return menuService.deleteMenu(id)
|
||||
|
||||
+4
@@ -3,6 +3,8 @@ package cn.novalon.manage.sys.handler.stats;
|
||||
import cn.novalon.manage.sys.core.service.ISysUserService;
|
||||
import cn.novalon.manage.sys.core.service.ISysRoleService;
|
||||
import cn.novalon.manage.sys.core.service.IOperationLogService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
@@ -15,6 +17,7 @@ import reactor.core.publisher.Mono;
|
||||
* @date 2026-03-14
|
||||
*/
|
||||
@Component
|
||||
@Tag(name = "统计信息", description = "系统统计相关操作")
|
||||
public class StatsHandler {
|
||||
|
||||
private final ISysUserService userService;
|
||||
@@ -27,6 +30,7 @@ public class StatsHandler {
|
||||
this.operationLogService = operationLogService;
|
||||
}
|
||||
|
||||
@Operation(summary = "获取系统概览", description = "获取系统统计概览信息")
|
||||
public Mono<ServerResponse> getOverview(ServerRequest request) {
|
||||
return Mono.zip(
|
||||
userService.count(),
|
||||
|
||||
+4
-2
@@ -1,5 +1,7 @@
|
||||
package cn.novalon.manage.sys.primitive;
|
||||
|
||||
import cn.novalon.manage.common.exception.ErrorCode;
|
||||
import cn.novalon.manage.common.exception.ValidationException;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
@@ -26,7 +28,7 @@ public final class Email {
|
||||
|
||||
public static Email of(String value) {
|
||||
if (StringUtils.isBlank(value)) {
|
||||
throw new IllegalArgumentException("Email is required");
|
||||
throw new ValidationException(ErrorCode.VALIDATION_REQUIRED, "Email is required");
|
||||
}
|
||||
validate(value);
|
||||
return new Email(value);
|
||||
@@ -42,7 +44,7 @@ public final class Email {
|
||||
|
||||
private static void validate(String value) {
|
||||
if (!EMAIL_PATTERN.matcher(value).matches()) {
|
||||
throw new IllegalArgumentException("Invalid email format");
|
||||
throw new ValidationException(ErrorCode.VALIDATION_INVALID_FORMAT, "Invalid email format");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
-3
@@ -1,5 +1,7 @@
|
||||
package cn.novalon.manage.sys.primitive;
|
||||
|
||||
import cn.novalon.manage.common.exception.ErrorCode;
|
||||
import cn.novalon.manage.common.exception.ValidationException;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -24,7 +26,7 @@ public final class Password {
|
||||
|
||||
public static Password of(String value) {
|
||||
if (StringUtils.isBlank(value)) {
|
||||
throw new IllegalArgumentException("Password is required");
|
||||
throw new ValidationException(ErrorCode.VALIDATION_REQUIRED, "Password is required");
|
||||
}
|
||||
validate(value);
|
||||
return new Password(value);
|
||||
@@ -32,7 +34,8 @@ public final class Password {
|
||||
|
||||
private static void validate(String value) {
|
||||
if (value.length() < MIN_LENGTH) {
|
||||
throw new IllegalArgumentException("Password must be at least " + MIN_LENGTH + " characters long");
|
||||
throw new ValidationException(ErrorCode.VALIDATION_INVALID_LENGTH,
|
||||
"Password must be at least " + MIN_LENGTH + " characters long");
|
||||
}
|
||||
|
||||
boolean hasUppercase = value.chars().anyMatch(Character::isUpperCase);
|
||||
@@ -41,7 +44,7 @@ public final class Password {
|
||||
boolean hasSpecial = value.chars().anyMatch(c -> !Character.isLetterOrDigit(c));
|
||||
|
||||
if (!hasUppercase || !hasLowercase || !hasDigit || !hasSpecial) {
|
||||
throw new IllegalArgumentException(
|
||||
throw new ValidationException(ErrorCode.VALIDATION_INVALID_VALUE,
|
||||
"Password must contain at least one uppercase letter, one lowercase letter, one digit, and one special character");
|
||||
}
|
||||
}
|
||||
|
||||
+9
-4
@@ -1,5 +1,7 @@
|
||||
package cn.novalon.manage.sys.primitive;
|
||||
|
||||
import cn.novalon.manage.common.exception.ErrorCode;
|
||||
import cn.novalon.manage.common.exception.ValidationException;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
@@ -28,7 +30,7 @@ public final class Username {
|
||||
|
||||
public static Username of(String value) {
|
||||
if (StringUtils.isBlank(value)) {
|
||||
throw new IllegalArgumentException("Username is required");
|
||||
throw new ValidationException(ErrorCode.VALIDATION_REQUIRED, "Username is required");
|
||||
}
|
||||
validate(value);
|
||||
return new Username(value);
|
||||
@@ -38,15 +40,18 @@ public final class Username {
|
||||
String trimmed = value.trim();
|
||||
|
||||
if (trimmed.length() < MIN_LENGTH) {
|
||||
throw new IllegalArgumentException("Username must be at least " + MIN_LENGTH + " characters long");
|
||||
throw new ValidationException(ErrorCode.VALIDATION_INVALID_LENGTH,
|
||||
"Username must be at least " + MIN_LENGTH + " characters long");
|
||||
}
|
||||
|
||||
if (trimmed.length() > MAX_LENGTH) {
|
||||
throw new IllegalArgumentException("Username must be at most " + MAX_LENGTH + " characters long");
|
||||
throw new ValidationException(ErrorCode.VALIDATION_INVALID_LENGTH,
|
||||
"Username must be at most " + MAX_LENGTH + " characters long");
|
||||
}
|
||||
|
||||
if (!USERNAME_PATTERN.matcher(trimmed).matches()) {
|
||||
throw new IllegalArgumentException("Username can only contain letters, numbers, and underscores");
|
||||
throw new ValidationException(ErrorCode.VALIDATION_INVALID_FORMAT,
|
||||
"Username can only contain letters, numbers, and underscores");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+300
@@ -0,0 +1,300 @@
|
||||
package cn.novalon.manage.sys.primitive;
|
||||
|
||||
import cn.novalon.manage.common.exception.ErrorCode;
|
||||
import cn.novalon.manage.common.exception.ValidationException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Password详细测试 - 提升分支覆盖率
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-24
|
||||
*/
|
||||
class PasswordDetailedTest {
|
||||
|
||||
@Test
|
||||
void testValidPassword() {
|
||||
Password password = Password.of("Valid@123");
|
||||
assertNotNull(password);
|
||||
assertEquals("Valid@123", password.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNullPassword() {
|
||||
ValidationException exception = assertThrows(ValidationException.class, () -> {
|
||||
Password.of(null);
|
||||
});
|
||||
assertEquals(ErrorCode.VALIDATION_REQUIRED, exception.getErrorCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEmptyPassword() {
|
||||
ValidationException exception = assertThrows(ValidationException.class, () -> {
|
||||
Password.of("");
|
||||
});
|
||||
assertEquals(ErrorCode.VALIDATION_REQUIRED, exception.getErrorCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testWhitespacePassword() {
|
||||
ValidationException exception = assertThrows(ValidationException.class, () -> {
|
||||
Password.of(" ");
|
||||
});
|
||||
assertEquals(ErrorCode.VALIDATION_REQUIRED, exception.getErrorCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTooShortPassword() {
|
||||
ValidationException exception = assertThrows(ValidationException.class, () -> {
|
||||
Password.of("Short1@");
|
||||
});
|
||||
assertEquals(ErrorCode.VALIDATION_INVALID_LENGTH, exception.getErrorCode());
|
||||
assertTrue(exception.getMessage().contains("at least 8 characters"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExactlyMinLengthPassword() {
|
||||
Password password = Password.of("Valid1@");
|
||||
assertNotNull(password);
|
||||
assertEquals("Valid1@", password.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPasswordWithoutUppercase() {
|
||||
ValidationException exception = assertThrows(ValidationException.class, () -> {
|
||||
Password.of("lowercase1@");
|
||||
});
|
||||
assertEquals(ErrorCode.VALIDATION_INVALID_VALUE, exception.getErrorCode());
|
||||
assertTrue(exception.getMessage().contains("uppercase letter"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPasswordWithoutLowercase() {
|
||||
ValidationException exception = assertThrows(ValidationException.class, () -> {
|
||||
Password.of("UPPERCASE1@");
|
||||
});
|
||||
assertEquals(ErrorCode.VALIDATION_INVALID_VALUE, exception.getErrorCode());
|
||||
assertTrue(exception.getMessage().contains("lowercase letter"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPasswordWithoutDigit() {
|
||||
ValidationException exception = assertThrows(ValidationException.class, () -> {
|
||||
Password.of("NoDigits@");
|
||||
});
|
||||
assertEquals(ErrorCode.VALIDATION_INVALID_VALUE, exception.getErrorCode());
|
||||
assertTrue(exception.getMessage().contains("digit"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPasswordWithoutSpecialCharacter() {
|
||||
ValidationException exception = assertThrows(ValidationException.class, () -> {
|
||||
Password.of("NoSpecial123");
|
||||
});
|
||||
assertEquals(ErrorCode.VALIDATION_INVALID_VALUE, exception.getErrorCode());
|
||||
assertTrue(exception.getMessage().contains("special character"));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {
|
||||
"Valid@123",
|
||||
"Another@456",
|
||||
"Test@789",
|
||||
"Complex@Pass123",
|
||||
"Simple@Pass456"
|
||||
})
|
||||
void testMultipleValidPasswords(String password) {
|
||||
Password pwd = Password.of(password);
|
||||
assertNotNull(pwd);
|
||||
assertEquals(password, pwd.getValue());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {
|
||||
"lowercase@123",
|
||||
"UPPERCASE@123",
|
||||
"MixedCase@abc",
|
||||
"MixedCase123"
|
||||
})
|
||||
void testMultipleInvalidPasswords(String password) {
|
||||
assertThrows(ValidationException.class, () -> {
|
||||
Password.of(password);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPasswordWithOnlyUppercaseAndDigit() {
|
||||
ValidationException exception = assertThrows(ValidationException.class, () -> {
|
||||
Password.of("UPPERCASE123");
|
||||
});
|
||||
assertEquals(ErrorCode.VALIDATION_INVALID_VALUE, exception.getErrorCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPasswordWithOnlyLowercaseAndDigit() {
|
||||
ValidationException exception = assertThrows(ValidationException.class, () -> {
|
||||
Password.of("lowercase123");
|
||||
});
|
||||
assertEquals(ErrorCode.VALIDATION_INVALID_VALUE, exception.getErrorCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPasswordWithOnlyUppercaseAndSpecial() {
|
||||
ValidationException exception = assertThrows(ValidationException.class, () -> {
|
||||
Password.of("UPPERCASE@");
|
||||
});
|
||||
assertEquals(ErrorCode.VALIDATION_INVALID_VALUE, exception.getErrorCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPasswordWithOnlyLowercaseAndSpecial() {
|
||||
ValidationException exception = assertThrows(ValidationException.class, () -> {
|
||||
Password.of("lowercase@");
|
||||
});
|
||||
assertEquals(ErrorCode.VALIDATION_INVALID_VALUE, exception.getErrorCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPasswordWithOnlyDigitAndSpecial() {
|
||||
ValidationException exception = assertThrows(ValidationException.class, () -> {
|
||||
Password.of("123456@");
|
||||
});
|
||||
assertEquals(ErrorCode.VALIDATION_INVALID_VALUE, exception.getErrorCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPasswordWithMultipleSpecialCharacters() {
|
||||
Password password = Password.of("Valid@#$123");
|
||||
assertNotNull(password);
|
||||
assertEquals("Valid@#$123", password.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPasswordWithSpaces() {
|
||||
ValidationException exception = assertThrows(ValidationException.class, () -> {
|
||||
Password.of("Valid @123");
|
||||
});
|
||||
assertEquals(ErrorCode.VALIDATION_REQUIRED, exception.getErrorCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testVeryLongPassword() {
|
||||
Password password = Password.of("VeryLongPassword@1234567890");
|
||||
assertNotNull(password);
|
||||
assertEquals("VeryLongPassword@1234567890", password.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPasswordEquals() {
|
||||
Password password1 = Password.of("Valid@123");
|
||||
Password password2 = Password.of("Valid@123");
|
||||
assertEquals(password1, password2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPasswordNotEquals() {
|
||||
Password password1 = Password.of("Valid@123");
|
||||
Password password2 = Password.of("Different@456");
|
||||
assertNotEquals(password1, password2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPasswordEqualsNull() {
|
||||
Password password = Password.of("Valid@123");
|
||||
assertNotEquals(password, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPasswordEqualsDifferentClass() {
|
||||
Password password = Password.of("Valid@123");
|
||||
assertNotEquals(password, "Valid@123");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPasswordEqualsSameInstance() {
|
||||
Password password = Password.of("Valid@123");
|
||||
assertEquals(password, password);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPasswordHashCode() {
|
||||
Password password1 = Password.of("Valid@123");
|
||||
Password password2 = Password.of("Valid@123");
|
||||
assertEquals(password1.hashCode(), password2.hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPasswordHashCodeDifferent() {
|
||||
Password password1 = Password.of("Valid@123");
|
||||
Password password2 = Password.of("Different@456");
|
||||
assertNotEquals(password1.hashCode(), password2.hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPasswordToString() {
|
||||
Password password = Password.of("Valid@123");
|
||||
String toString = password.toString();
|
||||
assertEquals("********", toString);
|
||||
assertFalse(toString.contains("Valid"));
|
||||
assertFalse(toString.contains("123"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPasswordWithUnicodeCharacters() {
|
||||
Password password = Password.of("密码@123");
|
||||
assertNotNull(password);
|
||||
assertEquals("密码@123", password.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPasswordWithNumbersOnly() {
|
||||
ValidationException exception = assertThrows(ValidationException.class, () -> {
|
||||
Password.of("12345678");
|
||||
});
|
||||
assertEquals(ErrorCode.VALIDATION_INVALID_VALUE, exception.getErrorCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPasswordWithLettersOnly() {
|
||||
ValidationException exception = assertThrows(ValidationException.class, () -> {
|
||||
Password.of("abcdefgh");
|
||||
});
|
||||
assertEquals(ErrorCode.VALIDATION_INVALID_VALUE, exception.getErrorCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPasswordWithSpecialOnly() {
|
||||
ValidationException exception = assertThrows(ValidationException.class, () -> {
|
||||
Password.of("@#$%^&*()");
|
||||
});
|
||||
assertEquals(ErrorCode.VALIDATION_INVALID_VALUE, exception.getErrorCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPasswordWithUppercaseLowercaseOnly() {
|
||||
ValidationException exception = assertThrows(ValidationException.class, () -> {
|
||||
Password.of("AbCdEfGh");
|
||||
});
|
||||
assertEquals(ErrorCode.VALIDATION_INVALID_VALUE, exception.getErrorCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPasswordWithUppercaseDigitOnly() {
|
||||
ValidationException exception = assertThrows(ValidationException.class, () -> {
|
||||
Password.of("ABCDEFGH12345678");
|
||||
});
|
||||
assertEquals(ErrorCode.VALIDATION_INVALID_VALUE, exception.getErrorCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPasswordWithLowercaseDigitOnly() {
|
||||
ValidationException exception = assertThrows(ValidationException.class, () -> {
|
||||
Password.of("abcdefgh12345678");
|
||||
});
|
||||
assertEquals(ErrorCode.VALIDATION_INVALID_VALUE, exception.getErrorCode());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user