package com.tcm.training.admin.common; import cn.dev33.satoken.exception.NotLoginException; import com.fasterxml.jackson.databind.exc.InvalidFormatException; import com.tcm.training.admin.app.auth.exception.InviteCodeRequiredException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.validation.BindException; import org.springframework.validation.FieldError; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.format.DateTimeParseException; @RestControllerAdvice public class GlobalExceptionHandler { private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); private static final String DEFAULT_SERVER_ERROR_MESSAGE = "服务器开小差啦,请稍后重试"; @ExceptionHandler(NotLoginException.class) public ApiResponse handleNotLogin(NotLoginException e, HttpServletRequest request) { log.warn("未登录访问 [{} {}]: {}", request.getMethod(), request.getRequestURI(), e.getMessage()); return ApiResponse.fail(401, "未登录"); } @ExceptionHandler({MethodArgumentNotValidException.class, BindException.class}) public ApiResponse handleValidate(Exception e, HttpServletRequest request) { log.warn("请求参数校验失败 [{} {}]", request.getMethod(), request.getRequestURI(), e); if (e instanceof MethodArgumentNotValidException) { FieldError fieldError = ((MethodArgumentNotValidException) e).getBindingResult().getFieldError(); if (fieldError != null && fieldError.getDefaultMessage() != null) { return ApiResponse.fail(400, fieldError.getDefaultMessage()); } } if (e instanceof BindException) { FieldError fieldError = ((BindException) e).getBindingResult().getFieldError(); if (fieldError != null && fieldError.getDefaultMessage() != null) { return ApiResponse.fail(400, fieldError.getDefaultMessage()); } } return ApiResponse.fail(400, "参数错误"); } @ExceptionHandler(InviteCodeRequiredException.class) public ApiResponse handleInviteCodeRequired(InviteCodeRequiredException e, HttpServletRequest request) { log.warn("第三方授权缺少邀请码 [{} {}]: {}", request.getMethod(), request.getRequestURI(), e.getMessage()); return ApiResponse.fail(506, e.getMessage()); } @ExceptionHandler(IllegalArgumentException.class) public ApiResponse handleIllegalArgument(IllegalArgumentException e, HttpServletRequest request) { log.warn("业务参数异常 [{} {}]: {}", request.getMethod(), request.getRequestURI(), e.getMessage(), e); return ApiResponse.fail(400, e.getMessage()); } @ExceptionHandler(IllegalStateException.class) public ApiResponse handleIllegalState(IllegalStateException e, HttpServletRequest request) { log.warn("业务状态异常 [{} {}]: {}", request.getMethod(), request.getRequestURI(), e.getMessage(), e); return ApiResponse.fail(500, resolveClientMessage(e.getMessage())); } @ExceptionHandler(HttpMessageNotReadableException.class) public ApiResponse handleHttpMessageNotReadable(HttpMessageNotReadableException e, HttpServletRequest request) { log.warn("请求体解析失败 [{} {}]", request.getMethod(), request.getRequestURI(), e); return ApiResponse.fail(400, resolveReadableMessage(e)); } @ExceptionHandler(IOException.class) public ApiResponse handleIO(IOException e, HttpServletRequest request) { log.error("IO异常 [{} {}]", request.getMethod(), request.getRequestURI(), e); return ApiResponse.fail(500, "文件操作失败: " + e.getMessage()); } @ExceptionHandler(Exception.class) public ApiResponse handleException(Exception e, HttpServletRequest request) { log.error("未捕获异常 [{} {}]", request.getMethod(), request.getRequestURI(), e); return ApiResponse.fail(500, DEFAULT_SERVER_ERROR_MESSAGE); } private String resolveClientMessage(String message) { if (message == null || message.trim().isEmpty()) { return DEFAULT_SERVER_ERROR_MESSAGE; } return message; } private String resolveReadableMessage(Throwable throwable) { Throwable current = throwable; while (current != null) { if (current instanceof InvalidFormatException) { String message = resolveTimeFormatMessage(((InvalidFormatException) current).getTargetType()); if (message != null) { return message; } } if (current instanceof DateTimeParseException) { return "时间格式错误,请使用 " + DateTimes.LOCAL_DATE_TIME_PATTERN + " / " + DateTimes.LOCAL_DATE_PATTERN + " / " + DateTimes.LOCAL_TIME_PATTERN; } current = current.getCause(); } return "请求参数格式错误"; } private String resolveTimeFormatMessage(Class targetType) { if (targetType == null) { return null; } if (LocalDateTime.class.isAssignableFrom(targetType)) { return "时间格式错误,需 " + DateTimes.LOCAL_DATE_TIME_PATTERN; } if (LocalDate.class.isAssignableFrom(targetType)) { return "日期格式错误,需 " + DateTimes.LOCAL_DATE_PATTERN; } if (LocalTime.class.isAssignableFrom(targetType)) { return "时间格式错误,需 " + DateTimes.LOCAL_TIME_PATTERN; } return null; } }