运行代码节点开发
运行代码是行为流中的后端代码扩展节点。开发者可以在受限的 JavaScript 运行环境中处理数据、执行 GraphQL、调用 API 和行为流,并将结果传递给后续节点。
当现有节点无法直接表达所需逻辑时,再使用运行代码。数据库操作、API 调用和行为流调用如果可以通过专用节点完成,优先使用对应节点,便于配置、维护和排错。
开发流程
添加节点
在行为流中添加 运行代码,选中节点后在右侧编辑器中编写 JavaScript。需要更大编辑区域时,可以展开代码编辑器。
声明输入
在节点的输入配置中添加代码需要使用的参数,并绑定行为流输入、前序节点结果或其他数据。
在代码中通过 context.getArg() 按名称读取:
const quantity = context.getArg('quantity');
const unitPrice = context.getArg('unitPrice');参数名称必须与节点中声明的输入完全一致。
声明输出
在节点的输出配置中添加需要传给后续节点的字段,并选择对应类型。在代码中通过 context.setReturn() 设置结果:
const total = quantity * unitPrice;
context.setReturn('total', total);输出名称和类型必须与节点配置一致。没有声明的返回值不会成为下游节点可以绑定的结果。
测试节点
为输入提供测试值后运行节点,检查输出结果和运行日志。确认节点本身正确后,再测试包含下游节点的完整行为流。
最小完整示例
以下代码读取订单数量和单价,校验输入并返回总价:
const quantity = context.getArg('quantity');
const unitPrice = context.getArg('unitPrice');
if (typeof quantity !== 'number' || quantity <= 0) {
context.error('quantity must be greater than 0');
throw new Error('Invalid quantity');
}
const total = quantity * unitPrice;
context.log('Calculated total:', total);
context.setReturn('total', total);该节点需要声明两个数字输入 quantity、unitPrice,以及一个数字输出 total。
运行环境
代码在 Zion 后端的 JavaScript 沙箱中同步执行。它不是浏览器环境,也不是完整的 Node.js 环境。
| 限制 | 说明 |
|---|---|
| 同步执行 | 不支持 async、await、Promise。Context API 也会阻塞并直接返回结果。 |
| 不支持模块 | 不能使用 import、require 或动态加载第三方包。 |
| 不支持浏览器 API | 不能使用 window、document、localStorage、alert 等对象。 |
| 不支持直接网络请求 | 不能使用 fetch、XMLHttpRequest;应调用已配置的 API 或使用 runGql。 |
| 不支持文件系统 | 不能访问 fs、服务器目录或其他本地文件。 |
| 不支持定时器 | 不能使用 setTimeout、setInterval 等异步调度能力。 |
| 使用专用日志 | 使用 context.log() 或 context.error(),不要依赖 console.log()。 |
节点运行时间受行为流执行模式、项目版本和全局生命周期限制,详见搭建行为流 - 超时策略。
Context API
运行环境通过全局 context 对象提供输入、输出、日志和后端服务调用能力。编辑器中的 代码工具 列表会显示当前可用方法。
输入、输出和日志
| 方法 | 返回值 | 说明 |
|---|---|---|
context.getArg(name) | any | 读取节点已声明的输入。 |
context.setReturn(name, value) | void | 设置节点已声明的输出。 |
context.log(...values) | void | 写入普通运行日志。 |
context.error(...values) | void | 写入错误级别运行日志。 |
context.error() 只记录日志,不会自动终止节点。需要让节点失败时,应显式抛出异常。
数据与服务调用
runGql
执行项目 Runtime API 中的 GraphQL Query 或 Mutation:
context.runGql(
operationName: string,
gql: string,
variables: Record<string, any>,
permission: { role: string }
): anyoperationName必须与 GraphQL 文本中的 Operation 名称一致。- 没有变量时传入空对象
{}。 permission固定使用{ role: 'admin' }。- 返回 GraphQL 响应中的
data对象;执行失败时节点抛出错误。
在 Zion 编辑器中打开 开发者接入 → GraphiQL,可以查看当前项目的 Schema 并调试 Operation。数据模型发生变化后,应先同步变更或发布,再刷新 Schema。
const orderId = context.getArg('orderId');
const gql = `query GetOrder($id: bigint!) {
order_by_pk(id: $id) {
id
status
total
}
}`;
const result = context.runGql(
'GetOrder',
gql,
{ id: orderId },
{ role: 'admin' }
);
if (!result.order_by_pk) {
throw new Error('Order not found');
}
context.setReturn('order', result.order_by_pk);数据库字段和 GraphQL 语法参阅 Runtime API 参考。
callThirdPartyApi
调用项目中已经配置的第三方 API:
context.callThirdPartyApi(
operationId: string,
args: Record<string, any>
): { code: number; data: any }operationId 和参数名称必须与 API 配置一致。返回后先检查 HTTP 状态码,再读取响应数据:
const response = context.callThirdPartyApi('get_shipping_quote', {
fz_body: {
postcode: context.getArg('postcode'),
},
});
if (response.code < 200 || response.code >= 300) {
context.error('API request failed:', JSON.stringify(response));
throw new Error('Shipping API request failed');
}
context.setReturn('price', response.data.price);callActionFlow
同步调用另一个同步行为流,并等待其执行完成:
context.callActionFlow(
actionFlowId: string,
versionId: number | null,
args: Record<string, any>
): any返回目标行为流声明的输出。第二个参数是为兼容早期接口保留的版本参数;调用时固定传入 null,不需要指定行为流版本。
const result = context.callActionFlow(
'8a76458b-3572-48c3-ae54-b478286eac61',
null,
{ orderId: context.getArg('orderId') }
);
context.setReturn('status', result.status);callActionFlow 只能调用同步行为流。异步行为流使用 createActionFlowTask。
createActionFlowTask
创建异步行为流任务:
context.createActionFlowTask(
actionFlowId: string,
versionId: number | null,
args: Record<string, any>
): number第二个参数同样固定传入 null。该方法立即返回任务 ID,不会返回目标行为流的最终输出:
const taskId = context.createActionFlowTask(
'd3ea4f95-5d34-46e1-b940-91c4028caff5',
null,
{ reportId: context.getArg('reportId') }
);
context.setReturn('taskId', taskId);uploadMedia
从可访问的 URL 下载资源并保存到 Zion 媒体存储:
context.uploadMedia(
url: string,
headers: Record<string, string>
): { mediaType: string; id: number }不需要请求头时传入 {}。返回的 id 可以写入图片、视频或文件字段,也可以传给后续节点。
const media = context.uploadMedia(
context.getArg('fileUrl'),
{}
);
context.setReturn('mediaId', media.id);
context.setReturn('mediaType', media.mediaType);用户与平台能力
SSO 用户信息
| 方法 | 返回值 | 说明 |
|---|---|---|
context.getSsoAccountId() | number | null | 返回当前 SSO 用户对应的账号 ID;没有 SSO 身份时返回 null。 |
context.getSsoUserInfo() | string | null | 返回 SSO 提供方的用户信息 JSON 字符串;没有 SSO 身份时返回 null。 |
需要读取用户信息字段时,先判断是否为空,再解析 JSON:
const rawUserInfo = context.getSsoUserInfo();
const userInfo = rawUserInfo ? JSON.parse(rawUserInfo) : null;微信模板消息
Zion 的代码工具还提供 wechatSendTemplateMessageWithUserOpenId,用于向指定微信 OpenID 发送模板消息:
context.wechatSendTemplateMessageWithUserOpenId(
token: string,
wechatAppId: string,
accountOpenId: string,
templateId: string,
pagepath: string,
data: Record<string, any>,
clientMsgId: string,
url: string
): string具体 Token、模板 ID、跳转地址和消息字段应来自当前微信服务号与小程序配置。可选字符串没有值时传入空字符串。
使用第三方库
运行环境不能直接执行 import 或 require,但可以在本地将兼容的第三方库打包为自包含脚本,再把构建结果放在节点业务代码之前。
适合使用的库应同时满足以下条件:
- 可以同步执行,不依赖
Promise或异步初始化。 - 不依赖 Node.js 内置模块、文件系统或原生扩展。
- 不依赖
window、document、Worker 等浏览器能力。 - 可以打包为单个 JavaScript 文件,且构建产物体积可控。
打包自包含脚本
以下示例使用 Webpack 将 crypto-js 输出为全局变量。先创建独立目录并安装依赖:
mkdir run-code-library
cd run-code-library
npm init --yes
npm install --save-dev webpack webpack-cli
npm install crypto-js创建 entry.js:
const CryptoJS = require('crypto-js');
module.exports = CryptoJS;创建 webpack.config.js:
const path = require('path');
module.exports = {
mode: 'production',
entry: './entry.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'crypto-js.bundle.js',
library: {
name: 'CryptoJS',
type: 'var',
},
},
};运行构建:
npx webpack --config webpack.config.js将 dist/crypto-js.bundle.js 的内容放在节点代码开头,再使用生成的全局变量:
// crypto-js.bundle.js 的内容
// ...
const content = context.getArg('content');
const digest = CryptoJS.SHA256(content).toString();
context.setReturn('digest', digest);第三方库仍受节点沙箱和超时限制。固定并记录依赖版本,检查许可证和代码来源;升级依赖后重新构建并测试。不要使用来源不明的构建产物,也不要在库代码或日志中写入密钥。
测试与部署
- 使用节点测试覆盖正常输入、空值、边界值和失败场景。
- 通过
context.log()、context.error()和运行日志检查执行过程。 - 测试完整行为流,确认输出类型和下游绑定正确。
- 保存行为流后,通过 同步变更或发布部署后端配置。
同步变更和发布都会更新行为流。部署后,所有调用该行为流的客户端都会使用新的代码逻辑。
不要在日志中输出密码、Token、密钥、完整用户资料或其他敏感信息。
常见错误
| 错误 | 检查方法 |
|---|---|
getArg 无法读取值 | 确认输入已经在节点中声明、名称完全一致且绑定有值。 |
| 下游找不到返回值 | 确认输出已经声明,且 setReturn 使用相同名称和兼容类型。 |
async、Promise 或定时器报错 | 将逻辑改为同步执行,使用会同步返回结果的 Context API。 |
window、fetch、require 不存在 | 运行环境不是浏览器或 Node.js;使用平台提供的 Context API。 |
| GraphQL 字段不存在 | 同步数据模型并刷新 GraphiQL,使用当前项目实际生成的字段和类型。 |
| GraphQL Operation 执行失败 | 检查 Operation 名称、Variables、{ role: 'admin' } 和运行日志。 |
| API 返回空数据 | 先检查 callThirdPartyApi 返回的 code,再按 API 配置读取 data。 |
| 行为流调用失败 | 确认同步行为流使用 callActionFlow,异步行为流使用 createActionFlowTask。 |
| 节点超时 | 检查循环、GraphQL、API 和同步行为流耗时,并确认项目版本对应的超时限制。 |