☰
Midway Hooks 函数式中间件实战:useContext 驱动的 Web 中间件体系
2026/10/10 1:22:20 网站建设 项目流程
  • 后端
  • 微服务
  • 云原生

【免费下载链接】midway

🍔 A Node.js Serverless Framework for front-end/full-stack developers. Build the application for next decade. Works on AWS, Alibaba Cloud, Tencent Cloud and traditional VM/Container. Super easy integrate with React and Vue. 🌈

项目地址:https://gitcode.com/gh_mirrors/mi/midway
点击查看免费下载

Midway Hooks 是 Midway 面向前后端一体化开发的函数式 API 形态(@midwayjs/hooks),它把传统「类控制器 + 装饰器」的中间件注册方式,重构为「普通函数 +useContext()」的函数式写法。本文围绕site/versioned_docs/version-3.0.0/hooks/middleware.md展开,系统讲解 Hooks 中间件的语法、三种注册粒度(全局 / 文件级 / 单函数)、Koa 中间件复用方法,并结合仓库源码(packages/core/src/functional/hooks.ts、packages/core/src/common/webGenerator.ts等)剖析其底层原理。读完本文,你将能独立编写、挂载、组合任意粒度的 Hooks 中间件,并理解它与 Midway 经典中间件体系的对接方式。

中间件语法:函数 +useContext()

与 Midway 经典 Web 中间件((ctx, next) => {})不同,Hooks 中间件是一个只接收next一个参数的异步函数,上下文ctx需要通过useContext()获取:

  • next:指向下游处理流程(下一个中间件或最终 Api 函数),必须被调用才能让请求继续往下走;
  • useContext():从当前异步上下文(Async Context)中取出请求上下文ctx,其底层实现位于 packages/core/src/functional/hooks.ts#L18-L23:
export function useContext<T = any>(): T | undefined { const ctx = getCurrentAsyncContextManager() .active() .getValue(ASYNC_CONTEXT_KEY); return ctx as T; }

