木灵鱼儿 - Nestjs
type: channel
link: https://www.mulingyuer.com/tag/Nestjs/
items: 16 NestJS 企业级 RBAC 权限控制体系
items: 15 NestJS 统一响应体设计（信封模式）
items: 14 NestJS 生产级错误过滤方案
items: 13 NestJS 集成 TypeORM 完全指南
items: 12 NestJS 集成 Prisma ORM 完全指南（Prisma v7）
items: 11 NestJS 注册与登录接口的密码安全设计
items: 10 NestJS JWT 身份验证完全指南
items: 09 NestJS 使用 @nestjs-swagger 生成 API 文档
items: 08 NestJS DTO 校验、Entity 脱敏与 Mapped Types 实战（TypeORM & Prisma）
items: 07 NestJS 使用Caching缓存（cache-manager）

16 NestJS 企业级 RBAC 权限控制体系
type: item
date: 2026-08-16T11:04:00+08:00
description: 前言权限控制是企业级应用中最容易"看起来做了，实则一捅就破"的模块。常见的伪 RBAC 实现往往是在 Controller 里直接判断 user.role === 'admin'，把角色硬编码散落在各处，后期维护极其痛苦。本文将从架构设计出发，基于 NestJS + Prisma + Redis + CASL 构建一套真正可扩展的 RBAC 体系，覆盖以下核心问题：纯角色判断为何不够？如何建模"资源:动作"级别的原子权限？NestJS 的守卫管道中，认证与授权如何职能分离？每次请求都查数据库权限太慢，如何用 Redis 将查询压缩到毫秒级？用户拥有 article:delete 权限，如何限制只能删自己的文章？阅读本文需要具备 NestJS 模块化开发经验，熟悉 Prisma 基本用法和 JWT 认证流程。建议先阅读本系列的 《10 NestJS JWT 身份验证完全指南》 和 《12 NestJS 集成 Prisma ORM 完全指南》。[hide]一、架构设计与理论基础1.1 RBAC 模型的演进基础 RBAC（User ➔ Role）最简单的实现：给用户打上 admin、editor、viewer 标签，守卫里判断角色字符串。问题在于角色是粗粒度的——同样是 editor，有人可以发布文章，有人只能保存草稿。随着业务增长，角色数量膨胀（senior_editor、junior_editor……），维护成本失控。标准 RBAC（User ➔ Role ➔ Permission）在角色和用户之间引入"权限"这一中间层。权限采用 [资源]:[动作] 的命名规范：article:create article:read article:update article:delete article:publish user:create user:read user:update user:delete system:config:read system:config:update角色是权限的集合，用户通过角色继承权限。这样可以精确控制每个角色的能力边界，新增权限点只需修改角色配置，不用改代码。RBAC + ABAC 融合（行级数据权限）标准 RBAC 仍然无法解决"只能操作自己创建的数据"这类行级权限问题。此时需要引入属性访问控制（ABAC）的思想——判断资源的属性（authorId）是否与当前用户匹配。本文第六节使用 CASL 处理这个场景。1.2 NestJS 授权生命周期请求在 NestJS 管道中的执行顺序：HTTP 请求 ↓ Middleware（如日志、请求 ID） ↓ Guards（身份验证 → 权限验证） ← 授权在这里发生 ↓ Interceptors（前置） ↓ Pipes（参数转换与校验） ↓ Route Handler ↓ Interceptors（后置） ↓ Exception Filters（异常时触发）守卫（Guard）是授权逻辑的正确位置。本文将守卫拆为两层：守卫职责JwtAuthGuard认证：验证 token 有效性，将用户信息挂载到 req.userPermissionsGuard授权：检查当前用户是否拥有访问该端点所需的权限两者注册顺序必须保证认证先于授权，详见第四节。1.3 技术栈库用途@nestjs/jwtJWT 签发与验证prismaORM，权限数据持久化ioredisRedis 客户端，权限缓存@casl/abilityABAC 策略引擎，处理行级权限二、数据库建模2.1 Prisma Schema 设计// prisma/schema.prisma generator client { provider = "prisma-client-js" output = "../src/generated/prisma" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } model User { /// 用户唯一标识，自增主键 id Int @id @default(autoincrement()) /// 登录邮箱，全局唯一 email String @unique /// Argon2id 哈希后的密码，禁止明文存储 password String /// 记录创建时间，由数据库自动填充 createdAt DateTime @default(now()) /// 记录最后更新时间，由 Prisma 自动维护 updatedAt DateTime @updatedAt /// 软删除时间戳；非 null 表示该用户已被删除，所有查询必须附加 deletedAt: null 过滤条件 deletedA…

