| | |
| | | import org.springframework.web.bind.annotation.RestControllerAdvice; |
| | | import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; |
| | | import org.springframework.web.multipart.MaxUploadSizeExceededException; |
| | | import org.springframework.web.multipart.MultipartException; |
| | | import org.springframework.web.servlet.NoHandlerFoundException; |
| | | import org.springframework.web.servlet.resource.NoResourceFoundException; |
| | | import org.springdoc.api.OpenApiResourceNotFoundException; |
| | | import tools.jackson.databind.exc.InvalidFormatException; |
| | | |
| | | import java.time.LocalDateTime; |
| | |
| | | if (ex instanceof MaxUploadSizeExceededException) { |
| | | return maxUploadSizeExceededExceptionHandler((MaxUploadSizeExceededException) ex); |
| | | } |
| | | // 上传类接口收到非 multipart 请求(前端漏传文件 / 用 JSON 调用了导入接口)属于客户端问题,不应按 500 处理 |
| | | if (ex instanceof MultipartException) { |
| | | return multipartExceptionHandler((MultipartException) ex); |
| | | } |
| | | if (ex instanceof NoHandlerFoundException) { |
| | | return noHandlerFoundExceptionHandler((NoHandlerFoundException) ex); |
| | | } |
| | | if (ex instanceof NoResourceFoundException) { |
| | | return noResourceFoundExceptionHandler(request, (NoResourceFoundException) ex); |
| | | } |
| | | // /v3/api-docs/{group} 传入不存在的分组:属于资源不存在,应返回 404 而不是 500 |
| | | if (ex instanceof OpenApiResourceNotFoundException) { |
| | | log.warn("[openApiResourceNotFoundExceptionHandler][{}]", ex.getMessage()); |
| | | return CommonResult.error(NOT_FOUND.getCode(), "接口分组不存在: " + ex.getMessage()); |
| | | } |
| | | if (ex instanceof HttpRequestMethodNotSupportedException) { |
| | | return httpRequestMethodNotSupportedExceptionHandler((HttpRequestMethodNotSupportedException) ex); |
| | |
| | | * |
| | | * 例如说,接口上设置了 @RequestBody 实体中 xx 属性类型为 Integer,结果传递 xx 参数类型为 String |
| | | */ |
| | | /** |
| | | * 处理文件上传接口收到非 multipart 请求 |
| | | * |
| | | * <p>例如说,Excel 导入接口漏传 file 字段、或前端用 application/json 调用了导入接口。 |
| | | * 这类都是客户端用法问题,返回 400 明确提示,而不是 500「系统异常」。</p> |
| | | */ |
| | | @ExceptionHandler(MultipartException.class) |
| | | public CommonResult<?> multipartExceptionHandler(MultipartException ex) { |
| | | log.warn("[multipartExceptionHandler]", ex); |
| | | return CommonResult.error(BAD_REQUEST.getCode(), "请求参数不正确:请使用 multipart/form-data 上传文件"); |
| | | } |
| | | |
| | | @ExceptionHandler(HttpMessageNotReadableException.class) |
| | | @SuppressWarnings("PatternVariableCanBeUsed") |
| | | public CommonResult<?> methodArgumentTypeInvalidFormatExceptionHandler(HttpMessageNotReadableException ex) { |
| | |
| | | if (StrUtil.startWith(ex.getMessage(), "Required request body is missing")) { |
| | | return CommonResult.error(BAD_REQUEST.getCode(), "请求参数类型错误: request body 缺失"); |
| | | } |
| | | return defaultExceptionHandler(ServletUtils.getRequest(), ex); |
| | | // 其它反序列化失败(如把对象传成数组、字段类型不匹配)同样是客户端报文问题,不应按 500 处理 |
| | | return CommonResult.error(BAD_REQUEST.getCode(), |
| | | String.format("请求参数类型错误:%s", StrUtil.maxLength(ex.getMessage(), 120))); |
| | | } |
| | | |
| | | /** |
| | |
| | | errorLog.setUserType(WebFrameworkUtils.getLoginUserType(request)); |
| | | // 设置异常字段 |
| | | errorLog.setExceptionName(e.getClass().getName()); |
| | | errorLog.setExceptionMessage(ExceptionUtil.getMessage(e)); |
| | | errorLog.setExceptionRootCauseMessage(ExceptionUtil.getRootCauseMessage(e)); |
| | | errorLog.setExceptionStackTrace(ExceptionUtil.stacktraceToString(e)); |
| | | // infra_api_error_log 的这些列都是 NOT NULL;而 MyBatis-Plus 插入时会跳过 null 字段, |
| | | // 于是任一字段为 null 都会变成 "Field 'xxx' doesn't have a default value",异常日志反而记不下来 |
| | | errorLog.setExceptionMessage(StrUtil.nullToEmpty(ExceptionUtil.getMessage(e))); |
| | | errorLog.setExceptionRootCauseMessage(StrUtil.nullToEmpty(ExceptionUtil.getRootCauseMessage(e))); |
| | | errorLog.setExceptionStackTrace(StrUtil.nullToEmpty(ExceptionUtil.stacktraceToString(e))); |
| | | StackTraceElement[] stackTraceElements = e.getStackTrace(); |
| | | Assert.notEmpty(stackTraceElements, "异常 stackTraceElements 不能为空"); |
| | | StackTraceElement stackTraceElement = stackTraceElements[0]; |
| | | errorLog.setExceptionClassName(stackTraceElement.getClassName()); |
| | | errorLog.setExceptionFileName(stackTraceElement.getFileName()); |
| | | errorLog.setExceptionMethodName(stackTraceElement.getMethodName()); |
| | | // 栈顶元素可能是 native 方法或 Lambda,getFileName() 为 null; |
| | | // infra_api_error_log 的这些列是 NOT NULL,为 null 会导致异常日志插入失败 |
| | | errorLog.setExceptionFileName(StrUtil.nullToEmpty(stackTraceElement.getFileName())); |
| | | errorLog.setExceptionMethodName(StrUtil.nullToEmpty(stackTraceElement.getMethodName())); |
| | | errorLog.setExceptionLineNumber(stackTraceElement.getLineNumber()); |
| | | // 设置其它字段 |
| | | errorLog.setTraceId(TracerUtils.getTraceId()); |
| | | errorLog.setApplicationName(applicationName); |
| | | errorLog.setRequestUrl(request.getRequestURI()); |
| | | errorLog.setTraceId(StrUtil.nullToEmpty(TracerUtils.getTraceId())); |
| | | errorLog.setApplicationName(StrUtil.nullToEmpty(applicationName)); |
| | | errorLog.setRequestUrl(StrUtil.nullToEmpty(request.getRequestURI())); |
| | | Map<String, Object> requestParams = MapUtil.<String, Object>builder() |
| | | .put("query", ServletUtils.getParamMap(request)) |
| | | .put("body", ServletUtils.getBody(request)).build(); |
| | | errorLog.setRequestParams(JsonUtils.toJsonString(requestParams)); |
| | | errorLog.setRequestMethod(request.getMethod()); |
| | | errorLog.setUserAgent(ServletUtils.getUserAgent(request)); |
| | | errorLog.setUserIp(ServletUtils.getClientIP(request)); |
| | | errorLog.setRequestParams(StrUtil.nullToEmpty(JsonUtils.toJsonString(requestParams))); |
| | | errorLog.setRequestMethod(StrUtil.nullToEmpty(request.getMethod())); |
| | | errorLog.setUserAgent(StrUtil.nullToEmpty(ServletUtils.getUserAgent(request))); |
| | | errorLog.setUserIp(StrUtil.nullToEmpty(ServletUtils.getClientIP(request))); |
| | | errorLog.setExceptionTime(LocalDateTime.now()); |
| | | } |
| | | |