后端API设计【免费下载链接】graphql-yoga Rewrite of a fully-featured GraphQL Server with focus on easy setup, performance great developer experience. The core of Yoga implements WHATWG Fetch API and can run/deploy on any JS environment.项目地址https://gitcode.com/gh_mirrors/gr/graphql-yoga点击查看免费下载envelop/rate-limiter是 graphql-yoga 仓库中 Envelop 插件体系的一员用于为 GraphQL 的查询与变更字段提供基于滑动时间窗口的限流能力。本文以该插件 10.1.010.2.1 版本的变化为核心结合插件源码与测试用例完整讲解指令式限流、按字段编程式限流、身份标识identifyFn/identifier模板的两种写法以及内存 / Redis 存储的实现原理帮助你在 Envelop 与 GraphQL Yoga 应用中落地可复制的限流方案。插件定位与快速上手envelop/rate-limiter基于社区库graphql-rate-limit实现通过 Envelop 插件机制在字段解析层拦截超额调用。安装后将其注册进 Envelop 插件列表即可yarn add envelop/rate-limiterimport { execute, parse, specifiedRules, subscribe, validate } from graphql import { envelop, useEngine } from envelop/core import { IdentifyFn, useRateLimiter } from envelop/rate-limiter const identifyFn: IdentifyFn context { return context.request.ip } const getEnveloped envelop({ plugins: [ useEngine({ parse, validate, specifiedRules, execute, subscribe }), // ... other plugins ... useRateLimiter({ identifyFn }) ] })按默认约定插件假定你的 GraphQL schema 中已经声明了限流指令定义。源码 index.ts 导出了标准指令 SDLdirective rateLimit( max: Int window: String message: String identityArgs: [String] arrayLengthField: String readOnly: Boolean uncountRejected: Boolean ) on FIELD_DEFINITION默认情况下插件假定 schema 中存在该指令定义你也可以通过rateLimitDirectiveName选项自定义指令名称测试用例 use-rate-limiter.spec.ts 验证了customDirective这一用法。指令式限流在 SDL 中声明字段配额在 schema SDL 中为字段挂上rateLimit指令插件只在解析该字段时才触发限流不影响未标记字段type Query { posts: [Post]! rateLimit( window: 5s, // 限流时间窗口配额重置周期 max: 10, // 时间窗口内允许的最大请求数 message: Too many calls! // 达到配额时的错误消息 ) # unlimitedField: String }该指令可以作用于任意 GraphQLfield定义不限于根字段Root Field即嵌套对象字段同样可被限流。动态错误消息插值message支持{{var}}或{{ var }}语法做变量插值目前唯一可用的变量是id即当前请求的身份标识type Query { posts: [Post]! rateLimit(window: 5s, max: 10, message: Too many calls made by {{ id }}) }在实现上默认插值函数defaultInterpolateMessageFn将标识符替换进消息index.ts并且你还可以通过interpolateMessage选项自定义插值逻辑该回调会收到消息、identifier 以及字段执行参数。测试 use-rate-limiter.spec.ts 验证了too many calls for {{ id }}会被替换为实际标识符。按字段编程式配置configByField对于不希望或无法修改 schema SDL 的场景插件提供configByField数组按「类型 字段」编程式声明限流规则。匹配采用 picomatch 通配模式type与field都支持 glob 表达式见 index.ts 中的picomatch编译过程useRateLimiter({ identifyFn: ctx ctx.ip, configByField: [ { type: Query, field: getProduct, window: 1m, max: 10, identifier: {args.id} }, { type: Query, field: search, window: 1m, max: 30, identifier: {context.ip} } ] })configByField每个条目支持与指令同名的参数max、window、message、identityArgs、arrayLengthField、readOnly、uncountRejected另加identifyFn与identifier详见类型定义 index.ts。关于配置冲突插件内置了双重防护index.ts同一字段命中多条configByField规则时抛出Config error: field Query.foo has multiple matching configuration同一字段同时存在configByField配置与指令时抛出Config error: field Query.foo has both a configuration and a directive。这两条规则在测试 use-rate-limiter.spec.ts 中均有对应用例而未被任何规则覆盖的字段则完全不受限流影响见「should not rate limit fields that are not in configByField」用例。两个关键默认值在字段配置解析时插件会将max强制转换为数字Number(rateLimitConfig.max)底层限流器 get-graphql-rate-limiter.ts 定义了默认值窗口DEFAULT_WINDOW 60 * 1000即 1 分钟、最大次数DEFAULT_MAX 5因此未显式指定window/max时按「60 秒内最多 5 次」执行。window字符串由ms库解析支持5s、1m、0.1s等常见时间单位写法。身份标识的两种新写法10.1.0 核心特性限流本质上是按「身份标识identity」分桶计数。插件级identifyFn接收执行上下文返回唯一标识调用者的字符串例如context.request.ip、context.user.id。10.1.0 版本变更记录见 CHANGELOG.md带来了两个重要增强1.identifier模板字符串identifyFn的轻量替代configByField条目可配置identifier模板支持{args.argName}与{context.propName}两种点路径插值。它等价于指令中的rateLimit(identityArgs: [...])但可直接在编程式配置中使用useRateLimiter({ identifyFn: ctx ctx.ip, configByField: [ { type: Query, field: getProduct, window: 1m, max: 10, identifier: {args.id} // 按 id 参数值分桶 }, { type: Query, field: search, window: 1m, max: 30, identifier: {context.ip} // 按 IP 分桶无需登录态 } ] })实现上模板通过resolveIdentifierTemplate用lodash.get做点路径取值index.ts取不到的值回退为空字符串。当同时设置了identifier与identifyFn时identifier优先见类型注释 index.ts。2.identifyFn现在接收解析后的字段参数当identifyFn通过configByField使用无论是条目级还是插件级兜底时会收到第二个参数解析后的字段参数值。这使得无需指令即可按参数值对未认证请求做限流useRateLimiter({ identifyFn: ctx ctx.ip, configByField: [ { type: Query, field: getProduct, // getProduct(id: ID!): Product! window: 1m, max: 10, identifyFn: (ctx, args) String(args.id) } ] })需要留意的一个行为细节CHANGELOG 中特别注明插件级identifyFn作为configByField条目的兜底时也会收到args但在指令式限流场景下被调用时args为空对象。源码中的类型定义也明确了这一点index.tsargs仅在经由configByField调用时才有值。与identityArgs的关系identityArgs指定参与限流 key 的字段参数名。当条目设置了identifyFn或identifier时插件会自动在identityArgs前注入identifier项index.ts使每次调用以「字段名 参数值」为 key 单独分桶。底层 key 的拼装逻辑位于 get-graphql-rate-limiter.ts以getFieldIdentity将fieldName与各identityArgs值用冒号连接例如books:1:Foo表示字段books、参数id1、titleFoo的独立桶。测试 use-rate-limiter.spec.ts 分别验证了identityArgs、{args.*}模板与{context.*}模板都能做到「同一参数/上下文值被限流不同值互不影响」。底层原理滑动窗口计数与执行期拦截字段执行前的 AST 拦截插件通过onExecute钩子在真正执行前对操作文档做visit遍历对文档中每个Field节点结合 schema 的TypeInfo反查出所属类型与字段定义然后调用getRateLimitConfig合并出最终限流配置指令优先或字段配置优先二者冲突即报错。命中限流的字段会通过rateLimiterFn检查配额index.ts。超额时的响应构造当限流触发时插件不会直接抛异常中止整个请求而是收集错误并调用setResultAndStopExecution构造一个带extensions.http.statusCode 429的GraphQLError并依据响应路径path精准定位到被限流的字段若配置了window还会附加Retry-After响应头值为窗口字符串。这也解释了为什么超额时 resolver 根本不会被调用——测试中numberOfCalls始终停留在 5。测试 use-rate-limiter.spec.ts 中有一个被it.skip挂起的用例「should return other fields even if one of them fails」提示当前版本中当同一次查询内某个字段被限流时其余字段的行为可能受停止执行策略影响使用时建议针对你的字段组合做验证。滑动时间窗口的计数方式底层getGraphQLRateLimiterget-graphql-rate-limiter.ts是核心计数逻辑由identifyContext得到 context 身份、由getFieldIdentity得到字段身份二者合并为Identity计算windowMs默认 60_000ms与maxCalls默认 5callCount可由arrayLengthField指定的数组参数长度决定例如按批量操作的条目数计费否则为 1从 store 读取历史时间戳过滤掉过期项t windowMs Date.now()加上本次批量请求缓存的时间戳判断是否limitReached除非readOnly否则将时间戳写回 storeuncountRejected为 true 时被拒绝的请求不会计入后续计数超额时返回错误消息字段级message或全局formatError默认文案为You are trying to access fieldName too often否则返回undefined。另外enableBatchRequestCache开启时同一请求内批量执行的同字段调用会通过弱引用缓存共享计数确保单次请求内多次调用也被统计见 batch-request-cache.ts。存储后端内存 Store 与 Redis Store插件通过Store抽象store.ts隔离计数存储接口仅两个方法setForIdentity(identity, timestamps, windowMs?)与getForIdentity(identity)。默认使用进程内内存存储 InMemoryStore以「contextIdentity → fieldIdentity → 时间戳数组」的嵌套结构保存适合单实例部署。多实例部署时则应改用 RedisStore将时间戳数组 JSON 序列化后写入以redis-store-id::为前缀的 key形如redis-store-id::contextIdentity:fieldIdentity并在写入时依据windowMs计算EX过期时间TTL使配额能跨实例共享import { RedisStore } from envelop/rate-limiter useRateLimiter({ identifyFn: ctx ctx.ip, store: new RedisStore(redisClient) })graphql-rate-limit的全部可用选项如store、identifyContext、formatError、enableBatchRequestCache等都可以原样传入useRateLimiterRateLimiterPluginOptions通过OmitGraphQLRateLimitConfig, identifyContext继承了这些字段见 index.ts。其他可用选项一览useRateLimiter的完整选项index.ts除上述外还包括选项说明identifyFn必填。返回标识调用者的字符串接收 context 与解析后的 argsrateLimitDirectiveName自定义限流指令名默认rateLimittransformError将限流错误消息转换为自定义Error抛出onRateLimitError限流触发时的回调收到错误消息、identifier 及字段执行参数可用于打点统计interpolateMessage自定义错误消息插值函数configByField按type/fieldpicomatch 通配的编程式限流配置数组版本演进小结10.1.0 → 10.2.x10.1.0新增identifier模板字符串{args.*}/{context.*}点路径插值identifyFn通过configByField调用时可接收字段参数值作为第二参数。10.2.0升级以兼容 graphql-js 17调整类型推断并修正与subscribe的兼容性。10.2.1仅为 package.json 补充homepage与bugs.url元数据同时随依赖链升级envelop/core与envelop/on-resolve。上述变更细节与 PR 链接可查看插件根目录的 CHANGELOG.md。若要进一步深入可继续阅读源码入口 index.ts、核心计数实现 get-graphql-rate-limiter.ts以及覆盖指令式与编程式两种路径的测试 use-rate-limiter.spec.ts。赞分享后端API设计【免费下载链接】graphql-yoga Rewrite of a fully-featured GraphQL Server with focus on easy setup, performance great developer experience. The core of Yoga implements WHATWG Fetch API and can run/deploy on any JS environment.项目地址https://gitcode.com/gh_mirrors/gr/graphql-yoga点击查看免费下载相关推荐使用 envelop/rate-limiter 为 GraphQL Yoga 服务实现字段级限流使用 envelop/rate limiter 为 GraphQL Yoga 服务实现字段级限流 本篇技术指南围绕 Envelop 生态中的 envelop后端API设计Node.js限流器Rate Limiter实战指南基于jhurliman/node-rate-limiterNode.js限流器Rate Limiter实战指南基于jhurliman/node rate limiter 欢迎来到本教程我们将深入探索 jhurl后端GraphQL Yoga 生态实战基于 Envelop 与 graphql-sse 实现 Server-Sent Events 订阅服务GraphQL Yoga 生态实战基于 Envelop 与 graphql sse 实现 Server Sent Events 订阅服务 本文以仓库中 exa后端API设计上一篇PentestGPTAI驱动的自动化渗透测试工具零基础入门指南下一篇终极指南Diem智能合约审计的第三方安全审查最佳实践创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考