Files
xiu_b/src/main/java/com/tailbet/config/GlobalExceptionHandler.java
T
2026-08-08 14:56:37 +08:00

209 lines
8.3 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.tailbet.config;
import com.tailbet.common.enums.ErrorCodeEnum;
import com.tailbet.common.exception.BusinessException;
import com.tailbet.model.vo.R;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.MessageSource;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
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 org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import java.util.Locale;
import java.util.stream.Collectors;
/**
* 全局异常处理器
* <p>
* 统一捕获并处理Controller层抛出的各类异常,将异常转换为统一的Result响应格式。
* 处理优先级:具体异常 > 通用异常 > 兜底异常。
* 支持国际化:根据请求头 Accept-Language 自动返回对应语言的错误信息。
* </p>
*
* @author socialapp团队
* @since 1.0.0
*/
@Slf4j
@RestControllerAdvice
@RequiredArgsConstructor
public class GlobalExceptionHandler {
/**
* 国际化消息源,用于将消息 key 解析为对应语言的文本
*/
private final MessageSource messageSource;
/**
* 处理业务异常(最高优先级)
*
* @param e 业务异常
* @param request HTTP请求
* @return 统一错误响应
*/
@ExceptionHandler(BusinessException.class)
@SuppressWarnings({"unchecked", "rawtypes"})
public R<?> handleBusinessException(BusinessException e, HttpServletRequest request) {
// 业务异常的 message 可能是 key,也可能是业务自定义文本,统一走 resolveMessage 处理
String message = e.getMessage();
log.warn("【业务异常】URI: {}, 错误码: {}, 消息: {}",
request.getRequestURI(), e.getCode(), message);
if (e.getData() != null) {
R r = R.fail(e.getCode(), message);
r.setData(e.getData());
return r;
}
return R.fail(e.getCode(), message);
}
/**
* 处理参数校验异常(@Valid / @RequestBody
*
* @param e 方法参数校验异常
* @param request HTTP请求
* @return 统一错误响应
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public R<Void> handleMethodArgumentNotValid(MethodArgumentNotValidException e, HttpServletRequest request) {
String message = e.getBindingResult().getFieldErrors().stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining(", "));
log.warn("【参数校验失败】URI: {}, 错误: {}", request.getRequestURI(), message);
// 参数校验失败的 message 来自注解,直接使用,不走国际化 key 解析
return R.fail(ErrorCodeEnum.PARAM_VALIDATE_ERROR, message);
}
/**
* 处理参数绑定异常(@ModelAttribute
*
* @param e 参数绑定异常
* @param request HTTP请求
* @return 统一错误响应
*/
@ExceptionHandler(BindException.class)
public R<Void> handleBindException(BindException e, HttpServletRequest request) {
String message = e.getFieldErrors().stream()
.map(error -> error.getField() + ": " + error.getDefaultMessage())
.collect(Collectors.joining(", "));
log.warn("【参数绑定失败】URI: {}, 错误: {}", request.getRequestURI(), message);
return R.fail(ErrorCodeEnum.PARAM_VALIDATE_ERROR, message);
}
/**
* 处理约束校验异常(@Validated 方法级别)
*
* @param e 约束校验异常
* @param request HTTP请求
* @return 统一错误响应
*/
@ExceptionHandler(ConstraintViolationException.class)
public R<Void> handleConstraintViolation(ConstraintViolationException e, HttpServletRequest request) {
String message = e.getConstraintViolations().stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.joining(", "));
log.warn("【约束校验失败】URI: {}, 错误: {}", request.getRequestURI(), message);
return R.fail(ErrorCodeEnum.PARAM_VALIDATE_ERROR, message);
}
/**
* 处理缺少请求参数异常
*
* @param e 缺少参数异常
* @param request HTTP请求
* @return 统一错误响应
*/
@ExceptionHandler(MissingServletRequestParameterException.class)
public R<Void> handleMissingParam(MissingServletRequestParameterException e, HttpServletRequest request) {
// 缺少参数的提示信息拼接参数名,不走国际化 key
String message = e.getMessage()
+ ": " + e.getParameterName();
log.warn("【缺少参数】URI: {}, 错误: {}", request.getRequestURI(), message);
return R.fail(ErrorCodeEnum.PARAM_ERROR, message);
}
/**
* 处理请求体不可读异常(请求体缺失、格式错误、缺少必填字段等)
*
* @param e 请求体不可读异常
* @param request HTTP请求
* @return 统一错误响应
*/
@ExceptionHandler(HttpMessageNotReadableException.class)
public R<Void> handleHttpMessageNotReadable(HttpMessageNotReadableException e, HttpServletRequest request) {
// 请求体格式错误,使用国际化后的错误码消息
String message = e.getMessage();
log.warn("【请求体不可读】URI: {}, 错误: {}", request.getRequestURI(), e.getMessage());
return R.fail(ErrorCodeEnum.PARAM_FORMAT_ERROR, message);
}
/**
* 处理参数类型不匹配异常
*
* @param e 参数类型不匹配异常
* @param request HTTP请求
* @return 统一错误响应
*/
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public R<Void> handleTypeMismatch(MethodArgumentTypeMismatchException e, HttpServletRequest request) {
String message = String.format("参数[%s]类型错误,期望类型: %s",
e.getName(), e.getRequiredType() != null ? e.getRequiredType().getSimpleName() : "未知");
log.warn("【参数类型错误】URI: {}, 错误: {}", request.getRequestURI(), message);
return R.fail(ErrorCodeEnum.PARAM_FORMAT_ERROR, message);
}
/**
* 处理非法参数异常
*
* @param e 非法参数异常
* @param request HTTP请求
* @return 统一错误响应
*/
@ExceptionHandler(IllegalArgumentException.class)
public R<Void> handleIllegalArgument(IllegalArgumentException e, HttpServletRequest request) {
log.warn("【非法参数】URI: {}, 错误: {}", request.getRequestURI(), e.getMessage());
return R.fail(ErrorCodeEnum.PARAM_ERROR, e.getMessage());
}
/**
* 处理非法状态异常
*
* @param e 非法状态异常
* @param request HTTP请求
* @return 统一错误响应
*/
@ExceptionHandler(IllegalStateException.class)
public R<Void> handleIllegalState(IllegalStateException e, HttpServletRequest request) {
log.warn("【非法状态】URI: {}, 错误: {}", request.getRequestURI(), e.getMessage());
return R.fail(ErrorCodeEnum.OPERATION_NOT_ALLOWED, e.getMessage());
}
/**
* 兜底异常处理器(最低优先级)
* <p>
* 捕获所有未被前面处理器捕获的异常,避免异常信息直接暴露给客户端。
* </p>
*
* @param e 异常对象
* @param request HTTP请求
* @return 统一错误响应
*/
@ExceptionHandler(Exception.class)
public R<Void> handleException(Exception e, HttpServletRequest request) {
log.error("【系统异常】URI: {}, 错误类型: {}, 消息: {}",
request.getRequestURI(), e.getClass().getName(), e.getMessage(), e);
// 兜底异常使用国际化后的系统繁忙提示
return R.fail(ErrorCodeEnum.INTERNAL_ERROR.getCode(), e.getMessage());
}
}