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; } } }