一次文件上传异常的踩坑、定位与修复复盘(Spring Boot + 接口测试)

本文记录一次真实的接口质量问题:文件超限时接口返回 200 + 业务码 -999,以及我是如何通过测试发现问题、分析原因、推动后端改造异常处理机制的全过程。既包含问题定位方法,也提供可复用的改造方案,适合 Spring Boot 开发者与接口测试工程师参考。


一、业务背景

在个人博客项目中,我负责测试封面图片上传接口:

复制代码
POST /user/uploadCover

业务流程如下:

  1. 前端上传图片文件
  2. 后端接收 MultipartFile
  3. 校验文件格式与大小
  4. 上传至七牛云
  5. 返回图片访问 URL

关键配置(application.yml):

yaml 复制代码
spring:
  servlet:
    multipart:
      enabled: true
      max-file-size: 5MB
      max-request-size: 35MB

初版接口核心代码(简化版)如下:

java 复制代码
@PostMapping("/user/uploadCover")
public Result uploadCover(@RequestParam(value="file", required=false) MultipartFile file) {

    if (file == null || file.isEmpty()) {
        return Result.fail(400,"上传文件不能为空");
    }

    String suffix = StringUtils.substringAfterLast(file.getOriginalFilename(), ".");
    if (!Arrays.asList("png","jpg","jpeg","gif","bmp")
            .contains(suffix.toLowerCase())) {
        return Result.fail(400, "仅支持图片格式文件");
    }

    String fileName = UUID.randomUUID() + "." + suffix;

    if (!qiniuUtils.upload(file, fileName)) {
        return Result.fail(FILE_UPLOAD_ERROR.getCode(),
                           FILE_UPLOAD_ERROR.getMsg());
    }

    return Result.success(QiniuUtils.url + fileName);
}

二、测试发现异常行为

我编写了一个超大文件上传的反向测试用例(伪代码):

python 复制代码
def test_upload_oversize_file(request_util, auth_token):
    with open("big_image_8MB.jpg", "rb") as f:
        files = {"file": f}
        response = request_util.post("/user/uploadCover", files=files)

    json_data = response.json()
    assert response.status_code == 200
    assert json_data["code"] == 413

实际返回结果却是:

json 复制代码
{
  "success": false,
  "code": -999,
  "msg": "系统异常,请联系管理员哦~",
  "data": null
}

表面上测试"通过",但明显存在质量问题:

  • 文件超过 5MB 时,Spring 抛出了 MaxUploadSizeExceededException
  • 但最终被全局异常兜底成 -999
  • HTTP 状态码却仍然是 200

这意味着:

用户看不到"文件太大"的明确提示,测试也没有真正发挥质量门禁作用。

因此,我将其判定为有效缺陷,并开始定位根因。


三、从日志定位根本原因

关键日志如下:

复制代码
Resolved [MaxUploadSizeExceededException: Maximum upload size exceeded]

说明:

  • 异常发生在 Spring 解析 multipart 阶段
  • 请求尚未进入 Controller

随后又出现:

复制代码
No static resource user/user/uploadCover

说明:

  • 异常触发后,DispatcherServlet 发生了转发
  • 请求并未正常进入业务 Controller

核心结论:

这个异常发生在 Controller 之外,因此写在 Controller 层的异常处理器根本"看不到"它。


四、为什么原有全局异常处理不生效

原有异常处理类:

java 复制代码
@ControllerAdvice
public class AllExceptionHandler {
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public Result doException(Exception ex){
        ex.printStackTrace();
        return Result.fail(-999,"系统异常,请联系管理员哦~");
    }

    @ExceptionHandler(MaxUploadSizeExceededException.class)
    public Result handleMaxSizeException(MaxUploadSizeExceededException e) {
        return Result.fail(413, "文件超过最大限制 5MB");
    }
}

存在两个关键问题:

❌ 问题 1:注解不够合适

@ControllerAdvice 不保证始终返回 JSON,某些场景可能走视图解析或转发逻辑。

更合适的做法是:

java 复制代码
@RestControllerAdvice

它等价于:

复制代码
@ControllerAdvice + @ResponseBody

更适合接口项目。


❌ 问题 2:异常发生阶段不匹配

MaxUploadSizeExceededException 是:

  • Servlet 层 / Spring Multipart 解析层异常
  • 不是 Controller 内部异常

因此,普通的 ControllerAdvice 无法稳定拦截,需要更强的 RestControllerAdvice + 专门的异常处理方法。


五、为什么"只改一个地方就好了"

很多人会问:

为什么只把 @ControllerAdvice 改成 @RestControllerAdvice 就解决了?

本质上,你改的不是"细节",而是异常拦截的层级。

改之前

复制代码
文件太大
   ↓
Spring Multipart 解析异常
   ↓
ControllerAdvice:我看不到 👀
   ↓
系统兜底 → code = -999

改之后

复制代码
文件太大
   ↓
Spring Multipart 解析异常
   ↓
RestControllerAdvice:我能看见 ✅
   ↓
你的处理器 → 返回 413

一句话总结:

你不是修了一个"小 bug",而是把异常处理放到了正确的位置。


六、最终修复方案(可直接复用)

✅ 改造一:统一异常处理器

java 复制代码
@RestControllerAdvice   // 关键改动
public class AllExceptionHandler {

    @ExceptionHandler(MaxUploadSizeExceededException.class)
    public Result handleMaxSizeException(MaxUploadSizeExceededException e) {
        return Result.fail(413, "文件超过最大限制 5MB");
    }

    @ExceptionHandler(Exception.class)
    public Result doException(Exception ex){
        ex.printStackTrace();
        return Result.fail(-999,"系统异常,请联系管理员哦~");
    }
}

✅ 改造二:保持 Multipart 配置

yaml 复制代码
spring:
  servlet:
    multipart:
      enabled: true
      max-file-size: 5MB
      max-request-size: 35MB

✅ 修复后接口表现

上传超过 5MB 的文件时,返回:

json 复制代码
{
  "success": false,
  "code": 413,
  "msg": "文件超过最大限制 5MB",
  "data": null
}

这才是可被前端、测试、运维识别的清晰业务异常。

相关推荐
夏幻灵21 分钟前
JavaScript this 面试:五种绑定规则、优先级与常见面试陷阱
开发语言·javascript·面试
程序猿编码33 分钟前
榨干 RTX5090 算力!Qwen3 专用单卡推理引擎,手写C++/CUDA 算子实现 MTP 推测解码
开发语言·c++·大模型推理·qwen3·多模态推理
苍何1 小时前
开源微信流 Windows,微信聊天记录,可以直接给 Codex 和 Obsidan 了
后端
bkspiderx2 小时前
Qt 插件机制:动态扩展应用功能的核心框架
开发语言·qt·元数据·qt 插件·qpluginloader
代码什么用2 小时前
Spring基础使用
java·后端·spring
程序员老陆2 小时前
深入理解 C++ thread_local:线程私有存储的正确打开方式
开发语言·c++·程序设计
沐晓时光2 小时前
C语言入门,深入理解指针(3)
c语言·开发语言
Su米苏3 小时前
Spring AI 中MCP 与普通 @Tool的区别
java·人工智能·spring
明月_清风3 小时前
Deno 终局来了:从挑战 Node 到被 Cloudflare 收编
前端·后端·node.js
码云数智-园园3 小时前
unique_ptr 还是 shared_ptr?C++ 智能指针选型与内存泄漏实战分析
java·开发语言