工作流集成
工作流集成用于在工作流运行到指定位置时调用租户侧内部领域方法、执行 OpenLink 服务调用流程,或者等待外部系统通过 Webhook 返回处理结果。审批流程 和 自动化任务 两套设计画布使用同一份集成契约,区别只在下面的载荷来源和运行时差异。
| 工作流 | 设计画布 | 载荷来源 | 等待回调差异 |
|---|---|---|---|
| 审批流程 | 审批流程设计画布 | source.type = 'approval',包含实例、节点、触发、历史与回调结果 | 等待回调节点没有超时;实例会一直等待回调到达 |
| 自动化任务 | 自动化任务设计画布 | source.type = 'automation',包含定义、执行、节点、触发快照与步骤输出 | 等待回调节点必须配置超时(单位为秒时不能少于 60),超时后本次执行失败 |
这不是一组供外部系统直接调用的 Runtime API。实际集成入口是设计画布中的 服务调用 和 等待回调 节点:
| 场景 | 节点组合 | 适用方式 |
|---|---|---|
| 调用 Soln 内部能力 | 服务调用(Studio 脚本) | 在脚本中调用 context.form.*、context.http.* 等 Studio Runtime 方法 |
| 同步调用外部系统 | 服务调用(集成流) | 执行已发布、触发方式为“服务调用”的 OpenLink 入站或出站流程 |
| 等待外部异步结果 | 服务调用 → 等待回调 | 先发起外部任务,再由入站 Webhook 流程完成工作流回调 |
租户数据和集成执行记录均位于当前租户侧。外部系统只访问为 OpenLink 配置的公开入口,不直接访问内部 Runtime 路由。
前置准备
打开目标工作流并进入设计画布:审批流程从 审批流程管理 进入,自动化任务从 自动化任务 进入。集成前确认:
- 工作流已经关联正确的本地表单
- 使用 Studio 脚本时,脚本资产和入口文件已经发布
- 使用 OpenLink 集成流时,OpenLink、表单绑定和流程版本已经启用并发布
- 服务调用流程的本地表单与工作流关联表单一致
- 异步回调流程是已发布的入站 Webhook 流程,其本地表单同样与工作流一致
服务调用 Payload
Studio 脚本和 OpenLink 服务调用流程接收同一个 soln.runtime-context/v1 上下文,节点配置面板会展示当前工作流对应的 Payload 预览。
审批流程发送的 source 结构:
type ApprovalServiceCallPayload = {
schema: 'soln.runtime-context/v1';
source: {
type: 'approval';
definitionId: string;
instanceId: string;
nodeKey: string;
nodeName: string;
trigger: {
action: 'create' | 'update' | 'delete';
changedFields: string[];
sourceDataStatus: string | null;
sourceApprovalStatus: string | null;
inheritedOutcome: 'approved' | 'rejected' | 'returned' | null;
};
history: Array<{
nodeKey: string;
nodeName: string;
nodeType: string;
status: string;
outcome: string | null;
detail: Record<string, unknown>;
startedAt: string;
completedAt: string | null;
}>;
callback: {
flowDefinitionId: string | null;
correlationKey: string | null;
outcome: 'approved' | 'rejected' | 'returned' | null;
payload: Record<string, unknown>;
receivedAt: string | null;
} | null;
};
form: {
id: string;
apiName: string;
name: string | null;
} | null;
record: {
id: string;
data: Record<string, unknown>;
system: Record<string, unknown>;
} | null;
runtime: {
locale: string;
timezone: string;
executionId: string;
traceId: string;
};
};
自动化任务发送的 source 结构:
type AutomationServiceCallPayload = {
schema: 'soln.runtime-context/v1';
source: {
type: 'automation';
definitionId: string;
executionId: string;
nodeKey: string;
trigger: Record<string, unknown>;
steps: Record<string, { output: unknown }>;
};
form: { id: string; apiName: string; name: string | null } | null;
record: { id: string; data: Record<string, unknown> } | null;
result: unknown;
runtime: {
executionId: string;
};
};
两类工作流共用的字段:
| 字段 | 用途 |
|---|---|
record.id | 当前工作流处理的记录 ID;调用面向记录的内部方法时使用 |
record.data | 服务调用执行时的记录数据快照,按字段 API 名读取 |
form.apiName | 当前关联表单 API 名;调用面向表单的方法时使用 |
source.nodeKey | 当前服务调用节点 key |
runtime.traceId | 当前节点稳定的执行追踪 ID,适合传给外部系统 |
审批流程专有字段:
| 字段 | 用途 |
|---|---|
source.instanceId | 当前审批实例 ID |
source.trigger | 启动审批的记录动作和字段变化信息 |
source.history | 当前节点之前的审批节点执行历史 |
source.callback | 上游已经完成等待回调时的最近一次回调结果 |
自动化任务专有字段:
| 字段 | 用途 |
|---|---|
source.definitionId | 自动化任务定义 ID |
source.executionId | 当前自动化执行 ID |
source.trigger | 触发方式配置的快照 |
source.steps | 已经执行过的节点步骤输出 |
result | 上游节点结果 |
两类载荷都会提供完整的 record.data 快照,不需要再通过 URL 参数寻找当前记录。
调用内部方法
配置服务调用节点
- 在审批设计画布添加 服务调用 节点
- 把 调用方式 设置为 Studio 脚本
- 选择已经发布的脚本资产和入口文件
- 查看配置面板中的 Payload 预览,确认脚本使用的字段 API 名
- 为节点连接一条入口和一条出口
- 保存并发布审批流程
这里的“内部方法”是 Studio Runtime 暴露给服务端脚本的租户领域方法,例如 context.form.get、context.form.update 和 context.form.create。脚本不应调用旧 MCP 工具,也不应自行连接租户数据库。
示例:审批通过后更新当前记录
type StudioRuntimeContext = {
form: {
update(input: {
recordId: string;
data: Record<string, unknown>;
}): Promise<unknown>;
};
};
export default async function main(
payload: ApprovalServiceCallPayload,
context: StudioRuntimeContext,
) {
if (!payload.record?.id) {
return {
ok: false,
message: 'Approval record is unavailable.',
};
}
if (payload.source.trigger.inheritedOutcome !== 'approved') {
return { ok: true, skipped: true };
}
await context.form.update({
recordId: payload.record.id,
data: {
integrationStatus: 'approved',
approvalInstanceId: payload.source.instanceId,
},
});
return {
ok: true,
recordId: payload.record.id,
};
}
服务调用脚本返回以下任一失败结果时,审批实例会阻塞在当前节点:
return { ok: false, message: 'Unable to update the record.' };
return { success: false, errorMessage: 'External validation failed.' };
status 为 failed、failure、error 或 rejected 也会被识别为失败。脚本抛出异常、脚本执行失败或选择的发布文件不存在,同样会阻塞节点,并在审批执行日志中记录原因。处理问题后可以从审批实例中重试该服务调用。
示例:在脚本中同步调用外部 HTTP 服务
简单的同步 HTTP 集成也可以直接使用 context.http.requestJson:
type StudioRuntimeContext = {
http: {
requestJson<T>(input: {
method: string;
url: string;
headers?: Record<string, string>;
json?: unknown;
timeoutMs?: number;
}): Promise<{ status: number; data: T }>;
};
};
export default async function main(
payload: ApprovalServiceCallPayload,
context: StudioRuntimeContext,
) {
if (!payload.record) {
return { ok: false, message: 'Approval record is unavailable.' };
}
const response = await context.http.requestJson<{
accepted: boolean;
referenceId?: string;
}>({
method: 'POST',
url: 'https://api.example.com/risk-checks',
headers: {
'Idempotency-Key': payload.runtime.traceId,
},
json: {
recordId: payload.record.id,
amount: payload.record.data.amount,
approvalInstanceId: payload.source.instanceId,
},
timeoutMs: 10_000,
});
if (!response.data.accepted) {
return {
ok: false,
message: 'Risk check was rejected.',
};
}
return {
ok: true,
referenceId: response.data.referenceId ?? null,
};
}
生产集成不要在脚本中硬编码凭证。外部鉴权应配置在 OpenLink 集成系统中;复杂映射、审计和重用场景优先使用服务调用型 OpenLink 流程。
使用服务调用节点执行集成流
准备服务调用型 OpenLink 流程
- 进入目标 OpenLink 的 集成流配置
- 新建入站或出站流程
- 把触发方式设置为 服务调用
- 选择与审批流程相同的本地表单绑定
- 在流程设计画布配置来源、转换、目标以及需要的消息节点
- 保存、发布并启用流程
服务调用节点只会列出:
- 已发布且存在活动版本的流程;
- 触发方式为“服务调用”的流程;
- 本地表单与当前工作流关联表单一致的流程;
- 入站或出站 OpenLink 流程
配置节点
- 在设计画布添加 服务调用 节点
- 把 调用方式 设置为 集成流
- 从按本地表单分组的列表中选择已发布流程
- 连接入口和出口,保存并发布工作流
运行时,节点把完整的服务调用 Payload 传给 OpenLink 流程,并等待该次服务调用流程执行结束:
- OpenLink 执行成功:服务调用节点完成,工作流继续后续路径
- OpenLink 执行失败:当前节点和本次工作流执行进入阻塞状态
- 重试服务调用:重试同一次执行的同一个节点,不创建新的路径
示例:同步推送采购订单
推荐画布:
开始 → 审批 → 服务调用(集成流:发送采购订单) → 结束
OpenLink 出站流程:
开始 → 来源 → 转换 → 目标(Studio 脚本或外部调用) → 结束
目标脚本从运行上下文中的记录数据读取采购订单,并向外部系统发送请求。外部系统必须在当前调用内返回最终成功或失败。需要稍后才能确定结果的任务,不应让同步服务调用长期等待,应改用“服务调用 → 等待回调”。
等待外部回调
适用场景
外部系统先接收任务,经过人工审核、异步作业或第三方处理后才有最终结果时,使用:
服务调用 → 等待回调 → 后续节点
等待回调节点必须直接连接在服务调用节点后面。不能在两者之间插入条件、消息或审批节点。
审批流程的等待回调节点没有超时设置,实例会一直等待;自动化任务的等待回调节点必须配置超时(单位为秒时不能少于 60),超时后等待节点和本次执行失败。
准备回调 OpenLink 流程
- 在 OpenLink 中创建 入站 流程
- 把触发方式设置为 Webhook
- 使用与工作流相同的本地表单绑定
- 在流程设计画布中,把目标动作设置为 完成工作流回调
- 保存、发布并启用该流程
- 复制流程提供的 Webhook URL
回调流程必须是已发布、有活动版本且绑定已启用的入站 Webhook 流程。不要把“服务调用”流程当作回调入口。
配置等待回调节点
- 在审批画布中,把 等待回调 直接连接到发起异步任务的服务调用节点
- 选择前面准备好的入站 Webhook 流程
- 配置关联键模板。常见配置是
{{data.record.id}} - 把关联键传给外部系统,并确保外部回调时原样返回
- 从等待回调节点连接唯一一条后续路径
- 保存并发布工作流
审批流程的等待回调节点没有超时,实例会一直等待回调;自动化任务的等待回调节点必须配置超时(单位为秒时不能少于 60),超时后等待节点和本次执行失败。
关联键在当前租户、回调流程和等待中的审批实例之间用于匹配。它必须稳定且能够唯一定位等待项。如果同一个记录可能同时存在多个等待实例,应使用包含审批实例 ID 或外部任务 ID 的唯一键,并以设计器实际支持的模板上下文为准。
示例:发起异步任务
服务调用节点可以通过 Studio 脚本创建外部任务:
export default async function main(
payload: ApprovalServiceCallPayload,
context: StudioRuntimeContext,
) {
if (!payload.record) {
return { ok: false, message: 'Approval record is unavailable.' };
}
const correlationKey = payload.record.id;
const response = await context.http.requestJson<{
taskId: string;
}>({
method: 'POST',
url: 'https://api.example.com/manual-reviews',
headers: {
'Idempotency-Key': payload.runtime.traceId,
},
json: {
correlationKey,
recordId: payload.record.id,
callbackUrl: 'https://soln.example.com/api/open-link/webhooks/review-callback',
},
timeoutMs: 10_000,
});
return {
ok: true,
correlationKey,
externalTaskId: response.data.taskId,
};
}
这里脚本发送的 correlationKey 必须和等待回调节点模板渲染出的值一致。Webhook URL 应使用等待回调节点配置面板展示的真实地址,不要照抄示例地址。
外部系统回调请求
外部系统处理成功后,向回调流程的 Webhook URL 发送 JSON:
{
"callbackId": "review-result-8f1d31",
"correlationKey": "record-7c831b",
"status": "succeeded",
"result": {
"decision": "approved",
"externalTaskId": "review-2048",
"comment": "Risk review passed"
},
"error": null,
"completedAt": "2026-09-10T08:30:00.000Z"
}
字段说明:
| 字段 | 必填 | 说明 |
|---|---|---|
callbackId | 是 | 外部系统生成的唯一回调 ID,用于标识本次回调 |
correlationKey | 是 | 必须与等待回调节点渲染出的关联键完全一致 |
status | 是 | 审批回调必须为 succeeded |
result | 是 | 回调结果对象 |
result.decision | 否 | approved、rejected 或 returned |
error | 是 | 成功时必须为 null |
completedAt | 否 | 合法 ISO 时间;也可以为 null |
审批回调不接受 failed 或 canceled 状态。如果外部任务失败,应由外部系统保留任务供重试,或按业务约定使用成功回调中的 result 表达可路由的业务结果。
匹配成功后,Webhook 流程会完成等待节点并恢复审批实例。找不到等待项时返回 WORKFLOW_CALLBACK_WAIT_NOT_FOUND;同一个流程和关联键匹配到多个等待项时返回 WORKFLOW_CALLBACK_WAIT_NOT_UNIQUE。
自动化任务用 分支 节点做后续路由:分支条件读取记录字段,回调结果可以通过 source.steps 提供给后续节点。
根据回调结果继续路由
等待回调完成后,后续服务调用和消息节点可以通过 source.callback 读取最近的回调:
export default async function main(payload: ApprovalServiceCallPayload) {
const callback = payload.source.callback;
if (callback?.outcome === 'rejected') {
return {
ok: false,
message: String(callback.payload.comment || 'External review rejected.'),
};
}
return {
ok: true,
externalTaskId: callback?.payload.externalTaskId ?? null,
};
}
需要在画布中分支时,在等待回调后添加 分支 节点,并选择 回调结果 字段:
服务调用 → 等待回调 → 分支(回调结果)
├─ approved → 后续处理
└─ rejected / returned → 其他处理
“回调结果”只在分支节点的上游路径已经经过等待回调时可用。
完整集成示例
同步集成
开始
→ 审批
→ 服务调用(Studio 脚本或服务调用型 OpenLink 流程)
→ 消息
→ 结束
适用于外部服务能在一次请求内返回最终结果的场景。服务调用失败会阻塞审批实例,处理错误后重试节点。
异步审批集成
开始
→ 审批
→ 服务调用(创建外部任务,并传递 correlationKey)
→ 等待回调(入站 Webhook + workflow_callback)
→ 分支(context.callback.outcome)
├─ approved → 更新记录 → 结束
├─ rejected → 消息 → 结束
└─ returned → 人工处理 → 结束
这条链路中:
- 服务调用只负责创建外部任务
- 等待回调节点持久化等待状态并暂停审批
- 外部系统调用 OpenLink Webhook
- Webhook 流程验证回调协议并匹配等待项
- 审批恢复,回调结果进入后续条件和服务调用上下文
自动化任务示例
同步调用
开始(数据变化:订单新增)
→ 服务调用(Studio 脚本:调用外部系统)
→ 数据处理(更新订单)
→ 结束
外部系统能在当前调用内返回最终结果时使用这种方式。失败会阻塞该节点和本次自动化执行,修好问题后可重试节点。
异步等待回调
开始(数据变化:订单新增)
→ 服务调用(Studio 脚本:创建外部审核任务并回传 correlationKey)
→ 等待回调(入站 Webhook,配置等待超时)
→ 数据处理(把回调结果写回记录)
→ 结束
- 服务调用只负责创建外部任务
- 等待回调节点持久化等待状态并暂停执行;等待超时必须有效,单位为秒时不能少于 60
- 外部系统按回调协议调用 OpenLink Webhook
- 执行恢复,后续节点可以从
source.steps读取之前的步骤输出,或用数据处理节点把结果写回记录
失败处理与幂等
- 服务调用脚本应使用
runtime.traceId或稳定业务键作为外部幂等键 - 回调
callbackId应由外部系统生成并保持唯一 correlationKey必须与等待节点完全一致,并且不能同时匹配多个等待实例- Studio 脚本返回
ok: false、success: false或失败status会阻塞工作流 - OpenLink 服务调用执行失败也会阻塞工作流,并保留执行日志
- 外部系统不应调用内部 Runtime 路由;公开回调入口是 OpenLink Webhook
- 不要在服务调用脚本中使用旧 MCP 工具或直接操作租户数据库
- 更新租户业务记录时,使用 Studio Runtime 的领域方法,并传入真实的
record.id - 同步调用只用于能够在有界时间内完成的操作;长耗时处理使用等待回调
- 发布前用真实租户记录验证字段 API 名、关联键、外部鉴权、回调协议和错误分支