15 NestJS 统一响应体设计（信封模式）
type: item
date: 2026-08-16T08:09:00+08:00
description: NestJS 统一响应体设计：信封格式与正确 HTTP 状态码在前后端分离项目中，API 响应格式会同时影响前端开发体验、错误处理和系统可观测性。本文以 NestJS 为例，设计一套“信封格式 + 正确 HTTP 状态码”的统一响应方案，覆盖成功响应、异常响应和 Swagger 文档三个部分。本文默认读者已经了解 NestJS 的拦截器、异常过滤器，以及上一篇《NestJS 生产级错误过滤方案》中的 Prisma 过滤器链路。[hide]一、先确定响应格式1.1 两种常见方案业界主要有两种 API 响应风格。HTTP 语义派（Stripe、GitHub、Google API）：成功请求返回 HTTP 2xx，body 直接是数据；失败请求返回 HTTP 4xx/5xx，body 是结构化错误信息；代理、网关、监控系统可以直接根据 HTTP 状态码判断请求是否成功。统一信封派：成功和失败都返回 HTTP 200；前端通过 body 中的 code 字段判断业务结果；前端拦截器实现简单，但基础设施层无法准确感知失败请求。1.2 本文采用的折中方案本文保留信封格式，同时使用正确的 HTTP 状态码：// 成功：HTTP 200 { "code": 0, "message": "success", "data": { "id": 1 } } // 失败：HTTP 404 { "code": 40402, "message": "用户不存在", "data": null }这样，前端可以统一读取 code、message 和 data，Nginx、网关、Prometheus 等基础设施也仍然可以根据 HTTP 状态码进行统计和告警。需要特别区分两个字段：HTTP 状态码：表示 HTTP 层面的请求结果；code：表示业务层面的结果，成功固定为 0，失败使用数字业务码。1.3 这套方案需要改造什么目标实现方式统一成功响应全局响应拦截器统一错误响应异常过滤器返回信封格式统一业务错误码数字枚举集中管理正确展示泛型响应自定义 Swagger 响应装饰器避免重复声明公共错误Swagger 文档后处理器Controller 和 Service 不需要手动调用 success() 或 failed()。正常返回值由拦截器包装，异常则由过滤器处理。二、目录结构与实现顺序建议按下面的顺序落地：先定义响应模型，再接入运行时组件，最后补齐 Swagger 描述。src/ ├── main.ts ├── app.module.ts ├── common/ │ ├── dto/ │ │ └── api-response.dto.ts │ ├── decorators/ │ │ ├── skip-transform.decorator.ts │ │ ├── api-object-response.decorator.ts │ │ ├── api-array-response.decorator.ts │ │ └── api-paginated-response.decorator.ts │ ├── exceptions/ │ │ ├── business.exception.ts │ │ └── error-codes.ts │ ├── filters/ │ │ ├── all-exceptions.filter.ts │ │ └── prisma-exception.filter.ts │ ├── interceptors/ │ │ └── transform.interceptor.ts │ └── swagger/ │ └── inject-global-errors.ts └── users/ ├── dto/ │ └── create-user.dto.ts ├── entities/ │ └── user.entity.ts └── users.controller.ts三、定义统一响应模型3.1 通用响应 DTOApiResponseDto<T> 同时表示成功和失败响应。普通对象和数组可以复用同一个类，区别只在于泛型参数是 T 还是 T[]。// src/common/dto/api-response.dto.ts import { ApiProperty } from "@nestjs/swagger"; export class ApiResponseDto<T> { @ApiProperty({ example: 0, description: "业务状态码，0 表示成功" }) code: number; @ApiProperty({ example: "success" }) message: string; data: T | null;…