即通过getCurrentAsyncContextManager().active()获取当前异步上下文的激活状态,再按ASYNC_CONTEXT_KEY(定义于 packages/core/src/constants.ts#L19,值为Symbol('ASYNC_CONTEXT_KEY'))读取请求上下文。因此只要请求处理链路仍在同一个异步上下文内,中间件与 Api 函数中就能一致地拿到当前请求的ctx。

由于中间件本质上是普通函数,你可以在其中使用任意 Hooks(如useLogger()、useConfig()、useInject()等,均定义于 packages/core/src/functional/hooks.ts)。例如useLogger()在存在ctx时返回ctx.logger,否则回退到主应用 Logger(packages/core/src/functional/hooks.ts#L25-L35)。

基础示例:记录请求日志

以一个请求日志中间件为例,它打印请求进入时间与处理耗时:

import { Context } from '@midwayjs/koa'; import { useContext } from '@midwayjs/hooks'; const logger = async (next: any) => { const ctx = useContext<Context>(); console.log( `<-- [${ctx.method}] ${ctx.url}` ); const start = Date.now(); await next(); const cost = Date.now() - start; console.log( `--> [${ctx.method}] ${ctx.url} ${cost}ms` ); };

该中间件展示了完整的洋葱模型时序:await next()之前的代码在请求进入时执行,await next()之后的代码在响应返回时执行。ctx.method、ctx.url来自 Koa 类型Context,说明 Hooks 中间件直接运行在底层 Web 框架(Koa)的请求上下文之上。

全局中间件:作用于所有接口

全局中间件在src/configuration.ts中通过createConfiguration+hooks({ middleware })定义,对所有接口生效:

import { hooks, createConfiguration, } from '@midwayjs/hooks'; import logger from './logger'; // Global Middleware export default createConfiguration({ imports: [ hooks({ middleware: [logger], }), ], });

仓库中的真实示例可见 site/example/function/src/apis/configuration.ts,它在全局挂载了koa-bodyparser:

import { hooks, createConfiguration } from '@midwayjs/hooks'; import bodyParser from 'koa-bodyparser'; export default createConfiguration({ imports: [ hooks({ middleware: [bodyParser()], }), ], });

hooks()返回的组件配置会在 Midway 启动时被扫描、注册,middleware数组中的每一项都会进入该 Web 应用的中间件链,从而对 Hooks 注册的所有路由生效。

文件级中间件:作用于单个 Api 文件

文件级中间件定义在 Api 文件中,通过导出的config.middleware声明,对该文件内的所有 Api 函数生效:

import { ApiConfig, Api, Get, } from '@midwayjs/hooks'; import logger from './logger'; // File Level Middleware export const config: ApiConfig = { middleware: [logger], }; export default Api(Get(), async () => { return 'Hello World!'; });

其中ApiConfig是 Midway Hooks 约定的文件级配置类型。这种粒度非常适合对一组相近接口统一注入鉴权、参数校验、限流等中间件,而无需逐个函数声明。

单函数中间件:作用于单个 Api 函数

通过Middleware(...middlewares)声明的中间件仅对单个函数生效,粒度最细:

import { Api, Get, Middleware, } from '@midwayjs/hooks'; import logger from './logger'; export default Api( Get(), Middleware(logger), async () => { return 'Hello World!'; } );

Middleware()接收一个或多个中间件,作为Api()的第二个参数传入,位置在 HTTP 方法装饰器(Get())之后、处理函数之前。

从源码实现看,单函数中间件会被收集到路由定义route.options.middleware中。createRouteBuilder初始化路由时默认给出middleware: [](packages/core/src/functional/api.ts#L202-L210),随后在defineApi中通过RequestMapping把route.options.middleware写入路由元数据(packages/core/src/functional/api.ts#L479-L487),最终由 Web 路由服务装配为实际的中间件链。

直接复用 Koa 中间件

Hooks 中间件与 Koa 中间件协议一致(async (ctx, next) => {}),因此你可以在上述三种粒度中直接传入任意 Koa 中间件,生态无缝衔接。以@koa/cors为例:

全局启用:

import { hooks, createConfiguration, } from '@midwayjs/hooks'; import logger from './logger'; import cors from '@koa/cors'; // Global Middleware export default createConfiguration({ imports: [ hooks({ middleware: [logger, cors()], }), ], });

文件级启用:

import { ApiConfig, Api, Get, } from '@midwayjs/hooks'; import logger from './logger'; import cors from '@koa/cors'; // File Level Middleware export const config: ApiConfig = { middleware: [logger, cors], }; export default Api(Get(), async () => { return 'Hello World!'; });

函数级启用:

import { Api, Get, Middleware, } from '@midwayjs/hooks'; import logger from './logger'; import cors from '@koa/cors'; export default Api( Get(), Middleware(logger, cors), async () => { return 'Hello World!'; } );

注意:@koa/cors属于工厂函数形态,全局挂载时需要执行cors()生成实例;文件级/函数级数组声明中传入的则是中间件函数本身。同理,类似koa-bodyparser这类工厂中间件,在全局配置中也要执行一次(见 site/example/function/src/apis/configuration.ts 中的bodyParser()用法)。仓库站点文档另有 site/docs/hooks/cors.md 对跨域场景做专题说明。

底层原理:中间件链的组装与执行

从路由元数据到真实中间件链

无论中间件声明在哪一级,最终都会归一到路由级元数据:文件级config.middleware与单函数Middleware()在路由生成阶段被合并进route.options.middleware,全局hooks({ middleware })则作用于整个应用层。

在 Web 路由生成阶段(packages/core/src/common/webGenerator.ts#L126-L148),框架会对每个 controller 与每条 route 的中间件数组调用middlewareService.compose(...)组装为可执行的中间件链,并分别挂载到newRouter.use(...)与路由方法上。compose正是 koa-compose 风格的洋葱模型编排器,其行为由 packages/core/test/service/middlewareService.test.ts 中的大量用例覆盖,包括:中间件数组为空时的兜底(should work with 0 middleware)、中间件抛错时的错误传播(should reject on errors in middleware)、嵌套组合、以及不会污染原中间件数组(should not affect the original middleware array)等。

请求上下文的传递

Hooks 中间件之所以能只用next一个参数就能访问ctx,关键在useContext()依赖的异步上下文(Async Context)机制。Midway 在请求入口将ctx以ASYNC_CONTEXT_KEY为键写入异步上下文管理器,随后在中间件与 Api 函数执行的整条链路上都能读取到同一份上下文。这也意味着:

  • 中间件可以安全地在await next()前后读写ctx(如设置响应头、记录耗时);
  • 若某个中间件不调用next(),请求会在该处被终结(例如鉴权失败直接返回 401);
  • 中间件顺序即数组顺序:先注册的先执行,先执行的await next()之前代码先运行,之后代码后运行(洋葱模型)。

与 Midway 经典中间件的异同

  • 经典写法(@midwayjs/core的类中间件)通过@Middleware()装饰器声明类,实现resolve()返回(ctx, next) => {}(见 packages/core/src/decorator/common/middleware.ts#L4);
  • Hooks 函数式写法把中间件简化为普通异步函数,配合useContext()免去了ctx参数的传递,与 Hooks 的函数式 Api 风格保持统一。

两者底层都汇入同一条中间件链(webGenerator+middlewareService.compose),因此可以混用,也都能直接消费 Koa 生态中间件。

实践建议:三种粒度如何选择

粒度声明位置生效范围典型场景
全局中间件configuration.ts的hooks({ middleware: [...] })所有接口请求日志、CORS、Body 解析、全局异常处理
文件级中间件Api 文件导出config: ApiConfig = { middleware: [...] }文件内所有 Api按模块统一鉴权、参数校验、限流
单函数中间件Api(Get(), Middleware(...), handler)单个 Api 函数单接口特例处理、局部权限控制

选择原则:能全局统一处理的放全局;需要按模块区分的用文件级;仅个别接口特殊的用单函数级。三者可叠加使用,实际执行顺序由注册先后与路由挂载顺序共同决定。由于中间件支持任意 Hooks(如useLogger、useConfig),你还可以把日志、配置读取、依赖注入等逻辑封装进中间件,进一步收敛横切关注点。

小结

Midway Hooks 用「函数 +useContext()」重新定义了 Web 中间件的编写方式:语法上只有一个next参数,上下文通过异步上下文机制透明注入;作用域上提供全局、文件级、单函数三种粒度,可灵活组合;生态上直接兼容 Koa 中间件。其底层实现(useContext的 Async Context 读取、webGenerator的中间件链组装、middlewareService.compose的洋葱编排)与 Midway 经典中间件体系同源,既保证了函数式 Api 的开发体验,又不牺牲框架既有的中间件能力。掌握这一套中间件体系,即可在 Hooks 应用中系统地落实日志、鉴权、跨域、校验等横切需求。

  • 后端
  • 微服务
  • 云原生

【免费下载链接】midway

🍔 A Node.js Serverless Framework for front-end/full-stack developers. Build the application for next decade. Works on AWS, Alibaba Cloud, Tencent Cloud and traditional VM/Container. Super easy integrate with React and Vue. 🌈

项目地址:https://gitcode.com/gh_mirrors/mi/midway
点击查看免费下载
上一篇:8cc:一个小型C编译器
下一篇:MCSManager与Steam游戏服务器:全面支持Palworld、Squad等热门游戏

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询