OpenIMApiClient
This commit is contained in:
@@ -1,26 +1,208 @@
|
||||
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 {
|
||||
|
||||
@ExceptionHandler(RuntimeException.class)
|
||||
public R<?> handleRuntimeException(RuntimeException e) {
|
||||
log.error("业务异常: {}", e.getMessage());
|
||||
return R.fail(e.getMessage());
|
||||
/**
|
||||
* 国际化消息源,用于将消息 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<?> handleException(Exception e) {
|
||||
log.error("系统异常: {}", e.getMessage(), e);
|
||||
return R.fail("系统错误: " + e.getMessage());
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user