14 NestJS 生产级错误过滤方案
type: item
date: 2026-08-16T06:23:39+08:00
description: 前言：为什么需要生产级错误过滤默认响应的局限NestJS 内置的异常处理机制已经相当完善，但在生产项目中仍然存在明显不足：格式不统一：不同层抛出的异常，响应结构各不相同。HttpException 返回 { statusCode, message }，未捕获的 Error 返回 500 的通用格式，Prisma 错误则直接泄露为 500 且没有任何业务上下文。泄露内部细节：默认情况下，数据库错误、堆栈信息、内部路径等敏感内容可能出现在生产环境的响应体中。可观测性差：没有统一的日志格式，无法关联请求链路，难以在监控系统中定位问题根源。生产环境的核心诉求诉求说明统一格式前端对接时只需处理一种响应结构安全隐藏内部细节生产环境不暴露数据库错误码、堆栈、文件路径可观测性每条错误日志可追溯到具体请求可维护性错误码集中管理，便于国际化和前端对接本文方案概览本文构建两层过滤器链路：请求进入 ↓ 业务逻辑 / ORM 操作 ↓ 抛出异常 PrismaExceptionFilter ← 优先处理 Prisma 错误，非 Prisma 错误继续上抛 ↓ 非 Prisma 错误 AllExceptionsFilter ← 兜底处理所有其余异常（HttpException、业务异常、未知 Error） ↓ 统一格式的 JSON 响应涉及的文件结构：src/ ├── common/ │ ├── filters/ │ │ ├── all-exceptions.filter.ts # 兜底过滤器 │ │ └── prisma-exception.filter.ts # Prisma 专用过滤器 │ ├── exceptions/ │ │ ├── business.exception.ts # 自定义业务异常基类 │ │ └── error-codes.ts # 统一错误码枚举 │ └── interfaces/ │ └── error-response.interface.ts # 统一响应结构接口 └── database/ └── utils/ └── prisma-error.util.ts # Prisma 错误判断工具函数[hide]二、统一响应格式设计2.1 先回答一个问题：错误格式要和成功格式统一吗？很多项目的成功响应采用"信封"结构：{ "code": 0, "message": "success", "data": { "id": 1 } }自然会有人问：错误响应是否也应该套同一层信封？这是一个架构决策，业界存在两种流派。流派一：统一信封，HTTP 状态码始终 200// 成功 { "code": 0, "message": "success", "data": { "id": 1 } } // 失败（HTTP 200） { "code": 40401, "message": "用户不存在", "data": null }优点：前端只需一个响应拦截器，统一判断 code !== 0 即为错误。国内对接微信小程序的项目偏好这种方式。缺点：滥用 HTTP 200 会让代理、网关、监控系统（Nginx 访问日志、Prometheus）无法通过状态码区分成功和失败，运维可观测性大幅下降。流派二：HTTP 语义，成功和失败结构不同（本文方案）// 成功 HTTP 200，body 直接是数据 { "id": 1, "email": "user@example.com" } // 失败 HTTP 4xx/5xx，body 是结构化错误体 { "statusCode": 404, "error": "Not Found", "message": "用户不存在" }优点：HTTP 状态码有原生语义，监控告警、负载均衡、CDN 都能直接感知；符合 RESTful 规范，与 Stripe、GitHub、Google API 的设计一致。折中方案：信封 + 正确的 HTTP 状态码并存如果团队有强烈的统一信封需求，推荐的做法是两者并存，而不是牺牲 HTTP 语义：// 失败 HTTP 404（状态码仍然正确） { "code": 40401, "message": "用户不存在", "data": null } // 成功 HTTP 200 { "code": 0, "message": "success", "data": { "id": 1 } }这样既满足前端统一解析，又不破坏基础设施层的感知能力。本文过滤器实现中，只需将 response.status(xxx).json(body) 的 body 替换为信封格式，过滤器主体逻辑无需任何改动。本文结论：采用流派二，不强行统一成功和失败结构。理由是：成功响应 data 字段的形态千变万化，强行套一层信封并不…

13 NestJS 集成 TypeORM 完全指南
type: item
date: 2026-08-16T05:21:00+08:00
description: 前言TypeORM 是 Node.js 生态中历史最悠久的 ORM，也是 NestJS 官方文档中首推的数据库解决方案之一。它以装饰器驱动的 Entity 定义为核心，支持 Active Record 与 Data Mapper 两种模式，与 TypeScript 有着天然的契合。相比 Prisma 的 Schema-first 理念，TypeORM 更贴近传统 ORM 思想：Entity 类既是数据库表的映射，也是业务对象。对于习惯 Java/Spring 或 C#/EF 体系的开发者来说，TypeORM 的上手成本更低。本文使用 Data Mapper 模式（Repository 模式），这是 NestJS 中更推荐的生产方式。与 Active Record 相比，Repository 模式将数据库操作与业务逻辑分离，测试更友好，依赖关系更清晰。本文基于 TypeORM 最新版 和 NestJS 最新版，以 MySQL 为主要示例（PostgreSQL 差异处会单独说明），从安装到生产级集成，覆盖以下内容：生产级目录结构与 data-source.ts 的设计原则与 ConfigService 整合，异步读取配置，避免硬编码自定义日志系统，与 NestJS Logger 对接Entity 定义：公共基类、软删除、索引、钩子数据库迁移（CLI 工作流）Repository 模式 CRUD 与 QueryBuilder 进阶关联关系（OneToOne / OneToMany / ManyToMany）事务管理（DataSource.transaction / QueryRunner）生产级全局异常过滤器本文假设你已有一个使用 @nestjs/config 的 NestJS 项目。ConfigModule 的配置请参考本系列第 01 篇。[hide]一、生产级项目目录结构在集成 TypeORM 之前，先规划一个符合业界最佳实践的目录结构：project-root/ ├── src/ │ ├── common/ │ │ └── filters/ │ │ └── typeorm-exception.filter.ts # 全局异常过滤器 │ ├── config/ │ │ └── database.config.ts # 数据源配置（registerAs） │ ├── database/ │ │ ├── database.module.ts # 全局数据库模块 │ │ ├── typeorm-logger.ts # 自定义 TypeORM 日志 │ │ └── migrations/ # 迁移文件（CLI 生成） │ │ └── 1700000000000-Init.ts │ ├── modules/ │ │ └── user/ │ │ ├── entities/ │ │ │ └── user.entity.ts │ │ ├── user.module.ts │ │ ├── user.service.ts │ │ └── user.controller.ts │ ├── app.module.ts │ └── main.ts ├── data-source.ts # CLI 专用数据源（迁移用） ├── .env └── tsconfig.jsondata-source.ts 为什么需要单独存在：TypeORM CLI（typeorm migration:generate 等命令）在运行时无法访问 NestJS 的 DI 容器，它只能读取一个导出 DataSource 实例的独立文件。因此需要将数据源配置提取为一个可独立运行的文件，与 NestJS 应用共享同一份配置逻辑，但不依赖任何 NestJS 模块。二、安装与初始化2.1 安装依赖# NestJS TypeORM 集成包 + TypeORM 核心 pnpm add @nestjs/typeorm typeorm # MySQL 驱动（选其一） pnpm add mysql2 # PostgreSQL 驱动（选其一） pnpm add pg pnpm add -D @types/pg2.2 配置 tsconfig.jsonTypeORM 的 Entity 装饰器（@Entity、@Column 等）依赖 TypeScript 的装饰器元数据功能，必须在 tsconfig.json 中开启以下两个选项：// tsconfig.json { "compilerOptions": { "experimentalDecorators": true, "emitDecoratorMetadata": true } }三、与 ConfigService 整合（DataSource 配置）3…

