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

Apollo Client前端校验GraphQL输入对象额外字段方案

问题根因

你当前遇到的问题来自两个核心原因:

  • 输入类型定义错误:GraphQL 中输入参数类型必须使用 GraphQLInputObjectType,你代码里用的 GraphQLObjectType 是输出类型,不能用于变量定义,且 fields 字段的写法也不符合规范,需要显式指定字段的 type 属性。
  • Apollo Client 默认遵循 GraphQL 官方规范,规范明确允许输入对象携带未定义的额外字段,默认校验逻辑只会检查必填项、基础类型匹配度,不会拦截多余字段,多余字段会直接透传给服务端。
前置修正:输入类型定义

先把错误的类型定义改成符合规范的写法:

import { GraphQLInputObjectType, GraphQLString } from 'graphql';
const mutationInput = new GraphQLInputObjectType({
  name: 'mutationInput',
  fields: {
    Id: { type: GraphQLString },
    statusCode: { type: GraphQLString },
  }
});
实现方案

以下两种方案均为纯前端实现,不需要服务端做任何修改即可实现多余字段拦截。

方案1:单请求自定义校验规则

适合仅需要对特定查询/变更做校验的场景,利用 Apollo Client 内置的 validationRules 配置项注入自定义校验逻辑:

  1. 编写禁止额外输入字段的校验规则:
import { GraphQLError } from 'graphql';

const noExtraInputFieldsRule = (context) => ({
  ObjectValue(node) {
    const currentInputType = context.getInputType();
    if (!currentInputType?.getFields) return;
    const allowedFieldNames = Object.keys(currentInputType.getFields());
    node.fields.forEach(fieldNode => {
      const fieldName = fieldNode.name.value;
      if (!allowedFieldNames.includes(fieldName)) {
        context.reportError(
          new GraphQLError(
            `字段"${fieldName}"未在输入类型"${currentInputType.name}"中定义,禁止传入`,
            [fieldNode]
          )
        );
      }
    });
  }
});
  1. 在对应的 hook 中注入规则,触发请求时如果存在多余字段会直接抛出校验错误,终止请求:
const [getData, { error, loading }] = useMutation(
  GET_DATA,
  {
    validationRules: [noExtraInputFieldsRule],
    // 其余原有配置
  }
);

方案2:全局链路统一校验

如果需要对项目内所有 GraphQL 请求统一做多余字段校验,可通过 Apollo Link 实现全局拦截,不需要每个请求单独配置:

import { ApolloLink } from '@apollo/client';
import { GraphQLError, TypeInfo, visit, visitWithTypeInfo } from 'graphql';

// 传入项目的完整schema(可通过内省获取,或构建时直接引入本地schema文件)
const inputValidationLink = (schema) => new ApolloLink((operation, forward) => {
  const { query } = operation;
  const typeInfo = new TypeInfo(schema);
  const validationErrors = [];

  const visitor = {
    ObjectValue(node) {
      const currentInputType = typeInfo.getInputType();
      if (!currentInputType?.getFields) return;
      const allowedFieldNames = Object.keys(currentInputType.getFields());
      node.fields.forEach(fieldNode => {
        const fieldName = fieldNode.name.value;
        if (!allowedFieldNames.includes(fieldName)) {
          validationErrors.push(
            new GraphQLError(`非法输入字段"${fieldName}",不属于输入类型"${currentInputType.name}"`)
          );
        }
      });
    }
  };

  visit(query, visitWithTypeInfo(typeInfo, visitor));

  if (validationErrors.length > 0) {
    throw new Error(`请求变量校验失败:${validationErrors.map(err => err.message).join(';')}`);
  }

  return forward(operation);
});

// 初始化Apollo Client时将该链路放在链路最前面
const client = new ApolloClient({
  link: inputValidationLink(projectSchema).concat(httpLink),
  cache: new InMemoryCache(),
  // 其余原有配置
});
注意事项
  • 字段名大小写敏感:你示例中定义的字段是Id,传入的是ID,开启校验后该字段也会被判定为非法字段,需要保证传入字段名和类型定义完全一致。
  • 上述两种方案默认支持递归校验嵌套输入对象,不需要额外编写递归处理逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 19:06:43