Skip to content

Feature 系统

Feature 是 Zhin.js 的核心抽象,统一管理框架中各种可扩展的功能模块。

什么是 Feature

Feature 解决的问题是:框架有很多可扩展的功能(命令、工具、定时任务、配置等),每种功能都需要:

  • 注册/注销机制
  • 按插件隔离
  • 序列化为 JSON(HTTP API 使用)
  • 向插件注入扩展方法

Feature 提供了统一的基类,所有内置功能都继承自它。

内置 Feature 列表

Featurename插件扩展方法说明
CommandFeaturecommandaddCommand()命令注册与匹配
ToolFeaturetooladdTool()AI 工具注册
SkillFeatureskilldeclareSkill()AI 技能声明
ConfigFeatureconfigaddConfig()配置项注册
CronFeaturecronaddCron()定时任务管理
PermissionFeaturepermissionaddPermission()权限规则管理
DatabaseFeaturedatabasedefineModel()数据库模型定义
ComponentFeaturecomponentaddComponent()消息组件注册

Feature 基类 API

typescript
abstract class Feature<T> {
  // 元数据(子类必须实现)
  abstract readonly name: string   // Feature 名称
  abstract readonly icon: string   // 图标(用于 UI)
  abstract readonly desc: string   // 描述

  // 核心操作
  add(item: T, pluginName: string): () => void   // 添加项目,返回 dispose
  remove(item: T): boolean                        // 移除项目

  // 查询
  getByPlugin(pluginName: string): T[]            // 按插件获取
  get items(): T[]                                // 所有项目

  // 序列化(HTTP API 使用)
  toJSON(pluginName?: string): FeatureJSON

  // 插件扩展(自动注入到 Plugin.prototype)
  get extensions(): Record<string, Function>

  // 生命周期(可选)
  mounted?(plugin: Plugin): void
  dispose?(): void
}

工作原理

1. 注册 Feature

Feature 通过 provide() 注册到框架中:

typescript
// 框架启动时自动注册(setup.ts)
provide(new CommandFeature())
provide(new ToolFeature())
provide(new SkillFeature())
// ...

2. 注入扩展方法

注册后,Feature 的 extensions 会自动注入到所有插件的 API 中。例如 CommandFeature 的 extensions:

typescript
class CommandFeature extends Feature<MessageCommand> {
  get extensions() {
    const feature = this
    return {
      addCommand(command: MessageCommand) {
        const plugin = getPlugin()
        const dispose = feature.add(command, plugin.name)
        plugin.recordFeatureContribution(feature.name, command.name)
        plugin.onDispose(dispose)
        return dispose
      }
    }
  }
}

这就是为什么插件中可以直接调用 addCommand()addTool() 等方法。

3. 按插件跟踪

每个 Feature 记录哪些项目属于哪个插件:

typescript
feature.getByPlugin('my-plugin')  // 获取 my-plugin 贡献的所有项目

当插件卸载时,其贡献的所有项目会自动清理。

4. JSON 序列化

Feature 可以序列化为 JSON,供 HTTP API 和 Web 控制台使用:

typescript
feature.toJSON('my-plugin')
// {
//   name: 'command',
//   icon: 'Terminal',
//   desc: '命令',
//   count: 3,
//   items: [{ name: 'hello', desc: '打招呼' }, ...]
// }

在插件中使用 Feature

通过扩展方法(推荐)

typescript
const { addCommand, addTool, addCron, declareSkill } = usePlugin()

// 直接调用扩展方法
addCommand(new MessageCommand('hello').action(() => '你好'))
addTool({ name: 'my_tool', ... })
addCron(new Cron('0 8 * * *', () => {}))
declareSkill({ description: '...' })

通过 inject(高级)

typescript
const { inject } = usePlugin()

const commandFeature = inject('command')
const allCommands = commandFeature?.items  // 获取所有命令

自定义 Feature

你可以创建自己的 Feature:

typescript
import { Feature, FeatureJSON, usePlugin, getPlugin } from 'zhin.js'

// 定义项目类型
interface Webhook {
  name: string
  url: string
  events: string[]
}

// 实现 Feature
class WebhookFeature extends Feature<Webhook> {
  readonly name = 'webhook' as const
  readonly icon = 'Link'
  readonly desc = 'Webhook'

  // 自定义方法
  trigger(name: string, data: any) {
    const webhook = this.items.find(w => w.name === name)
    if (webhook) {
      // 发送 HTTP 请求...
    }
  }

  toJSON(pluginName?: string): FeatureJSON {
    const list = pluginName ? this.getByPlugin(pluginName) : this.items
    return {
      name: this.name,
      icon: this.icon,
      desc: this.desc,
      count: list.length,
      items: list.map(w => ({ name: w.name, desc: w.url })),
    }
  }

  // 注入插件扩展方法
  get extensions() {
    const feature = this
    return {
      addWebhook(webhook: Webhook) {
        const plugin = getPlugin()
        const dispose = feature.add(webhook, plugin.name)
        plugin.recordFeatureContribution(feature.name, webhook.name)
        plugin.onDispose(dispose)
        return dispose
      }
    }
  }
}

// 注册
const { provide } = usePlugin()
provide(new WebhookFeature())

注册后,其他插件就可以使用 addWebhook() 方法了:

typescript
// 在另一个插件中
const { addWebhook } = usePlugin()

addWebhook({
  name: 'deploy',
  url: 'https://example.com/webhook',
  events: ['push'],
})

基于 MIT 许可发布