12 NestJS 集成 Prisma ORM 完全指南（Prisma v7）
type: item
date: 2026-08-16T04:51:00+08:00
description: 前言Prisma 是 Node.js 和 TypeScript 生态中最受欢迎的 ORM 之一。相比 TypeORM，它提供了更强的类型安全性、更直观的 Schema 语法，以及自动生成的迁移文件。Prisma v7 已全面转向 ES Module，而 NestJS 默认使用 CommonJS，因此需要做一些特殊处理——本文会详细说明。本文基于 Prisma v7 和 NestJS 最新版，从安装到生产级集成，覆盖以下内容：正确安装配置 Prisma，与 ConfigService 和日志系统整合使用 prisma.config.ts 统一管理 schema、迁移、seed 与数据源配置生产级项目目录结构设计定义数据模型，生成和执行迁移常用 CLI 命令详解（包括迁移解决和回滚）Prisma 完整错误类型与处理策略生产级全局异常过滤器实现本文假设你已有一个使用 @nestjs/config 的 NestJS 项目。如需了解 ConfigModule 的配置，请参考本系列第 01 篇。[hide]一、生产级项目目录结构在集成 Prisma 之前，先规划一个符合业界最佳实践的目录结构：project-root/ ├── prisma/ │ ├── schema.prisma # 数据模型定义 │ ├── seed.ts # 数据库种子文件 │ └── migrations/ # 迁移文件目录（自动生成） │ └── 20240101000000_init/ │ └── migration.sql ├── prisma.config.ts # Prisma CLI 配置（v7 新增） ├── src/ │ ├── common/ # 通用模块 │ │ ├── filters/ # 全局过滤器 │ │ │ └── prisma-exception.filter.ts │ │ ├── interceptors/ # 全局拦截器 │ │ └── guards/ # 全局守卫 │ ├── config/ # 配置相关 │ │ └── database.config.ts # 数据库配置 │ ├── database/ # 数据库基础设施层 │ │ ├── prisma.service.ts # Prisma 服务 │ │ ├── prisma.module.ts # Prisma 模块 │ │ └── utils/ │ │ └── prisma-error.util.ts # Prisma 错误判断工具 │ ├── modules/ # 业务模块 │ │ ├── user/ │ │ │ ├── user.module.ts │ │ │ ├── user.service.ts │ │ │ ├── user.controller.ts │ │ │ └── dto/ │ │ └── post/ │ ├── generated/ # Prisma 生成的客户端（.gitignore） │ │ └── prisma/ │ ├── app.module.ts │ └── main.ts ├── .env # 环境变量 ├── .env.example # 环境变量示例 ├── .gitignore ├── package.json └── tsconfig.json核心设计原则：关注点分离：数据库基础设施（database/）与业务逻辑（modules/）分离可维护性：通用功能（过滤器、拦截器）集中在 common/ 目录安全性：生成的代码（generated/）和环境变量（.env）不提交到版本控制可测试性：清晰的模块划分便于单元测试和集成测试二、安装与初始化配置2.1 安装依赖首先安装 Prisma CLI（开发依赖）和运行时所需的包：# Prisma CLI（仅开发阶段使用） pnpm add prisma --save-dev # Prisma 客户端 + PostgreSQL 驱动适配器 pnpm add @prisma/client @prisma/adapter-pg pg # Prisma Config 加载 .env，以及执行 TypeScript seed 脚本 pnpm add dotenv pnpm add -D tsx pnpm add -D @types/pg数据库驱动说明：Prisma v7 采用驱动适配器（Driver Adapter）架构，不同数据库需安装对应包：数据库驱动适配器包PostgreSQL@prisma/adapter-pg + pgMySQL@prisma/adapter-mysql2 + mysql2SQLite@prisma/adapter-better-sqlite3 + better-sqlite…

