本文记录一次真实的接口质量问题:文件超限时接口返回 200 + 业务码 -999,以及我是如何通过测试发现问题、分析原因、推动后端改造异常处理机制的全过程。既包含问题定位方法,也提供可复用的改造方案,适合 Spring Boot 开发者与接口测试工程师参考。
一、业务背景
在个人博客项目中,我负责测试封面图片上传接口:
POST /user/uploadCover
业务流程如下:
- 前端上传图片文件
- 后端接收
MultipartFile - 校验文件格式与大小
- 上传至七牛云
- 返回图片访问 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
}
这才是可被前端、测试、运维识别的清晰业务异常。