@gadmin2n/schematics 0.0.72 → 0.0.74
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/job.prisma +62 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/system.prisma +0 -21
- package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/workflow.prisma +171 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/AgendaJob.ts +60 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/Event.ts +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/WorkflowEventOutbox.ts +62 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/WorkflowNodeInstance.ts +62 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/WorkflowNodeType.ts +62 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/.env +5 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/package.json +5 -4
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/prisma.config.ts +14 -7
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/index.ts +4 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/permissions.ts +49 -3
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow-node-types.ts +746 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflows.ts +786 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/agenda/agenda.controller.ts +6 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/agenda/agenda.service.ts +79 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/agendaJob/agendaJob.controller.spec.ts +20 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/agendaJob/agendaJob.controller.ts +145 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/agendaJob/agendaJob.module.ts +10 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/{canvas/canvas.service.spec.ts → agendaJob/agendaJob.service.spec.ts} +71 -65
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/agendaJob/agendaJob.service.ts +83 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/index.ts +2 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/temporal.module.ts +9 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/temporal.service.ts +100 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-execution.dto.ts +19 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-export.dto.ts +43 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-export.service.ts +317 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-node-type.controller.ts +16 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-node-type.service.ts +13 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.controller.ts +220 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.dto.ts +82 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.module.ts +16 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.ts +505 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowEventOutbox/workflowEventOutbox.controller.spec.ts +22 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowEventOutbox/workflowEventOutbox.controller.ts +147 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowEventOutbox/workflowEventOutbox.module.ts +10 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowEventOutbox/workflowEventOutbox.service.spec.ts +356 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowEventOutbox/workflowEventOutbox.service.ts +110 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowNodeInstance/workflowNodeInstance.controller.spec.ts +22 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowNodeInstance/workflowNodeInstance.controller.ts +216 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowNodeInstance/workflowNodeInstance.module.ts +10 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowNodeInstance/workflowNodeInstance.service.spec.ts +356 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowNodeInstance/workflowNodeInstance.service.ts +168 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowNodeType/workflowNodeType.controller.spec.ts +22 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowNodeType/workflowNodeType.controller.ts +199 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowNodeType/workflowNodeType.module.ts +10 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowNodeType/workflowNodeType.service.spec.ts +348 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowNodeType/workflowNodeType.service.ts +106 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/yarn.lock +579 -1082
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/README.md +278 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/config/development-sql.yaml +5 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/docker-compose.yml +25 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/package.json +13 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/sql/create-event-trigger.sql +87 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/.env +7 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/SANDBOX.md +122 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/package-lock.json +4285 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/package.json +28 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/__tests__/activities/code-execute.test.ts +44 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/__tests__/activities/http-request.test.ts +87 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/__tests__/helpers.test.ts +225 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/__tests__/node-type-consistency.test.ts +101 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/code-execute.ts +51 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/db-execute.ts +85 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/db-query.ts +35 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/http-request.ts +54 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/index.ts +6 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/reporting.ts +62 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/send-notification.ts +47 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/config.ts +13 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/condition.ts +101 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/context.ts +58 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/graph.ts +184 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/helpers.ts +133 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/node-types.ts +57 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/types.ts +77 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/index.ts +36 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/outbox-poller.ts +226 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/workflows/dsl-workflow.ts +411 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/tsconfig.json +19 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/vitest.config.ts +8 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/yarn.lock +1905 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/package-lock.json +17555 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/package.json +5 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/App.tsx +1 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/sider.tsx +5 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/title.tsx +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/config/routeRegistry.tsx +63 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/dev-shell/DevShell.tsx +91 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/helpers/list.tsx +48 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/helpers/show.tsx +43 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/en/common.json +14 -9
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/zh_CN/common.json +14 -9
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/agenda/index.tsx +309 -56
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/agenda/show.tsx +1 -3
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/agendaJob/create.tsx +108 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/agendaJob/edit.tsx +124 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/agendaJob/index.tsx +4 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/agendaJob/list.tsx +245 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/agendaJob/show.tsx +70 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasListPage.tsx +0 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasPage.tsx +160 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasToolbar.tsx +120 -148
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CodeFloatWindow.tsx +74 -181
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/LivePreview.tsx +15 -13
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/canvasConfigRegistry.tsx +2 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/canvasContextMenuRegistry.tsx +338 -3
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/canvasDefaults.ts +18 -17
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/components/BarChartDataSourceModal.tsx +10 -4
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/components/LineChartDataSourceModal.tsx +10 -4
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/components/{ChartViewerConfigModal.tsx → MultiChartConfigModal.tsx} +30 -18
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/components/MultiChartDataSourceModal.tsx +427 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/components/NumCardDataSourceModal.tsx +10 -4
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/components/PromptModal.tsx +6 -14
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/components/RadarChartDataSourceModal.tsx +10 -4
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/components/TableDataSourceModal.tsx +10 -4
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/components/canvasModalProps.ts +24 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/demos.ts +45 -63
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/CustomNode.tsx +99 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/ExportModal.tsx +87 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/FlowRenderer.tsx +322 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/ImportModal.tsx +175 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/NodeEditModal.tsx +60 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/NodePropertyPanel.tsx +1150 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/RunWorkflowModal.tsx +101 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/StatusCards.tsx +198 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/VersionPanel.tsx +81 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/editor.tsx +566 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/hooks/useWorkflowAgent.ts +224 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/index.tsx +524 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/instance-detail.tsx +343 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/instances.tsx +243 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/components/CreateNodeInstanceModal.tsx +363 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/components/DynamicConfigForm.tsx +154 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/components/NodeInstanceForm.tsx +176 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/create.tsx +77 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/edit.tsx +112 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/index.tsx +305 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/show.tsx +282 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/show.tsx +469 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/types.ts +92 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflowEventOutbox/create.tsx +111 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflowEventOutbox/edit.tsx +127 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflowEventOutbox/index.tsx +4 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflowEventOutbox/list.tsx +254 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflowEventOutbox/show.tsx +74 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/yarn.lock +1501 -1199
- package/package.json +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/app.controller.spec.ts +0 -22
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/canvas/BarChart/index.tsx +0 -896
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/canvas/ChartSwitcher/index.tsx +0 -219
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/canvas/ChartViewer/index.tsx +0 -159
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/canvas/Filter/index.tsx +0 -192
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/canvas/LineChart/index.tsx +0 -1034
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/canvas/NumCard/NumCard.module.css +0 -8
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/canvas/NumCard/index.tsx +0 -509
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/canvas/NumLineCard/index.tsx +0 -66
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/canvas/PieChart/index.tsx +0 -552
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/canvas/RadarChart/index.tsx +0 -263
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/canvas/Section/index.tsx +0 -35
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/canvas/Table/index.tsx +0 -207
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/canvas/TreemapChart/index.tsx +0 -382
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/canvas/WorldMap/index.tsx +0 -135
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/canvas/chart-constants.ts +0 -53
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/canvas/icon/InfoIcon.tsx +0 -8
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/canvas/icon/index.ts +0 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/canvas/map/config.ts +0 -31
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/canvas/map/nameMap.json +0 -9
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/canvas/map/world.geo.json +0 -39349
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/canvas/metric-info-tooltip/index.tsx +0 -19
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
# Temporal Workflow Runtime
|
|
2
|
+
|
|
3
|
+
本目录包含 Workflow 执行引擎的所有组件。
|
|
4
|
+
|
|
5
|
+
## 架构
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
9
|
+
│ Temporal Server │
|
|
10
|
+
│ (调度中心,不执行业务代码) │
|
|
11
|
+
│ │
|
|
12
|
+
│ ┌──────────┐ ┌──────────────┐ ┌────────────────────┐ │
|
|
13
|
+
│ │ 状态存储 │ │ 任务队列管理 │ │ 历史/可见性引擎 │ │
|
|
14
|
+
│ │ (PG) │ │ (分发任务) │ │ (查询/搜索) │ │
|
|
15
|
+
│ └──────────┘ └──────┬───────┘ └────────────────────┘ │
|
|
16
|
+
│ │ │
|
|
17
|
+
└────────────────────────┼─────────────────────────────────────┘
|
|
18
|
+
│ gRPC (任务分发 + 状态回报)
|
|
19
|
+
▼
|
|
20
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
21
|
+
│ Temporal Worker │
|
|
22
|
+
│ (你的代码,执行业务逻辑) │
|
|
23
|
+
│ │
|
|
24
|
+
│ ┌─────────────────┐ ┌──────────────────────────┐ │
|
|
25
|
+
│ │ Workflow 代码 │ │ Activity 代码 │ │
|
|
26
|
+
│ │ (DSL 解释器) │ │ (HTTP调用/DB/通知/...) │ │
|
|
27
|
+
│ └─────────────────┘ └──────────────────────────┘ │
|
|
28
|
+
└─────────────────────────────────────────────────────────────┘
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Server vs Worker
|
|
32
|
+
|
|
33
|
+
| | Server | Worker |
|
|
34
|
+
|--|--------|--------|
|
|
35
|
+
| 谁提供的 | Temporal 官方 Docker 镜像 | **你写的代码** |
|
|
36
|
+
| 执行业务逻辑? | ❌ 完全不碰 | ✅ 全部在这里 |
|
|
37
|
+
| 有状态? | ✅ 持久化到 PG | ❌ 无状态,可随时重启/扩容 |
|
|
38
|
+
| 挂了怎么办 | 所有 workflow 暂停 | Server 保留进度,Worker 恢复后继续 |
|
|
39
|
+
| 能横向扩展? | 可以但复杂 | **随便加**,同一 task queue 多个 Worker |
|
|
40
|
+
|
|
41
|
+
**一句话:Server 是"大脑"(调度+记忆),Worker 是"手脚"(干活)。**
|
|
42
|
+
|
|
43
|
+
## 目录结构
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
temporal/
|
|
47
|
+
├── package.json # 便捷脚本
|
|
48
|
+
├── docker-compose.yml # Temporal Server + Web UI
|
|
49
|
+
├── config/
|
|
50
|
+
│ └── development-sql.yaml # Server 动态配置
|
|
51
|
+
├── sql/
|
|
52
|
+
│ └── create-event-trigger.sql # Outbox 表 + PG 触发器函数
|
|
53
|
+
└── worker/ # DSL 解释器 Worker
|
|
54
|
+
├── package.json
|
|
55
|
+
├── tsconfig.json
|
|
56
|
+
├── .env # Worker 环境变量
|
|
57
|
+
└── src/
|
|
58
|
+
├── index.ts # Worker 启动入口
|
|
59
|
+
├── config.ts # 配置读取
|
|
60
|
+
├── outbox-poller.ts # 事件轮询器(扫描 outbox 表触发 workflow)
|
|
61
|
+
├── workflows/
|
|
62
|
+
│ └── dsl-workflow.ts # 核心:DSL 动态解释器
|
|
63
|
+
├── activities/
|
|
64
|
+
│ ├── index.ts
|
|
65
|
+
│ ├── http-request.ts # HTTP 调用
|
|
66
|
+
│ ├── db-query.ts # 数据库查询
|
|
67
|
+
│ ├── db-execute.ts # 数据库写入
|
|
68
|
+
│ ├── send-notification.ts # 多渠道通知
|
|
69
|
+
│ ├── code-execute.ts # 沙箱 JS 执行
|
|
70
|
+
│ └── reporting.ts # 状态回写到业务 DB
|
|
71
|
+
└── dsl/
|
|
72
|
+
├── types.ts # DSL 类型定义
|
|
73
|
+
├── graph.ts # DAG 解析 + 拓扑排序
|
|
74
|
+
├── context.ts # 变量/上下文管理
|
|
75
|
+
└── condition.ts # 条件表达式求值
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## 快速启动
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
cd temporal
|
|
82
|
+
|
|
83
|
+
# 1. 启动 Temporal Server(复用项目现有 PostgreSQL)
|
|
84
|
+
npm run up
|
|
85
|
+
|
|
86
|
+
# 2. 安装 Worker 依赖
|
|
87
|
+
npm run worker:install
|
|
88
|
+
|
|
89
|
+
# 3. 启动 Worker(开发模式)
|
|
90
|
+
npm run worker:dev
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## 工作流程
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
1. 用户在前端点击 "Run" 按钮
|
|
97
|
+
│
|
|
98
|
+
▼
|
|
99
|
+
2. NestJS 调用 temporalService.startWorkflow()
|
|
100
|
+
│
|
|
101
|
+
▼
|
|
102
|
+
3. Temporal Server 收到请求,创建记录,放入 task queue
|
|
103
|
+
│
|
|
104
|
+
▼
|
|
105
|
+
4. Worker 长轮询 Server,收到新任务
|
|
106
|
+
│
|
|
107
|
+
▼
|
|
108
|
+
5. Worker 执行 dslWorkflow():
|
|
109
|
+
- 解析 DSL JSON → DAG
|
|
110
|
+
- 拓扑排序确定执行顺序
|
|
111
|
+
- 逐节点执行对应 Activity
|
|
112
|
+
- 条件节点做分支选择
|
|
113
|
+
- 并行节点用 Promise.all
|
|
114
|
+
- 审批节点等待 Signal
|
|
115
|
+
│
|
|
116
|
+
▼
|
|
117
|
+
6. 每个节点执行结果回写到 t_workflow_node_execution
|
|
118
|
+
│
|
|
119
|
+
▼
|
|
120
|
+
7. Workflow 完成/失败 → 更新 t_workflow_instance 状态
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## 环境变量
|
|
124
|
+
|
|
125
|
+
| 变量 | 默认值 | 说明 |
|
|
126
|
+
|------|--------|------|
|
|
127
|
+
| `TEMPORAL_ADDRESS` | `localhost:7233` | Temporal Server gRPC 地址 |
|
|
128
|
+
| `TEMPORAL_NAMESPACE` | `default` | Temporal 命名空间 |
|
|
129
|
+
| `TEMPORAL_TASK_QUEUE` | `workflow-execution` | 任务队列名称 |
|
|
130
|
+
| `DATABASE_URL` | (见 .env) | Worker 回写状态用的数据库连接 |
|
|
131
|
+
|
|
132
|
+
## 常用命令
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
npm run up # 启动 Server + UI
|
|
136
|
+
npm run down # 停止 Server + UI
|
|
137
|
+
npm run logs # 查看 Server 日志
|
|
138
|
+
npm run worker:dev # 开发模式启动 Worker
|
|
139
|
+
npm run worker:build # 编译 Worker
|
|
140
|
+
npm run worker:start # 生产模式启动 Worker
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Web UI
|
|
144
|
+
|
|
145
|
+
启动后访问 http://localhost:8080 可查看:
|
|
146
|
+
- 运行中的 Workflow 列表
|
|
147
|
+
- Workflow 执行历史
|
|
148
|
+
- 单个 Workflow 的事件时间线
|
|
149
|
+
- Worker 连接状态
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## Event Trigger(数据库事件触发 Workflow)
|
|
154
|
+
|
|
155
|
+
通过 Transactional Outbox 模式,当业务表发生变更时自动触发对应的 Workflow。
|
|
156
|
+
|
|
157
|
+
### 原理
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
┌─────────────┐ INSERT/UPDATE/DELETE ┌──────────────────────────┐
|
|
161
|
+
│ 任何来源 │ ────────────────────────→ │ PostgreSQL │
|
|
162
|
+
│ (NestJS / │ │ │
|
|
163
|
+
│ 外部系统) │ │ AFTER TRIGGER │
|
|
164
|
+
└─────────────┘ │ → INSERT INTO outbox │
|
|
165
|
+
│ (同一事务,保证不丢) │
|
|
166
|
+
└─────────────┬─────────────┘
|
|
167
|
+
│
|
|
168
|
+
outbox 表中有新行
|
|
169
|
+
│
|
|
170
|
+
▼
|
|
171
|
+
┌──────────────────────────┐
|
|
172
|
+
│ Worker │
|
|
173
|
+
│ outbox-poller.ts │
|
|
174
|
+
│ │
|
|
175
|
+
│ 每 10s 扫描一次: │
|
|
176
|
+
│ 1. SELECT 未处理行 │
|
|
177
|
+
│ 2. 匹配 event_trigger │
|
|
178
|
+
│ 3. 启动 Temporal workflow │
|
|
179
|
+
│ 4. 标记行为已处理 │
|
|
180
|
+
└──────────────────────────┘
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### 为什么选轮询而不是 PG LISTEN/NOTIFY
|
|
184
|
+
|
|
185
|
+
| | PG NOTIFY | Outbox 轮询 |
|
|
186
|
+
|--|--|--|
|
|
187
|
+
| Worker 离线期间 | ❌ 事件丢失 | ✅ 事件留在表中,恢复后处理 |
|
|
188
|
+
| 可靠性 | best-effort | **at-least-once(事务保证)** |
|
|
189
|
+
| 复杂度 | 需要长连接 + 去重 | 只需定时 SELECT |
|
|
190
|
+
| 延迟 | <100ms | ≤ 轮询间隔(默认 10s) |
|
|
191
|
+
|
|
192
|
+
### 支持的操作类型
|
|
193
|
+
|
|
194
|
+
| 数据库操作 | eventName 示例 |
|
|
195
|
+
|-----------|---------------|
|
|
196
|
+
| 新增记录 | `t_order.insert` |
|
|
197
|
+
| 修改记录 | `t_order.update` |
|
|
198
|
+
| 删除记录 | `t_order.delete` |
|
|
199
|
+
|
|
200
|
+
### eventName 通配符匹配
|
|
201
|
+
|
|
202
|
+
在 Workflow DSL 的 `event_trigger` 节点中配置 `eventName`:
|
|
203
|
+
|
|
204
|
+
| eventName 配置 | 触发条件 |
|
|
205
|
+
|---|---|
|
|
206
|
+
| `t_order.insert` | 仅该表新增时触发 |
|
|
207
|
+
| `t_order.update` | 仅该表修改时触发 |
|
|
208
|
+
| `t_order.delete` | 仅该表删除时触发 |
|
|
209
|
+
| `t_order.*` | 该表任何变更都触发 |
|
|
210
|
+
| `*.insert` | 所有表的新增都触发 |
|
|
211
|
+
|
|
212
|
+
### 使用步骤
|
|
213
|
+
|
|
214
|
+
**Step 1:创建 outbox 表 + 通用触发器函数(只需执行一次)**
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
psql -d gadmin_demo -f temporal/sql/create-event-trigger.sql
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
**Step 2:给需要监听的表挂触发器**
|
|
221
|
+
|
|
222
|
+
```sql
|
|
223
|
+
-- 只监听 INSERT
|
|
224
|
+
CREATE TRIGGER trg_order_workflow
|
|
225
|
+
AFTER INSERT ON t_order
|
|
226
|
+
FOR EACH ROW EXECUTE FUNCTION notify_workflow_event();
|
|
227
|
+
|
|
228
|
+
-- 监听所有变更(INSERT + UPDATE + DELETE)
|
|
229
|
+
CREATE TRIGGER trg_order_workflow
|
|
230
|
+
AFTER INSERT OR UPDATE OR DELETE ON t_order
|
|
231
|
+
FOR EACH ROW EXECUTE FUNCTION notify_workflow_event();
|
|
232
|
+
|
|
233
|
+
-- 只在特定条件下触发(例如状态变为 APPROVED)
|
|
234
|
+
CREATE OR REPLACE FUNCTION notify_order_approved() RETURNS trigger AS $$
|
|
235
|
+
BEGIN
|
|
236
|
+
IF NEW.status = 'APPROVED' AND (OLD.status IS NULL OR OLD.status != 'APPROVED') THEN
|
|
237
|
+
INSERT INTO t_workflow_event_outbox (event_name, payload)
|
|
238
|
+
VALUES ('t_order.status_approved', row_to_json(NEW)::jsonb);
|
|
239
|
+
END IF;
|
|
240
|
+
RETURN NEW;
|
|
241
|
+
END;
|
|
242
|
+
$$ LANGUAGE plpgsql;
|
|
243
|
+
|
|
244
|
+
CREATE TRIGGER trg_order_approved
|
|
245
|
+
AFTER UPDATE ON t_order
|
|
246
|
+
FOR EACH ROW EXECUTE FUNCTION notify_order_approved();
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
**Step 3:在 Workflow 编辑器中配置 event_trigger 节点**
|
|
250
|
+
|
|
251
|
+
```json
|
|
252
|
+
{
|
|
253
|
+
"type": "event_trigger",
|
|
254
|
+
"config": {
|
|
255
|
+
"eventName": "t_order.insert",
|
|
256
|
+
"filterExpression": "data.amount > 100"
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
- `eventName`:匹配规则(支持通配符)
|
|
262
|
+
- `filterExpression`(可选):JS 表达式,对 payload 的过滤条件
|
|
263
|
+
|
|
264
|
+
**Step 4:发布 Workflow**
|
|
265
|
+
|
|
266
|
+
发布后 Worker 的 outbox-poller 会自动匹配新事件到该 Workflow。
|
|
267
|
+
|
|
268
|
+
### 配置
|
|
269
|
+
|
|
270
|
+
| 环境变量 | 默认值 | 说明 |
|
|
271
|
+
|---------|--------|------|
|
|
272
|
+
| `EVENT_POLL_INTERVAL_MS` | `10000` | 轮询间隔(毫秒) |
|
|
273
|
+
|
|
274
|
+
### 注意事项
|
|
275
|
+
|
|
276
|
+
- Outbox 表会持续增长,建议定期清理已处理的旧行(如保留 7 天)
|
|
277
|
+
- 同一个 eventName 可以匹配多个 Workflow,它们会并行启动
|
|
278
|
+
- `filterExpression` 在 Worker 侧求值,不满足条件的事件仍会写入 outbox 但不触发 workflow
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
services:
|
|
2
|
+
temporal:
|
|
3
|
+
image: temporalio/auto-setup:latest
|
|
4
|
+
ports:
|
|
5
|
+
- "7233:7233"
|
|
6
|
+
environment:
|
|
7
|
+
- DB=postgres12
|
|
8
|
+
- DB_PORT=5432
|
|
9
|
+
- POSTGRES_USER=kavenma
|
|
10
|
+
- POSTGRES_PWD=kavenma
|
|
11
|
+
- POSTGRES_SEEDS=host.docker.internal
|
|
12
|
+
- DYNAMIC_CONFIG_FILE_PATH=config/dynamicconfig/development-sql.yaml
|
|
13
|
+
volumes:
|
|
14
|
+
- ./config:/etc/temporal/config/dynamicconfig
|
|
15
|
+
extra_hosts:
|
|
16
|
+
- "host.docker.internal:host-gateway"
|
|
17
|
+
|
|
18
|
+
temporal-ui:
|
|
19
|
+
image: temporalio/ui:latest
|
|
20
|
+
ports:
|
|
21
|
+
- "8080:8080"
|
|
22
|
+
environment:
|
|
23
|
+
- TEMPORAL_ADDRESS=temporal:7233
|
|
24
|
+
depends_on:
|
|
25
|
+
- temporal
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "gadmin-temporal",
|
|
3
|
+
"private": true,
|
|
4
|
+
"scripts": {
|
|
5
|
+
"up": "docker compose up -d",
|
|
6
|
+
"down": "docker compose down",
|
|
7
|
+
"logs": "docker compose logs -f",
|
|
8
|
+
"worker:install": "cd worker && npm install",
|
|
9
|
+
"worker:dev": "cd worker && npm run dev",
|
|
10
|
+
"worker:build": "cd worker && npm run build",
|
|
11
|
+
"worker:start": "cd worker && npm start"
|
|
12
|
+
}
|
|
13
|
+
}
|
package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/sql/create-event-trigger.sql
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
-- ============================================================================
|
|
2
|
+
-- Workflow Event Trigger — Outbox Pattern (轮询方案)
|
|
3
|
+
-- ============================================================================
|
|
4
|
+
-- 当业务表发生 INSERT/UPDATE/DELETE 时,触发器将事件写入 outbox 表。
|
|
5
|
+
-- Worker 的 outbox-poller 定时扫描未处理事件,匹配并触发对应的 workflow。
|
|
6
|
+
--
|
|
7
|
+
-- 优点:
|
|
8
|
+
-- - 事务保证不丢事件(outbox INSERT 与业务操作在同一事务)
|
|
9
|
+
-- - Worker 重启不会丢事件(积压事件恢复后立即处理)
|
|
10
|
+
-- - 无需 PG LISTEN/NOTIFY,逻辑更简单
|
|
11
|
+
--
|
|
12
|
+
-- 使用方式:
|
|
13
|
+
-- 1. 执行本文件创建 outbox 表和通用触发器函数
|
|
14
|
+
-- 2. 对需要监听的表创建触发器(见下方示例)
|
|
15
|
+
-- ============================================================================
|
|
16
|
+
|
|
17
|
+
-- ─── Outbox 表 ───────────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
CREATE TABLE IF NOT EXISTS t_workflow_event_outbox (
|
|
20
|
+
id BIGSERIAL PRIMARY KEY,
|
|
21
|
+
event_name VARCHAR(128) NOT NULL, -- e.g. "t_order.insert"
|
|
22
|
+
payload JSONB NOT NULL, -- 行数据
|
|
23
|
+
processed BOOLEAN DEFAULT FALSE,
|
|
24
|
+
created_at TIMESTAMP DEFAULT NOW(),
|
|
25
|
+
processed_at TIMESTAMP,
|
|
26
|
+
retry_count INT NOT NULL DEFAULT 0, -- 处理失败重试次数; >= MAX_RETRIES 视为终态失败
|
|
27
|
+
last_error TEXT -- 最近一次失败的错误信息(成功时不清空,排障用)
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
-- 只扫未处理行的局部索引
|
|
31
|
+
CREATE INDEX IF NOT EXISTS idx_outbox_unprocessed
|
|
32
|
+
ON t_workflow_event_outbox (created_at)
|
|
33
|
+
WHERE processed = FALSE;
|
|
34
|
+
|
|
35
|
+
-- ─── 通用触发器函数 ──────────────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
CREATE OR REPLACE FUNCTION notify_workflow_event() RETURNS trigger AS $$
|
|
38
|
+
DECLARE
|
|
39
|
+
event_payload jsonb;
|
|
40
|
+
event_name text;
|
|
41
|
+
BEGIN
|
|
42
|
+
-- 拼接事件名:表名.操作 (e.g. "t_order.insert")
|
|
43
|
+
event_name := TG_TABLE_NAME || '.' || lower(TG_OP);
|
|
44
|
+
|
|
45
|
+
-- 构建 payload
|
|
46
|
+
IF (TG_OP = 'DELETE') THEN
|
|
47
|
+
event_payload = row_to_json(OLD)::jsonb;
|
|
48
|
+
ELSE
|
|
49
|
+
event_payload = row_to_json(NEW)::jsonb;
|
|
50
|
+
END IF;
|
|
51
|
+
|
|
52
|
+
-- 写入 outbox(与业务操作同一事务,保证不丢)
|
|
53
|
+
INSERT INTO t_workflow_event_outbox (event_name, payload)
|
|
54
|
+
VALUES (event_name, event_payload);
|
|
55
|
+
|
|
56
|
+
RETURN COALESCE(NEW, OLD);
|
|
57
|
+
END;
|
|
58
|
+
$$ LANGUAGE plpgsql;
|
|
59
|
+
|
|
60
|
+
-- ============================================================================
|
|
61
|
+
-- 示例
|
|
62
|
+
-- ============================================================================
|
|
63
|
+
|
|
64
|
+
-- 监听 t_order 表的 INSERT
|
|
65
|
+
-- CREATE TRIGGER trg_order_workflow
|
|
66
|
+
-- AFTER INSERT ON t_order
|
|
67
|
+
-- FOR EACH ROW EXECUTE FUNCTION notify_workflow_event();
|
|
68
|
+
|
|
69
|
+
-- 监听 t_order 表的所有变更
|
|
70
|
+
-- CREATE TRIGGER trg_order_workflow
|
|
71
|
+
-- AFTER INSERT OR UPDATE OR DELETE ON t_order
|
|
72
|
+
-- FOR EACH ROW EXECUTE FUNCTION notify_workflow_event();
|
|
73
|
+
|
|
74
|
+
-- 只在特定条件下写入 outbox(例如状态变为 APPROVED)
|
|
75
|
+
-- CREATE OR REPLACE FUNCTION notify_order_approved() RETURNS trigger AS $$
|
|
76
|
+
-- BEGIN
|
|
77
|
+
-- IF NEW.status = 'APPROVED' AND (OLD.status IS NULL OR OLD.status != 'APPROVED') THEN
|
|
78
|
+
-- INSERT INTO t_workflow_event_outbox (event_name, payload)
|
|
79
|
+
-- VALUES ('t_order.status_approved', row_to_json(NEW)::jsonb);
|
|
80
|
+
-- END IF;
|
|
81
|
+
-- RETURN NEW;
|
|
82
|
+
-- END;
|
|
83
|
+
-- $$ LANGUAGE plpgsql;
|
|
84
|
+
--
|
|
85
|
+
-- CREATE TRIGGER trg_order_approved
|
|
86
|
+
-- AFTER UPDATE ON t_order
|
|
87
|
+
-- FOR EACH ROW EXECUTE FUNCTION notify_order_approved();
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Code 节点沙箱方案
|
|
2
|
+
|
|
3
|
+
## 当前方案:Node.js `vm` 模块
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
// activities/code-execute.ts
|
|
7
|
+
const vm = await import('vm');
|
|
8
|
+
const sandbox = { data, items, result: undefined };
|
|
9
|
+
const context = vm.createContext(sandbox);
|
|
10
|
+
new vm.Script(`result = (function() { ${script} })()`).runInContext(context, { timeout });
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
**优点:** 零依赖,零开销,简单直接
|
|
14
|
+
**缺点:** 隔离性有限,恶意代码可通过原型链逃逸访问 Node.js API
|
|
15
|
+
**适用:** 内部管理后台,脚本由可信管理员编写
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 备选方案:isolated-vm(强隔离)
|
|
20
|
+
|
|
21
|
+
当需要执行**不可信用户代码**时,切换到 `isolated-vm`。
|
|
22
|
+
|
|
23
|
+
### 什么是 isolated-vm
|
|
24
|
+
|
|
25
|
+
- V8 引擎 Isolate 接口的 Node.js 封装
|
|
26
|
+
- 在独立 V8 实例中执行代码,和主进程**零共享内存**
|
|
27
|
+
- 无法访问 Node.js API(fs、net、process 等全部不可用)
|
|
28
|
+
- 周下载量 100万+,生产级稳定
|
|
29
|
+
- `vm2` 已停止维护(不可修复的逃逸漏洞),`isolated-vm` 是社区推荐替代
|
|
30
|
+
|
|
31
|
+
### 层级结构
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
Isolate (V8 引擎实例) ← 创建开销 ~5ms,可复用
|
|
35
|
+
└── Context (执行上下文) ← 创建开销 <1ms,每次新建保证隔离
|
|
36
|
+
└── Script (编译后代码) ← 可缓存
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### 推荐实现:复用 Isolate + 每次新 Context
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
// activities/code-execute.ts(isolated-vm 版本)
|
|
43
|
+
import ivm from 'isolated-vm';
|
|
44
|
+
|
|
45
|
+
// Worker 启动时创建一个 Isolate(复用,不用每次新建)
|
|
46
|
+
const isolate = new ivm.Isolate({ memoryLimit: 128 }); // 128MB 内存上限
|
|
47
|
+
|
|
48
|
+
export async function codeExecute(input: CodeExecuteInput): Promise<CodeExecuteOutput> {
|
|
49
|
+
const { script, mode = 'runOnceForAll', timeout = 5000, data, items } = input;
|
|
50
|
+
|
|
51
|
+
if (mode === 'runOnceForEach' && Array.isArray(items)) {
|
|
52
|
+
const results: any[] = [];
|
|
53
|
+
for (const item of items) {
|
|
54
|
+
const result = await runInSandbox(script, { item, data }, timeout);
|
|
55
|
+
results.push(result);
|
|
56
|
+
}
|
|
57
|
+
return { result: results };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const result = await runInSandbox(script, { data, items: items || [] }, timeout);
|
|
61
|
+
return { result };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function runInSandbox(
|
|
65
|
+
script: string,
|
|
66
|
+
globals: Record<string, any>,
|
|
67
|
+
timeout: number,
|
|
68
|
+
): Promise<any> {
|
|
69
|
+
// 每次执行创建新 Context(<1ms),保证执行间完全隔离
|
|
70
|
+
const context = await isolate.createContext();
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const jail = context.global;
|
|
74
|
+
|
|
75
|
+
// 注入变量(深拷贝进 Isolate,主进程对象不可直接访问)
|
|
76
|
+
for (const [key, value] of Object.entries(globals)) {
|
|
77
|
+
await jail.set(key, new ivm.ExternalCopy(value).copyInto());
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// 执行脚本
|
|
81
|
+
const wrapped = `(function() { ${script} })()`;
|
|
82
|
+
const result = await context.eval(wrapped, { timeout });
|
|
83
|
+
|
|
84
|
+
return result;
|
|
85
|
+
} finally {
|
|
86
|
+
context.release(); // 释放 Context 内存,Isolate 继续复用
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### 性能对比
|
|
92
|
+
|
|
93
|
+
| 策略 | 每次执行开销 | 内存 |
|
|
94
|
+
|------|------------|------|
|
|
95
|
+
| 每次新建 Isolate | ~5ms | 每次分配独立堆 |
|
|
96
|
+
| **复用 Isolate + 每次新 Context** | **<1ms** | 共享 Isolate 堆 |
|
|
97
|
+
| 复用 Isolate + 复用 Context | ~0 | ⚠️ 执行间状态泄漏 |
|
|
98
|
+
|
|
99
|
+
推荐第二种:性能和隔离性的最佳平衡。
|
|
100
|
+
|
|
101
|
+
### 安全保障
|
|
102
|
+
|
|
103
|
+
| 防护 | 说明 |
|
|
104
|
+
|------|------|
|
|
105
|
+
| 内存限制 | `memoryLimit: 128` → 超限直接 kill |
|
|
106
|
+
| CPU 限制 | `timeout` 参数 → 超时抛异常 |
|
|
107
|
+
| 网络隔离 | 无 `require`/`import`,无法访问 `net`/`http` |
|
|
108
|
+
| 文件系统隔离 | 无 `fs` 模块 |
|
|
109
|
+
| 进程隔离 | 无 `process`/`child_process` |
|
|
110
|
+
| 原型链隔离 | 独立 V8 堆,原型链攻击无效 |
|
|
111
|
+
|
|
112
|
+
### 何时切换
|
|
113
|
+
|
|
114
|
+
- ✅ 当前 `vm` 方案:适用于内部可信用户(管理员自己写脚本)
|
|
115
|
+
- 🔄 切换到 `isolated-vm`:当开放给外部用户或非技术人员编写脚本时
|
|
116
|
+
|
|
117
|
+
### 切换步骤
|
|
118
|
+
|
|
119
|
+
1. 依赖已在 `package.json` 中(`"isolated-vm": "^5.x"`)
|
|
120
|
+
2. 替换 `activities/code-execute.ts` 的实现(如上)
|
|
121
|
+
3. 接口不变,调用方无需修改
|
|
122
|
+
4. 测试:验证 `runOnceForAll` 和 `runOnceForEach` 两种模式
|