GlobalExceptionHandler.java 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  1. package com.backendsys.exception;
  2. import cn.hutool.core.convert.Convert;
  3. import cn.hutool.core.date.DateUtil;
  4. import com.backendsys.modules.dingtalk.utils.DingTalkUtil;
  5. import com.fasterxml.jackson.databind.ObjectMapper;
  6. import com.backendsys.utils.response.Result;
  7. import com.backendsys.utils.response.ResultEnum;
  8. import com.google.common.collect.Lists;
  9. import jakarta.servlet.http.HttpServletRequest;
  10. import jakarta.validation.ConstraintViolation;
  11. import jakarta.validation.ConstraintViolationException;
  12. import lombok.extern.slf4j.Slf4j;
  13. import org.apache.ibatis.exceptions.PersistenceException;
  14. import org.apache.ibatis.exceptions.TooManyResultsException;
  15. import org.mybatis.spring.MyBatisSystemException;
  16. import org.springframework.beans.factory.annotation.Autowired;
  17. import org.springframework.core.MethodParameter;
  18. import org.springframework.dao.DuplicateKeyException;
  19. import org.springframework.data.redis.RedisConnectionFailureException;
  20. import org.springframework.http.MediaType;
  21. import org.springframework.http.converter.HttpMessageConverter;
  22. import org.springframework.http.converter.HttpMessageNotReadableException;
  23. import org.springframework.http.server.ServerHttpRequest;
  24. import org.springframework.http.server.ServerHttpResponse;
  25. import org.springframework.security.access.AccessDeniedException;
  26. import org.springframework.validation.BindingResult;
  27. import org.springframework.web.HttpRequestMethodNotSupportedException;
  28. import org.springframework.web.bind.MethodArgumentNotValidException;
  29. import org.springframework.web.bind.MissingServletRequestParameterException;
  30. import org.springframework.web.bind.annotation.ExceptionHandler;
  31. import org.springframework.web.bind.annotation.RestControllerAdvice;
  32. import org.springframework.web.context.request.RequestContextHolder;
  33. import org.springframework.web.context.request.ServletRequestAttributes;
  34. import org.springframework.web.servlet.NoHandlerFoundException;
  35. import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
  36. import javax.security.auth.login.LoginException;
  37. import java.util.*;
  38. import java.util.concurrent.CompletableFuture;
  39. /**
  40. * https://juejin.cn/post/6844903902811275278
  41. * https://juejin.cn/post/7132747726965440520
  42. * 待看
  43. */
  44. @Slf4j
  45. @RestControllerAdvice
  46. public class GlobalExceptionHandler implements ResponseBodyAdvice<Object> {
  47. @Autowired
  48. private ObjectMapper objectMapper;
  49. /**
  50. * 是否开启功能 true:开启
  51. */
  52. @Override
  53. public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
  54. return true;
  55. }
  56. /**
  57. * 返回结果拦截器 (可以修改响应体的逻辑,或添加其他自定义逻辑)
  58. */
  59. @Override
  60. public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
  61. // System.out.println("beforeBodyWrite:");
  62. // System.out.println(o);
  63. // if(o instanceof String){
  64. // return o;
  65. // try {
  66. // return objectMapper.writeValueAsString(Result.success(o));
  67. // } catch (JsonProcessingException e) {
  68. // e.printStackTrace();
  69. // }
  70. // }
  71. // //返回类型是否已经封装
  72. // if(o instanceof Result){
  73. // return Result.success(o);
  74. // }
  75. return o;
  76. }
  77. // -- 记录当前 访问URL、访问人IP --------------------------------------------------
  78. private static void printRequestInfo() {
  79. // 获取请求的URL
  80. ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
  81. if (attributes != null) {
  82. HttpServletRequest request = attributes.getRequest();
  83. if (request != null) {
  84. String method = request.getMethod();
  85. String url = request.getRequestURL().toString();
  86. String ip = request.getRemoteAddr();
  87. // 记录URL和IP地址
  88. log.warn("[" + method + "] " + url + ", (" + ip + ")");
  89. }
  90. }
  91. }
  92. // -- 自定义异常输出结构 (警告) ----------------------------------------------------
  93. private static void printWarnException(Exception e) {
  94. printRequestInfo();
  95. log.warn(e.getClass().getName() + ": " + e.getMessage());
  96. }
  97. // -- 自定义异常输出结构 (错误) ----------------------------------------------------
  98. private static void printErrorException(Exception e, Boolean isPrintStack) {
  99. log.error("========================================================================");
  100. // printRequestInfo();
  101. // 获取请求的URL
  102. ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
  103. if (attributes != null) {
  104. HttpServletRequest request = attributes.getRequest();
  105. if (request != null) {
  106. String url = request.getRequestURL().toString();
  107. String ip = request.getRemoteAddr();
  108. String method = request.getMethod();
  109. String createTime = DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss");
  110. String serverName = request.getServerName();
  111. // 访问根目录报404也会触发此错误提示
  112. // // --- 向钉钉发送消息 -----------------------------------
  113. // // (本地环境不触发)
  114. // if (!"localhost".equals(serverName)) {
  115. // // 创建一个 CompletableFuture 来执行异步任务
  116. // CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
  117. // try {
  118. // String message = "【系统异常】:" + e.getMessage() + "\n" +
  119. // "【异常接口】:" + url + "\n" +
  120. // "【异常堆栈】:" + Convert.toStr(e.getStackTrace()).substring(0, 400) + "\n" +
  121. // "【异常时间】:" + createTime + "\n" +
  122. // "【异常来源】:" + ip;
  123. // DingTalkUtil.sendTextMsg(message, Lists.newArrayList(), Lists.newLinkedList(), false);
  124. // } catch (Exception ex) {
  125. // System.out.println("钉钉群消息发送异常,异常原因:" + ex.getMessage());
  126. // log.error(ex.getMessage());
  127. // }
  128. // });
  129. // }
  130. // // ---------------------------------------------------
  131. // 记录URL和IP地址
  132. log.error("[" + method + "] " + url + ", (" + ip + ")");
  133. }
  134. }
  135. // 记录 当前异常类的class名称、异常消息
  136. log.error(e.getMessage() + " (" + e.getClass().getName() + ")");
  137. // 记录异常的详细信息
  138. if (isPrintStack) {
  139. log.error(Convert.toStr(e.getStackTrace()));
  140. e.printStackTrace();
  141. }
  142. log.error("========================================================================");
  143. }
  144. private static void printErrorException(Exception e) {
  145. printErrorException(e, false);
  146. }
  147. // -----------------------------------------------------------------------
  148. /**
  149. * 异常 参数错误
  150. */
  151. @ExceptionHandler(ConstraintViolationException.class)
  152. public Result handleConstraintViolationException(ConstraintViolationException e) {
  153. System.out.println("****** ConstraintViolationException.class: ******");
  154. printErrorException(e);
  155. Set<ConstraintViolation<?>> constraintViolations = e.getConstraintViolations();
  156. Iterator<ConstraintViolation<?>> iterator = constraintViolations.iterator();
  157. List<String> msgList = new ArrayList<>();
  158. while (iterator.hasNext()) {
  159. ConstraintViolation<?> cvl = iterator.next();
  160. msgList.add(cvl.getMessageTemplate());
  161. }
  162. System.out.println(msgList.get(0));
  163. log.error(msgList.get(0));
  164. return Result.error(ResultEnum.PARAMETER_EXCEPTION.getCode(), msgList.get(0));
  165. }
  166. /**
  167. * 参数校验异常 (Validate)
  168. */
  169. // 未测试
  170. @ExceptionHandler(MethodArgumentNotValidException.class)
  171. public Result handlerMethodArgumentException(MethodArgumentNotValidException e){
  172. System.out.println("****** MethodArgumentNotValidException.class: ******");
  173. printWarnException(e);
  174. return Result.error(
  175. ResultEnum.PARAMETER_EXCEPTION.getCode(),
  176. e.getBindingResult().getAllErrors().get(0).getDefaultMessage(),
  177. "Field: " + e.getBindingResult().getFieldError().getField()
  178. );
  179. }
  180. @ExceptionHandler(MissingServletRequestParameterException.class)
  181. public Result handleMissingServletRequestParameterException(MissingServletRequestParameterException e) {
  182. System.out.println("****** MissingServletRequestParameterException.class: ******");
  183. printErrorException(e, true);
  184. return Result.error(
  185. ResultEnum.PARAMETER_EXCEPTION.getCode(),
  186. "缺少参数 " + e.getParameterName() + " 或类型不匹配 (" + e.getParameterType() + ")",
  187. e.getMessage()
  188. );
  189. }
  190. /**
  191. * 参数类型异常
  192. */
  193. @ExceptionHandler(NumberFormatException.class)
  194. public Result handleNumberFormatException(NumberFormatException e) {
  195. System.out.println("****** NumberFormatException.class: ******");
  196. printErrorException(e, true);
  197. return Result.error(ResultEnum.PARAMETER_EXCEPTION.getCode(), e.getMessage());
  198. }
  199. /**
  200. * 自定义异常类
  201. * throw new CustException("错误提示")
  202. */
  203. @ExceptionHandler(CustException.class)
  204. public Result handleCustException(CustException e) {
  205. System.out.println("****** CustException.class: ******");
  206. printWarnException(e);
  207. // printErrorException(e);
  208. return Result.error(e.getErrorCode() != null ? e.getErrorCode() : ResultEnum.PARAMETER_EXCEPTION.getCode(), e.getMessage(), e.getErrorObject());
  209. }
  210. /**
  211. * 自定义异常类 (仅返回文本)
  212. */
  213. @ExceptionHandler(CustExceptionSimple.class)
  214. public String handleCustExceptionSimple(CustExceptionSimple e) {
  215. System.out.println("****** CustExceptionSimple.class: ******");
  216. printErrorException(e);
  217. return e.getMessage();
  218. }
  219. /**
  220. * MyBatis异常
  221. */
  222. @ExceptionHandler(TooManyResultsException.class)
  223. public Result handleTooManyResultsException(TooManyResultsException e) {
  224. System.out.println("****** TooManyResultsException.class: ******");
  225. printErrorException(e, true);
  226. return Result.error(ResultEnum.SERVICE_EXCEPTION.getCode(), e.getMessage());
  227. }
  228. /**
  229. * 登录异常
  230. */
  231. @ExceptionHandler(LoginException.class)
  232. public Result handleLoginException(LoginException e) {
  233. System.out.println("****** LoginException.class: ******");
  234. printErrorException(e, true);
  235. return Result.error(ResultEnum.AUTH_ERROR.getCode(), e.getMessage());
  236. }
  237. /**
  238. * 权限异常
  239. */
  240. @ExceptionHandler(AccessDeniedException.class)
  241. public Result handleAccessDeniedException(AccessDeniedException e) {
  242. System.out.println("****** AccessDeniedException.class: ******");
  243. printWarnException(e);
  244. return Result.error(ResultEnum.AUTH_ROLE_ERROR.getCode(), ResultEnum.AUTH_ROLE_ERROR.getMessage());
  245. }
  246. /**
  247. * 接口不存在 异常
  248. */
  249. @ExceptionHandler(NoHandlerFoundException.class)
  250. public Result handleNoHandlerFoundException(NoHandlerFoundException e) {
  251. System.out.println("****** NoHandlerFoundException.class: ******");
  252. printErrorException(e, true);
  253. return Result.error(ResultEnum.INTERNAL_ERROR.getCode(), e.getMessage());
  254. }
  255. /**
  256. * Mybatis 异常抛出
  257. */
  258. @ExceptionHandler(MyBatisSystemException.class)
  259. public Result handleMyBatisSystemException(MyBatisSystemException e, BindingResult bindingResult) { // , BindingResult bindingResult
  260. System.out.println("****** MyBatisSystemException.class: ******");
  261. printErrorException(e, true);
  262. Throwable cause = e.getCause();
  263. if (cause instanceof PersistenceException) {
  264. Throwable sqlException = cause.getCause();
  265. if (sqlException != null ) {
  266. if (sqlException.getMessage() == "argument type mismatch") {
  267. System.out.println(sqlException.getMessage() + ", 参数类型不匹配");
  268. log.error(sqlException.getMessage() + ", 参数类型不匹配");
  269. }
  270. return Result.error(ResultEnum.INTERNAL_ERROR.getCode(), ResultEnum.INTERNAL_ERROR.getMessage(), sqlException.getMessage());
  271. } else {
  272. // [抛出异常] 实体类缺少字段
  273. return Result.error(ResultEnum.INTERNAL_ERROR.getCode(), ResultEnum.INTERNAL_ERROR.getMessage(), cause.getMessage());
  274. }
  275. }
  276. return Result.error(ResultEnum.INTERNAL_ERROR.getCode(), e.getMessage());
  277. }
  278. /**
  279. * MySQL 数据库 唯一值异常
  280. */
  281. @ExceptionHandler(DuplicateKeyException.class)
  282. public Result handleDuplicateKeyException(DuplicateKeyException e) {
  283. System.out.println("****** DuplicateKeyException.class: ******");
  284. printErrorException(e, true);
  285. DuplicateKeyExceptionHandler handler = new DuplicateKeyExceptionHandler();
  286. return handler.handleDuplicateKeyException(e);
  287. }
  288. /**
  289. * Redis 连接异常
  290. */
  291. @ExceptionHandler(RedisConnectionFailureException.class)
  292. public Result handleRedisConnectionFailureException(Exception e) {
  293. System.out.println("****** RedisConnectionFailureException.class: ******");
  294. printErrorException(e, true);
  295. return Result.error(ResultEnum.REDIS_ERROR.getCode(), e.getMessage());
  296. }
  297. /**
  298. * 请求体参数不能为空 (body)
  299. */
  300. @ExceptionHandler(HttpMessageNotReadableException.class)
  301. public Result handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
  302. System.out.println("****** HttpMessageNotReadableException.class: ******");
  303. printWarnException(e);
  304. return Result.error(ResultEnum.HTTP_BODY_EMPTY.getCode(), e.getMessage() != null ? e.getMessage() : ResultEnum.HTTP_BODY_EMPTY.getMessage());
  305. }
  306. /**
  307. * 请求方法不支持 (Get/Post)
  308. */
  309. @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
  310. public Result handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
  311. System.out.println("****** HttpRequestMethodNotSupportedException.class: ******");
  312. printWarnException(e);
  313. return Result.error(ResultEnum.HTTP_METHOD_ERROR.getCode(), ResultEnum.HTTP_METHOD_ERROR.getMessage());
  314. }
  315. /**
  316. * 其他异常:
  317. */
  318. @ExceptionHandler(Exception.class)
  319. public Result handleException(Exception e) { // , BindingResult bindingResult
  320. System.out.println("****** Exception.class: ******");
  321. printErrorException(e, true);
  322. return Result.error(ResultEnum.INTERNAL_ERROR.getCode(), e.getMessage());
  323. }
  324. // 非法参数
  325. @ExceptionHandler(IllegalArgumentException.class)
  326. public Result handleIllegalArgumentException(IllegalArgumentException e) {
  327. System.out.println("****** IllegalArgumentException.class: ******");
  328. printErrorException(e);
  329. return Result.error(ResultEnum.PARAMETER_EXCEPTION.getCode(), ResultEnum.PARAMETER_EXCEPTION.getMessage(), e.getMessage());
  330. }
  331. // 优先级太高,会覆盖其他异常
  332. // @ExceptionHandler(RuntimeException.class)
  333. // public Result handleRuntimeException(RuntimeException e) {
  334. // System.out.println("****** RuntimeException.class: ******");
  335. // System.out.println(e);
  336. //// System.out.println(e.getMessage());
  337. //// if (e.getClass().getTypeName() == "java.lang.NullPointerException") {
  338. //// return Result.error(ResultEnum.NULL_POINTER.getCode(), e.getMessage());
  339. //// }
  340. // return Result.error(ResultEnum.SERVICE_EXCEPTION.getCode(), e.getMessage());
  341. // }
  342. }