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

基于Spring Boot Starter的GraphQL SPQR响应扩展添加方法问询

解决方案:为GraphQL响应添加根节点与错误节点的extensions字段

针对你的技术栈(Spring Boot + graphql-spqr-spring-boot-starter 0.0.7 + Apache Camel),可以通过以下两种独立的方案分别实现根节点和错误节点的extensions字段填充:

一、给响应根节点添加extensions

方案1:自定义Instrumentation(推荐)

利用graphql-java的Instrumentation扩展点,拦截查询执行结果并添加自定义extensions,无需替换核心执行器:

import graphql.ExecutionResult;
import graphql.execution.instrumentation.Instrumentation;
import graphql.execution.instrumentation.InstrumentationExecutionParameters;
import graphql.execution.ExecutionResultImpl;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

@Component
public class RootExtensionsInstrumentation implements Instrumentation {
    @Override
    public ExecutionResult instrumentExecutionResult(ExecutionResult executionResult, InstrumentationExecutionParameters parameters) {
        // 保留原始extensions(如果有),添加自定义信息
        Map<String, Object> extensions = new HashMap<>(executionResult.getExtensions());
        extensions.put("requestId", UUID.randomUUID().toString()); // 示例:请求唯一标识
        extensions.put("serviceVersion", "v1.0.0"); // 示例:服务版本
        extensions.put("executionDurationMs", System.currentTimeMillis() - parameters.getStartTime()); // 示例:执行耗时

        // 包装成新的ExecutionResult返回
        return new ExecutionResultImpl(
                executionResult.getData(),
                executionResult.getErrors(),
                extensions
        );
    }
}

Spring会自动检测并注册这个Instrumentation,无需额外配置。

方案2:自定义GraphQLMvcExecutor(修复你之前的实现)

你之前的自定义Executor问题在于直接调用super.execute后无法修改已序列化的响应对象,正确做法是先手动执行查询得到ExecutionResult,修改后再交给父类处理序列化:

import com.fasterxml.jackson.databind.ObjectMapper;
import graphql.ExecutionResult;
import graphql.GraphQL;
import io.leangen.graphql.execution.ExecutorParams;
import io.leangen.graphql.execution.context.ContextFactory;
import io.leangen.graphql.execution.dataLoader.DataLoaderRegistryFactory;
import io.leangen.graphql.server.execution.HttpExecutor;
import io.leangen.graphql.server.execution.GraphQLMvcExecutor;
import org.springframework.web.context.request.NativeWebRequest;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

public class CustomGraphQLExecutor extends HttpExecutor<NativeWebRequest> implements GraphQLMvcExecutor {

    private final ObjectMapper objectMapper;

    public CustomGraphQLExecutor(ContextFactory<NativeWebRequest> contextFactory,
                                 DataLoaderRegistryFactory dataLoaderRegistryFactory,
                                 ObjectMapper objectMapper) {
        super(contextFactory, dataLoaderRegistryFactory);
        this.objectMapper = objectMapper;
    }

    @Override
    public Object execute(GraphQL graphQL, ExecutorParams<NativeWebRequest> executorParams) {
        // 1. 手动执行GraphQL查询,获取原始ExecutionResult
        ExecutionResult rawResult = graphQL.execute(executorParams.getExecutionInput());

        // 2. 添加根节点extensions
        Map<String, Object> extensions = new HashMap<>(rawResult.getExtensions());
        extensions.put("requestId", UUID.randomUUID().toString());
        extensions.put("gatewayNode", "node-01");

        // 3. 包装成带自定义extensions的结果
        ExecutionResult customResult = new ExecutionResultImpl(
                rawResult.getData(),
                rawResult.getErrors(),
                extensions
        );

        // 4. 交给父类处理成最终响应
        return super.processExecutionResult(customResult, executorParams);
    }
}

然后注册这个Executor为Bean,替换默认实现:

import com.fasterxml.jackson.databind.ObjectMapper;
import io.leangen.graphql.execution.context.ContextFactory;
import io.leangen.graphql.execution.dataLoader.DataLoaderRegistryFactory;
import io.leangen.graphql.server.execution.GraphQLMvcExecutor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.NativeWebRequest;

@Configuration
public class GraphQLConfig {
    @Bean
    public GraphQLMvcExecutor graphQLMvcExecutor(ContextFactory<NativeWebRequest> contextFactory,
                                                DataLoaderRegistryFactory dataLoaderRegistryFactory,
                                                ObjectMapper objectMapper) {
        return new CustomGraphQLExecutor(contextFactory, dataLoaderRegistryFactory, objectMapper);
    }
}

