一次文件上传异常的踩坑、定位与修复复盘(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
}

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

相关推荐
芯思路6 小时前
STM32开发学习笔记之十【AI部署】
笔记·stm32·学习
时早玖沐6 小时前
KOOK「找不到语音频道」:同一个错误假设,我犯了三次
python·开源·ai编程
师夷长技以自愈6 小时前
第 3 讲:多 Agent——Supervisor 分诊 + 专职 Worker
面试
_冷眸_6 小时前
Python 调用大模型 API:最小请求、Base URL 和 401/404 排查
开发语言·人工智能·python
AI架构师之家6 小时前
我把 Hermes 这匹"马"养了两周,它干了一件让我有点发毛的事
后端
dpharness6 小时前
块协议用了两周,我把九个坑按顺序记下来了
后端
niucloud-admin6 小时前
NIUCLOUD PHP SAAS上门家政使用文档:服务保障
java·开发语言·php
加宝的起点雷达6 小时前
跑了两周的信号日报,我把降级链改了四次
人工智能·后端
Dumbness6 小时前
UIA 读微信群消息没有 id,怎么判断哪条是新的:后缀对齐在翻历史和撤回时整屏重报
python·算法
怕浪猫6 小时前
我有个朋友,工作八年,公司从 300 人减到 80 人
面试·程序员