| | |
| | | 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 tools.jackson.databind.exc.InvalidFormatException; |
| | |
| | | if (ex instanceof MaxUploadSizeExceededException) { |
| | | return maxUploadSizeExceededExceptionHandler((MaxUploadSizeExceededException) ex); |
| | | } |
| | | // 上传类接口收到非 multipart 请求(前端漏传文件 / 用 JSON 调用了导入接口)属于客户端问题 |
| | | if (ex instanceof MultipartException) { |
| | | return multipartExceptionHandler((MultipartException) ex); |
| | | } |
| | | if (ex instanceof NoHandlerFoundException) { |
| | | return noHandlerFoundExceptionHandler((NoHandlerFoundException) ex); |
| | | } |
| | |
| | | } |
| | | if (ex instanceof AccessDeniedException) { |
| | | return accessDeniedExceptionHandler(request, (AccessDeniedException) ex); |
| | | } |
| | | // 参数校验类异常(如 Assert.notNull 抛出的 IllegalArgumentException)属于客户端问题,不应按 500 处理 |
| | | if (ex instanceof IllegalArgumentException) { |
| | | return illegalArgumentExceptionHandler((IllegalArgumentException) ex); |
| | | } |
| | | return defaultExceptionHandler(request, ex); |
| | | } |
| | |
| | | } |
| | | |
| | | /** |
| | | * 处理文件上传接口收到非 multipart 请求 |
| | | * |
| | | * 例如说,Excel 导入接口漏传 file 字段、或前端用 application/json 调用了导入接口 |
| | | */ |
| | | @ExceptionHandler(MultipartException.class) |
| | | public CommonResult<?> multipartExceptionHandler(MultipartException ex) { |
| | | log.warn("[multipartExceptionHandler]", ex); |
| | | return CommonResult.error(BAD_REQUEST.getCode(), "请求参数不正确:请使用 multipart/form-data 上传文件"); |
| | | } |
| | | |
| | | /** |
| | | * 处理 SpringMVC 参数绑定不正确,本质上也是通过 Validator 校验 |
| | | */ |
| | | @ExceptionHandler(BindException.class) |
| | |
| | | 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))); |
| | | } |
| | | |
| | | /** |
| | |
| | | public CommonResult<?> httpMediaTypeNotSupportedExceptionHandler(HttpMediaTypeNotSupportedException ex) { |
| | | log.warn("[httpMediaTypeNotSupportedExceptionHandler]", ex); |
| | | return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求类型不正确:%s", ex.getMessage())); |
| | | } |
| | | |
| | | /** |
| | | * 处理参数非法异常 |
| | | * |
| | | * 来源是,业务代码中使用 {@link Assert#notNull} 等断言校验入参,或缓存注解使用空 key。 |
| | | * 这类问题属于调用方传参不正确,返回 400 而不是 500,避免前端把参数问题误判为系统故障。 |
| | | */ |
| | | @ExceptionHandler(value = IllegalArgumentException.class) |
| | | public CommonResult<?> illegalArgumentExceptionHandler(IllegalArgumentException ex) { |
| | | log.warn("[illegalArgumentExceptionHandler]", ex); |
| | | return CommonResult.error(BAD_REQUEST.getCode(), ex.getMessage()); |
| | | } |
| | | |
| | | /** |
| | |
| | | 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 会导致异常日志插入失败(日志里只剩一条 SQL 报错) |
| | | 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()); |
| | | } |
| | | |