ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

TypeGraphQL 中间件与守卫(Middleware Guards)完全指南:从装饰器到全局拦截

TypeGraphQL 中间件与守卫(Middleware  Guards)完全指南:从装饰器到全局拦截 后端GraphQLAPI设计【免费下载链接】type-graphqlCreate GraphQL schema and resolvers with TypeScript, using classes and decorators!项目地址https://gitcode.com/gh_mirrors/ty/type-graphql点击查看免费下载导读中间件Middleware是 TypeGraphQL 中一段可复用的代码能够轻松挂载到解析器resolver方法与字段上用于抽取和复用日志、耗时统计、参数校验、访问控制、错误兜底这类横切逻辑。本指南以 TypeGraphQL 0.17 官方文档 middlewares.md 为骨架结合仓库源码src/resolvers/create.ts、src/resolvers/helpers.ts、类型定义src/typings/middleware.ts与测试用例tests/functional/middlewares.ts系统讲解中间件的创建、挂载、全局注册、守卫拦截、结果改写与错误捕获读完即可在项目中落地一整套中间件体系。一、什么是中间件中间件本质上是一个函数接收两个参数resolver dataaction与解析器收到的数据一致即{ root, args, context, info }对应类型 ResolverData由 src/resolvers/create.ts#L30 构造并传入next函数用于控制后续中间件以及最终解析器resolver的执行其类型在 src/typings/middleware.ts#L3 定义为NextFn () Promiseany。如果你熟悉 express.js 的中间件可以类比但 TypeGraphQL 中间件更接近 koa.js 的模型next返回的是剩余中间件栈 解析器执行结果的 Promise。这意味着不仅能做执行前的处理还能在await next()之后做执行后的处理——洋葱模型。例如测量执行耗时export const ResolveTime: MiddlewareFn async ({ info }, next) { const start Date.now(); await next(); const resolveTime Date.now() - start; console.log(${info.parentType.name}.${info.fieldName} [${resolveTime} ms]); };info.parentType.name与info.fieldName来自 GraphQL 执行信息能精确打印哪个类型上的哪个字段的耗时。仓库示例 examples/middlewares-custom-decorators/middlewares/resolve-time.ts 即为此写法的落地版本。二、创建中间件的七种形态2.1 拦截并改写执行结果中间件不仅能看到解析器的返回值还能用新值替换它export const CompetitorInterceptor: MiddlewareFn async (_, next) { const result await next(); if (result typegql) { return type-graphql; } return result; };这个特性对普通业务用户或许用不上但它主要服务于插件系统与第三方库集成例如把返回值包装成懒加载lazy-relation代理使数据库关联字段在被访问时才自动查询。2.2 简单前置中间件如果只希望在动作之前做点事比如记录访问日志在中间件末尾放一句return next()即可const LogAccess: MiddlewareFnTContext ({ context, info }, next) { const username: string context.username || guest; console.log(Logging access: ${username} - ${info.parentType.name}.${info.fieldName}); return next(); };这里TContext是上下文泛型可约束context字段的类型。2.3 守卫Guard中断执行中间件可以不调用next从而打破中间件栈此时中间件自己返回的值将直接作为结果返回给客户端解析器不会被执行也可以直接抛错让错误返回给用户例如参数不合法时export const CompetitorDetector: MiddlewareFn async ({ args }, next) { if (args.frameworkName type-graphql) { return TypeGraphQL; } if (args.frameworkName typegql) { throw new Error(Competitive framework detected!); } return next(); };这就是守卫guard的核心思想——拦截访问、阻止解析器执行或数据泄露。TypeGraphQL 的Authorized()装饰器本质上也基于此机制src/helpers/auth-middleware.ts。2.4 可复用的中间件工厂有时中间件需要可配置化就像给Authorized()传roles数组一样此时应创建中间件工厂——一个接收配置参数、返回中间件函数的函数export function NumberInterceptor(minValue: number): MiddlewareFn { return async (_, next) { const result await next(); // hide values below minValue if (typeof result number result minValue) { return null; } return result; }; }注意挂载时必须调用它并传参例如NumberInterceptor(3.0)而不是传函数引用。仓库中的 examples/middlewares-custom-decorators/middlewares/number-interceptor.ts 即为此工厂的真实实现。2.5 错误拦截器Error Interceptor中间件可以捕获执行过程中抛出的错误便于统一记录日志甚至过滤不该返回给用户的错误信息比如避免把数据库 SQL 语句暴露给客户端export const ErrorInterceptor: MiddlewareFnany async ({ context, info }, next) { try { return await next(); } catch (err) { // write error to file log fileLog.write(err, context, info); // hide errors from db like printing sql query if (someCondition(err)) { throw new Error(Unknown error occurred!); } // rethrow the error throw err; } };仓库示例 examples/middlewares-custom-decorators/middlewares/error-logger.ts 展示了一个更完整的实战版本记录错误信息后若非ArgumentValidationError参数校验错误则一律替换为通用错误信息Unknown error occurred. Try again later!避免泄露内部细节。2.6 类中间件Class-based Middleware当中间件逻辑变复杂要访问数据库、写日志文件、依赖注入、单测 mock时可以改用类中间件。只需要实现MiddlewareInterface即提供一个符合MiddlewareFn签名的use方法。LogAccess的类版本如下export class LogAccess implements MiddlewareInterfaceTContext { constructor(private readonly logger: Logger) {} async use({ context, info }: ResolverDataTContext, next: NextFn) { const username: string context.username || guest; this.logger.log(Logging access: ${username} - ${info.parentType.name}.${info.fieldName}); return next(); } }类中间件通过use方法注入Logger等依赖天然支持依赖注入与测试 mock。类型定义见 src/typings/middleware.ts#L10-L15。仓库中 examples/middlewares-custom-decorators/middlewares/log-access.ts 用typedi的Service()注解演示了类中间件与 IOC 容器配合的完整写法。2.7 中间件的底层调度原理从源码看函数式与类式中间件在运行时被归一化为MiddlewareFn在 src/resolvers/helpers.ts#L102-L141 的applyMiddlewares中若中间件是类有prototype则先通过container.getInstance()实例化并绑定use方法随后按索引递归dispatchHandler逐个执行中间件栈按顺序进入最后一个位置是真正的resolverHandlerFunction解析器主体若某个中间件重复调用next()会抛出next() called multiple times错误src/resolvers/helpers.ts#L113-L115next()返回的是下一层含解析器的执行结果中间件若显式return了新值则会覆盖该结果若返回undefined则透传下一层结果src/resolvers/helpers.ts#L133-L138。测试用例 tests/functional/middlewares.ts#L499-L508 专门验证了重复调用next()会报错这一行为。三、如何使用中间件3.1 用UseMiddleware()挂载在解析器方法或字段声明上方放置UseMiddleware()装饰器即可。它接受一个中间件数组按传入顺序执行也支持 rest 参数直接平铺传入源码见 src/decorators/UseMiddleware.tsResolver() export class RecipeResolver { Query() UseMiddleware(ResolveTime, LogAccess) randomValue(): number { return Math.random(); } }装饰器内部通过getMetadataStorage()分别收集方法级中间件collectMiddlewareMetadata与类级中间件collectResolverMiddlewareMetadata元数据结构见 src/metadata/definitions/middleware-metadata.ts。3.2 挂载到 ObjectType 字段中间件同样可以挂到ObjectType字段上与Authorized()用法一致适用于对纯字段如数组属性做统一处理ObjectType() export class Recipe { Field() title: string; Field(type [Int]) UseMiddleware(LogAccess) ratings: number[]; }源码中纯字段解析器无显式 resolver 的字段同样会拼接全局中间件与字段级中间件后再执行src/resolvers/create.ts#L120-L131。仓库示例 examples/middlewares-custom-decorators/recipe/recipe.type.ts 还展示了把中间件挂到 getter 计算字段averageRating上的用法。3.3 注册全局中间件对测量耗时、捕获错误这类通用需求逐个字段手写UseMiddleware(ResolveTime)太繁琐。TypeGraphQL 允许通过buildSchema配置对象的globalMiddlewares属性注册全局中间件——它会作用于每个 query、mutation、subscription 和字段解析器const schema await buildSchema({ resolvers: [RecipeResolver], globalMiddlewares: [ErrorInterceptor, ResolveTime], });底层实现中BuildContext持有静态的globalMiddlewares数组src/schema/build-context.ts#L57三种解析器handler、advanced field resolver、basic field resolver在创建时都会先执行globalMiddlewares.concat(...)再拼接局部中间件src/resolvers/create.ts#L26、#L91、#L124。仓库示例 examples/middlewares-custom-decorators/index.ts#L13-L22 展示了buildSchema中注册全局中间件并配合typedi容器的完整启动代码。执行顺序重要测试 tests/functional/middlewares.ts#L510-L546 验证了完整调用顺序为——全局中间件1 → 全局中间件2 → 方法/字段中间件1 → 方法/字段中间件2 → ... → 解析器本体 → ... → 方法/字段中间件N(after) → 全局中间件N(after)即洋葱模型全局中间件在最外层越靠前的中间件越晚拿到next()的最终结果。此外类级中间件先于方法级中间件执行——这一规则在 官方文档 与当前版本文档 docs/middlewares.md 中都有强调仓库示例 examples/middlewares-custom-decorators/recipe/recipe.resolver.ts#L12-L14 也演示了把UseMiddleware(ResolveTimeMiddleware)放在类上的写法。3.4 自定义装饰器封装中间件如果希望 API 更语义化、更具描述性可以自定义装饰器返回UseMiddleware(...)即可复用整套中间件机制export function ValidateArgsT extends object(schema: SchemaT) { return UseMiddleware(async ({ args }, next) { // here place your validation logic, e.g. based on schema using joi await joiValidate(schema, args); return next(); }); }用法上它就是一个普通方法装饰器可与显式中间件混排Resolver() export class RecipeResolver { Query() ValidateArgs(MyArgsSchema) // custom decorator UseMiddleware(ResolveTime) // explicit middleware randomValue(Args() { scale }: MyArgs): number { return Math.random() * scale; } }仓库示例 examples/middlewares-custom-decorators/decorators/validate-args.ts 给出了更完整的落地实现它使用createMethodMiddlewareDecoratorsrc/decorators/createMethodMiddlewareDecorator.ts把class-validator的校验逻辑包装成语义化装饰器ValidateArgs并在校验失败时抛出ArgumentValidationError对应的使用位置见 examples/middlewares-custom-decorators/recipe/recipe.resolver.ts#L24。更完整的方法装饰器范式可参考 自定义装饰器文档。四、从源码看中间件与解析器生命周期的关系结合 src/resolvers/create.ts 与 src/resolvers/helpers.ts一次带中间件的查询执行流程可概括为构建期buildSchema接收globalMiddlewares存入BuildContextsrc/schema/build-context.ts#L93-L95解析器生成期createHandlerResolver/createAdvancedFieldResolver/createBasicFieldResolver分别将全局中间件与对应局部中间件拼接并在此阶段把Authorized的鉴权中间件unshift到最前src/resolvers/helpers.ts#L90-L100运行期每个 GraphQL 字段解析器收到(root, args, context, info)后构造resolverData经applyMiddlewares走洋葱链路最终执行targetInstance[methodName].apply(...)参数先经getParams完成Args/Arg转换与 class-validator 校验见 src/resolvers/helpers.ts#L12-L88。因此中间件能看到参数校验错误、能覆盖返回值、能中断链路根源都在于next即剩余栈执行结果这一设计。五、最佳实践与常见陷阱耗时统计、日志、全局错误捕获建议注册为全局中间件避免在每个字段重复挂载守卫类中间件拦截访问与Authorized()职责重叠时优先使用Authorized()authChecker见 授权文档中间件守卫适合基于参数等更灵活的拦截逻辑不要在中间件里重复调用next()会触发运行时错误next() called multiple times每个中间件要么return next()要么return一个自定义值/抛错中间件工厂务必调用后再挂载UseMiddleware(NumberInterceptor(3.0))而非UseMiddleware(NumberInterceptor)类中间件依赖注入类中间件的实例由容器创建container.getInstance因此在buildSchema中配置了container如typedi时类中间件的构造函数依赖可被自动注入便于单测与替换如 mock 文件日志器或数据库仓储见 依赖注入文档。仓库示例 examples/middlewares-custom-decorators/index.ts 展示了容器与全局中间件的组合配置方式错误兜底注意粒度像 error-logger.ts 那样把ArgumentValidationError与内部错误区分对待避免把用户的校验错误也吞成Unknown error。小结TypeGraphQL 的中间件体系以 koa 风格洋葱模型为核心函数式与类式两种形态统一调度UseMiddleware支持方法、字段与类级挂载globalMiddlewares提供全局横切能力自定义装饰器则把中间件封装成语义化 API。配合源码中的applyMiddlewares调度器与测试中的顺序断言你可以在不侵入解析器主体代码的前提下稳定地完成日志、耗时、校验、守卫与错误兜底等全部横切需求。赞分享后端GraphQLAPI设计【免费下载链接】type-graphqlCreate GraphQL schema and resolvers with TypeScript, using classes and decorators!项目地址https://gitcode.com/gh_mirrors/ty/type-graphql点击查看免费下载相关推荐TypeGraphQL 中间件Middleware与守卫Guards完全指南从装饰器到全局注册的实战解析TypeGraphQL 中间件Middleware与守卫Guards完全指南从装饰器到全局注册的实战解析 中间件是 TypeGraphQL 中一类可复后端GraphQLAPI设计TypeGraphQL 中间件Middleware与守卫Guards完整实战指南从函数到类、从局部到全局TypeGraphQL 中间件Middleware与守卫Guards完整实战指南从函数到类、从局部到全局 本文基于 TypeGraphQL v1.0.后端GraphQLAPI设计TypeGraphQL 中间件Middlewares完整实战指南从守卫到全局拦截器TypeGraphQL 中间件Middlewares完整实战指南从守卫到全局拦截器 本指南以 TypeGraphQL 官方文档中的中间件章节为主线系统讲后端GraphQLAPI设计上一篇5分钟掌握猫抓插件小白也能轻松下载网页视频音频的完整指南下一篇游戏存档的翻译官用UESave解锁虚幻引擎的二进制密码创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表