创建命令
本指南介绍如何使用 Ace 命令行创建命令。你将了解以下主题:
- 创建自定义命令
- 配置命令元数据
- 使用生命周期方法
- 注入依赖
- 处理错误
- 管理长时间运行的进程
创建你的第一个命令
你可以使用 make:command Ace 命令生成一个新的命令。这会创建一个基本的命令(位于 commands 目录中),并带有所有必要的样板代码。
另见: Make command
node ace make:command greet
# CREATE: commands/greet.ts
生成的文件包含一个继承自 BaseCommand 的命令类。一个命令至少必须定义一个 commandName 并实现 run 方法。
import { BaseCommand } from '@adonisjs/core/ace'
export default class GreetCommand extends BaseCommand {
static commandName = 'greet'
static description = 'Greet a user by name'
async run() {
this.logger.info('Hello world!')
}
}
现在你可以使用你定义的命令名称来执行你的命令。
node ace greet
配置命令元数据
命令元数据控制你的命令在帮助界面中的显示方式,以及它在执行期间的行为。元数据包括命令名称、描述、帮助文本、别名和执行选项。
设置命令名称
commandName 属性定义了用户将键入以执行你的命令的名称。命令名称不应包含空格,并应避免使用不熟悉的特殊字符,如 *、& 或斜杠。
export default class GreetCommand extends BaseCommand {
static commandName = 'greet'
}
命令名称可以通过使用冒号分隔符来包含命名空间。这有助于将相关命令组织在帮助输出中。
export default class MakeControllerCommand extends BaseCommand {
/**
* 该命令出现在 "make" 命名空间下
*/
static commandName = 'make:controller'
}
编写命令描述
命令描述会出现在命令列表以及你的命令的帮助界面中。描述应简洁明了,较长的说明请使用帮助文本。
export default class GreetCommand extends BaseCommand {
static commandName = 'greet'
static description = 'Greet a user by name'
}
添加详细的帮助文本
帮助文本允许你提供较长的描述、使用示例或适合简短描述之外的额外上下文。将帮助文本定义为字符串数组,其中每个字符串表示一行输出。
export default class GreetCommand extends BaseCommand {
static commandName = 'greet'
static description = 'Greet a user by name'
static help = [
'The greet command is used to greet a user by name',
'',
'You can also send flowers to a user, if they have an updated address',
'{{ binaryName }} greet --send-flowers',
]
}
{{ binaryName }} 变量替换会引用用于执行 ace 命令的二进制文件(通常是 node ace),确保你的帮助文本无论用户如何运行 Ace 都能显示正确的命令语法。
定义命令别名
别名为你的命令提供了替代名称。当你想为常用命令提供更短或更直观的名称时,这会很有用。
export default class GreetCommand extends BaseCommand {
static commandName = 'greet'
static aliases = ['welcome', 'sayhi']
}
用户现在可以使用任何已定义的名称来运行你的命令。
node ace greet
node ace welcome
node ace sayhi
配置命令选项
命令选项控制你的命令的执行行为。这些选项使用静态的 options 属性定义,并影响 Ace 如何启动应用、处理标志以及管理命令的生命周期。
import { BaseCommand } from '@adonisjs/core/ace'
import type { CommandOptions } from '@adonisjs/core/types/ace'
export default class GreetCommand extends BaseCommand {
static commandName = 'greet'
static options: CommandOptions = {
startApp: false,
allowUnknownFlags: false,
staysAlive: false,
}
}
启动应用
默认情况下,Ace 在运行命令时不会启动你的 AdonisJS 应用。这使得命令运行更快,并防止对不需要应用状态的简单任务进行不必要的应用初始化。
但是,如果你的命令需要访问模型、服务或其他应用资源,你必须告诉 Ace 在执行命令之前启动应用。
import { BaseCommand } from '@adonisjs/core/ace'
import type { CommandOptions } from '@adonisjs/core/types/ace'
export default class SendEmailCommand extends BaseCommand {
static options: CommandOptions = {
/**
* 启动应用以访问模型和服务
*/
startApp: true
}
async run() {
/**
* 现在可以使用模型和等服务之类的应用资源
*/
const users = await User.all()
}
}
允许未知标志
默认情况下,如果你传递了一个命令未定义的标志,Ace 会显示错误。这种严格的解析有助于捕获拼写错误和错误的标志用法。
但是,某些命令需要接受任意标志并将它们传递给其他工具。你可以使用 allowUnknownFlags 选项禁用严格的标志解析。
import { BaseCommand } from '@adonisjs/core/ace'
import type { CommandOptions } from '@adonisjs/core/types/ace'
export default class ProxyCommand extends BaseCommand {
static options: CommandOptions = {
/**
* 接受任何标志并将其传递给外部工具
*/
allowUnknownFlags: true
}
}
创建长时间运行的命令
Ace 会在你的命令的 run 方法完成后自动终止应用。对于执行任务后退出的大多数命令来说,这是期望的行为。
但是,如果你的命令需要无限期运行(如队列 worker 或开发服务器),你必须告诉 Ace 不要使用 staysAlive 选项终止应用。
import { BaseCommand } from '@adonisjs/core/ace'
import type { CommandOptions } from '@adonisjs/core/types/ace'
export default class QueueWorkerCommand extends BaseCommand {
static options: CommandOptions = {
startApp: true,
/**
* 保持进程存活
*/
staysAlive: true
}
async run() {
/**
* 开始无限期处理任务
*/
await this.startJobProcessor()
}
}
理解命令生命周期
Ace 按预定义的顺序执行命令生命周期方法,让你可以将命令逻辑组织到不同的阶段。每个生命周期方法在命令执行流程中都有特定的用途。
import { BaseCommand } from '@adonisjs/core/ace'
export default class GreetCommand extends BaseCommand {
/**
* 首先运行 - 设置初始状态
*/
async prepare() {
console.log('preparing')
}
/**
* 其次运行 - 与用户交互
*/
async interact() {
console.log('interacting')
}
/**
* 第三运行 - 执行主要的命令逻辑
*/
async run() {
console.log('running')
}
/**
* 最后运行 - 清理与错误处理
*/
async completed() {
console.log('completed')
}
}
下表描述了每个生命周期方法及其预期用途:
| 方法 | 说明 |
|---|---|
prepare | Ace 执行的第一个方法。用它来初始化后续方法所需的 state 或数据。 |
interact | 在 prepare 之后执行。用它来显示提示并收集用户输入。 |
run | 命令的主要逻辑。该方法在 interact 之后调用。 |
completed | 在所有其他生命周期方法之后调用。用它来执行清理或处理之前方法的错误。 |
你不需要实现所有的生命周期方法。只定义你的命令实际所需的方法即可。对于简单的命令,仅实现 run 方法就足够了。
使用依赖注入
Ace 使用 IoC 容器 构造并执行命令,使你能够将依赖注入到任何生命周期方法中。这对于访问你的命令所需的服务、仓库或其他资源特别有用。
要注入依赖,请将它们作为方法参数的类型提示,并用 @inject 装饰器装饰该方法。
import { inject } from '@adonisjs/core'
import { BaseCommand } from '@adonisjs/core/ace'
import UserService from '#services/user_service'
export default class GreetCommand extends BaseCommand {
/**
* 将 UserService 注入到 prepare 方法
*/
@inject()
async prepare(userService: UserService) {
// 使用注入的服务
}
/**
* 依赖可以注入到任何生命周期方法
*/
@inject()
async interact(userService: UserService) {
}
@inject()
async run(userService: UserService) {
}
@inject()
async completed(userService: UserService) {
}
}
容器会自动解析依赖,包括嵌套依赖,让你无需手动实例化即可轻松访问应用的服务。
处理错误和退出码
当你的命令抛出异常时,Ace 会使用 CLI logger 显示错误,并将命令的退出码设置为 1,表示失败。非零退出码会向 shell 或 CI/CD 系统发出命令失败的信号。
但是,你也可以使用 try/catch 块或 completed 生命周期方法来显式处理错误。当你自己处理错误时,必须更新命令的 exitCode 和 error 属性,以确保命令正确报告其状态。
使用 try/catch 处理错误
使用 try/catch 块直接在可能发生错误的处理方法中处理错误。这让你对错误处理有精细的控制。
import { BaseCommand } from '@adonisjs/core/ace'
export default class GreetCommand extends BaseCommand {
async run() {
try {
await runSomeOperation()
} catch (error) {
/**
* 记录错误消息
*/
this.logger.error(error.message)
/**
* 更新命令状态以表示失败
*/
this.error = error
this.exitCode = 1
}
}
}
在 completed 方法中处理错误
completed 生命周期方法提供了一个集中的位置,用于处理来自任何先前生命周期方法的错误。当你想要跨所有命令阶段保持一致的错误处理时,这会很有用。
import { BaseCommand } from '@adonisjs/core/ace'
export default class GreetCommand extends BaseCommand {
async run() {
/**
* 如果这里抛出异常,错误将在 completed() 中可用
*/
await runSomeOperation()
}
async completed() {
if (this.error) {
/**
* 处理来自任何生命周期方法的错误
*/
this.logger.error(this.error.message)
/**
* 返回 true 以通知 Ace 你已经处理了错误
* 这会防止 Ace 再次记录该错误
*/
return true
}
}
}
终止应用
Ace 会在执行完你的命令后自动终止应用。但是,当你为长时间运行的命令启用 staysAlive 选项时,你必须在命令完成或发生错误时显式终止应用。
使用 this.terminate 方法优雅地关闭应用。这常用于需要基于特定条件退出的长时间运行进程。
import { BaseCommand } from '@adonisjs/core/ace'
import type { CommandOptions } from '@adonisjs/core/types/ace'
export default class MonitorRedisCommand extends BaseCommand {
static options: CommandOptions = {
startApp: true,
staysAlive: true
}
async run() {
const redis = createRedisConnection()
/**
* 当连接失败时终止应用
*/
redis.on('error', (error) => {
this.logger.error(error)
this.terminate()
})
/**
* 开始监控 Redis
*/
redis.monitor()
}
}
在应用终止前清理
多个事件都可能触发应用终止,包括进程管理器发送的
SIGTERM 信号
,或用户按下 Ctrl+C 时。为确保你的命令在关闭前执行必要的清理,请监听 terminating 钩子。
terminating 钩子应在 prepare 生命周期方法中注册,该方法在你的命令主要逻辑之前运行。这确保清理处理器在任何工作开始之前就已就绪。
import { BaseCommand } from '@adonisjs/core/ace'
import type { CommandOptions } from '@adonisjs/core/types/ace'
export default class QueueWorkerCommand extends BaseCommand {
static options: CommandOptions = {
startApp: true,
staysAlive: true
}
prepare() {
/**
* 注册在终止前运行的清理逻辑
*/
this.app.terminating(() => {
/**
* 关闭数据库连接、刷新日志等。
*/
this.logger.info('Shutting down gracefully...')
})
}
async run() {
/**
* 开始长时间运行的工作
*/
await this.processJobs()
}
}