- 后端
- 微服务
- 云原生
【免费下载链接】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. 🌈
本文围绕 Midway 官方扩展@midwayjs/one-shot展开,介绍如何在一个普通 Node.js 项目中以 IoC(依赖注入)方式编排一次性脚本(one-shot task),包括组件安装、入口配置、生命周期内执行、基于 request-scope 的runScript运行方式以及内置日志器的定制。读完本文,你将掌握一个不启动 HTTP/Serverless 服务、只跑一次业务逻辑、却能完整复用 Midway 依赖注入与中间件链路的轻量框架用法。
什么是 @midwayjs/one-shot
@midwayjs/one-shot是一个"只提供 Framework"的一次性脚本框架。它不包含独立的 HTTP 服务、不面向 Serverless 平台,也不参与应用集成场景,定位非常纯粹:在一个已有项目中,借助 Midway 的 IoC 容器执行一次性的任务。
官方文档给出了该组件的能力矩阵:
| 描述 | 是否支持 |
|---|---|
| 可用于标准 Web 应用 | ❌ |
| 可用于 Serverless 场景 | ❌ |
| 可用于集成场景(integrated) | ❌ |
| 包含独立的核心(standalone core) | ✅ |
| 包含独立的日志器(standalone logger) | ✅ |
换句话说,它把 Midway 的"核心 + 日志"能力以独立框架的形式拆出来,供批处理、数据同步、定时补偿等场景复用,而不会引入多余的网络层。从仓库结构看,该组件位于 packages/one-shot,当前版本为4.2.5,依赖仅@midwayjs/core(见 package.json),运行时要求 Node.js >= 20。
安装组件
在已有项目中安装 one-shot 组件依赖:
$ npm i @midwayjs/one-shot@4 --save也可以在package.json中直接声明依赖后重新安装:
{ "dependencies": { "@midwayjs/one-shot": "^4.0.0" } }由于@midwayjs/one-shot在src/index.ts中通过export { MidwayOneShotFramework as Framework }与export { OneShotConfiguration as Configuration }对外暴露框架与配置入口(见 src/index.ts),安装后只需在配置类中导入即可使用。
启用组件
在入口配置src/configuration.ts中导入并注册组件:
// src/configuration.ts import { Configuration } from '@midwayjs/core'; import * as oneShot from '@midwayjs/one-shot'; @Configuration({ imports: [oneShot], }) export class MainConfiguration {}组件内部通过@Configuration({ namespace: 'oneShot', importConfigs: [...] })注册了名为oneShot的命名空间,并顺带声明了默认日志配置(见 src/configuration.ts),因此无需任何额外初始化动作即可工作。
在生命周期中执行一次性逻辑
@midwayjs/one-shot不需要额外的 Runner 概念,官方推荐的执行时机是应用生命周期钩子onServerReady。此时应用已完成装配、所有 IoC 依赖均可注入,可以直接调用业务服务:
// src/configuration.ts import { Configuration, Inject } from '@midwayjs/core'; import * as oneShot from '@midwayjs/one-shot'; import { ScriptService } from './service/script'; @Configuration({ imports: [oneShot], }) export class MainConfiguration { @Inject() scriptService: ScriptService; async onServerReady() { await this.scriptService.runOnce(); } }这里ScriptService是项目内普通@Provide()业务类,依赖注入完全走 Midway 容器。因为 one-shot 框架的run()方法是按需执行的(源码中为空的public async run(): Promise<void>,见 src/framework.ts),所以整体启动流程很短,适合"进程启动 → 跑一次 → 退出"的命令行任务形态。
当脚本需要请求级作用域(request scope)时
如果你的脚本依赖请求级(request-scope)服务,生命周期钩子里直接注入的方式就不够用了——生命周期钩子运行在应用级作用域,无法天然获得请求级上下文。此时应使用框架提供的runScriptAPI 并搭配一个固定的 Runner 类:
框架会为这一次执行创建一个独立的 context,并运行完整的中间件(middleware)与过滤器(filter)链路。
定义 Runner 类
Runner 需要实现OneShotRunner<T, R>接口,其中泛型T为入参 payload 类型、R为返回值类型:
// src/script/syncUser.ts import { Provide } from '@midwayjs/core'; import { OneShotRunner, Context } from '@midwayjs/one-shot'; @Provide() export class SyncUserScript implements OneShotRunner<{ id: number }, void> { async run(payload?: { id: number }, ctx?: Context) { // use payload / ctx void payload; void ctx; } }接口定义位于 src/interface.ts:
export interface OneShotRunner<T = unknown, R = unknown> { run(payload?: T, ctx?: IMidwayOneShotContext): R | Promise<R>; }其中上下文IMidwayOneShotContext继承自 Midway 通用上下文,并额外带有一个payload字段(见 src/interface.ts),因此你既可以通过run的第二个参数拿到ctx,也可以在 Runner 内部用@Inject() ctx: Context注入上下文并读取ctx.payload。
调用 runScript
在配置类生命周期中通过注入的Framework调用:
// src/configuration.ts import { Configuration, Inject } from '@midwayjs/core'; import * as oneShot from '@midwayjs/one-shot'; import { Framework } from '@midwayjs/one-shot'; import { SyncUserScript } from './script/syncUser'; @Configuration({ imports: [oneShot], }) export class MainConfiguration { @Inject() framework: Framework; async onServerReady() { await this.framework.runScript(SyncUserScript, { id: 42 }); } }runScript的完整签名支持三个参数(见 src/framework.ts):
public async runScript<T = unknown, R = unknown>( Runner: new (...args: unknown[]) => OneShotRunner<T, R>, payload?: T, ctxData: Partial<IMidwayOneShotContext> = {} ): Promise<R>底层执行链路(源码解读)
runScript的内部实现(src/framework.ts)清晰地展现了它的设计意图:
- 创建匿名上下文:调用
this.app.createAnonymousContext({ ...ctxData, payload })生成一次执行专属的上下文,payload被直接挂到上下文中。createAnonymousContext是BaseFramework提供的能力(见 packages/core/src/baseFramework.ts),它会为上下文补齐startTime、上下文日志器、requestContext(请求级容器)以及traceId等属性——这正是 one-shot 能够运行请求级依赖和中间件的根本原因。 - 链路追踪:通过
MidwayTraceService.runWithEntrySpan创建名为oneshot ${Runner.name}的入口 span,并写入midway.protocol: 'one-shot'、midway.oneshot.runner等属性。tracing配置项可通过oneShot.tracing.enable关闭,并支持自定义extractor/meta回调来提取链路载体。 - 执行中间件链:用
applyMiddleware包裹真实业务逻辑,然后在匿名上下文上运行整条中间件链。真正执行业务时,先从ctx.requestContext(请求级容器)中getAsync(Runner)解析出 Runner 实例——这一步保证了 Runner 可以注入 request-scope 服务——随后校验实例上存在run()方法,不存在则抛出MidwayCommonError('One-shot runner must implement run().'),最后调用instance.run(payload, ctx)并把结果作为R返回。
仓库自带的测试 test/index.test.ts 验证了这套链路:SampleScript通过@Inject() ctx: Context注入上下文,runScript(SampleScript, { id: 42 })返回'42:42'(payload 与 ctx.payload 一致);第二个用例还通过监听MidwayTraceService.runWithEntrySpan断言每次runScript都会创建入口 span。
日志:内置 oneShotLogger
组件默认注册一个名为oneShotLogger的日志器,写入文件midway-one-shot.log。默认配置在组件的importConfigs中声明(见 src/configuration.ts)。
你可以在脚本服务中通过@Logger('oneShotLogger')注入并使用:
import { Logger, ILogger } from '@midwayjs/core'; export class ScriptService { @Logger('oneShotLogger') logger: ILogger; async runOnce() { this.logger.info('run one-shot task'); } }如果想自定义日志文件名或日志级别,可以在应用配置中覆盖midwayLogger.clients.oneShotLogger:
// src/config/config.default.ts export default { midwayLogger: { clients: { oneShotLogger: { fileLogName: 'my-one-shot.log', level: 'info', }, }, }, };由于默认配置与用户配置都会合并进midwayLogger.clients,因此只需覆写需要变更的字段(如fileLogName、level)即可,其余日志器行为仍沿用 Midway 统一的日志配置体系。
小结与适用场景
@midwayjs/one-shot适合以下场景:
- 需要跑一次的数据迁移、数据同步、批量补偿任务;
- 希望复用项目既有 IoC 服务与配置,又不想引入完整 Web 或 Serverless 框架;
- 脚本逻辑依赖 request-scope 服务或需要经过中间件/过滤器链路时,使用
runScript+ Runner 类。
需要注意的前提:它不适用于标准 Web 应用、Serverless 或集成模式(见上文能力矩阵),核心定位就是"跑一次即退出的 IoC 化脚本"。实际使用中只需安装组件、在configuration.ts注册,并在onServerReady中按需触发即可,源码层面的运行链路可继续参考 packages/one-shot/src/framework.ts 与 packages/one-shot/test/index.test.ts。
- 后端
- 微服务
- 云原生
【免费下载链接】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. 🌈
相关推荐
Midway 一次性脚本执行指南:使用 @midwayjs/one-shot 在 IoC 容器中触发单次任务
Midway 一次性脚本执行指南:使用 @midwayjs/one shot 在 IoC 容器中触发单次任务 @midwayjs/one shot 是 Midw
后端微服务云原生Midway 4.0 Beta.10 新组件实战:@midwayjs/one-shot 一次性脚本执行 与 @midwayjs/commander 命令行组件全解析
Midway 4.0 Beta.10 新组件实战:@midwayjs/one shot 一次性脚本执行 与 @midwayjs/commander 命令行组件全
后端微服务云原生Dokku 一次性任务(One-off Tasks)实战指南:用 run 命令在应用容器中执行临时命令
Dokku 一次性任务(One off Tasks)实战指南:用 run 命令在应用容器中执行临时命令 本文围绕 Dokku 平台的一次性任务(one off
云原生DevOps后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考