NestJS 中的自定义 ClassSerializerInterceptor 基于用户角色进行序列化(表达 req 对象)

问题描述 投票:0回答:2

在NestJS路由中,我想根据请求(用户角色)序列化响应。这意味着我需要将选项

groups: []
传递到
transformToPlain
中的
ClassSerializerInterceptor
方法中,以便类转换器可以正确返回过滤格式:

这是原始拦截器源代码:https://github.com/nestjs/nest/blob/master/packages/common/serializer/class-serializer.interceptor.ts

我刚刚扩展了此类并更改了

intercept
方法,以根据请求包含
group
选项:

export class CustomClassSerializerInterceptor extends ClassSerializerInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    // @ts-ignore
    const contextOptions = this.getContextOptions(context);
    const options = {
      ...this.defaultOptions,
      ...contextOptions,
      groups: request.user.roles // Pseudo
    };
    return next
      .handle()
      .pipe(map((res: PlainLiteralObject | Array<PlainLiteralObject>) => this.serialize(res, options)));
  }
}

我的实体:

export class Content extends BaseDatabaseEntity {
  @Column()
  @Transform((type) => EContentType[type])
  type: EContentType;

  @Expose({ groups: ["MODERATOR", "ADMIN"] })
  @Column({ default: "[]", type: "json" })
  data: TContentDataColumn[];
}

这是正确的做法吗?例如

this.getContextOptions
方法在原始源中是私有的,所以我需要在这里执行 ts-ignore ,以覆盖类的默认预期隐私,这在我看来是很大的禁忌。

我是否应该根据用户的角色来转换 API 响应,或者这是反模式?

typescript nestjs interceptor
2个回答
3
投票

您可以使用

@SerializationOptions()
装饰器根据触发的路由来传递额外选项。由于您似乎需要动态执行此操作,因此您甚至可以使用
Reflect
命名空间并设置每个请求的元数据。元数据标记是
'class_serializer:options'
,所以你可以做类似的事情

Reflect.defineMetadata(
  'class_serializer:options',
  { groups: req.user.roles },
  Class.prototype,
  'method'
);

不一定很漂亮,但可以用


0
投票

对于 2023 年阅读本文的人

是的,扩展

ClassSerializerInterceptor
是向其中添加更多功能的充分方法。

正如@Baterka在他的问题中指出的那样,这总是有效的,但会触发编译器警告。

从那时起,源代码已更改,因此以前的 private 字段(如

defaultOptions
getContextOptions
)现在改为 protected,这意味着它们可用于派生类。

这是一个与 NestJS 8.0 或更高版本完全兼容的工作示例:

角色序列化器.interceptor.ts

import { CallHandler, ClassSerializerInterceptor, ExecutionContext, Injectable, PlainLiteralObject } from '@nestjs/common';
import { map, Observable } from 'rxjs';
 
@Injectable()
export class RolesSerializerInterceptor extends ClassSerializerInterceptor {
 intercept(
   context: ExecutionContext,
   next: CallHandler
 ): Observable<any> {
   const userRoles  = context.switchToHttp().getRequest().user?.roles ?? [];
   const contextOptions = this.getContextOptions(context);
   const options = {
     ...this.defaultOptions,
     ...contextOptions,
     groups: userRoles
   };
   return next
     .handle()
     .pipe(
       map((res: PlainLiteralObject | Array<PlainLiteralObject>) =>
         this.serialize(res, options)
       )
     );
 }
}```
© www.soinside.com 2019 - 2024. All rights reserved.