GlobalExceptionHandler.java 8.06 KB
package com.xly.exception;

import com.xly.constant.ErrorCode;
import com.xly.exception.dto.BusinessException;
import com.xly.tts.bean.TTSResponseDTO;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Order;
import org.springframework.core.Ordered;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.converter.HttpMessageConversionException;
import org.springframework.jdbc.BadSqlGrammarException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Slf4j
@RestControllerAdvice
@Order(Ordered.HIGHEST_PRECEDENCE)
public class GlobalExceptionHandler {


    /**
     * 处理所有异常
     */
    @ExceptionHandler(Exception.class)
    public TTSResponseDTO handleException(HttpServletRequest request, Exception e) {
        log.error("请求地址: {}, 全局异常: ", request.getRequestURI(), e);

        // 获取请求信息
        String method = request.getMethod();
        String uri = request.getRequestURI();
        String queryString = request.getQueryString();

        // 记录异常日志(可存入数据库)
//        ErrorLog errorLog = ErrorLog.builder()
//                .method(method)
//                .uri(uri)
//                .queryString(queryString)
//                .exceptionName(e.getClass().getName())
//                .exceptionMessage(e.getMessage())
//                .stackTrace(ExceptionUtils.getStackTrace(e))
//                .ip(IpUtil.getIpAddr(request))
//                .userAgent(request.getHeader("User-Agent"))
//                .timestamp(new Date())
//                .build();
//
//        // 异步保存错误日志
//        saveErrorLogAsync(errorLog);

        // 生产环境隐藏详细错误信息
        if (isProduction()) {
            return TTSResponseDTO.error(ErrorCode.SYSTEM_ERROR);
        } else {
            // 开发环境返回详细错误信息
            Map<String, Object> errorDetail = new HashMap<>();
            errorDetail.put("exception", e.getClass().getName());
            errorDetail.put("message", e.getMessage());
            errorDetail.put("path", uri);
            errorDetail.put("method", method);
            errorDetail.put("timestamp", new Date());
            return TTSResponseDTO.error(ErrorCode.SYSTEM_ERROR.getCode(), "系统异常: " + e.getMessage());
        }
    }

    /**
     * 处理业务异常
     */
    @ExceptionHandler(BusinessException.class)
    public TTSResponseDTO handleBusinessException(BusinessException e) {
        log.warn("业务异常: {}", e.getMessage());
        return TTSResponseDTO.error(e.getCode(), e.getMessage());
    }

    /**
     * 处理数据校验异常
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public TTSResponseDTO handleMethodArgumentNotValidException(
            MethodArgumentNotValidException e) {
        List<String> errors = e.getBindingResult().getFieldErrors()
                .stream()
                .map(error -> error.getField() + ": " + error.getDefaultMessage())
                .collect(Collectors.toList());

        String message = String.join("; ", errors);
        log.warn("参数校验失败: {}", message);
        return TTSResponseDTO.error(ErrorCode.PARAM_ERROR.getCode(), message);
    }

    /**
     * 处理约束违反异常
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public TTSResponseDTO handleConstraintViolationException(
            ConstraintViolationException e) {
        List<String> errors = e.getConstraintViolations()
                .stream()
                .map(violation ->
                        violation.getPropertyPath() + ": " + violation.getMessage())
                .collect(Collectors.toList());

        String message = String.join("; ", errors);
        log.warn("参数约束违反: {}", message);
        return TTSResponseDTO.error(ErrorCode.PARAM_ERROR.getCode(), message);
    }

    /**
     * 处理Http请求方法不支持异常
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public TTSResponseDTO handleHttpRequestMethodNotSupportedException(
            HttpRequestMethodNotSupportedException e) {
        log.warn("HTTP方法不支持: {} {}", e.getMethod(), e.getSupportedHttpMethods());
        return TTSResponseDTO.error(ErrorCode.BAD_REQUEST.getCode(),
                "请求方法 '" + e.getMethod() + "' 不支持");
    }

    /**
     * 处理媒体类型不支持异常
     */
    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    public TTSResponseDTO handleHttpMediaTypeNotSupportedException(
            HttpMediaTypeNotSupportedException e) {
        log.warn("媒体类型不支持: {}", e.getContentType());
        return TTSResponseDTO.error(ErrorCode.BAD_REQUEST.getCode(),
                "媒体类型不支持: " + e.getContentType());
    }

    /**
     * 处理404异常
     */
    @ExceptionHandler(NoHandlerFoundException.class)
    public TTSResponseDTO handleNoHandlerFoundException(
            NoHandlerFoundException e) {
        log.warn("接口不存在: {} {}", e.getHttpMethod(), e.getRequestURL());
        return TTSResponseDTO.error(ErrorCode.NOT_FOUND.getCode(),
                "接口不存在: " + e.getRequestURL());
    }

    /**
     * 处理类型转换异常
     */
    @ExceptionHandler(HttpMessageConversionException.class)
    public TTSResponseDTO handleHttpMessageConversionException(
            HttpMessageConversionException e) {
        log.warn("HTTP消息转换异常: {}", e.getMessage());
        return TTSResponseDTO.error(ErrorCode.PARAM_ERROR.getCode(),
                "参数格式错误");
    }

    /**
     * 处理数据库异常
     */
    @ExceptionHandler(DataAccessException.class)
    public TTSResponseDTO handleDataAccessException(DataAccessException e) {
        log.error("数据库异常: {}", e.getMessage(), e);

        // 根据具体异常类型细化处理
        if (e instanceof DuplicateKeyException) {
            return TTSResponseDTO.error(ErrorCode.DATA_EXISTS.getCode(),
                    "数据已存在");
        } else if (e instanceof DataIntegrityViolationException) {
            return TTSResponseDTO.error(ErrorCode.DATA_ERROR.getCode(),
                    "数据完整性违反");
        } else if (e instanceof BadSqlGrammarException) {
            return TTSResponseDTO.error(ErrorCode.DB_ERROR.getCode(),
                    "SQL语法错误");
        }

        return TTSResponseDTO.error(ErrorCode.DB_ERROR);
    }

    /**
     * 处理JWT异常
     */
//    @ExceptionHandler(JwtException.class)
//    public TTSResponseDTO handleJwtException(JwtException e) {
//        log.warn("JWT异常: {}", e.getMessage());
//        return ApiResult.error(ErrorCode.UNAUTHORIZED.getCode(),
//                "Token无效或已过期");
//    }

//    /**
//     * 异步保存错误日志
//     */
//    @Async
//    public void saveErrorLogAsync(ErrorLog errorLog) {
//        try {
//            // 保存到数据库或日志文件
//            errorLogService.save(errorLog);
//        } catch (Exception ex) {
//            log.error("保存错误日志失败", ex);
//        }
//    }

    /***
     * @Author 钱豹
     * @Date 23:21 2026/1/30
     * @Param []
     * @return boolean
     * @Description 判断是否生产环境还是开发环境 应该根据yml 配置来  后期拓展
     **/
    private boolean isProduction() {
        //TODO
        String profile = "dev";
        return "prod".equals(profile);
    }
}