From eeefb83058583791e6d25309a553ddf0d02a9320 Mon Sep 17 00:00:00 2001 From: meowrain Date: Mon, 17 Nov 2025 20:36:20 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=9F=BA=E6=9C=AC?= =?UTF-8?q?=E5=BC=82=E5=B8=B8=E5=A4=84=E7=90=86=EF=BC=8C=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E7=A0=81=EF=BC=8C=E6=B3=A8=E5=86=8C=E5=85=A8=E5=B1=80=E5=BC=82?= =?UTF-8?q?=E5=B8=B8=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../framework/annotation/AuthCheck.java | 12 +++ .../config/WebAutoConfiguration.java | 14 ++++ .../framework/errorcode/ErrorCode.java | 38 +++++++++ .../framework/errorcode/IErrorCode.java | 6 ++ .../exception/AbstractException.java | 24 ++++++ .../framework/exception/ClientException.java | 28 +++++++ .../framework/exception/RemoteException.java | 27 ++++++ .../framework/exception/ServiceException.java | 28 +++++++ .../handler/GlobalExceptionHandler.java | 84 +++++++++++++++++++ .../aioj/backend/framework/web/Result.java | 50 +++++++++++ .../aioj/backend/framework/web/Results.java | 78 +++++++++++++++++ ...ot.autoconfigure.AutoConfiguration.imports | 1 + 12 files changed, 390 insertions(+) create mode 100644 aioj-backend-common/src/main/java/cn/meowrain/aioj/backend/framework/annotation/AuthCheck.java create mode 100644 aioj-backend-common/src/main/java/cn/meowrain/aioj/backend/framework/config/WebAutoConfiguration.java create mode 100644 aioj-backend-common/src/main/java/cn/meowrain/aioj/backend/framework/errorcode/ErrorCode.java create mode 100644 aioj-backend-common/src/main/java/cn/meowrain/aioj/backend/framework/errorcode/IErrorCode.java create mode 100644 aioj-backend-common/src/main/java/cn/meowrain/aioj/backend/framework/exception/AbstractException.java create mode 100644 aioj-backend-common/src/main/java/cn/meowrain/aioj/backend/framework/exception/ClientException.java create mode 100644 aioj-backend-common/src/main/java/cn/meowrain/aioj/backend/framework/exception/RemoteException.java create mode 100644 aioj-backend-common/src/main/java/cn/meowrain/aioj/backend/framework/exception/ServiceException.java create mode 100644 aioj-backend-common/src/main/java/cn/meowrain/aioj/backend/framework/exception/handler/GlobalExceptionHandler.java create mode 100644 aioj-backend-common/src/main/java/cn/meowrain/aioj/backend/framework/web/Result.java create mode 100644 aioj-backend-common/src/main/java/cn/meowrain/aioj/backend/framework/web/Results.java create mode 100644 aioj-backend-common/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports diff --git a/aioj-backend-common/src/main/java/cn/meowrain/aioj/backend/framework/annotation/AuthCheck.java b/aioj-backend-common/src/main/java/cn/meowrain/aioj/backend/framework/annotation/AuthCheck.java new file mode 100644 index 0000000..637491d --- /dev/null +++ b/aioj-backend-common/src/main/java/cn/meowrain/aioj/backend/framework/annotation/AuthCheck.java @@ -0,0 +1,12 @@ +package cn.meowrain.aioj.backend.framework.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface AuthCheck { + String mustRole() default ""; +} diff --git a/aioj-backend-common/src/main/java/cn/meowrain/aioj/backend/framework/config/WebAutoConfiguration.java b/aioj-backend-common/src/main/java/cn/meowrain/aioj/backend/framework/config/WebAutoConfiguration.java new file mode 100644 index 0000000..f54e9e5 --- /dev/null +++ b/aioj-backend-common/src/main/java/cn/meowrain/aioj/backend/framework/config/WebAutoConfiguration.java @@ -0,0 +1,14 @@ +package cn.meowrain.aioj.backend.framework.config; + +import cn.meowrain.aioj.backend.framework.exception.handler.GlobalExceptionHandler; +import org.springframework.context.annotation.Bean; + +/** + * 注册为bean,全局异常拦截器 + */ +public class WebAutoConfiguration { + @Bean + public GlobalExceptionHandler globalExceptionHandler() { + return new GlobalExceptionHandler(); + } +} diff --git a/aioj-backend-common/src/main/java/cn/meowrain/aioj/backend/framework/errorcode/ErrorCode.java b/aioj-backend-common/src/main/java/cn/meowrain/aioj/backend/framework/errorcode/ErrorCode.java new file mode 100644 index 0000000..ec33820 --- /dev/null +++ b/aioj-backend-common/src/main/java/cn/meowrain/aioj/backend/framework/errorcode/ErrorCode.java @@ -0,0 +1,38 @@ +package cn.meowrain.aioj.backend.framework.errorcode; + +public enum ErrorCode implements IErrorCode { + SUCCESS("0", "ok"), + PARAMS_ERROR("40000", "请求参数错误"), + NOT_LOGIN_ERROR("40100", "未登录"), + NO_AUTH_ERROR("40101", "无权限"), + NOT_FOUND_ERROR("40400", "请求数据不存在"), + FORBIDDEN_ERROR("40300", "禁止访问"), + SYSTEM_ERROR("50000", "系统内部异常"), + OPERATION_ERROR("50001", "操作失败"), + API_REQUEST_ERROR("50010", "接口调用失败"); + /** + * 状态码 + */ + + private final String code; + + /** + * 信息 + */ + private final String message; + + ErrorCode(String code, String message) { + this.code = code; + this.message = message; + } + + @Override + public String code() { + return code; + } + + @Override + public String message() { + return message; + } +} diff --git a/aioj-backend-common/src/main/java/cn/meowrain/aioj/backend/framework/errorcode/IErrorCode.java b/aioj-backend-common/src/main/java/cn/meowrain/aioj/backend/framework/errorcode/IErrorCode.java new file mode 100644 index 0000000..920843c --- /dev/null +++ b/aioj-backend-common/src/main/java/cn/meowrain/aioj/backend/framework/errorcode/IErrorCode.java @@ -0,0 +1,6 @@ +package cn.meowrain.aioj.backend.framework.errorcode; + +public interface IErrorCode { + String code(); + String message(); +} diff --git a/aioj-backend-common/src/main/java/cn/meowrain/aioj/backend/framework/exception/AbstractException.java b/aioj-backend-common/src/main/java/cn/meowrain/aioj/backend/framework/exception/AbstractException.java new file mode 100644 index 0000000..f6341a0 --- /dev/null +++ b/aioj-backend-common/src/main/java/cn/meowrain/aioj/backend/framework/exception/AbstractException.java @@ -0,0 +1,24 @@ +package cn.meowrain.aioj.backend.framework.exception; + +import cn.meowrain.aioj.backend.framework.errorcode.IErrorCode; +import lombok.Getter; +import org.springframework.util.StringUtils; + +import java.util.Optional; + +/** + * 抽象错误处理Exception,基于这个抽象类,我们能创建很多其它类型的exception,定义错误类型 + */ +@Getter + +public class AbstractException extends RuntimeException { + public final String errorCode; + public final String errorMessage; + + public AbstractException(String message, Throwable throwable, IErrorCode errorCode) { + super(message); + this.errorCode = errorCode.code(); + this.errorMessage = Optional.ofNullable(StringUtils.hasLength(message) ? message : null) + .orElse(errorCode.message()); + } +} diff --git a/aioj-backend-common/src/main/java/cn/meowrain/aioj/backend/framework/exception/ClientException.java b/aioj-backend-common/src/main/java/cn/meowrain/aioj/backend/framework/exception/ClientException.java new file mode 100644 index 0000000..c93d39f --- /dev/null +++ b/aioj-backend-common/src/main/java/cn/meowrain/aioj/backend/framework/exception/ClientException.java @@ -0,0 +1,28 @@ +package cn.meowrain.aioj.backend.framework.exception; + +import cn.meowrain.aioj.backend.framework.errorcode.ErrorCode; +import cn.meowrain.aioj.backend.framework.errorcode.IErrorCode; +import lombok.ToString; + +/** + * 客户端异常 + */ +@ToString +public class ClientException extends AbstractException{ + public ClientException(String message, Throwable throwable, IErrorCode errorCode) { + super(message, throwable, errorCode); + } + + public ClientException(IErrorCode errorCode) { + this(null, null, errorCode); + } + + public ClientException(String message, IErrorCode errorCode) { + this(message, null, errorCode); + } + + public ClientException(String message) { + this(message, null, ErrorCode.PARAMS_ERROR); + } + +} diff --git a/aioj-backend-common/src/main/java/cn/meowrain/aioj/backend/framework/exception/RemoteException.java b/aioj-backend-common/src/main/java/cn/meowrain/aioj/backend/framework/exception/RemoteException.java new file mode 100644 index 0000000..a4b82fb --- /dev/null +++ b/aioj-backend-common/src/main/java/cn/meowrain/aioj/backend/framework/exception/RemoteException.java @@ -0,0 +1,27 @@ +package cn.meowrain.aioj.backend.framework.exception; + +import cn.meowrain.aioj.backend.framework.errorcode.ErrorCode; +import cn.meowrain.aioj.backend.framework.errorcode.IErrorCode; +import lombok.ToString; + +/** + * 调用第三方服务异常 + */ +@ToString +public class RemoteException extends AbstractException{ + public RemoteException(IErrorCode errorCode) { + this(null, null, errorCode); + } + + public RemoteException(String message, IErrorCode errorCode) { + this(message, null, errorCode); + } + + public RemoteException(String message, Throwable throwable, IErrorCode errorCode) { + super(message, throwable, errorCode); + } + public RemoteException(String message) { + this(message, null, ErrorCode.API_REQUEST_ERROR); + } + +} diff --git a/aioj-backend-common/src/main/java/cn/meowrain/aioj/backend/framework/exception/ServiceException.java b/aioj-backend-common/src/main/java/cn/meowrain/aioj/backend/framework/exception/ServiceException.java new file mode 100644 index 0000000..9350f26 --- /dev/null +++ b/aioj-backend-common/src/main/java/cn/meowrain/aioj/backend/framework/exception/ServiceException.java @@ -0,0 +1,28 @@ +package cn.meowrain.aioj.backend.framework.exception; + +import cn.meowrain.aioj.backend.framework.errorcode.ErrorCode; +import cn.meowrain.aioj.backend.framework.errorcode.IErrorCode; +import lombok.ToString; + +/** + * 系统执行异常 + */ +@ToString +public class ServiceException extends AbstractException { + public ServiceException(String message, IErrorCode errorCode) { + this(message, null, errorCode); + } + + public ServiceException(String message) { + this(message, null, ErrorCode.SYSTEM_ERROR); + } + + public ServiceException(IErrorCode errorCode) { + this(null, null, errorCode); + } + + public ServiceException(String message, Throwable throwable, IErrorCode errorCode) { + super(message, throwable, errorCode); + } + +} diff --git a/aioj-backend-common/src/main/java/cn/meowrain/aioj/backend/framework/exception/handler/GlobalExceptionHandler.java b/aioj-backend-common/src/main/java/cn/meowrain/aioj/backend/framework/exception/handler/GlobalExceptionHandler.java new file mode 100644 index 0000000..d4c940f --- /dev/null +++ b/aioj-backend-common/src/main/java/cn/meowrain/aioj/backend/framework/exception/handler/GlobalExceptionHandler.java @@ -0,0 +1,84 @@ +package cn.meowrain.aioj.backend.framework.exception.handler; + +import cn.meowrain.aioj.backend.framework.exception.AbstractException; +import cn.meowrain.aioj.backend.framework.web.Result; +import cn.meowrain.aioj.backend.framework.web.Results; +import jakarta.servlet.http.HttpServletRequest; +import lombok.experimental.StandardException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.StringUtils; +import org.springframework.validation.BindingResult; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import java.util.List; + +/** + * 全局错误捕获器 + */ +@Slf4j +@RestControllerAdvice +public class GlobalExceptionHandler { + /** + * 捕获所有参数错误,然后统一捕获并且抛出 + * @param request {@link HttpServletRequest} + * @param ex {@link org.springframework.validation.method.MethodValidationException} + * @return {@link Result} + */ + @ExceptionHandler(value = MethodArgumentNotValidException.class) + public Result validExceptionHandler(HttpServletRequest request, MethodArgumentNotValidException ex) { + BindingResult bindingResult = ex.getBindingResult(); + // 收集所有错误字段 + List errorMessages = bindingResult.getFieldErrors().stream() + .map(FieldError::getDefaultMessage).toList(); + String exceptionMessage = String.join(",", errorMessages); + log.error("[{}] {} [ex] {}", request.getMethod(), getUrl(request), exceptionMessage); + return Results.paramsValidFailure(); + } + + /** + * 抽象异常捕获其 + * @param request {@link HttpServletRequest} + * @param ex {@link AbstractException} + * @return {@link Result} + */ + @ExceptionHandler(value = {AbstractException.class}) + public Result abstractExceptionHandler(HttpServletRequest request,AbstractException ex ) { + if (ex.getCause() != null) { + log.error("[{}] {} [ex] {}", request.getMethod(), request.getRequestURL().toString(), ex, ex.getCause()); + return Results.failure(ex); + } + StringBuilder stackTraceBuilder = new StringBuilder(); + stackTraceBuilder.append(ex.getClass().getName()).append(": ").append(ex.getErrorMessage()).append("\n"); + StackTraceElement[] stackTrace = ex.getStackTrace(); + for (int i = 0; i < Math.min(5, stackTrace.length); i++) { + stackTraceBuilder.append("\tat ").append(stackTrace[i]).append("\n"); + } + log.error("[{}] {} [ex] {} \n\n{}", request.getMethod(), request.getRequestURL().toString(), ex, + stackTraceBuilder); + return Results.failure(ex); + } + + /** + * 拦截未捕获异常 + */ + @ExceptionHandler(value = Throwable.class) + public Result defaultErrorHandler(HttpServletRequest request, Throwable throwable) { + log.error("[{}] {} ", request.getMethod(), getUrl(request), throwable); + return Results.failure(); + } + + /** + * 获取请求URL + * @param request {@link HttpServletRequest} + * @return String + */ + private String getUrl(HttpServletRequest request) { + if (!StringUtils.hasText(request.getQueryString())) { + return request.getRequestURI(); + } + return request.getRequestURL() + "?" + request.getQueryString(); + } +} diff --git a/aioj-backend-common/src/main/java/cn/meowrain/aioj/backend/framework/web/Result.java b/aioj-backend-common/src/main/java/cn/meowrain/aioj/backend/framework/web/Result.java new file mode 100644 index 0000000..87e9a2b --- /dev/null +++ b/aioj-backend-common/src/main/java/cn/meowrain/aioj/backend/framework/web/Result.java @@ -0,0 +1,50 @@ +package cn.meowrain.aioj.backend.framework.web; + +import lombok.Data; +import lombok.experimental.Accessors; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 全局返回对象 + */ +@Data +@Accessors(chain = true) +public class Result implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 正确返回码 + * */ + public static final String SUCCESS_CODE = "0"; + /** + * 响应码 + */ + private String code; + /** + * 响应数据 + */ + private T data; + /** + * 响应信息 + */ + private String message; + + /** + * 返回是否是正确响应 + * + * @return boolean + */ + public boolean isSuccess() { + return SUCCESS_CODE.equals(code); + } + + /** + * 返回是否是错误响应 + * @return boolean + */ + public boolean isFail() { + return !isSuccess(); + } +} diff --git a/aioj-backend-common/src/main/java/cn/meowrain/aioj/backend/framework/web/Results.java b/aioj-backend-common/src/main/java/cn/meowrain/aioj/backend/framework/web/Results.java new file mode 100644 index 0000000..8ef3960 --- /dev/null +++ b/aioj-backend-common/src/main/java/cn/meowrain/aioj/backend/framework/web/Results.java @@ -0,0 +1,78 @@ +package cn.meowrain.aioj.backend.framework.web; + +import cn.meowrain.aioj.backend.framework.errorcode.ErrorCode; +import cn.meowrain.aioj.backend.framework.exception.AbstractException; + +import java.util.Optional; + +/** + * 构建响应的工具类 + */ +public final class Results { + /** + * 成功响应,不返回任何信息 + * + * @return {@link Result} + */ + public static Result success() { + return new Result().setCode(Result.SUCCESS_CODE); + } + + /** + * 成功响应 返回数据 + * + * @param data 返回的响应体信息 + * @param 泛型 + * @return {@link Result} + */ + public static Result success(T data) { + return new Result().setCode(Result.SUCCESS_CODE) + .setData(data); + } + + /** + * 客户端请求参数错误 + * @return {@link Result} + */ + public static Result paramsValidFailure() { + return failure(ErrorCode.PARAMS_ERROR.code(), ErrorCode.PARAMS_ERROR.message()); + } + + /** + * 服务端错误默认响应 -- 内部错误 + * + * @return {@link Result} + */ + public static Result failure() { + return new Result().setCode(ErrorCode.SYSTEM_ERROR.code()) + .setMessage(ErrorCode.SYSTEM_ERROR.message()); + } + + /** + * 服务端错误响应 - 接收一个AbstractException,里面定义了错误码和错误信息 + * + * @param exception {@link AbstractException} + * @return {@link Result} + */ + public static Result failure(AbstractException exception) { + String errorCode = Optional.ofNullable(exception.getErrorCode()).orElse(ErrorCode.SYSTEM_ERROR.code()); + String errorMessage = Optional.ofNullable(exception.getMessage()).orElse(ErrorCode.SYSTEM_ERROR.message()); + + return new Result() + .setCode(errorCode) + .setMessage(errorMessage); + } + + /** + * 服务端错误响应,自定义错误码和错误信息 + * + * @param errorCode {@link String} + * @param errorMessage {@link String} + * @return {@link Result} + */ + public static Result failure(String errorCode, String errorMessage) { + return new Result() + .setCode(errorCode) + .setMessage(errorMessage); + } +} diff --git a/aioj-backend-common/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/aioj-backend-common/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..2dc59e3 --- /dev/null +++ b/aioj-backend-common/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +cn.meowrain.aioj.backend.framework.config.WebAutoConfiguration \ No newline at end of file