From 158a93d60842292b39e2e26a9700507d4382ec59 Mon Sep 17 00:00:00 2001 From: admin Date: Tue, 13 May 2025 12:41:41 +0900 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=85=A8=E5=B1=80=E5=BC=82?= =?UTF-8?q?=E5=B8=B8=E5=A4=84=E7=90=86=E5=92=8C=E4=B8=9A=E5=8A=A1=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 --- .../jp/app/exception/BusinessException.java | 38 ++++++ .../app/exception/GlobalExceptionHandler.java | 127 ++++++++++++++++++ 2 files changed, 165 insertions(+) create mode 100644 src/main/java/co/jp/app/exception/BusinessException.java create mode 100644 src/main/java/co/jp/app/exception/GlobalExceptionHandler.java diff --git a/src/main/java/co/jp/app/exception/BusinessException.java b/src/main/java/co/jp/app/exception/BusinessException.java new file mode 100644 index 0000000..8bdf825 --- /dev/null +++ b/src/main/java/co/jp/app/exception/BusinessException.java @@ -0,0 +1,38 @@ +package co.jp.app.exception; + +import co.jp.app.common.ResultCode; + +public class BusinessException extends RuntimeException { + + private final ResultCode resultCode; + private final String detailMessage; // 附加详细信息 + + public BusinessException(ResultCode resultCode) { + super(resultCode.getMessage()); + this.resultCode = resultCode; + this.detailMessage = null; + } + + // 有详细信息的构造函数 + public BusinessException(ResultCode resultCode, String detailMessage) { + super(detailMessage != null && !detailMessage.isEmpty() ? detailMessage : resultCode.getMessage()); + this.resultCode = resultCode; + this.detailMessage = detailMessage; + } + + // 无详细信息的构造函数 + public BusinessException(ResultCode resultCode, Throwable cause) { + super(resultCode.getMessage(), cause); + this.resultCode = resultCode; + this.detailMessage = null; + } + + public ResultCode getResultCode() { + return resultCode; + } + + public String getDetailMessage() { + return detailMessage; + } + +} \ No newline at end of file diff --git a/src/main/java/co/jp/app/exception/GlobalExceptionHandler.java b/src/main/java/co/jp/app/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..0482aaf --- /dev/null +++ b/src/main/java/co/jp/app/exception/GlobalExceptionHandler.java @@ -0,0 +1,127 @@ +package co.jp.app.exception; + +import co.jp.app.common.ApiResponse; +import co.jp.app.common.ResultCode; +import co.jp.app.exception.BusinessException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.validation.BindException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.MissingServletRequestParameterException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import java.util.stream.Collectors; + +@RestControllerAdvice +public class GlobalExceptionHandler { + + // 日志记录器 + private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class); + + // 业务异常 + @ExceptionHandler(BusinessException.class) + public ResponseEntity> handleBusinessException(BusinessException ex) { + logger.warn("业务异常: Code={}, Message={}, Detail={}", ex.getResultCode().getCode(), ex.getResultCode().getMessage(), ex.getDetailMessage(), ex); + + ApiResponse body; + + if (ex.getDetailMessage() != null && !ex.getDetailMessage().isEmpty()) { + body = ApiResponse.fail(ex.getResultCode(), ex.getDetailMessage()); + } else { + body = ApiResponse.fail(ex.getResultCode()); + } + + HttpStatus httpStatus = determineHttpStatusFromResultCode(ex.getResultCode()); + return new ResponseEntity<>(body, httpStatus); + } + + // 参数校验异常 + @ExceptionHandler({MethodArgumentNotValidException.class, BindException.class}) + public ResponseEntity> handleValidationExceptions(BindException ex) { // BindException 是 MethodArgumentNotValidException 的父类 + String errorMessage = ex.getBindingResult().getFieldErrors().stream() + .map(fieldError -> fieldError.getField() + ": " + fieldError.getDefaultMessage()) + .collect(Collectors.joining("; ")); + logger.warn("参数校验失败: {}", errorMessage, ex); + return new ResponseEntity<>(ApiResponse.fail(ResultCode.VALIDATION_ERROR, errorMessage), HttpStatus.BAD_REQUEST); + } + + // 参数缺失异常 + @ExceptionHandler(MissingServletRequestParameterException.class) + public ResponseEntity> handleMissingServletRequestParameterException(MissingServletRequestParameterException ex) { + String message = "请求参数 '" + ex.getParameterName() + "' 不能为空"; + logger.warn(message, ex); + return new ResponseEntity<>(ApiResponse.fail(ResultCode.BAD_REQUEST, message), HttpStatus.BAD_REQUEST); + } + + // Spring Security 用户认证异常 + @ExceptionHandler(AuthenticationException.class) + public ResponseEntity> handleAuthenticationException(AuthenticationException ex) { + logger.warn("认证失败: {}", ex.getMessage(), ex); + + if (ex instanceof BadCredentialsException) { + + return new ResponseEntity<>(ApiResponse.fail(ResultCode.USER_INVALID_CREDENTIALS), HttpStatus.UNAUTHORIZED); + } else if (ex instanceof UsernameNotFoundException) { + + return new ResponseEntity<>(ApiResponse.fail(ResultCode.USER_INVALID_CREDENTIALS, "用户名或密码错误(用户不存在)"), HttpStatus.UNAUTHORIZED); + } + + return new ResponseEntity<>(ApiResponse.fail(ResultCode.UNAUTHORIZED, ex.getMessage()), HttpStatus.UNAUTHORIZED); + } + + // Spring Security 权限异常 +// @ExceptionHandler(AccessDeniedException.class) +// public ResponseEntity> handleAccessDeniedException(AccessDeniedException ex) { +// logger.warn("权限不足: {}", ex.getMessage(), ex); +// return new ResponseEntity<>(ApiResponse.fail(ResultCode.FORBIDDEN), HttpStatus.FORBIDDEN); +// } + + // 其他异常 + @ExceptionHandler(Exception.class) + public ResponseEntity> handleAllUncaughtException(Exception ex) { + logger.error("发生未捕获的服务器内部错误!", ex); + + return new ResponseEntity<>(ApiResponse.fail(ResultCode.INTERNAL_SERVER_ERROR), HttpStatus.INTERNAL_SERVER_ERROR); + } + + private HttpStatus determineHttpStatusFromResultCode(ResultCode resultCode) { + if (resultCode == null) return HttpStatus.INTERNAL_SERVER_ERROR; + + switch (resultCode) { + case SUCCESS: + return HttpStatus.OK; + case UNAUTHORIZED: + case USER_INVALID_CREDENTIALS: + case USER_TOKEN_INVALID: + case USER_TOKEN_EXPIRED: + return HttpStatus.UNAUTHORIZED; + case FORBIDDEN: + return HttpStatus.FORBIDDEN; + case NOT_FOUND: + case USER_ACCOUNT_NOT_FOUND: + case USER_PROFILE_NOT_FOUND: + return HttpStatus.NOT_FOUND; + case CONFLICT: + case USER_EMAIL_ALREADY_EXISTS: + return HttpStatus.CONFLICT; + case BAD_REQUEST: + case VALIDATION_ERROR: + case USER_PASSWORD_TOO_SHORT: + return HttpStatus.BAD_REQUEST; + default: + if (resultCode.getCode() >= 2000 && resultCode.getCode() < 3000) { + return HttpStatus.INTERNAL_SERVER_ERROR; + } + + return HttpStatus.BAD_REQUEST; + } + } +} \ No newline at end of file