11 NestJS 注册与登录接口的密码安全设计
type: item
date: 2026-08-16T01:34:57+08:00
description: 前言在上一篇《10 NestJS JWT 身份验证完全指南》中，我们完整实现了基于 JWT 的身份验证体系，其中已涉及 Argon2id 哈希和时序攻击防护。但密码安全不止于此 —— 注册和登录接口是攻击者最频繁的目标，一旦设计失误，后果往往是大规模账号泄露。本文聚焦于密码在整个生命周期（注册 → 存储 → 验证 → 重置）中的威胁模型和防护实践，所有代码示例均为生产级实现，可直接集成到已有项目中。阅读本文需要完成《10 NestJS JWT 身份验证完全指南》的实践，或具备等同的 NestJS 认证体系基础。[hide]第一部分：威胁模型在写代码前，先明确我们要对抗的攻击类型。不理解攻击原理，就无法评估防御是否有效。1.1 彩虹表攻击（Rainbow Table Attack）攻击原理：攻击者预先计算大量明文密码的哈希值，建成一张"哈希 → 明文"的查找表（即彩虹表）。一旦获取数据库中的哈希值，只需查表就能反查出原始密码，无需逆向哈希算法本身。攻击流程： 1. 攻击者拖库，获得 user 表中的 password_hash 字段 2. 对比彩虹表：e10adc3949ba59abbe56e057f20f883e → 123456 3. 无需暴力破解，直接得到明文密码为什么彩虹表能奏效？因为哈希是确定性的：同一个输入永远产生同一个输出。MD5("123456") 永远是 e10adc3...，全球所有使用 MD5 存储 123456 的系统哈希值都一样。彩虹表一次构建，处处可用。防御：加盐（Salt）盐是一段随机字符串，在哈希前拼接到密码上：hash("123456") → e10adc3...（可查彩虹表） hash("123456" + "x7k9mQ2p") → a3f8c1...（彩虹表中没有这条记录）每个用户的盐都不同，即便两个用户的密码相同，哈希值也不同，彩虹表完全失效。Argon2id 自动处理盐： 调用 hash() 时，库会在内部生成随机盐并嵌入哈希结果字符串中，验证时自动提取——你无需手动管理盐。解释：通过加盐的方式，可以让相同的密码在不同用户之间产生不同的哈希值，从而防止彩虹表攻击（1:1对照成为不可能）。即便攻击者获取了数据库中的哈希值，从哈希中解析出盐 （盐（Salt）从来就不是用来保密的，它的公开完全在设计预期之内）和密码的哈希值，也无法直接通过彩虹表反查出原始密码，它需要加上盐生成一份新的彩虹表，而每个用户的盐都不同（不设置固定Salt属性值情况下），攻击者无法为每个用户单独生成彩虹表，成本极高。方式2上就已经不能称之为彩虹表攻击了，因为彩虹表是预先计算好的，而重新针对性生成一般都是暴力破解的方式。1.2 字典攻击（Dictionary Attack）攻击者不穷举所有组合，而是使用包含数百万常用密码的字典（RockYou、SecLists 等），逐一尝试。弱密码（123456、password、qwerty）在秒级内被破解。防御： 密码强度策略 + 禁止使用已知弱密码（HIBP API）。1.3 凭证填充攻击（Credential Stuffing）其他网站泄露的"用户名 + 密码"组合，被自动化工具批量在你的系统上尝试登录。由于大量用户在多个网站使用相同密码，成功率远高于暴力破解。2024 年已公开泄露：RockYou2024，含 100 亿条明文密码记录 这些记录被直接用于凭证填充防御： 登录限流 + IP 封锁 + 异常行为检测 + 强制注册时使用未泄露密码（HIBP）。1.4 暴力破解（Brute Force）穷举所有可能的密码组合。现代 GPU 每秒可计算数十亿次 MD5，对于 8 位纯数字密码（10^8 = 1 亿种组合）只需数秒。防御： 使用计算成本高的哈希算法（Argon2id）+ 账号锁定 + 登录限流。1.5 时序攻击（Timing Attack）通过精确测量操作响应时间，推断内部逻辑分支。例如：用户不存在：服务器 2ms 返回"用户名或密码错误"用户存在但密码错误：服务器 350ms 返回"用户名或密码错误"攻击者遍历用户名，响应时间明显更长的表示该用户名已注册——这叫用户枚举（User Enumeration）。防御： 无论用户是否存在，都执行完整的 verify 操作，响应时间趋于一致。第二部分：密码存储安全2.1 哈希算法选型先看清楚现状：算法状态原因MD5禁止使用非密码学安全哈希，GPU 每秒数十亿次，彩虹表完备SHA-1/2禁止使用同上，设计用于速度，密码哈希恰恰需要"慢"bcrypt可用老牌算法，自带盐，但内存需求低，GPU 并行破解较易scrypt可用内存硬化，强于 bcrypt，但参数调优复杂Argon2id推荐使用2015 密码哈希竞赛冠军，内存+CPU 双…

