:用 maxTake 保护列表查询的实战指南)
后端【免费下载链接】keystoneThe superpowered headless CMS for Node.js — built with GraphQL and React项目地址https://gitcode.com/gh_mirrors/key/keystone点击查看免费下载导读在 Headless CMS 的日常使用中一次查询返回多少条记录是一个容易被忽视却直接影响性能与安全的配置。本指南基于 Keystone 官方示例项目 examples/limits 展开演示如何通过graphql.maxTake为列表的 GraphQL 查询设置单次最多返回记录数上限并深入源码剖析该限制在 Keystone 底层是如何被生成进 Schema、又如何被强制执行与报错的。读完本文你将掌握maxTake的配置写法、它的默认值行为、超出上限时的报错形态以及如何用示例自带的批量种子脚本做压测验证。示例项目概览一个只展示 limits 能力的最小工程examples/limits/README.md 开门见山地说明这个项目专门用来演示在 Keystone 中给 GraphQL Schema 添加不同类型上限limits的能力。它是刻意做小的最小可运行工程方便你专注观察maxTake一个特性。项目的核心文件布局如下keystone.tsKeystone 入口配置使用 SQLitebetter-sqlite3 adapter作为数据库schema.ts定义了唯一的Post列表并在其中配置graphql.maxTake: 20seed-data.ts批量造数脚本默认写入10 万条Post记录用于验证上限效果package.json提供dev、seed-data等 npm scriptsschema.graphqlKeystone 根据配置自动生成的 GraphQL Schema 快照。可以看出这个工程刻意将业务复杂度降到最低——只有一个Post列表、只有一个title字段——全部注意力都集中在maxTake这一个配置点上。快速启动clone、安装、运行按照 examples/limits/README.md 中的说明运行步骤如下在本地 clone Keystone 仓库后在仓库根目录执行pnpm install安装全部 workspace 依赖进入示例目录并启动开发服务pnpm dev启动成功后Admin UI运行在http://localhost:3000你可以在界面中直接创建数据库记录GraphQL Playground运行在http://localhost:3000/api/graphql可以直接执行查询query和变更mutation。可选灌入示例数据示例自带了一个非常激进的造数脚本用于把数据量撑到能触发上限的程度。使用方法至少先运行一次pnpm dev确保数据库已初始化执行pnpm seed-data灌入示例数据重新运行pnpm dev此时 Admin UI 中即包含样例数据。值得说明的是seed-data.ts 的实现是把1e5即 10 万条Post循环写入数据库for (let i 0; i 1e5; i) { console.log(...Post.createOne ${i}) await context.db.Post.createOne({ data: { title: Post #${i}, run ${run} } }) }这个脚本刻意不追求速度每条都await且打印日志目的就是让你能直观地感受到当列表里躺着 10 万条记录时如果不设上限一个不带take的查询会造成多大的数据量压力——这正是maxTake要解决的问题。核心配置一行代码为列表加上查询上限示例中最关键的部分在 schema.tsimport { list } from keystone-6/core import { allowAll } from keystone-6/core/access import { text } from keystone-6/core/fields import type { Lists } from ./generated/keystone/types export const lists { Post: list({ access: allowAll, fields: { title: text({ validation: { isRequired: true } }), }, graphql: { // enforce that only 20 posts can be retrieved in a Query // this additionally defaults the GraphQL schema take value to 20 maxTake: 20, }, }), } satisfies Lists要点拆解graphql是list()配置中的一个可选子配置块maxTake是该配置块中的上限声明注释里写得很清楚maxTake: 20做了两件事——一是强制任何一次列表查询最多只返回 20 条二是把 GraphQL Schema 中take参数的默认值也同时设为 20从源码类型定义看maxTake声明为可选数字maxTake?: number定义于 packages/core/src/types/config/lists.ts并会在列表级配置合并时被读取见 packages/core/src/types/config/index.ts。配置的默认值继承关系maxTake不仅支持在单个列表上配置也支持作为全局默认值配置。在 packages/core/src/schema.ts 中可以看到这一合并逻辑maxTake: list.graphql?.maxTake ?? config.listDefaults?.graphql?.maxTake,也就是说若列表自身没有声明maxTake则会回退使用config.listDefaults.graphql.maxTake这一全局默认值。这种列表级覆盖、默认值兜底的设计让你既能全站统一设限又能针对热点列表单独收紧。上限如何进入 GraphQL Schema自动生成的类型快照配置maxTake之后Keystone 在构建时会把上限写进自动生成的 GraphQL Schema。在 examples/limits/schema.graphql 中可以看到posts查询的完整签名type Query { post(where: PostWhereUniqueInput!): Post posts( where: PostWhereInput! {} orderBy: [PostOrderByInput!]! [] take: Int! 20 skip: Int! 0 cursor: PostWhereUniqueInput ): [Post!] postsCount(where: PostWhereInput! {}): Int }请特别注意take: Int! 20这一节它同时体现了两层含义——类型是非空的Int!take不再是可以省略的可选参数而是必须存在的参数默认值是20这正是maxTake配置被写入 Schema 的结果来自 packages/core/src/lib/core/initialise-lists.ts 的生成逻辑let take: any g.arg({ type: g.Int }) if (listConfig.graphql?.maxTake ! undefined listConfig.graphql.maxTake ! Infinity) { take g.arg({ type: g.nonNull(g.Int), // WARNING: used by queries/resolvers.ts to enforce the limit defaultValue: listConfig.graphql.maxTake, }) }从源码可以推断只有显式配置了maxTake且不是Infinity时take参数才会被生成为带默认值的非空参数如果没有配置上限take就是普通的可空Int参数。这一点也解释了为什么 schema.graphql 快照里posts的take是Int! 20。上限如何被执行resolver 层的强制校验Schema 里的默认值只是软默认真正硬性的强制校验发生在查询解析resolver阶段。在 packages/core/src/lib/core/queries/resolvers.ts 的findMany实现中export async function findMany(...): PromiseBaseItem[] { const maxTake (list.graphql.types.findManyArgs.take.defaultValue ?? Infinity) as number if (Math.abs(take ?? Infinity) maxTake) { throw limitsExceededError({ list: list.listKey, type: maxTake, limit: maxTake }) } ... }这里有几个值得深挖的实现细节上限值从哪里来findMany直接从 Schema 层已经生成好的findManyArgs.take.defaultValue读取上限也就是说运行时强制的上限值与 Schema 中暴露的默认值天然保持一致避免了两处配置漂移绝对值校验代码用的是Math.abs(take ?? Infinity) maxTake这意味着负数take如-30Prisma 语义下表示反向取数同样受上限约束Math.abs(-30) 30 20一样会触发报错无上限时的行为当maxTake未配置defaultValue为undefined时上限回退为Infinity此时任何take值都不会触发该校验。超出上限时的报错形态触发上限后会抛出limitsExceededError其定义在 packages/core/src/lib/core/graphql-errors.tsexport const limitsExceededError (_: { type: string; limit: number; list: string }) new GraphQLError(Your request exceeded server limits, { extensions: { code: KS_LIMITS_EXCEEDED, }, })也就是说当你在 GraphQL Playground 中执行query { posts(take: 100) { id title } }时会收到一条错误码为KS_LIMITS_EXCEEDED、消息为Your request exceeded server limits的 GraphQL 错误响应而不是静默截断或返回部分数据。这种硬失败设计避免了客户端以为拿到了全部数据、实际却被悄悄截断的隐患。数据库与运行环境说明keystone.ts 中的数据库配置采用 SQLite 加 better-sqlite3 适配器import { PrismaBetterSqlite3 } from prisma/adapter-better-sqlite3 import { config } from keystone-6/core import { lists } from ./schema import type { TypeInfo } from ./generated/keystone/types export default configTypeInfo({ db: { provider: sqlite, prismaClientOptions: () ({ adapter: new PrismaBetterSqlite3({ url: process.env.DATABASE_URL || file:./keystone-example.db, }), }), }, lists, })两点提示数据库文件默认为./keystone-example.db可通过环境变量DATABASE_URL覆盖package.json 中的 scripts 与本例配套dev开发模式、start生产启动、build构建、checkpostinstall 校验、seed-data通过tsx执行造数脚本。其中keystone-6/core以workspace:^引入说明该示例设计为在 Keystone 仓库monorepo内直接运行与根目录pnpm install的安装方式相呼应。实操验证完整走一遍上限触发链路结合示例自带的造数脚本可以完整复现数据量大 → 查询被上限拦截的链路初始化在示例目录运行pnpm dev让 Keystone 完成数据库迁移与 Schema 生成造数运行pnpm seed-data写入 10 万条Post脚本逐条await可观察进度输出重启服务再次pnpm dev让服务带着海量数据运行验证默认上限在 GraphQL Playground 执行不带take的查询query { posts { id title } }由于 Schema 默认take 20最多只会返回 20 条记录验证强制拦截执行query { posts(take: 100) { id title } }响应将带有KS_LIMITS_EXCEEDED错误码对照实验可选把 schema.ts 中的maxTake改大或删除后重新运行pnpm dev观察 schema.graphql 中posts的take类型与默认值如何随之变化——这能直观印证上一节介绍的 Schema 生成逻辑。小结graphql.maxTake是 Keystone 为 GraphQL 列表查询提供的一道简单而有效的防线配置上它Schema 中take参数自动带上默认值resolver 层则对所有查询包括负值take做绝对值上限校验超出即抛出KS_LIMITS_EXCEEDED错误。从 examples/limits 这个最小示例出发你可以把这套机制无缝迁移到自己的真实业务中——尤其是那些记录量可能快速膨胀的列表例如文章、日志或用户操作记录。它不能替代分页但能保证不管客户端怎么写查询服务端都不会被一次请求拖垮。赞分享后端【免费下载链接】keystoneThe superpowered headless CMS for Node.js — built with GraphQL and React项目地址https://gitcode.com/gh_mirrors/key/keystone点击查看免费下载相关推荐kerberoast完全指南从原理到实战的Kerberos攻击工具包详解kerberoast完全指南从原理到实战的Kerberos攻击工具包详解 kerberoast是一套针对微软Kerberos协议实现的攻击工具集旨在帮助安全网络安全StarRocks SHOW PROFILELIST 指南查询 Profile 记录列表的查看与实战StarRocks SHOW PROFILELIST 指南查询 Profile 记录列表的查看与实战 SHOW PROFILELIST 是 StarRocks数据库OLAP数据仓库大数据湖仓一体数据分析告别多表查询烦恼APIJSON联表查询实战指南告别多表查询烦恼APIJSON联表查询实战指南 你是否还在为复杂的SQL联表查询而头疼是否在面对一对一、一对多、多对多关系时感到无从下手本文将带你一文掌握后端ORM低代码上一篇从卡顿到丝滑Flutter开发者必须掌握的Skia渲染引擎优化指南下一篇MobileNetV4 Hybrid Medium.e200_r256_in12k与PyTorch集成开发高效AI应用的10个技巧创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考