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

如何在Apollo Server中实现单订阅查询FilterSet,避免重复调用第三方API?

解决Apollo Server中DataLoader重复调用第三方API的问题

你的DataLoader实现存在两个核心问题,导致无法正确批处理请求,进而重复调用第三方API:

  1. 返回结果与请求keys顺序不匹配:DataLoader要求batch函数返回的数组必须和传入的incIDs顺序完全对应,每个位置的元素对应对应下标的key。你当前代码里过滤后的foundSets是API返回的原始顺序,和请求顺序不一致,这会让DataLoader无法正确关联key和结果,最终触发重复加载。
  2. 未处理缺失的FilterSet:如果某个请求的FilterSet不存在,返回数组里没有对应位置的元素,DataLoader会认为该key未被加载,后续会重新发起请求。

修正后的代码实现

1. 修复IPFilterSetDataSource的DataLoader逻辑

import axios from 'axios';
import DataLoader from 'dataloader';

const TemplateHeaders = {
    "Content-Type": "application/json",
    "Accept": "application/json"
};

export class IPFilterSetDataSource {
    apiKey;
    batchIPFilterSets;

    constructor(apiKey) {
        this.apiKey = apiKey;
        this.batchIPFilterSets = new DataLoader(async (incIDs) => {
            // 先按订阅ID分组,避免跨订阅的请求混在一起处理
            const subscriptionGroups = incIDs.reduce((groups, fullId) => {
                const [subscriptionId, filterId] = fullId.split("_");
                if (!groups[subscriptionId]) {
                    groups[subscriptionId] = [];
                }
                groups[subscriptionId].push({ fullId, filterId });
                return groups;
            }, {});

            // 存储每个请求key对应的结果
            const resultMap = {};

            // 对每个订阅单独发起一次API请求
            await Promise.all(Object.entries(subscriptionGroups).map(async ([subscriptionId, requests]) => {
                const apiOptions = {
                    headers: {
                        ...TemplateHeaders,
                        "Authorization": `Bearer ${this.apiKey}`
                    }
                };
                const ipFilterSets = (await axios.get(`https://example.api.com/v1/subscriptions/${subscriptionId}/ipfiltersets`, apiOptions)).data;
                
                // 把FilterSet用code做key存到Map里,方便快速查找
                const filterSetMap = ipFilterSets.reduce((map, set) => {
                    map[set.code] = set;
                    return map;
                }, {});

                // 为当前订阅下的每个请求匹配结果,找不到就返回null
                requests.forEach(({ fullId, filterId }) => {
                    resultMap[fullId] = filterSetMap[filterId] || null;
                });
            }));

            // 严格按照原始请求的keys顺序返回结果
            return incIDs.map(fullId => resultMap[fullId]);
        });
    }

    async getIpFilterSetForId(subscriptionId, id) {
        return this.batchIPFilterSets.load(`${subscriptionId}_${id}`);
    }
}

2. Resolver代码(可选优化)

Resolver可以保持原样,不过用Promise.all显式处理Promise数组会更清晰,效果和直接返回map结果一致:

Environment: {
    ipFilteringSets: async (parent, __, { subscriptionCode, dataSources }) => {
        return Promise.all(
            parent.ipFilteringSets.map(s => dataSources.ipFilterSet.getIpFilterSetForId(subscriptionCode, s))
        );
    }
}

关键修改说明

  • 按订阅分组处理:同一批次里的请求会按订阅ID分组,每个订阅只发起一次API调用,彻底避免重复请求。
  • 结果顺序严格匹配:通过Map快速查找后,按照原始请求的keys顺序返回结果,确保DataLoader能正确关联每个key和对应的结果。
  • 处理缺失的FilterSet:对于不存在的FilterSet返回null,避免DataLoader因结果缺失而重复发起请求。

这样修改后,同一请求中同一订阅的所有FilterSet请求会合并成一次第三方API调用,解决你遇到的重复调用问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 08:14:54