10 NestJS JWT 身份验证完全指南
type: item
date: 2026-08-15T21:53:19+08:00
description: NestJS JWT 身份验证完全指南前言JWT（JSON Web Token）是目前 RESTful API 身份验证领域的主流方案。其核心优势在于无状态性——服务端无需维护会话存储，token 本身即携带经签名保护的身份信息。然而，仅理解 JWT 的基本签发与验证远远不够。在实际工程落地中，还需要系统性地考量以下问题：安全边界：payload 明文可解码，哪些字段可以放，哪些绝对不能放？token 生命周期：单一长期 token 的泄露风险如何通过双 Token 策略加以控制？主动吊销：JWT 无状态的特性与"立即登出"的业务需求之间，如何取得平衡？密码安全：bcrypt 之外，为何 Argon2id 是当前更优的选择？配置安全：密钥、过期时间等敏感配置，如何在启动阶段完成强类型校验而非等到运行时才暴露错误？本文基于 @nestjs/jwt 原生方案，不引入 Passport（后者在纯 JWT 场景下引入了额外的策略抽象层，增加理解成本而收益有限）。文章从模块骨架搭建出发，逐步深入到 Refresh Token Rotation、时序攻击防护、反向代理下的限流陷阱，最终给出一份覆盖 OWASP 核心要点的安全加固清单。阅读本文需要具备 NestJS 模块化开发经验（依赖注入、守卫、装饰器），以及对 JWT 基本结构有初步认识。完成本文的实践后，你将得到一套结构清晰、类型安全的认证体系骨架，可直接在此基础上扩展 RBAC 权限模型或 OAuth2 三方登录。[hide]前置准备安装所需依赖：pnpm add @nestjs/jwt @nestjs/config @node-rs/argon2 class-validator class-transformer依赖说明：@nestjs/jwt：NestJS 官方 JWT 工具，封装 token 的签发与验证。@nestjs/config：环境变量加载，配合 JwtModule.registerAsync 使用。@node-rs/argon2：密码哈希库。选它不选 bcrypt 的原因：bcrypt 是 C++ 原生扩展，Windows 下需要 node-gyp + Python + 编译工具链，极易踩坑；原版 argon2 同理。@node-rs/argon2 基于 Rust，提供全平台预编译二进制，pnpm add 即用，性能也优于 bcrypt。Argon2id 是 2015 年密码哈希竞赛冠军，在抗 GPU/ASIC 并行破解上强于 bcrypt，是目前的推荐算法。class-validator / class-transformer：DTO 校验。目录结构约定在中大型项目中，守卫、装饰器、拦截器等横切关注点应放在 src/common 下，Auth 领域内部只保留自己的业务：src/ ├── common/ │ ├── decorators/ │ │ ├── current-user.decorator.ts │ │ └── public.decorator.ts │ └── guards/ │ └── jwt-auth.guard.ts ├── auth/ │ ├── dto/ │ │ └── sign-in.dto.ts │ ├── types/ │ │ └── jwt-payload.type.ts │ ├── auth.controller.ts │ ├── auth.module.ts │ └── auth.service.ts ├── users/ │ ├── users.module.ts │ └── users.service.ts ├── config/ │ └── env.validation.ts └── app.module.ts这么拆的好处：JwtAuthGuard 会被 UsersModule、OrdersModule 等多个业务模块使用，放 common 目录避免循环依赖，同时职责清晰。第一部分：创建身份验证模块1. 生成模块骨架nest g module auth nest g controller auth --no-spec nest g service auth --no-spec nest g module users nest g service users --no-spec2. 环境变量与 JWT 配置.env 文件：# 至少 32 字节的随机字符串，可用 `openssl rand -base64 48` 生成 JWT_ACCESS_SECRET=your-access-secret-at-least-256-bits JWT_ACCESS_EXPIRES_IN=15m # refresh token 使用独立密钥，与 a…

