增加全局异常处理和业务异常处理
This commit is contained in:
38
src/main/java/co/jp/app/exception/BusinessException.java
Normal file
38
src/main/java/co/jp/app/exception/BusinessException.java
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
127
src/main/java/co/jp/app/exception/GlobalExceptionHandler.java
Normal file
127
src/main/java/co/jp/app/exception/GlobalExceptionHandler.java
Normal file
@ -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<ApiResponse<Object>> handleBusinessException(BusinessException ex) {
|
||||||
|
logger.warn("业务异常: Code={}, Message={}, Detail={}", ex.getResultCode().getCode(), ex.getResultCode().getMessage(), ex.getDetailMessage(), ex);
|
||||||
|
|
||||||
|
ApiResponse<Object> 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<ApiResponse<Object>> 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<ApiResponse<Object>> 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<ApiResponse<Object>> 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<ApiResponse<Object>> handleAccessDeniedException(AccessDeniedException ex) {
|
||||||
|
// logger.warn("权限不足: {}", ex.getMessage(), ex);
|
||||||
|
// return new ResponseEntity<>(ApiResponse.fail(ResultCode.FORBIDDEN), HttpStatus.FORBIDDEN);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 其他异常
|
||||||
|
@ExceptionHandler(Exception.class)
|
||||||
|
public ResponseEntity<ApiResponse<Object>> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user