如何在NestJS中根据用户角色动态调整API返回字段
在NestJS中根据用户角色动态隐藏/显示返回字段的方案
方案一:使用ClassSerializerInterceptor配合class-transformer分组功能(推荐)
这是NestJS官方推荐的序列化方式,能优雅地控制字段的显示逻辑。
步骤1:安装依赖
确保项目中已安装class-transformer和class-validator(NestJS CLI创建的项目通常默认包含):
npm install class-transformer class-validator
步骤2:定义带分组的DTO
创建产品DTO,通过@Exclude默认隐藏createdBy,再用@Expose指定仅管理员/店铺管理员角色可见:
import { Exclude, Expose } from 'class-transformer'; export class ProductDto { title: string; price: number; category: string; // 默认隐藏该字段 @Exclude() createdBy: string; // 仅指定分组的请求能看到这个字段 @Expose({ groups: ['admin', 'shop-admin'] }) get adminCreatedBy() { return this.createdBy; } }
步骤3:控制器中动态设置序列化分组
通过@SerializeOptions的回调函数,根据当前请求的用户角色返回对应分组,同时启用ClassSerializerInterceptor:
import { Controller, Get, Param, UseInterceptors, SerializeOptions, Request } from '@nestjs/common'; import { ClassSerializerInterceptor } from '@nestjs/common/serializer'; import { plainToInstance } from 'class-transformer'; import { ProductDto } from './product.dto'; import { ProductService } from './product.service'; @Controller('product') @UseInterceptors(ClassSerializerInterceptor) export class ProductController { constructor(private readonly productService: ProductService) {} @Get(':id') @SerializeOptions({ groups: (context) => { // 从请求中获取已认证用户的角色信息(需先完成身份验证,比如JWT) const user = context.switchToHttp().getRequest().user; const groups = []; if (user?.roles?.includes('admin') || user?.roles?.includes('shop-admin')) { groups.push('admin', 'shop-admin'); } return groups; }, }) async getProduct(@Param('id') id: string): Promise<ProductDto> { const product = await this.productService.getProductById(id); // 将数据库原始数据转换为DTO实例,触发序列化逻辑 return plainToInstance(ProductDto, product); } }
方案二:手动过滤字段(简单直接)
如果需求简单,可在控制器或服务层直接根据角色删除不需要的字段:
import { Controller, Get, Param, Request } from '@nestjs/common'; import { ProductService } from './product.service'; @Controller('product') export class ProductController { constructor(private readonly productService: ProductService) {} @Get(':id') async getProduct(@Param('id') id: string, @Request() req) { const product = await this.productService.getProductById(id); const user = req.user; // 非管理员角色删除createdBy字段 if (!(user?.roles?.includes('admin') || user?.roles?.includes('shop-admin'))) { delete product.createdBy; } return product; } }
这种方式适合字段少、逻辑简单的场景,但字段增多后维护成本会上升。
方案三:自定义拦截器统一处理
创建自定义拦截器,在全局或控制器层面统一处理字段过滤逻辑:
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; @Injectable() export class DynamicFieldInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable<any> { const request = context.switchToHttp().getRequest(); const user = request.user; return next.handle().pipe( map((responseData) => { // 非管理员角色过滤字段 if (!(user?.roles?.includes('admin') || user?.roles?.includes('shop-admin'))) { delete responseData.createdBy; } return responseData; }), ); } }
然后在控制器中使用该拦截器:
import { Controller, Get, Param, UseInterceptors } from '@nestjs/common'; import { DynamicFieldInterceptor } from './dynamic-field.interceptor'; import { ProductService } from './product.service'; @Controller('product') @UseInterceptors(DynamicFieldInterceptor) export class ProductController { constructor(private readonly productService: ProductService) {} @Get(':id') async getProduct(@Param('id') id: string) { return this.productService.getProductById(id); } }
注意事项
- 所有方案的前提是已完成用户身份验证,能从
req.user中获取用户角色信息(比如通过JWT守卫解析token)。 - 如果使用TypeORM实体,可直接在实体类上添加
class-transformer装饰器,无需额外创建DTO,查询出的实体实例可直接参与序列化。
内容的提问来源于stack exchange,提问作者Night owl
相关产品推荐
相关产品推荐