09 NestJS 使用 @nestjs-swagger 生成 API 文档
type: item
date: 2026-08-15T05:24:49+08:00
description: 前言在前后端分离的项目中，API 文档是前后端协作的核心契约。手动维护文档不仅繁琐，还容易与代码脱节。@nestjs/swagger 基于 OpenAPI 规范，通过装饰器直接从代码生成交互式 API 文档，做到了文档即代码。本文基于 @nestjs/swagger v8（NestJS v10/v11），覆盖从基础配置到生产级最佳实践的完整流程。[hide]安装pnpm add @nestjs/swagger一、基础配置（main.ts）在 main.ts 中初始化 Swagger，这是所有配置的起点。// src/main.ts import { NestFactory } from "@nestjs/core"; import { ValidationPipe } from "@nestjs/common"; import { SwaggerModule, DocumentBuilder } from "@nestjs/swagger"; import { AppModule } from "./app.module"; async function bootstrap() { const app = await NestFactory.create(AppModule); // 全局前缀（可选） app.setGlobalPrefix("api"); // 全局校验管道 app.useGlobalPipes(new ValidationPipe({ transform: true, whitelist: true })); // ---- Swagger 配置 ---- const config = new DocumentBuilder() .setTitle("My API") .setDescription("生产级 REST API 接口文档") .setVersion("1.0") .addBearerAuth( { type: "http", scheme: "bearer", bearerFormat: "JWT", description: "请输入 JWT Token", }, "access-token", // 安全方案名称，需与 @ApiBearerAuth() 保持一致 ) .addTag("用户", "用户相关接口") .addTag("文章", "文章相关接口") .build(); const documentFactory = () => SwaggerModule.createDocument(app, config); SwaggerModule.setup("docs", app, documentFactory, { jsonDocumentUrl: "docs/json", // 暴露 JSON 规范：GET /docs/json yamlDocumentUrl: "docs/yaml", // 暴露 YAML 规范：GET /docs/yaml swaggerOptions: { persistAuthorization: true, // 刷新页面后保留 Token tagsSorter: "alpha", // 标签按字母排序 operationsSorter: "alpha", // 接口按字母排序 }, }); // ---- Swagger 配置结束 ---- await app.listen(process.env.PORT ?? 3000); } bootstrap();启动后访问 http://localhost:3000/docs 即可看到 Swagger UI。生产环境建议：通过环境变量控制是否启用文档，避免在生产中暴露接口细节（详见末节）。二、DTO 装饰器SwaggerModule 通过读取 DTO 类上的装饰器来生成 Schema。2.1 @ApiProperty最核心的属性装饰器，用于描述 DTO 的每个字段。// src/users/dto/create-user.dto.ts import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger"; import { IsEmail, IsString, IsOptional, MinLength, IsEnum } from "class-validator"; export enum UserRole { Admin = "admin", Editor = "editor", Viewer = "viewer", } export class CreateUserDto { @ApiProperty({ description: …

08 NestJS DTO 校验、Entity 脱敏与 Mapped Types 实战（TypeORM & Prisma）
type: item
date: 2026-08-15T04:49:00+08:00
description: 前言在了解DTO与Entity的区别之前，我们先来了解一下它们在项目中的数据路径。[客户端 Client] │ ▼ (1. 携带 Request DTO) [Controller] ── (2. 校验与格式转换: ValidationPipe) │ ▼ (3. 传递 DTO 或 解构参数) [Service] ──── (4. 业务逻辑，将 DTO 转换为 Entity 或操作 ORM) │ ▼ (5. 写入/读取 Entity) [Database] │ ▼ (6. 返回 Entity 给 Service) [Service / Controller] ── (7. Entity 转换为 Response DTO / 序列化脱敏) │ ▼ (8. 返回安全响应) [客户端 Client]DTO（Data Transfer Object）是数据传输对象，主要用于客户端与服务端之间的数据传输。它通常用于接收请求数据和返回响应数据，具有数据验证和格式转换的功能。Entity（实体）是数据库中的数据模型，通常用于表示数据库表结构和数据关系。它主要用于与数据库进行交互，执行增删改查等操作。虽然是这么说，但是在实际项目中，Entity 常常在 NestJS 中被兼职将数据安全返回给客户端（与 @Exclude() 和 ClassSerializerInterceptor 配合使用），这也是 NestJS 提供的一种极具性价比的偷懒方案，避免了开发者每个接口都去写一个 Response DTO。特别是在与 Prisma ORM 结合使用时，Prisma 的 Entity 是由 Prisma 自动生成的，我们常常通过 TypeScript 的 implements 来约束 Entity 类必须实现 Prisma 的表结构类型，再通过 @Exclude() 等装饰器来实现数据脱敏转化，最后返回给客户端。[hide]生产级目录结构在正式展开之前，先看一下生产项目中推荐的目录结构，后续的所有示例都基于此结构。src/ ├── common/ # 全局公共模块 │ ├── decorators/ # 自定义装饰器 │ ├── filters/ # 异常过滤器 │ ├── guards/ # 守卫 │ ├── interceptors/ # 拦截器（含序列化拦截器配置） │ └── pipes/ # 管道（含全局 ValidationPipe） │ ├── modules/ │ └── user/ # 用户模块（以此为例） │ ├── dto/ │ │ ├── create-user.dto.ts # 创建用户请求 DTO │ │ ├── update-user.dto.ts # 更新用户请求 DTO（通常继承 create） │ │ └── query-user.dto.ts # 查询/分页 DTO │ ├── entities/ │ │ └── user.entity.ts # Entity（TypeORM）或 Prisma 的 Entity 包装类 │ ├── user.controller.ts │ ├── user.service.ts │ └── user.module.ts │ └── main.ts核心原则：dto/ 只负责"进出"，即请求校验与响应裁剪。entities/ 只负责数据库映射（TypeORM）或 Prisma 类型的装饰器增强包装。两者不混用，Service 层作为转换边界。依赖安装：class-validator 与 class-transformerDTO 的核心能力由这两个库提供，它们是搭档关系，缺一不可。pnpm add class-validator class-transformerclass-validator提供基于装饰器的数据校验能力。常用装饰器：装饰器说明@IsString()必须是字符串@IsInt()必须是整数@IsEmail()必须是合法邮箱@IsBoolean()必须是布尔值@IsOptional()字段可选，为空时跳过后续校验@IsNotEmpty()不能为空字符串/null/undefined@MinLength(n) / @MaxLength(n)字符串最小/最大长度@Min(n) / @Max(n)数值范围@IsEnum(Enum)必须是枚举值之一@IsArray()必须是数组@ValidateNested()嵌套对象递归校验需要全局启用管道，校验才会生效。有两种注册方式：方式一：在 main.ts 中注册// main.ts import { ValidationPipe } from "@nestjs/common"; async function bootstrap() { c…

