1

我已经设法将自定义指令添加到 GraphQL 架构,但我正在努力研究如何将自定义指令添加到字段定义中。有关正确实施的任何提示都会非常有帮助。我正在使用 GraphQL SPQR 0.9.6 来生成我的架构

4

1 回答 1

4

原始答案:(现已过时,请参阅下面的 2 个更新)

目前无法做到这一点。GraphQL SPQR v0.9.9 将率先支持自定义指令。

尽管如此,在 0.9.8 中仍有可能的解决方法,具体取决于您要实现的目标。SPQR 自己的关于字段或类型的元数据保存在自定义指令中。知道了这一点,您就可以掌握 GraphQL 字段定义底层的 Java 方法/字段。如果您想要的是例如基于指令执行某些操作的工具,则您可以获取底层元素上的任何注释,让您可以使用 Java 的全部功能。

获取方法的方式类似于:

Operation operation = Directives.getMappedOperation(env.getField()).get();
Resolver resolver = operation.getApplicableResolver(env.getArguments().keySet());
Member underlyingElement = resolver.getExecutable().getDelegate();

更新:我在这个 GitHub 问题 上发布了一个巨大的答案。也贴在这里。

您可以像这样注册一个附加指令:

generator.withSchemaProcessors(
    (schemaBuilder, buildContext) -> schemaBuilder.additionalDirective(...));

但是(根据我目前的理解),这只对查询指令有意义(客户端作为查询的一部分发送的东西,比如@skipor @deffered)。

像这样的指令@dateFormat在 SPQR 中根本没有意义:它们可以在解析 SDL 并将其映射到您的代码时为您提供帮助。在 SPQR 中,没有 SDL,您从代码开始。例如@dateFormat,用于告诉您在将特定字段映射到 Java 时需要为特定字段提供日期格式。在 SPQR 中,您从 Java 部分开始,GraphQL 字段是从 Java 方法生成的,因此该方法必须已经知道它应该返回什么格式。或者它已经有适当的注释。在 SPQR 中,Java 是事实的来源。您使用注释来提供额外的映射信息。指令基本上是 SDL 中的注释。

尽管如此,字段或类型级别的指令(或注释)在检测中非常有用。例如,如果您想拦截字段解析并检查身份验证指令。在这种情况下,我建议您只需将注释用于相同目的。

public class BookService {
     
      @Auth(roles= {"Admin"}) //example custom annotation
      public Book addBook(Book book) { /*insert a Book into the DB */ }
}

由于每个 GraphQLFieldDefinition 都由 Java 方法(或字段)支持,因此您可以在拦截器或任何地方获取底层对象:

GraphQLFieldDefinition field = ...;
Operation operation = Directives.getMappedOperation(field).get();

//Multiple methods can be hooked up to a single GraphQL operation. This gets the @Auth annotations from all of them
Set<Auth> allAuthAnnotations = operation.getResolvers().stream()
                .map(res -> res.getExecutable().getDelegate()) //get the underlying method
                .filter(method -> method.isAnnotationPresent(Auth.class))
                .map(method -> method.getAnnotation(Auth.class))
                .collect(Collectors.toSet());

或者,仅检查可以处理当前请求的方法:

DataFetchingEnvironment env = ...; //get it from the instrumentation params      
Auth auth = operation.getApplicableResolver(env.getArguments().keySet()).getExecutable().getDelegate().getAnnotation(Auth.class);

然后您可以根据需要检查您的注释,例如

Set<String> allNeededRoles = allAuthAnnotations.stream()
                                             .flatMap(auth -> Arrays.stream(auth.roles))
                                             .collect(Collectors.toSet());

if (!currentUser.getRoles().containsAll(allNeededRoles)) {
    throw new AccessDeniedException(); //or whatever is appropriate
}

当然,实际上并没有真正需要以这种方式实现身份验证,因为您可能正在使用像 Spring 或 Guice 之类的框架(甚至可能 Jersey 具有所需的安全功能),它们已经有办法拦截所有方法并实现安全性。所以你可以用它来代替。更简单,更安全。例如对于 Spring Security,请继续正常使用它:

public class BookService {
     
      @PreAuth(...) //standard Spring Security
      public Book addBook(Book book) { /*insert a Book into the DB */ }
}

