yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java
@@ -36,6 +36,7 @@
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;
@@ -96,6 +97,10 @@
        }
        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);
@@ -181,6 +186,18 @@
     *
     * 例如说,接口上设置了 @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) {
@@ -192,7 +209,9 @@
        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)));
    }
    /**
@@ -358,27 +377,31 @@
        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());
    }