07 NestJS 使用Caching缓存（cache-manager）
type: item
date: 2026-08-15T02:35:00+08:00
description: 前言相比较Redis，Nestjs官方还提供了一个更为通用的缓存模块 @nestjs/cache-manager，它是基于 cache-manager 封装的，支持多种缓存存储方式（内存、Redis、Memcached等），并且可以通过配置来切换不同的缓存存储。常见的缓存场景包括：数据库查询结果缓存接口请求结果缓存统一不同缓存存储的接口在与Redis结合使用，已经是生产环境中最常见的缓存方案之一。如果条件允许，可以配合使用。[hide]核心依赖安装# 核心依赖 pnpm add @nestjs/cache-manager cache-manager全局与异步配置常见做法是在 AppModule 中注册全局缓存模块，并通过 ConfigService 读取环境变量进行异步配置。1. 模块注册// app.module.ts import { Module } from '@nestjs/common'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { CacheModule } from '@nestjs/cache-manager'; import { UsersModule } from './users/users.module'; @Module({ imports: [ ConfigModule.forRoot({ isGlobal: true }), CacheModule.registerAsync({ isGlobal: true, // 设为全局模块，其他 Module 无需重复导入 imports: [ConfigModule], inject: [ConfigService], useFactory: async (config: ConfigService) => ({ // TTL (Time-To-Live)：毫秒为单位（cache-manager v5+ 统一为 ms） ttl: config.get<number>('CACHE_TTL', 30000), // 内存中最大缓存条目数（超出后按 LRU 算法自动淘汰） max: config.get<number>('CACHE_MAX_ITEMS', 500), }), }), UsersModule, ], }) export class AppModule {}2. 关键参数解析参数默认行为（未配置时）生产建议ttl (过期时间)默认为 0（表示永不过期）必须按需配置。业务数据通常具备时效性，通过 TTL 保证数据在未被主动清理时能定期回源。max (最大容量)默认为 Infinity（无条目上限）内存缓存场景下强烈建议配置。防止突发流量或恶意请求撑爆 Node.js 堆内存（默认约 1.4~4GB）导致 OOM 崩溃。💡 存储介质选型差异：单机内存模式（In-Memory）：必须同时配置 max 与 ttl，筑牢内存安全防线。分布式 Redis 模式：内存压力转移到了 Redis 实例，max 可交由 Redis 自身的 maxmemory-policy 策略管理，但应用层仍需配置合理的全局 ttl。接口响应自动缓存（基于 Interceptor）如果仅需将 Controller 接口的完整响应直接缓存起来，使用 NestJS 内置的 CacheInterceptor 最为便捷。1. 基础用法与装饰器说明// users.controller.ts import { Controller, Get, UseInterceptors } from "@nestjs/common"; import { CacheInterceptor, CacheKey, CacheTTL } from "@nestjs/cache-manager"; import { UsersService } from "./users.service"; @Controller("users") export class UsersController { constructor(private readonly usersService: UsersService) {} /** * 基础用法：默认根据请求 URL 生成 Key，继承全局 TTL */ @Get("ranking") @UseInterceptors(CacheInterceptor) // 启用自动响应缓存 async getRanking() { return this.usersService.getHeavyRanking(); } /** * 进阶用法：自定义固定 Key 与局部 TTL */ @…