API v1
Documentation/开发指南

开发动作

把业务函数变成按钮和流程可执行的动作。

Plugin API v1

Documentation content is currently available in Simplified Chinese.

在飞块中,**动作(Action)**是用户主动触发的核心业务操作。无论是轻触一下实体按键,还是在自动化流程中组合连招,动作都是最基础、最强大的构建单元。

常见的动作场景非常广泛:

  • 数据转换类:比如“拆分文本”,接收一段文字并切分成数组供后续环节使用;
  • 系统联动类:比如“打开应用”或“调节音量”,接收用户指定的参数并与操作系统深度交互;
  • 网络服务类:比如“发送 Webhook”或“查询天气”,拉取外部服务并返回解析结果。

开发一个新动作的过程非常标准:先在清单中声明动作的输入与输出契约,再在后台入口中使用对应 ID 注册处理逻辑。


#1. 声明输入与输出(manifest.json)

输入与输出的契约是连接用户界面与后台逻辑的桥梁。下面是来自“文本工具箱”示例的清单声明,它接收一段主文本 text 和一个可选的分隔符 separator,并输出拆分后的列表与总项数:

json
{
  "apiVersion": 1,
  "id": "example.text-toolkit",
  "name": "文本处理工具",
  "version": "1.0.0",
  "description": "文本处理工具教学示例",
  "hostPlatforms": [
    "windows",
    "macos",
    "linux"
  ],
  "main": "index.js",
  "permissions": [],
  "contributes": {
    "actions": [
      {
        "id": "split",
        "title": "整理文本",
        "description": "按分隔符拆分,去除空白项,最多返回前 100 项",
        "argsSchema": {
          "type": "object",
          "properties": {
            "text": {
              "type": "string",
              "title": "文本",
              "format": "multiline",
              "maxLength": 2000,
              "titleKey": "actions.split.argsSchema.properties.text.title"
            },
            "separator": {
              "type": "string",
              "title": "分隔符",
              "default": ",",
              "maxLength": 8,
              "titleKey": "actions.split.argsSchema.properties.separator.title"
            }
          },
          "required": [
            "text"
          ],
          "additionalProperties": false
        },
        "outputSchema": {
          "type": "object",
          "properties": {
            "items": {
              "type": "array",
              "items": {
                "type": "string"
              },
              "maxItems": 100
            },
            "count": {
              "type": "integer"
            }
          },
          "required": [
            "items",
            "count"
          ],
          "additionalProperties": false
        },
        "titleKey": "actions.split.title",
        "descriptionKey": "actions.split.description"
      }
    ]
  },
  "i18n": {
    "defaultLocale": "zh-CN",
    "resources": {
      "zh-CN": "locales/zh-CN.json",
      "en-US": "locales/en-US.json"
    }
  },
  "nameKey": "plugin.name",
  "descriptionKey": "plugin.description"
}

为什么这样设计?

  • argsSchema(参数表单):飞块会根据你声明的数据结构,自动在动作设置面板中为用户生成直观易用的输入表单(支持文本、下拉选择、数字输入乃至文件选择器)。
  • outputSchema(步骤结果):定义了动作执行完成后的返回结构。这里之所以推荐返回一个对象(如 { items, count }),是因为后续流程中的其它动作可能各取所需——有的步骤只需要处理数组 items,而有的步骤仅需利用 count 做条件分支判断。

📚 深入参考:完整的参数校验规则与字段属性,请参阅 Action 清单参考。


#2. 编写处理函数(index.js)

在后台 JavaScript 代码中,通过 definePlugin 注册该动作的具体业务逻辑:

js
"use strict";
const { definePlugin } = require(process.env.FLYONDECK_SDK_PATH);
definePlugin({
  actions: {
    split({ text, separator = "," }) {
      const items = text
        .split(separator)
        .map((s) => s.trim())
        .filter(Boolean)
        .slice(0, 100);
      return { items, count: items.length };
    },
  },
});

#参数处理与默认值

飞块会在调用前根据 argsSchema 自动完成数据类型的基础校验。然而,对于可选字段(如这里的 separator),当用户没有在界面填写时,参数可能为 undefined。因此,推荐使用 ES6 的函数默认参数或空值合并运算符(??)赋予安全初始值。

#优雅的错误反馈

当用户传入的参数符合数据类型、但因逻辑冲突无法继续执行时(例如处理 CSV 时发现“数据缺少关键列”),请直接抛出一个包含友好说明的 Error:

javascript
if (!fileContent.includes("name")) {
  throw new Error("处理失败:选中的 CSV 文件中未找到 'name' 列,请检查文件格式。");
}

