NestJS为ResolveField加Guard后如何让非管理员请求时对应字段返回null
可行解决方案
下面是两种可直接落地的实现方案:
方案1:业务层直接校验(改造成本最低)
你当前使用的@Roles装饰器绑定的Guard校验不通过时会直接抛出ForbiddenException,你可以把校验逻辑下沉到字段解析器内部实现需求:
- 先移除字段解析器上的
@Roles(ROLES.ADMIN)装饰器 - 在解析函数中注入请求上下文,手动判断当前登录用户角色
- 角色为ADMIN时返回正常查询结果,不符合要求时直接返回null
示例代码:
@ResolveField('product', () => String) async getProduct( @Parent() product: Product, @Context() ctx: any ) { // 上下文取用户的路径根据你实际存储登录态的逻辑调整即可 const currentUser = ctx.req.user; if (currentUser?.roles?.includes(ROLES.ADMIN)) { return this.productService.getProduct(product); } return null; }
方案2:调整全局Guard逻辑(适合多字段复用场景)
如果你不想把权限校验逻辑耦合到业务代码里,可以修改全局RolesGuard的拦截规则:
- Guard校验权限时先区分当前请求是普通Query/Mutation,还是字段解析器请求
- 如果是字段解析器请求,权限校验不通过时不抛出异常,而是往上下文注入一个权限不足的标识位
- 搭配全局字段拦截器,读取标识位后直接返回null,不执行解析器逻辑
示例Guard核心逻辑:
@Injectable() export class RolesGuard implements CanActivate { canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> { const requiredRoles = this.reflector.get<Role[]>('roles', context.getHandler()); if (!requiredRoles) return true; const gqlCtx = GqlExecutionContext.create(context); const currentUser = gqlCtx.getContext().req.user; const hasRequiredRole = currentUser?.roles?.some(role => requiredRoles.includes(role)); // 判断是否为字段解析器请求 const isFieldResolver = !['Query', 'Mutation'].includes(gqlCtx.getInfo().parentType.name); if (isFieldResolver && !hasRequiredRole) { gqlCtx.getContext().fieldForbidden = true; // 直接返回true放行,不抛异常 return true; } if (!hasRequiredRole) throw new ForbiddenException(); return true; } }
配套全局拦截器代码:
@Injectable() export class FieldForbiddenInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable<any> { const gqlCtx = GqlExecutionContext.create(context); if (gqlCtx.getContext().fieldForbidden) { return of(null); } return next.handle(); } }
注意:两种方案都需要确保你GraphQL Schema里的
product字段定义为可空类型(product: String而非product: String!),否则返回null时会触发类型校验错误。
内容的提问来源于stack exchange,提问作者Abderrahmane Arache
相关产品推荐
相关产品推荐