二、给错误节点添加extensions

通过自定义GraphQLErrorHandler和错误适配器,将业务异常或GraphQL原生错误转换为带extensions的格式:

1. 定义自定义业务异常

public class BackendServiceException extends RuntimeException {
    private final String errorCode;
    private final Map<String, Object> debugInfo;

    public BackendServiceException(String message, String errorCode, Map<String, Object> debugInfo) {
        super(message);
        this.errorCode = errorCode;
        this.debugInfo = debugInfo;
    }

    // Getter方法
    public String getErrorCode() { return errorCode; }
    public Map<String, Object> getDebugInfo() { return debugInfo; }
}

2. 自定义GraphQLError适配器

import graphql.ErrorClassification;
import graphql.GraphQLError;
import graphql.language.SourceLocation;
import graphql.execution.ExceptionWhileDataFetching;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Component
public class CustomGraphQLErrorAdapter implements GraphQLError {
    private final GraphQLError delegate;

    public CustomGraphQLErrorAdapter(GraphQLError delegate) {
        this.delegate = delegate;
    }

    @Override
    public String getMessage() {
        return delegate.getMessage();
    }

    @Override
    public List<SourceLocation> getLocations() {
        return delegate.getLocations();
    }

    @Override
    public ErrorClassification getErrorType() {
        return delegate.getErrorType();
    }

    @Override
    public List<Object> getPath() {
        return delegate.getPath();
    }

    @Override
    public Map<String, Object> getExtensions() {
        Map<String, Object> extensions = new HashMap<>(delegate.getExtensions());
        
        // 处理业务异常
        if (delegate instanceof ExceptionWhileDataFetching) {
            ExceptionWhileDataFetching dataFetchError = (ExceptionWhileDataFetching) delegate;
            Throwable cause = dataFetchError.getException();
            if (cause instanceof BackendServiceException) {
                BackendServiceException serviceEx = (BackendServiceException) cause;
                extensions.put("errorCode", serviceEx.getErrorCode());
                extensions.put("debugInfo", serviceEx.getDebugInfo());
            }
        }
        
        // 给所有错误添加通用信息
        extensions.put("timestamp", System.currentTimeMillis());
        extensions.put("service", "graphql-rest-gateway");
        
        return extensions;
    }

    @Override
    public Throwable getException() {
        return delegate.getException();
    }
}

3. 注册自定义GraphQLErrorHandler

import graphql.GraphQLError;
import graphql.GraphQLErrorHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.List;
import java.util.stream.Collectors;

@Configuration
public class ErrorHandlerConfig {
    @Bean
    public GraphQLErrorHandler graphQLErrorHandler(CustomGraphQLErrorAdapter errorAdapter) {
        return new GraphQLErrorHandler() {
            @Override
            public List<GraphQLError> processErrors(List<GraphQLError> errors) {
                // 将所有错误转换为自定义格式
                return errors.stream()
                        .map(errorAdapter::new)
                        .collect(Collectors.toList());
            }
        };
    }
}

4. 在业务代码中抛出自定义异常

修改你的GraphQLAPIService,捕获Camel调用异常并转换为自定义业务异常:

@Service
@GraphQLApi
public class GraphQLAPIService {

    private final ProducerTemplate producerTemplate;

    public GraphQLAPIService(ProducerTemplate producerTemplate) {
        this.producerTemplate = producerTemplate;
    }

    @GraphQLQuery(name = "getInfo")
    public LoginInfo[] getInfo(@GraphQLArgument(name = "name") String name, @GraphQLArgument(name = "nick") String nick) {
        try {
            final Map<String, Object> headers = new HashMap<>();
            headers.put("name", name);
            headers.put("nick", nick);
            return producerTemplate.requestBodyAndHeaders("direct:getInfo", null, headers, LoginInfo[].class);
        } catch (CamelExecutionException e) {
            // 包装Camel异常为自定义业务异常
            Map<String, Object> debugInfo = new HashMap<>();
            debugInfo.put("endpoint", "direct:getInfo");
            debugInfo.put("requestParams", Map.of("name", name, "nick", nick));
            debugInfo.put("backendError", e.getCause().getMessage());
            
            throw new BackendServiceException("Failed to fetch data from backend", "BACKEND_CALL_FAILED", debugInfo);
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 01:37:33