飞块会敏锐捕捉到抛出的异常,并在宿主界面中以红色醒目标记提示用户,同时中止后续依赖该结果的步骤。这样,用户能立即明白问题出在哪里,而不需要盲目排查。


#3. 在工作流中使用动作结果

动作不仅能单点绑定到按键上,更能在飞块的**动作流程(Workflow)**中大放异彩:

  • 当用户在“步骤结果”中选中本动作时,填入 items 即可提取拆分后的列表,填入 count 即可拿到项数。
  • 请务必保持函数返回值的字段名与 outputSchema 严格一致,否则工作流下游步骤将无法识别并获取到数据。

💡 概念辨析:动作(Action) vs 状态(State)
动作返回的结果是单次执行的瞬时产物,只属于本次触发。如果你需要展示的是持续变动的指标(例如系统音量、CPU 负载或歌曲播放进度),请移步阅读发布状态。


#4. 深度调用系统能力

动作处理函数的第二个参数 context 是飞块为插件准备的超级工具箱。例如,想要在用户默认浏览器中打开特定网址,无需引入第三方库,直接调用飞块提供的宿主能力:

javascript
await context.system.openExternal(url);

⚠️ 权限申请提醒:调用敏感系统操作或发起网络请求时,务必先在 manifest.json 的 permissions 数组中声明对应权限,以保障用户安全。具体 API 与权限对照请参阅 context.system 参考。


#5. 处理耗时任务与并发互斥

绝大多数动作使用飞块默认的执行策略即可。但在处理高频触发或大文件时,推荐了解以下高级配置:

  • 防止界面假死(超时配置):如果动作涉及大型文件转码或复杂的网络请求,可以在清单的 execution.timeoutMs 中合理配置等待超时(默认通常为合理区间)。
  • 资源互斥锁(并发控制):如果有两个不同的动作会并发操作同一份临界资源(比如都在向同一个本地数据库文件写数据),可以在它们各自的 execution.resource 中填入相同的资源标识字符串。飞块会自动为它们施加排队互斥锁,避免并发写冲突。

⚠️ 关于超时的注意事项:当触发 timeoutMs 时,意味着飞块的工作流引擎停止继续等待并判定该步骤超时,但后台 Node.js 进程中的异步任务可能仍在后台运行。因此,对于具有外部副作用的操作(例如写磁盘、扣费扣款等),在重试前请做好幂等确认。具体定义见 ActionExecution 参考。

#动态候选项

当参数值来自用户环境或插件数据时,在普通动作的顶层字符串参数声明 dynamicOptions: true。宿主提供搜索界面,插件自行查询候选数据;宿主不识别候选项的业务含义。

json
{
  "id": "choose-entry",
  "title": "选择条目",
  "argsSchema": {
    "type": "object",
    "properties": { "entryId": { "type": "string", "title": "条目", "dynamicOptions": true, "maxLength": 256 } },
    "required": ["entryId"],
    "additionalProperties": false
  }
}

后台按动作 ID 和参数名注册候选项处理器。下面的内存数据仅用于演示,实际插件可换成自己的文件或接口查询。

js
const { definePlugin } = require(process.env.FLYONDECK_SDK_PATH);
const entries = [{ label: "条目 A", value: "entry-a" }, { label: "条目 B", value: "entry-b" }];
definePlugin({
  actions: {
    "choose-entry"({ entryId }) {
      const entry = entries.find(item => item.value === entryId);
      if (!entry) throw new Error("所选条目已不存在,请重新选择");
      return { entryId: entry.value };
    }
  },
  actionOptions: {
    "choose-entry": {
      entryId({ query, selectedValue }) {
        const matched = entries.filter(item => item.label.includes(query));
        const selected = entries.find(item => item.value === selectedValue);
        return [...(selected ? [selected] : []), ...matched.filter(item => item.value !== selectedValue)].slice(0, 50);
      }
    }
  }
});

按钮动作、按钮预设、数值控件的固定参数均使用搜索选择器;流程动作参数旁的选择入口打开同一选择器。流程仍可把参数绑定到状态、输入或步骤结果;选择固定值前需先移除已连接的表达式。

查询不会执行动作。保存的配置只包含 value,不保存候选项列表或显示名称。插件返回错误时保留已有值并显示错误;选项更名后通过 selectedValue 回查新名称;目标已删除时保留原 ID,等待用户重新选择。字段联动、条件显示和全局设置动态候选项不在本接口范围内。

Updated 2026-11-04
View implementation sources