遇到了个task,需要将项目的依赖 NestJS 从v10 升级到 v11 ,将 Fastify 从v4升级到v5。
这是一个破坏性的大版本更新,会导致很多周边的生态插件(如文件上传、Swagger、测试工具)都需要同步调整。
以下是本次升级遇到的问题以及解决方案。
1. 核心冲突:@fastify/multipart 的版本地狱
问题现象
项目启动失败,控制台报错提示 Fastify 版本不匹配:
fastify-plugin: expected '4.x', but found '5.0.0'
原因分析
- NestJS 11 依赖 Fastify v5。
- 你当前的
@fastify/multipartv8.0.0 是为 Fastify v4 设计的。 - 两者互不兼容,导致应用无法初始化。
❌ 错误的解决方法
不要试图在 package.json 中使用 resolutions 或 overrides 强制锁定 Fastify 版本:
// ⚠️ 危险操作:不要这样做
"resolutions": {
"fastify": "^5.7.1"
}
这样做虽然能绕过安装报错,但会在运行时引发不可预知的 API 调用错误(Undefined function),因为旧插件的代码逻辑并不适配新版 Fastify。
✅ 正确的解决方案
必须升级插件到适配 v5 的版本:
npm uninstall @fastify/multipart
npm install @fastify/multipart@^9.0.0
版本对应法则:
- NestJS 10 + Fastify v4 ->
@fastify/multipartv8.x - NestJS 11 + Fastify v5 ->
@fastify/multipartv9.x
2. E2E 测试:修复 Jest 无法正常关闭 (Hang)
问题现象
升级版本后,E2E 测试虽然能跑通逻辑,但在所有用例结束后,Jest 迟迟不退出,提示 Open Handles。
原因分析
这通常不是 Multipart 插件的问题,而是 Redis (ioredis) 或 Prisma 在测试结束时未彻底断开连接。NestJS 的 app.close() 触发了模块销毁,但 ioredis 在 Cluster 模式下有时非常顽固,依然保持 TCP 连接。
解决方案
在 afterAll 钩子中,除了调用 app.close(),还需要显式强制断开 Redis 连接。
import { Cluster } from 'ioredis';
afterAll(async () => {
const redisClient = app.get<RedisCache>(CACHE_MANAGER).store.client as Cluster;
// 1. 执行你的清理逻辑
await Promise.all(
redisClient.nodes('master').map((node) => node.flushall()),
);
// 2. 关闭 NestJS 应用
await app.close();
// 3. [关键] 强制关闭 Redis 连接,防止 Jest 挂起
if (redisClient.status === 'ready') {
await redisClient.quit();
}
});
3. Swagger 升级:@ApiResponse 的类型变动
问题现象
升级 @nestjs/swagger 到 v8+ 后,代码中原本正常的装饰器突然爆红,提示 status 属性不可用。
根本原因
NestJS 团队为了规范代码语义,在 TypeScript 类型定义中做了严格区分:专用装饰器不再允许包含 status 属性,因为它们的名字已经代表了状态码。
修复对照表
| 场景 | 装饰器类型 | 是否允许 status | 代码示例 |
|---|---|---|---|
| 通用定义 | @ApiResponse | 必须 ✅ | @ApiResponse({ status: 201, description: '...' }) |
| 成功 (200) | @ApiOkResponse | 禁止 ❌ | @ApiOkResponse({ description: '...' }) |
| 创建 (201) | @ApiCreatedResponse | 禁止 ❌ | @ApiCreatedResponse({ description: '...' }) |
| 错误 (400) | @ApiBadRequestResponse | 禁止 ❌ | @ApiBadRequestResponse({ description: '...' }) |
行动指南: 检查你的 Controller,如果使用了快捷装饰器(如 ApiOkResponse),请直接删除里面的 status: 200 属性。
总结:升级检查清单 (Checklist)
- 依赖清理:移除
package.json中的resolutions锁定。 - 核心插件:将
@fastify/multipart升级至 ^9.0.0。 - Swagger 修正:删除所有专用装饰器(
@ApiOkResponse等)中的status字段。 - 测试稳健性:在 E2E 测试的
afterAll中加入redisClient.quit()以确保测试进程优雅退出。