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 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 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 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); } }