You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Nestjs+Apollo的GraphQL联邦:子图用户存在性验证实现

在GraphQL联邦中实现创建应用前的用户存在性验证

针对你的场景,有三种可行的实现方式,结合NestJS + Apollo GraphQL联邦的特性,具体如下:

方法一:在Application子图的Resolver中直接调用USER子图验证

在创建应用的Resolver逻辑里,先发起请求到USER子图的查询接口,验证传入的userId对应的用户是否存在,再执行创建操作。

步骤与代码示例:

  1. 注入HttpService用于发起HTTP请求到USER子图
  2. 在createApplication方法中构造查询USER子图的GraphQL语句
  3. 根据查询结果判断用户是否存在,不存在则抛出错误
import { Resolver, Mutation, Args } from '@nestjs/graphql';
import { HttpService } from '@nestjs/axios';
import { firstValueFrom } from 'rxjs';
import { ForbiddenException } from '@nestjs/common';
import { ApplicationService } from './application.service';
import { CreateApplicationInput, Application } from './dto';

@Resolver()
export class ApplicationResolver {
  constructor(
    private readonly appService: ApplicationService,
    private readonly httpService: HttpService
  ) {}

  @Mutation(() => Application)
  async createApplication(
    @Args('createApplicationInput') input: CreateApplicationInput
  ) {
    // 构造查询USER子图的GraphQL请求
    const userValidationQuery = `
      query ValidateUser($id: Int!) {
        user(id: $id) {
          id
        }
      }
    `;

    try {
      const response = await firstValueFrom(
        this.httpService.post('http://user-subgraph的实际地址/graphql', {
          query: userValidationQuery,
          variables: { id: input.userId }
        })
      );

      // 若查询不到用户,抛出错误
      if (!response.data.data.user) {
        throw new ForbiddenException('指定的用户不存在');
      }
    } catch (err) {
      throw new ForbiddenException('用户存在性验证失败,请稍后重试');
    }

    // 验证通过,执行创建应用逻辑
    return this.appService.create(input);
  }
}

方法二:在联邦网关层添加自定义验证指令

将验证逻辑集中在网关层,通过自定义GraphQL指令拦截createApplication请求,先验证用户存在性,再转发请求到Application子图。

步骤与代码示例:

  1. 在网关定义自定义指令@validateUserExists
  2. 实现指令的验证逻辑,调用USER子图查询用户
  3. 在Application子图的mutation上添加该指令

网关侧指令实现:

import { ApolloServerPlugin, GraphQLRequestContext } from '@apollo/server';
import { HttpService } from '@nestjs/axios';
import { firstValueFrom } from 'rxjs';
import { Injectable } from '@nestjs/common';

@Injectable()
export class ValidateUserPlugin implements ApolloServerPlugin {
  constructor(private readonly httpService: HttpService) {}

  async requestDidStart() {
    return {
      async didResolveOperation(context: GraphQLRequestContext) {
        // 定位当前mutation操作及验证指令
        const opDef = context.document.definitions.find(
          def => def.kind === 'OperationDefinition' && def.operation === 'mutation'
        ) as any;
        if (!opDef) return;

        const mutationField = opDef.selectionSet.selections[0];
        const validateDirective = mutationField.directives?.find(
          dir => dir.name.value === 'validateUserExists'
        );

        if (validateDirective) {
          // 提取需要验证的字段(这里是userId)
          const targetField = validateDirective.arguments.find(
            arg => arg.name.value === 'field'
          ).value.value;
          const userId = context.request.variables.createApplicationInput[targetField];

          // 调用USER子图验证用户
          const userQuery = `
            query GetUser($id: Int!) {
              user(id: $id) { id }
            }
          `;
          const response = await firstValueFrom(
            this.httpService.post('http://user-subgraph的实际地址/graphql', {
              query: userQuery,
              variables: { id: userId }
            })
          );

          if (!response.data.data.user) {
            throw new Error('指定的用户不存在');
          }
        }
      }
    };
  }
}

Application子图的Schema修改:

directive @validateUserExists(field: String!) on FIELD_DEFINITION

type Mutation {
  createApplication(
    createApplicationInput: CreateApplicationInput!
  ): Application! @validateUserExists(field: "userId")
}

input CreateApplicationInput {
  name: String!
  userId: Int!
}

方法三:利用联邦实体引用机制验证

通过在Application子图中定义User实体的引用Resolver,尝试加载指定userId的用户实体,加载失败则视为用户不存在。

步骤与代码示例:

  1. 在Application子图中定义User实体的联邦类型
  2. 实现User实体的引用Resolver,调用USER子图查询用户
  3. 在创建应用的Resolver中尝试加载User实体

Application子图的Schema定义:

type User @key(fields: "id") {
  id: Int!
}

type Mutation {
  createApplication(
    createApplicationInput: CreateApplicationInput!
  ): Application!
}

input CreateApplicationInput {
  name: String!
  userId: Int!
}

User实体的引用Resolver:

import { Resolver, ResolveReference } from '@nestjs/graphql';
import { HttpService } from '@nestjs/axios';
import { firstValueFrom } from 'rxjs';

@Resolver(() => User)
export class UserReferenceResolver {
  constructor(private readonly httpService: HttpService) {}

  @ResolveReference()
  async resolveReference(reference: { __typename: string; id: number }) {
    const response = await firstValueFrom(
      this.httpService.post('http://user-subgraph的实际地址/graphql', {
        query: `
          query GetUser($id: Int!) {
            user(id: $id) { id }
          }
        `,
        variables: { id: reference.id }
      })
    );
    return response.data.data.user;
  }
}

修改createApplication的Resolver:

@Mutation(() => Application)
async createApplication(
  @Args('createApplicationInput') input: CreateApplicationInput,
  @Context() context: any
) {
  try {
    // 通过联邦加载器尝试获取用户实体
    const user = await context.loaders.user.load(input.userId);
    if (!user) {
      throw new ForbiddenException('指定的用户不存在');
    }
  } catch (err) {
    throw new ForbiddenException('用户存在性验证失败');
  }

  return this.appService.create(input);
}

三种方式对比

  • 方法一:实现简单,逻辑闭环在子图内,适合小型项目;缺点是硬编码USER子图地址,扩展性一般。
  • 方法二:验证逻辑集中在网关,子图无需关心验证规则,适合多子图统一验证的场景;缺点是增加网关复杂度。
  • 方法三:符合GraphQL联邦的设计理念,复用实体引用机制,适合已有跨子图实体交互的项目;缺点是需要额外定义实体和Resolver。

内容的提问来源于stack exchange,提问作者Midhun Murali

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 15:05:17