feat: 完善系统配置审计通知功能并优化异常处理

- 新增异常处理体系(BaseException及其子类)
- 优化密码、邮箱、用户名等基础类型
- 添加字典管理、登录日志、操作日志的E2E测试
- 完善API集成测试和安全测试
- 添加性能测试配置和脚本
- 优化OpenAPI配置和全局异常处理器
This commit is contained in:
张翔
2026-03-24 14:05:35 +08:00
parent be5d5ede90
commit e4721053bd
47 changed files with 3006 additions and 816 deletions
@@ -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();
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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";
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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);
@@ -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;
}