如果这就是您所追求的,请确保您还阅读了我关于在 GraphQL 中实现安全性的答案。

您可以使用仪器以相同的方式动态过滤结果:在方法上添加注释,从仪器访问它,并动态处理结果:

public class BookService {
     
      @Filter("title ~ 'Monkey'") //example custom annotation
      public List<Book> findBooks(...) { /*get books from the DB */ }
}

new SimpleInstrumentation() {
    
    // You can also use beginFieldFetch and then onCompleted instead of instrumentDataFetcher
    @Override
    public DataFetcher<?> instrumentDataFetcher(DataFetcher<?> dataFetcher, InstrumentationFieldFetchParameters parameters) {
        GraphQLFieldDefinition field = parameters.getEnvironment().getFieldDefinition();
        Optional<String> filterExpression = Directives.getMappedOperation(field)
                .map(operation ->
                        operation.getApplicableResolver(parameters.getEnvironment().getArguments().keySet())
                                .getExecutable().getDelegate()
                                .getAnnotation(Filter.class).value()); //get the filtering expression from the annotation
        return filterExpression.isPresent() ? env -> filterResultBasedOn Expression(dataFetcher.get(parameters.getEnvironment()), filterExpression) : dataFetcher;
    }
}

对于类型的指令,同样,只需使用 Java 注释。您可以通过以下方式访问基础类型:

Directives.getMappedType(graphQLType).getAnnotation(...);

同样,这可能仅在仪器中才有意义。这么说是因为指令通常会提供额外的信息来将 SDL 映射到 GraphQL 类型。在 SPQR 中,您将 Java 类型映射到 GraphQL 类型,因此在大多数情况下,指令在该上下文中没有意义。

当然,如果你仍然需要一个类型的实际 GraphQL 指令,你总是可以提供一个自定义TypeMapper的把它们放在那里。

对于字段上的指令,目前在 0.9.8 中是不可能的。

0.9.9 将对任何元素提供完整的自定义指令支持,以防您仍然需要它们。

更新 2: GraphQL SPQR 0.9.9已经发布。

现在支持自定义指令。有关详细信息,请参见问题#200

任何带有元注释的自定义注释@GraphQLDirective都将被映射为注释元素上的指令。

例如,想象一个@Auth(requiredRole = "Admin")用于表示访问限制的自定义注释:

@GraphQLDirective //Should be mapped as a GraphQLDirective
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD}) //Applicable to methods
public @interface Auth {
        String requiredRole();
}

如果解析器方法被注释为@Auth

@GraphQLMutation
@Auth(requiredRole = {"Admin"})
public Book addBook(Book newBook) { ... }

生成的 GraphQL 字段填充如下所示:

type Mutation {
  addBook(newBook: BookInput): Book @auth(requiredRole : "Admin")
}

也就是说@Auth,由于@GraphQLDirective元注释的存在,注释被映射到指令。

可以通过以下方式添加客户端指令:GraphQLSchemaGenerator#withAdditionalDirectives(java.lang.reflect.Type...).

SPQR 0.9.9 还带有ResolverInterceptors,它可以拦截解析器方法调用并检查注释/指令。它们比Instrumentations 使用起来更方便,但没有那么通用(范围更有限)。有关详细信息,请参见问题#180,以及使用示例的相关测试

例如,利用@Auth上面的注释(不是@Auth 不需要作为指令才能工作):

public class AuthInterceptor implements ResolverInterceptor {

    @Override
    public Object aroundInvoke(InvocationContext context, Continuation continuation) throws Exception {
        Auth auth = context.getResolver().getExecutable().getDelegate().getAnnotation(Auth.class);
        User currentUser = context.getResolutionEnvironment().dataFetchingEnvironment.getContext();
        if (auth != null && !currentUser.getRoles().containsAll(Arrays.asList(auth.rolesRequired()))) {
            throw new IllegalAccessException("Access denied"); // or return null
            }
        return continuation.proceed(context);
    }
}

如果@Auth是指令,您也可以通过常规 API 获取它,例如

List<GraphQLDirective> directives = dataFetchingEnvironment.getFieldDefinition().get.getDirectives();
DirectivesUtil.directivesByName(directives);
于 2018-07-30T08:51:57.017 回答