概览
工作流引擎执行节点图。插件可贡献节点,并在领域事件发生时启动工作流。请从 @quan-erp/shared-backend-core 导入工作流类型。
- 自定义节点 — 实现 IWorkflowNode,在 @Module({ workflowNodes }) 注册
- 触发 — isTriggerable: true + WorkflowService.findByInitId / executeWorkflow
- API 入口 — 在控制器方法上使用 @WorkflowEntry
- 前端 — 通过 @quan-erp/base-frontend 的 workflowNodes() 注册对应 UI 节点
节点数据模型
- Props — 工作流编辑器 UI 提供的配置
- Args — 节点运行时注入的值
- ReturnType — 本节点产出、供下游使用的值
- type — 插件节点始终为 `${metadata.name}/${NODE_ID}`
Props 与返回值 schema
用 PropsType 声明 JSON schema,并用 SchemaToType 推断 TypeScript 类型。Schema 同时服务 process() 类型与前端编辑器。
1import {
2 PropsType,
3 SchemaToType,
4} from "@quan-erp/shared-backend-core";
5
6const inputSchema = {
7 type: "number",
8} as const satisfies PropsType;
9type Props = SchemaToType<typeof inputSchema>;
10
11const returnSchema = {
12 type: "object",
13 properties: {
14 bookingId: { type: "number" },
15 bookingSource: { type: "string" },
16 },
17} as const satisfies PropsType;
18type ReturnType = SchemaToType<typeof returnSchema>;
19
20export type Args = {
21 bookingId: number;
22 bookingSource: string;
23};执行生命周期
- 01
process()
执行器调用 process(context, uid, props, args)。返回 NodeResult.single(...)。
- 02
nextToExecute()
process 之后询问下一节点 id。null 跟随 UI next 边;string[] 覆盖路由。
- 03
路由
null → 使用画布 next 数组。string[] → 执行这些节点 id。
ExecutionContext
每次运行都会创建共享的 ExecutionContext。使用编辑器映射的 props 前必须先 resolve。
- context.pipeData.resolve(props) — 展开 ${{node_….output}};读取 props 前务必 resolve
- context.execute(nodeId, args) — 立即执行另一节点并等待 NodeResultType(T[])
- toolResult[0] — 子执行的原始载荷
- context.findWorkflowNodeById(uid) — 自定义 nextToExecute 时查看边
1import type {
2 ExecutionContext,
3 NodeResultType,
4 PromisableReturn,
5} from "@quan-erp/shared-backend-core";
6import { NodeResult } from "@quan-erp/shared-backend-core";
7
8function process(
9 context: ExecutionContext,
10 uid: string,
11 props: { expression: string },
12 args: unknown,
13): PromisableReturn<NodeResultType<boolean>> {
14 const resolvedProps = context.pipeData.resolve(props);
15 const evaluateResult = eval(resolvedProps.expression);
16 return NodeResult.single(evaluateResult as boolean);
17}1import type { ExecutionContext } from "@quan-erp/shared-backend-core";
2
3async function callTool(
4 context: ExecutionContext,
5 toolNodeId: string,
6 toolArgs: Record<string, unknown>,
7) {
8 const toolResult = await context.execute(toolNodeId, toolArgs);
9 return toolResult[0];
10}自定义路由(nextToExecute)
为条件/循环覆盖默认边遍历。若要沿用画布边,返回 null。nextToExecute 收到的是 process 的 NodeResultType 数组(使用 args[0])。
1import type {
2 ExecutionContext,
3 NodeResultType,
4 PromisableReturn,
5} from "@quan-erp/shared-backend-core";
6
7function nextToExecute(
8 context: ExecutionContext,
9 uid: string,
10 args: NodeResultType<boolean>,
11): PromisableReturn<string[]> | PromisableReturn<null> {
12 const current = context.findWorkflowNodeById(uid);
13 const ifTrue = current?.next?.[0];
14 const ifFalse = current?.next?.[1];
15 if (args[0] && ifTrue?.id) return [ifTrue.id];
16 if (!args[0] && ifFalse?.id) return [ifFalse.id];
17 return null;
18}完整触发节点
触发节点设置 isTriggerable: true,通常把 Args 作为返回值传给后续图。
1import {
2 ExecutionContext,
3 IWorkflowNode,
4 NodeResult,
5 NodeResultType,
6 PromisableReturn,
7 PropsType,
8 SchemaToType,
9 WorkflowNodeDefination,
10} from "@quan-erp/shared-backend-core";
11import metadata from "../../../module.metadata.json" with { type: "json" };
12
13const inputSchema = {
14 type: "number",
15} as const satisfies PropsType;
16type Props = SchemaToType<typeof inputSchema>;
17
18const returnSchema = {
19 type: "object",
20 properties: {
21 bookingId: { type: "number" },
22 bookingSource: { type: "string" },
23 },
24} as const satisfies PropsType;
25type ReturnType = SchemaToType<typeof returnSchema>;
26
27export type Args = {
28 bookingId: number;
29 bookingSource: string;
30};
31
32export class OnBookingWorkflowNode
33 implements IWorkflowNode<Props, ReturnType, Args>
34{
35 static NODE_ID = "bookings";
36
37 process(
38 context: ExecutionContext,
39 id: string,
40 props: Props,
41 args: Args,
42 ): PromisableReturn<NodeResultType<ReturnType>> {
43 return NodeResult.single(args);
44 }
45
46 nextToExecute(
47 context: ExecutionContext,
48 uid: string,
49 args: NodeResultType<ReturnType>,
50 ): PromisableReturn<string[]> | PromisableReturn<null> {
51 return null;
52 }
53
54 getDefination(): WorkflowNodeDefination {
55 return {
56 type: `${metadata.name}/${OnBookingWorkflowNode.NODE_ID}`,
57 group: ["any"],
58 displayName: "On Hotel Booked",
59 description: "Triggers when a hotel is booked",
60 props: inputSchema,
61 isTriggerable: true,
62 returnType: {
63 type: "array",
64 items: returnSchema,
65 },
66 };
67 }
68}在模块中注册
把节点类加入 workflowNodes,后端才会挂载并向编辑器暴露定义。
1import { Module } from "@quan-erp/shared-backend-core";
2import metadata from "../../module.metadata.json" with { type: "json" };
3import { OnBookingWorkflowNode } from "./workflow/on-booking.workflow.js";
4
5@Module({
6 name: metadata.name,
7 providers: [],
8 controllers: [],
9 entities: [],
10 workflowNodes: [OnBookingWorkflowNode],
11})
12export class MyPluginModule {}以编程方式触发
对 isTriggerable 节点,注入 builtin 的 WorkflowService,按 `${metadata.name}/${NODE_ID}` 查找活跃工作流,并用 Args 执行。
1import {
2 ContainerRegistryManager,
3 Controller,
4 Inject,
5 Post,
6 ResponseDto,
7 WorkflowService,
8} from "@quan-erp/shared-backend-core";
9import metadata from "../../module.metadata.json" with { type: "json" };
10import { OnBookingWorkflowNode } from "../workflow/on-booking.workflow.js";
11
12@Controller("/bookings")
13export class BookingController {
14 @Inject(WorkflowService, ContainerRegistryManager.BUILTIN_PLUGIN)
15 workflowService: WorkflowService;
16
17 @Post("/")
18 async createBooking() {
19 // … create booking …
20 const workflows = await this.workflowService.findByInitId(
21 `${metadata.name}/${OnBookingWorkflowNode.NODE_ID}`,
22 { active: true },
23 );
24
25 await Promise.all(
26 workflows.map((w) =>
27 this.workflowService.executeWorkflow({
28 workflow: w,
29 args: {
30 bookingId: 1,
31 bookingSource: "web",
32 },
33 }),
34 ),
35 );
36
37 return ResponseDto.okWithEmpty();
38 }
39}@WorkflowEntry(HTTP 步骤)
标记控制器方法,使工作流引擎可发现并调用该 API。提供 name、description,以及与响应形状匹配的 returnType schema。
1import {
2 Controller,
3 Get,
4 WorkflowEntry,
5} from "@quan-erp/shared-backend-core";
6
7@Controller("/branch")
8export class BranchController {
9 @Get("/")
10 @WorkflowEntry({
11 name: "get-branch",
12 description: "get all branches",
13 returnType: {
14 type: "object",
15 properties: {
16 payload: {
17 type: "array",
18 items: {
19 type: "object",
20 properties: {
21 id: { type: "number" },
22 name: { type: "string" },
23 },
24 },
25 },
26 },
27 },
28 })
29 async list() {
30 /* … */
31 }
32}检查清单
- Schemas:props 与 returnType 使用 PropsType + SchemaToType
- 在 IWorkflowNode 上实现 process、nextToExecute、getDefination
- type: `${metadata.name}/${NODE_ID}` — 不可省略插件前缀
- 在 @Module({ workflowNodes }) 注册
- 可触发节点:在领域事件上用 WorkflowService 触发
- 使用编辑器 props 前先 context.pipeData.resolve(props)
- 用 NodeResult.single(...) 返回结果