Files
baiyue-firstleg/common-web/src/main/java/com/baiyue/common/web/GlobalExceptionHandler.java
T
2026-08-27 14:53:27 +08:00

167 lines
8.0 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package com.baiyue.common.web;
import com.baiyue.common.core.api.ApiResponse;
import com.baiyue.common.core.api.ResultCode;
import com.baiyue.common.core.exception.BizException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintViolationException;
import io.micrometer.core.instrument.MeterRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.dao.CannotAcquireLockException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingRequestHeaderException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.resource.NoResourceFoundException;
import org.springframework.web.server.ResponseStatusException;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Servlet Web 全局异常处理器,将业务异常、参数校验异常和系统异常转换为统一 API 响应。
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@Autowired(required = false)
private MeterRegistry meterRegistry;
@ExceptionHandler(BizException.class)
public ResponseEntity<ApiResponse<Void>> handleBusiness(BizException exception, HttpServletRequest request) {
return failure(exception.getResultCode(), exception.getMessage(), request);
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ApiResponse<Map<String, String>>> handleValidation(
MethodArgumentNotValidException exception, HttpServletRequest request) {
return validationResponse(fieldErrors(exception.getBindingResult().getFieldErrors()), request);
}
@ExceptionHandler(BindException.class)
public ResponseEntity<ApiResponse<Map<String, String>>> handleBinding(
BindException exception, HttpServletRequest request) {
return validationResponse(fieldErrors(exception.getBindingResult().getFieldErrors()), request);
}
@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<ApiResponse<Map<String, String>>> handleConstraint(
ConstraintViolationException exception, HttpServletRequest request) {
Map<String, String> errors = new LinkedHashMap<>();
exception.getConstraintViolations().forEach(violation ->
errors.put(violation.getPropertyPath().toString(), violation.getMessage()));
return validationResponse(errors, request);
}
@ExceptionHandler({HttpMessageNotReadableException.class, MissingRequestHeaderException.class,
MissingServletRequestParameterException.class, MethodArgumentTypeMismatchException.class})
public ResponseEntity<ApiResponse<Void>> handleBadRequest(Exception exception, HttpServletRequest request) {
return failure(ResultCode.BAD_REQUEST, ResultCode.BAD_REQUEST.message(), request);
}
@ExceptionHandler(AuthenticationException.class)
public ResponseEntity<ApiResponse<Void>> handleAuthentication(
AuthenticationException exception, HttpServletRequest request) {
return failure(ResultCode.UNAUTHORIZED, ResultCode.UNAUTHORIZED.message(), request);
}
@ExceptionHandler(AccessDeniedException.class)
public ResponseEntity<ApiResponse<Void>> handleAccessDenied(
AccessDeniedException exception, HttpServletRequest request) {
return failure(ResultCode.FORBIDDEN, ResultCode.FORBIDDEN.message(), request);
}
@ExceptionHandler(ResponseStatusException.class)
public ResponseEntity<ApiResponse<Void>> handleResponseStatus(
ResponseStatusException exception, HttpServletRequest request) {
ResultCode code = resultCode(exception.getStatusCode().value());
String message = exception.getReason() == null || exception.getReason().isBlank()
? code.message() : exception.getReason();
return failure(code, message, request);
}
@ExceptionHandler(NoResourceFoundException.class)
public ResponseEntity<ApiResponse<Void>> handleNoResourceFound(
NoResourceFoundException exception, HttpServletRequest request) {
return failure(ResultCode.NOT_FOUND, ResultCode.NOT_FOUND.message(), request);
}
@ExceptionHandler(CannotAcquireLockException.class)
public ResponseEntity<ApiResponse<Void>> handleCannotAcquireLock(
CannotAcquireLockException exception, HttpServletRequest request) {
log.warn("Database lock acquisition conflict requestId={} traceId={}",
CorrelationContext.requestId(request), CorrelationContext.traceId(request));
return failure(ResultCode.CONFLICT, "资源正在处理中,请稍后重试", request);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ApiResponse<Void>> handleUnexpected(Exception exception, HttpServletRequest request) {
log.error("Unhandled request failure requestId={} traceId={}",
CorrelationContext.requestId(request), CorrelationContext.traceId(request), exception);
return failure(ResultCode.INTERNAL_ERROR, ResultCode.INTERNAL_ERROR.message(), request);
}
private Map<String, String> fieldErrors(Iterable<FieldError> fieldErrors) {
Map<String, String> errors = new LinkedHashMap<>();
fieldErrors.forEach(error -> errors.putIfAbsent(error.getField(), error.getDefaultMessage()));
return errors;
}
private ResponseEntity<ApiResponse<Map<String, String>>> validationResponse(
Map<String, String> errors, HttpServletRequest request) {
ResultCode code = ResultCode.VALIDATION_FAILED;
ApiResponse<Map<String, String>> body = ApiResponse.of(code, validationMessage(errors), errors,
CorrelationContext.requestId(request), CorrelationContext.traceId(request));
recordFailure(code);
return ResponseEntity.ok(body);
}
private String validationMessage(Map<String, String> errors) {
if (errors.isEmpty()) {
return ResultCode.VALIDATION_FAILED.message();
}
return errors.entrySet().stream()
.map(entry -> entry.getKey() + ": " + entry.getValue())
.collect(java.util.stream.Collectors.joining(";"));
}
private ResponseEntity<ApiResponse<Void>> failure(ResultCode code, String message,
HttpServletRequest request) {
ApiResponse<Void> body = ApiResponse.failure(code, message,
CorrelationContext.requestId(request), CorrelationContext.traceId(request));
recordFailure(code);
return ResponseEntity.ok(body);
}
private void recordFailure(ResultCode code) {
if (meterRegistry != null) {
meterRegistry.counter("baiyue.api.business.failures", "code", code.code()).increment();
}
}
private ResultCode resultCode(int status) {
return switch (status) {
case 400 -> ResultCode.BAD_REQUEST;
case 401 -> ResultCode.UNAUTHORIZED;
case 403 -> ResultCode.FORBIDDEN;
case 404 -> ResultCode.NOT_FOUND;
case 409 -> ResultCode.CONFLICT;
case 422 -> ResultCode.VALIDATION_FAILED;
case 429 -> ResultCode.TOO_MANY_REQUESTS;
case 503 -> ResultCode.SERVICE_UNAVAILABLE;
default -> ResultCode.INTERNAL_ERROR;
};
}
}